Skip to content

Commit

Permalink
Rename extension actions
Browse files Browse the repository at this point in the history
  • Loading branch information
twschiller committed Jun 21, 2024
1 parent ebc39a7 commit 8c89a9d
Show file tree
Hide file tree
Showing 31 changed files with 220 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@

import { type WizardValues } from "@/activation/wizardTypes";
import { renderHook } from "@/pageEditor/testHelpers";
import useActivateRecipe from "./useActivateRecipe";
import useActivateMod from "./useActivateMod";
import { validateRegistryId } from "@/types/helpers";
import { type StarterBrickDefinitionLike } from "@/starterBricks/types";
import { type ContextMenuDefinition } from "@/starterBricks/contextMenu/types";
import { uninstallRecipe } from "@/store/uninstallUtils";
import { uninstallMod } from "@/store/uninstallUtils";
import { type ModDefinition } from "@/types/modDefinitionTypes";
import extensionsSlice from "@/store/extensionsSlice";
import { type InnerDefinitions } from "@/types/registryTypes";
Expand All @@ -43,7 +43,7 @@ import type MockAdapter from "axios-mock-adapter";
jest.mock("@/contentScript/messenger/api");

const checkPermissionsMock = jest.mocked(checkModDefinitionPermissions);
const uninstallRecipeMock = jest.mocked(uninstallRecipe);
const uninstallModMock = jest.mocked(uninstallMod);
const reactivateEveryTabMock = jest.mocked(reactivateEveryTab);

function setupInputs(): {
Expand All @@ -56,13 +56,13 @@ function setupInputs(): {
optionsArgs: {},
};

const extensionPointId = validateRegistryId("test/starter-brick-1");
const starterBrickId = validateRegistryId("test/starter-brick-1");
const modComponentDefinition = modComponentDefinitionFactory({
id: extensionPointId,
id: starterBrickId,
});
const starterBrickDefinition = starterBrickDefinitionFactory({
metadata: metadataFactory({
id: extensionPointId,
id: starterBrickId,
name: "Text Starter Brick 1",
}),
definition: {
Expand All @@ -82,7 +82,7 @@ function setupInputs(): {
const modDefinition = defaultModDefinitionFactory({
extensionPoints: [modComponentDefinition],
definitions: {
[extensionPointId]: starterBrickDefinition,
[starterBrickId]: starterBrickDefinition,
} as unknown as InnerDefinitions,
});

Expand All @@ -92,7 +92,7 @@ function setupInputs(): {
};
}

function setRecipeHasPermissions(hasPermissions: boolean) {
function setModHasPermissions(hasPermissions: boolean) {
checkPermissionsMock.mockResolvedValue({
hasPermissions,
// The exact permissions don't matter because we're mocking the check also
Expand All @@ -104,20 +104,20 @@ function setUserAcceptedPermissions(accepted: boolean) {
jest.mocked(browser.permissions.request).mockResolvedValue(accepted);
}

describe("useActivateRecipe", () => {
describe("useActivateMod", () => {
beforeEach(() => {
reactivateEveryTabMock.mockClear();
});

it("returns error if permissions are not granted", async () => {
const { formValues, modDefinition } = setupInputs();
setRecipeHasPermissions(false);
setModHasPermissions(false);
setUserAcceptedPermissions(false);

const {
result: { current: activateRecipe },
getReduxStore,
} = renderHook(() => useActivateRecipe("marketplace"), {
} = renderHook(() => useActivateMod("marketplace"), {
setupRedux(dispatch, { store }) {
jest.spyOn(store, "dispatch");
},
Expand All @@ -131,19 +131,19 @@ describe("useActivateRecipe", () => {
const { dispatch } = getReduxStore();

expect(dispatch).not.toHaveBeenCalled();
expect(uninstallRecipeMock).not.toHaveBeenCalled();
expect(uninstallModMock).not.toHaveBeenCalled();
expect(reactivateEveryTabMock).not.toHaveBeenCalled();
});

it("ignores permissions if flag set", async () => {
const { formValues, modDefinition } = setupInputs();
setRecipeHasPermissions(false);
setModHasPermissions(false);
setUserAcceptedPermissions(false);

const {
result: { current: activateRecipe },
} = renderHook(
() => useActivateRecipe("marketplace", { checkPermissions: false }),
() => useActivateMod("marketplace", { checkPermissions: false }),
{
setupRedux(dispatch, { store }) {
jest.spyOn(store, "dispatch");
Expand All @@ -159,14 +159,14 @@ describe("useActivateRecipe", () => {

it("calls uninstallRecipe, installs to extensionsSlice, and calls reactivateEveryTab, if permissions are granted", async () => {
const { formValues, modDefinition } = setupInputs();
setRecipeHasPermissions(false);
setModHasPermissions(false);
setUserAcceptedPermissions(true);

const {
result: { current: activateRecipe },
getReduxStore,
act,
} = renderHook(() => useActivateRecipe("extensionConsole"), {
} = renderHook(() => useActivateMod("extensionConsole"), {
setupRedux(dispatch, { store }) {
jest.spyOn(store, "dispatch");
},
Expand All @@ -185,7 +185,7 @@ describe("useActivateRecipe", () => {

const { dispatch } = getReduxStore();

expect(uninstallRecipeMock).toHaveBeenCalledWith(
expect(uninstallModMock).toHaveBeenCalledWith(
modDefinition.metadata.id,
expect.toBeArray(),
dispatch,
Expand Down Expand Up @@ -230,7 +230,7 @@ describe("useActivateRecipe", () => {
uiSchema: inputModDefinition.options?.uiSchema,
},
};
setRecipeHasPermissions(true);
setModHasPermissions(true);
setUserAcceptedPermissions(true);

const createdDatabase = databaseFactory({ name: databaseName });
Expand All @@ -240,7 +240,7 @@ describe("useActivateRecipe", () => {
result: { current: activateRecipe },
getReduxStore,
act,
} = renderHook(() => useActivateRecipe("marketplace"), {
} = renderHook(() => useActivateMod("marketplace"), {
setupRedux(dispatch, { store }) {
jest.spyOn(store, "dispatch");
},
Expand Down Expand Up @@ -307,13 +307,13 @@ describe("useActivateRecipe", () => {
format: "preview",
},
);
setRecipeHasPermissions(true);
setModHasPermissions(true);
const errorMessage = "Error creating database";

const {
result: { current: activateRecipe },
act,
} = renderHook(() => useActivateRecipe("marketplace"), {
} = renderHook(() => useActivateMod("marketplace"), {
setupRedux(dispatch, { store }) {
jest.spyOn(store, "dispatch");
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { useDispatch, useSelector } from "react-redux";
import extensionsSlice from "@/store/extensionsSlice";
import reportEvent from "@/telemetry/reportEvent";
import { getErrorMessage } from "@/errors/errorHelpers";
import { uninstallRecipe } from "@/store/uninstallUtils";
import { uninstallMod } from "@/store/uninstallUtils";
import { selectActivatedModComponents } from "@/store/extensionsSelectors";
import { ensurePermissionsFromUserGesture } from "@/permissions/permissionsUtils";
import { checkModDefinitionPermissions } from "@/modDefinitions/modDefinitionPermissionsHelpers";
Expand All @@ -36,47 +36,50 @@ export type ActivateResult = {
error?: string;
};

export type ActivateRecipeFormCallback =
export type ActivateModFormCallback =
/**
* Callback for activating a recipe.
* Callback for activating a mod.
*
* @param formValues The form values for recipe configuration options
* @param recipe The recipe definition to install
* @param formValues The form values for mod configuration options
* @param modDefinition The mod definition to activate
* @returns a promise that resolves to an ActivateResult
*/
(formValues: WizardValues, recipe: ModDefinition) => Promise<ActivateResult>;
(
formValues: WizardValues,
modDefinition: ModDefinition,
) => Promise<ActivateResult>;

type ActivationSource = "marketplace" | "extensionConsole";

function selectActivateEventData(recipe: ModDefinition) {
function selectActivateEventData(modDefinition: ModDefinition) {
return {
blueprintId: recipe.metadata.id,
extensions: recipe.extensionPoints.map((x) => x.label),
blueprintId: modDefinition.metadata.id,
extensions: modDefinition.extensionPoints.map((x) => x.label),
};
}

/**
* React hook to install a recipe.
* React hook to install a mod.
*
* Prompts the user to grant permissions if PixieBrix does not already have the required permissions.
* @param source The source of the activation, only used for reporting purposes
* @param checkPermissions Whether to check for permissions before activating the recipe
* @returns A callback that can be used to activate a recipe
* @see useActivateRecipeWizard
* @param checkPermissions Whether to check for permissions before activating the mod
* @returns A callback that can be used to activate a mod
* @see useActivateModWizard
*/
function useActivateRecipe(
function useActivateMod(
source: ActivationSource,
{ checkPermissions = true }: { checkPermissions?: boolean } = {},
): ActivateRecipeFormCallback {
): ActivateModFormCallback {
const dispatch = useDispatch();
const extensions = useSelector(selectActivatedModComponents);
const activatedModComponents = useSelector(selectActivatedModComponents);

const [createDatabase] = useCreateDatabaseMutation();

return useCallback(
async (formValues: WizardValues, recipe: ModDefinition) => {
const isReactivate = extensions.some(
(extension) => extension._recipe?.id === recipe.metadata.id,
async (formValues: WizardValues, modDefinition: ModDefinition) => {
const isReactivate = activatedModComponents.some(
(x) => x._recipe?.id === modDefinition.metadata.id,
);

if (source === "extensionConsole") {
Expand All @@ -86,7 +89,7 @@ function useActivateRecipe(
// console, to distinguish that from the workshop.
// It's being kept to keep our metrics history clean.
reportEvent(Events.MARKETPLACE_ACTIVATE, {
...selectActivateEventData(recipe),
...selectActivateEventData(modDefinition),
reactivate: isReactivate,
});
}
Expand All @@ -96,14 +99,14 @@ function useActivateRecipe(
);

try {
const recipePermissions = await checkModDefinitionPermissions(
recipe,
const modPermissions = await checkModDefinitionPermissions(
modDefinition,
configuredDependencies,
);

if (checkPermissions) {
const isPermissionsAcceptedByUser =
await ensurePermissionsFromUserGesture(recipePermissions);
await ensurePermissionsFromUserGesture(modPermissions);

if (!isPermissionsAcceptedByUser) {
if (source === "extensionConsole") {
Expand All @@ -113,7 +116,7 @@ function useActivateRecipe(
// console, to distinguish that from the workshop.
// It's being kept like this so our metrics history stays clean.
reportEvent(Events.MARKETPLACE_REJECT_PERMISSIONS, {
...selectActivateEventData(recipe),
...selectActivateEventData(modDefinition),
reactivate: isReactivate,
});
}
Expand All @@ -128,35 +131,39 @@ function useActivateRecipe(
const { optionsArgs, integrationDependencies } = formValues;

await autoCreateDatabaseOptionsArgsInPlace(
recipe,
modDefinition,
optionsArgs,
async (args) => {
const result = await createDatabase(args).unwrap();
return result.id;
},
);

const recipeExtensions = extensions.filter(
(extension) => extension._recipe?.id === recipe.metadata.id,
const existingModComponents = activatedModComponents.filter(
(x) => x._recipe?.id === modDefinition.metadata.id,
);

await uninstallRecipe(recipe.metadata.id, recipeExtensions, dispatch);
await uninstallMod(
modDefinition.metadata.id,
existingModComponents,
dispatch,
);

dispatch(
extensionsSlice.actions.activateMod({
modDefinition: recipe,
modDefinition,
configuredDependencies: integrationDependencies,
optionsArgs,
screen: source,
isReactivate: recipeExtensions.length > 0,
isReactivate: existingModComponents.length > 0,
}),
);

reactivateEveryTab();
} catch (error) {
const errorMessage = getErrorMessage(error);

console.error(`Error activating mod: ${recipe.metadata.id}`, {
console.error(`Error activating mod: ${modDefinition.metadata.id}`, {
error,
});

Expand All @@ -172,8 +179,8 @@ function useActivateRecipe(
success: true,
};
},
[createDatabase, dispatch, extensions, source],
[createDatabase, dispatch, activatedModComponents, source],
);
}

export default useActivateRecipe;
export default useActivateMod;
2 changes: 1 addition & 1 deletion src/background/deploymentUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function deactivateModComponentFromStates(
): { options: ModComponentState; editor: EditorState } {
const options = optionsReducer(
optionsState,
optionsActions.removeExtension({ extensionId: modComponentId }),
optionsActions.removeModComponent({ modComponentId }),
);
const editor = editorState
? editorReducer(
Expand Down
4 changes: 2 additions & 2 deletions src/background/messenger/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ export const contextMenus = {
preload: getMethod("PRELOAD_CONTEXT_MENUS", bg),
};

export const removeExtensionForEveryTab = getNotifier(
"REMOVE_EXTENSION_EVERY_TAB",
export const removeModComponentForEveryTab = getNotifier(
"REMOVE_MOD_COMPONENT_EVERY_TAB",
bg,
);
export const clearServiceCache = getMethod("CLEAR_SERVICE_CACHE", bg);
Expand Down
4 changes: 2 additions & 2 deletions src/background/messenger/registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ declare global {
REQUEST_RUN_IN_OTHER_TABS: typeof requestRunInOtherTabs;
REQUEST_RUN_IN_ALL_FRAMES: typeof requestRunInAllFrames;
PRELOAD_CONTEXT_MENUS: typeof preloadContextMenus;
REMOVE_EXTENSION_EVERY_TAB: typeof removeExtensionForEveryTab;
REMOVE_MOD_COMPONENT_EVERY_TAB: typeof removeExtensionForEveryTab;
INSTALL_STARTER_BLUEPRINTS: typeof installStarterBlueprints;
}
}
Expand Down Expand Up @@ -212,7 +212,7 @@ export default function registerMessenger(): void {
REQUEST_RUN_IN_OTHER_TABS: requestRunInOtherTabs,
REQUEST_RUN_IN_ALL_FRAMES: requestRunInAllFrames,
PRELOAD_CONTEXT_MENUS: preloadContextMenus,
REMOVE_EXTENSION_EVERY_TAB: removeExtensionForEveryTab,
REMOVE_MOD_COMPONENT_EVERY_TAB: removeExtensionForEveryTab,
INSTALL_STARTER_BLUEPRINTS: installStarterBlueprints,
});
}
4 changes: 3 additions & 1 deletion src/background/modUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ function deactivateModComponent(

newOptionsState = extensionsSlice.reducer(
newOptionsState,
extensionsSlice.actions.removeExtension({ extensionId: modComponent.id }),
extensionsSlice.actions.removeModComponent({
modComponentId: modComponent.id,
}),
);

newEditorState = editorSlice.reducer(
Expand Down
Loading

0 comments on commit 8c89a9d

Please sign in to comment.