Skip to content

Commit

Permalink
#9103 No update button for pinned deployments (#9140)
Browse files Browse the repository at this point in the history
* refactor buildGetModVersionStatus - extract isLatestVersion

* swap isLatestVersion parameter position

* refactor replace VersionInfo | null with Nullishable type

* refactor rename VersionInfo -> ModVersionInfo

* refactor rename getModVersionInfo

* add isDeployment logic to update button clause

* change isDeployment logic -> sharingType === 'Deployment' to accommodate personal deployment feature

* add text-nowrap to update button

* remove todo

* add 'doesn't show update for deployment' test

* add PersonalDeployment test

* tweak test name
  • Loading branch information
mnholtz committed Sep 12, 2024
1 parent 58b8e49 commit dccfd89
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 60 deletions.
42 changes: 40 additions & 2 deletions src/extensionConsole/pages/mods/Status.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import React from "react";
import { modViewItemFactory } from "@/testUtils/factories/modViewItemFactory";
import { render, screen } from "@/extensionConsole/testHelpers";
import Status from "@/extensionConsole/pages/mods/Status";
import type { ModActionsEnabled } from "@/types/modTypes";
import type { ModActionsEnabled, SharingSource } from "@/types/modTypes";
import useModPermissions from "@/mods/hooks/useModPermissions";
import userEvent from "@testing-library/user-event";

Expand Down Expand Up @@ -73,7 +73,7 @@ describe("Status", () => {
expect(screen.getByText("Activate")).toBeInTheDocument();
});

it("shows update properly", () => {
it("shows update button for mod with update", () => {
const mod = modViewItemFactory({
hasUpdate: true,
modActions: {
Expand All @@ -87,6 +87,44 @@ describe("Status", () => {
expect(screen.getByText("Update")).toBeInTheDocument();
});

it("doesn't show update button for team deployments", () => {
const mod = modViewItemFactory({
hasUpdate: true,
modActions: {
showReactivate: true,
showActivate: false,
} as unknown as ModActionsEnabled,
sharingSource: {
type: "Deployment",
} as SharingSource,
});

render(<Status modViewItem={mod} />);

expect(screen.queryByText("Update")).not.toBeInTheDocument();
expect(screen.getByText("Active")).toBeInTheDocument();
});

it("shows update button for personal deployments", () => {
const mod = modViewItemFactory({
hasUpdate: true,
modActions: {
showReactivate: true,
showActivate: false,
} as unknown as ModActionsEnabled,
sharingSource: {
type: "PersonalDeployment",
} as SharingSource,
});

render(<Status modViewItem={mod} />);

expect(screen.getByText("Update")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Update" })).toHaveClass(
"btn-info",
);
});

it("shows allow properly", async () => {
jest.mocked(useModPermissions).mockReturnValue({
hasPermissions: false,
Expand Down
11 changes: 7 additions & 4 deletions src/extensionConsole/pages/mods/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@ const Status: React.VoidFunctionComponent<{
modId,
hasUpdate,
status,
sharingSource,
activatedModVersion,
isUnavailable,
modActions: { showActivate, showReactivate },
} = modViewItem;

const modComponents = useActivatedModComponents(modId);
const activatedModComponents = useActivatedModComponents(modId);

const { hasPermissions, requestPermissions } =
useModPermissions(modComponents);
const { hasPermissions, requestPermissions } = useModPermissions(
activatedModComponents,
);

if (isUnavailable) {
return (
Expand Down Expand Up @@ -88,11 +90,12 @@ const Status: React.VoidFunctionComponent<{
);
}

if (hasUpdate && showReactivate) {
if (hasUpdate && showReactivate && !(sharingSource.type === "Deployment")) {
return (
<Button
size="sm"
variant="info"
className="text-nowrap"
onClick={() => {
reportEvent(Events.START_MOD_ACTIVATE, {
modId,
Expand Down
131 changes: 77 additions & 54 deletions src/extensionConsole/pages/mods/utils/buildGetModVersionStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,73 +17,96 @@

import { type ActivatedModComponent } from "@/types/modComponentTypes";
import { type Mod, type ModVersionStatus } from "@/types/modTypes";
import { assertNotNullish } from "@/utils/nullishUtils";
import { assertNotNullish, type Nullishable } from "@/utils/nullishUtils";
import { isUnavailableMod } from "@/utils/modUtils";
import * as semver from "semver";
import { type SemVerString } from "@/types/registryTypes";
import { type Timestamp } from "@/types/stringTypes";

type ModVersionInfo = {
version: SemVerString;
updatedAt: Timestamp;
};

function isLatestVersion(
activated: Nullishable<ModVersionInfo>,
current: Nullishable<ModVersionInfo>,
): boolean {
if (current == null || activated == null) {
return true; // No update available if either version is null
}

if (semver.gt(current.version, activated.version)) {
return false;
}

if (semver.lt(current.version, activated.version)) {
return true;
}

// Versions are equal, compare updated_at timestamp
return new Date(current.updatedAt) <= new Date(activated.updatedAt);
}

function getModVersionInfo(mod: Mod): ModVersionInfo | null {
if (isUnavailableMod(mod)) {
return null;
}

assertNotNullish(
mod.metadata.version,
`Mod version is null for mod: ${mod.metadata.id}, something went wrong`,
);

return {
version: mod.metadata.version,
updatedAt: mod.updated_at,
};
}

function getActivatedModVersionInfo(
activatedModComponent: ActivatedModComponent | undefined,
): ModVersionInfo | null {
if (activatedModComponent == null) {
return null;
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We've migrated _recipe to be non-null everywhere but the type isn't updated yet
const modMetadata = activatedModComponent._recipe!;
const { version: activatedModVersion, updated_at: activatedModUpdatedAt } =
modMetadata;

assertNotNullish(
activatedModVersion,
`Activated mod version is null for mod: ${modMetadata.id}, something went wrong`,
);
assertNotNullish(
activatedModUpdatedAt,
`Activated mod updated_at is null for mod: ${modMetadata.id}, something went wrong`,
);

return {
version: activatedModVersion,
updatedAt: activatedModUpdatedAt,
};
}

export default function buildGetModVersionStatus(
activatedModComponents: ActivatedModComponent[],
): (mod: Mod) => ModVersionStatus {
return (mod: Mod) => {
const activatedComponentFromMod = activatedModComponents.find(
const activatedModComponent = activatedModComponents.find(
({ _recipe }) => _recipe?.id === mod.metadata.id,
);

if (activatedComponentFromMod == null) {
return {
hasUpdate: false,
activatedModVersion: null,
};
}

const metadataFromActivatedModComponent =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We've migrated _recipe to be non-null everywhere but the type isn't updated yet
activatedComponentFromMod._recipe!;

const { version: activatedModVersion, updated_at: activatedModUpdatedAt } =
metadataFromActivatedModComponent;
assertNotNullish(
activatedModVersion,
`Activated mod version is null for mod: ${mod.metadata.id}, something went wrong`,
);
assertNotNullish(
activatedModUpdatedAt,
`Activated mod updated_at is null for mod: ${mod.metadata.id}, something went wrong`,
);

if (isUnavailableMod(mod)) {
// Unavailable mods are never update-able
return {
hasUpdate: false,
activatedModVersion,
};
}

assertNotNullish(
mod.metadata.version,
`Mod version is null for mod: ${mod.metadata.id}, something went wrong`,
const currentVersionInfo = getModVersionInfo(mod);
const activatedVersionInfo = getActivatedModVersionInfo(
activatedModComponent,
);

if (semver.gt(mod.metadata.version, activatedModVersion)) {
return {
hasUpdate: true,
activatedModVersion,
};
}

if (semver.lt(mod.metadata.version, activatedModVersion)) {
return {
hasUpdate: false,
activatedModVersion,
};
}

// Versions are equal, compare updated_at
const modUpdatedDate = new Date(mod.updated_at);
const activatedComponentUpdatedDate = new Date(activatedModUpdatedAt);
return {
hasUpdate: modUpdatedDate > activatedComponentUpdatedDate,
activatedModVersion,
hasUpdate: !isLatestVersion(activatedVersionInfo, currentVersionInfo),
activatedModVersion: activatedVersionInfo?.version ?? null,
};
};
}
1 change: 1 addition & 0 deletions src/extensionConsole/pages/mods/utils/buildModViewItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function buildModViewItems({
hasUpdate,
activatedModVersion,
isUnavailable,
isDeployment,
modActions: {
showPublishToMarketplace:
canPublish && listingId == null && !isUnavailable && !isDeployment,
Expand Down
1 change: 1 addition & 0 deletions src/testUtils/factories/modViewItemFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const modViewItemFactory = define<ModViewItem>({
return `Mod view item ${n} created for testing`;
},
isUnavailable: false,
isDeployment: false,
sharingSource: {
type: "Personal",
label: "Personal",
Expand Down
1 change: 1 addition & 0 deletions src/types/modTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export type ModViewItem = {
* True if the source package is no longer available
*/
isUnavailable: boolean;
isDeployment: boolean;
modActions: ModActionsEnabled;
};

Expand Down

0 comments on commit dccfd89

Please sign in to comment.