Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make action helpers use the dispatch(actionCreator()) pattern #9341

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions src/__mocks__/@/store/deactivateModHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { AppDispatch } from "@/extensionConsole/store";
import { actions as modComponentActions } from "@/store/modComponents/modComponentSlice";
import type { RegistryId } from "@/types/registryTypes";
import type { UUID } from "@/types/stringTypes";
import type { Dispatch } from "react";

export const deactivateMod = jest.fn(
async (
modId: RegistryId,
_modComponentIds: UUID[],
dispatch: Dispatch<unknown>,
) => {
// Keep the call to dispatch, but leave off reading/writing to the Page Editor storage and runtime side effects
dispatch(modComponentActions.removeModById(modId));
},
(modId: RegistryId, _modComponentIds: UUID[]) =>
async (dispatch: AppDispatch) => {
// Keep the call to dispatch, but leave off reading/writing to the Page Editor storage and runtime side effects
dispatch(modComponentActions.removeModById(modId));
},
);

export const removeModDataAndInterfaceFromAllTabs = jest.fn();
1 change: 0 additions & 1 deletion src/activation/useActivateMod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ describe("useActivateMod", () => {
expect(deactivateModMock).toHaveBeenCalledWith(
modDefinition.metadata.id,
expect.toBeArray(),
dispatch,
);

expect(dispatch).toHaveBeenCalledWith(
Expand Down
12 changes: 7 additions & 5 deletions src/activation/useActivateMod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
} from "@/activation/modOptionsHelpers";
import { type ReportEventData } from "@/telemetry/telemetryTypes";
import { selectModInstanceMap } from "@/store/modComponents/modInstanceSelectors";
import { type AppDispatch } from "@/extensionConsole/store";

export type ActivateResult = {
success: boolean;
Expand Down Expand Up @@ -77,7 +78,7 @@ function useActivateMod(
source: ActivationSource,
{ checkPermissions = true }: { checkPermissions?: boolean } = {},
): ActivateModFormCallback {
const dispatch = useDispatch();
const dispatch = useDispatch<AppDispatch>();
const modInstanceMap = useSelector(selectModInstanceMap);

const [createDatabase] = useCreateDatabaseMutation();
Expand Down Expand Up @@ -145,10 +146,11 @@ function useActivateMod(
},
);

await deactivateMod(
modDefinition.metadata.id,
modInstance?.modComponentIds ?? [],
dispatch,
await dispatch(
deactivateMod(
modDefinition.metadata.id,
modInstance?.modComponentIds ?? [],
),
);

const userDeployment = await handleUserDeployment(
Expand Down
17 changes: 9 additions & 8 deletions src/extensionConsole/pages/deployments/DeploymentsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { Events } from "@/telemetry/events";
import notify from "@/utils/notify";
import { integrationConfigLocator } from "@/background/messenger/api";
import { refreshRegistries } from "@/hooks/useRefreshRegistries";
import { type Dispatch } from "@reduxjs/toolkit";
import useFlags, { type FlagHelpers } from "@/hooks/useFlags";
import {
checkExtensionUpdateRequired,
Expand Down Expand Up @@ -56,6 +55,7 @@ import { valueToAsyncState } from "@/utils/asyncStateUtils";
import { RestrictedFeatures } from "@/auth/featureFlags";
import { selectModInstances } from "@/store/modComponents/modInstanceSelectors";
import type { ModInstance } from "@/types/modInstanceTypes";
import { type AppDispatch } from "@/extensionConsole/store";

export type DeploymentsState = {
/**
Expand Down Expand Up @@ -95,7 +95,7 @@ export type DeploymentsState = {
};

function useDeployments(): DeploymentsState {
const dispatch = useDispatch<Dispatch>();
const dispatch = useDispatch<AppDispatch>();
const { data: browserIdentifier } = useBrowserIdentifier();
const modInstances = useSelector(selectModInstances);
const { state: flagsState } = useFlags();
Expand Down Expand Up @@ -250,12 +250,13 @@ function useDeployments(): DeploymentsState {
}

try {
await activateDeployments({
dispatch,
activatableDeployments,
modInstances,
reloadMode: "immediate",
});
await dispatch(
activateDeployments({
activatableDeployments,
modInstances,
reloadMode: "immediate",
}),
);
notify.success("Updated team deployments");
} catch (error) {
notify.error({ message: "Error updating team deployments", error });
Expand Down
129 changes: 65 additions & 64 deletions src/extensionConsole/pages/deployments/activateDeployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
queueReloadModEveryTab,
reloadModsEveryTab,
} from "@/contentScript/messenger/api";
import { persistor } from "@/extensionConsole/store";
import { type AppDispatch, persistor } from "@/extensionConsole/store";
import type { ModInstance } from "@/types/modInstanceTypes";

const { actions } = modComponentSlice;
Expand All @@ -41,88 +41,89 @@ async function flushAndPersist(mode: "queue" | "immediate") {
}
}

async function activateDeployment({
dispatch,
function activateDeployment({
activatableDeployment,
modInstances,
}: {
dispatch: Dispatch;
activatableDeployment: ActivatableDeployment;
modInstances: ModInstance[];
}): Promise<void> {
const { deployment, modDefinition } = activatableDeployment;
let isReactivate = false;

// Clear instances associated activated mod deployments, or packages that would be replaced by a deployment
for (const modInstance of modInstances) {
const activatedModId = modInstance.definition.metadata.id;

if (
modInstance.deploymentMetadata?.id === deployment.id ||
activatedModId === deployment.package.package_id
) {
dispatch(actions.removeModById(activatedModId));
isReactivate = true;
}) {
return async (dispatch: AppDispatch): Promise<void> => {
const { deployment, modDefinition } = activatableDeployment;
let isReactivate = false;

// Clear instances associated activated mod deployments, or packages that would be replaced by a deployment
for (const modInstance of modInstances) {
const activatedModId = modInstance.definition.metadata.id;

if (
modInstance.deploymentMetadata?.id === deployment.id ||
activatedModId === deployment.package.package_id
) {
dispatch(actions.removeModById(activatedModId));
isReactivate = true;
}
}
}

// Activate the mod with service definition
dispatch(
actions.activateMod({
modDefinition,
deployment,
configuredDependencies: await mergeDeploymentIntegrationDependencies(
activatableDeployment,
integrationConfigLocator.findAllSanitizedConfigsForIntegration,
),
// Assume validation on the backend for options
optionsArgs: deployment.options_config,
screen: "extensionConsole",
isReactivate,
}),
);

reportEvent(Events.DEPLOYMENT_ACTIVATE, {
deployment: deployment.id,
});
// Activate the mod with service definition
dispatch(
actions.activateMod({
modDefinition,
deployment,
configuredDependencies: await mergeDeploymentIntegrationDependencies(
activatableDeployment,
integrationConfigLocator.findAllSanitizedConfigsForIntegration,
),
// Assume validation on the backend for options
optionsArgs: deployment.options_config,
screen: "extensionConsole",
isReactivate,
}),
);

reportEvent(Events.DEPLOYMENT_ACTIVATE, {
deployment: deployment.id,
});
};
}

export async function activateDeployments({
dispatch,
export function activateDeployments({
activatableDeployments,
modInstances,
reloadMode,
}: {
dispatch: Dispatch;
activatableDeployments: ActivatableDeployment[];
modInstances: ModInstance[];
reloadMode: "queue" | "immediate";
}): Promise<void> {
// Activate as many as we can
const errors = [];

for (const activatableDeployment of activatableDeployments) {
try {
// eslint-disable-next-line no-await-in-loop -- modifies redux state
await activateDeployment({
dispatch,
activatableDeployment,
modInstances,
});
} catch (error) {
errors.push(error);
}) {
return async (dispatch: AppDispatch): Promise<void> => {
// Activate as many as we can
const errors = [];

for (const activatableDeployment of activatableDeployments) {
try {
// eslint-disable-next-line no-await-in-loop -- modifies redux state
await dispatch(
activateDeployment({
activatableDeployment,
modInstances,
}),
);
} catch (error) {
errors.push(error);
}
}
}

if (errors.length > 0) {
// XXX: only throwing the first is OK, because the user will see the next error if they fix this error and then
// activate deployments again
throw errors[0];
}
if (errors.length > 0) {
// XXX: only throwing the first is OK, because the user will see the next error if they fix this error and then
// activate deployments again
throw errors[0];
}

// Ensure the mod state is persisted before continuing so the content script can immediately pick up the changes
// when activating a deployment from the extension console. See: https://github.com/pixiebrix/pixiebrix-extension/issues/8744
await flushAndPersist(reloadMode);
// Ensure the mod state is persisted before continuing so the content script can immediately pick up the changes
// when activating a deployment from the extension console. See: https://github.com/pixiebrix/pixiebrix-extension/issues/8744
await flushAndPersist(reloadMode);
};
}

export function deactivateUnassignedModComponents({
Expand Down
16 changes: 9 additions & 7 deletions src/extensionConsole/pages/deployments/useAutoDeploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import useModPermissions from "@/mods/hooks/useModPermissions";
import { activatableDeploymentFactory } from "@/testUtils/factories/deploymentFactories";
import type { ActivatableDeployment } from "@/types/deploymentTypes";
import { modInstanceFactory } from "@/testUtils/factories/modInstanceFactories";
import { type AppDispatch } from "@/extensionConsole/store";

jest.mock("@/mods/hooks/useModPermissions");
jest.mock("@/hooks/useFlags");
Expand All @@ -31,11 +32,11 @@ jest.mock("@/extensionConsole/pages/deployments/activateDeployments");
const mockHooks = ({
restricted = true,
hasPermissions = true,
activateDeploymentsResponse = undefined,
activateDeploymentsResponse = jest.fn(),
}: {
restricted?: boolean;
hasPermissions?: boolean;
activateDeploymentsResponse?: Promise<void>;
activateDeploymentsResponse?: (dispatch: AppDispatch) => Promise<void>;
} = {}) => {
jest.mocked(useFlags).mockImplementation(() => ({
...jest.requireActual("@/hooks/useFlags"),
Expand All @@ -47,9 +48,7 @@ const mockHooks = ({
requestPermissions: jest.fn(),
}));

jest
.mocked(activateDeployments)
.mockResolvedValue(activateDeploymentsResponse);
jest.mocked(activateDeployments).mockReturnValue(activateDeploymentsResponse);
};

describe("useAutoDeploy", () => {
Expand Down Expand Up @@ -116,7 +115,6 @@ describe("useAutoDeploy", () => {

expect(result.current.isAutoDeploying).toBe(true);
expect(activateDeployments).toHaveBeenCalledWith({
dispatch: expect.any(Function),
activatableDeployments,
modInstances,
reloadMode: "queue",
Expand Down Expand Up @@ -157,7 +155,11 @@ describe("useAutoDeploy", () => {
}, 1000);
});

mockHooks({ activateDeploymentsResponse: promise });
mockHooks({
async activateDeploymentsResponse() {
await promise;
},
});

const activatableDeployment: ActivatableDeployment =
activatableDeploymentFactory();
Expand Down
17 changes: 9 additions & 8 deletions src/extensionConsole/pages/deployments/useAutoDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import { activateDeployments } from "@/extensionConsole/pages/deployments/activa
import useFlags from "@/hooks/useFlags";
import useModPermissions from "@/mods/hooks/useModPermissions";
import notify from "@/utils/notify";
import { type Dispatch } from "@reduxjs/toolkit";
import { useState } from "react";
import { useDispatch } from "react-redux";
import useAsyncEffect from "use-async-effect";
import type { ActivatableDeployment } from "@/types/deploymentTypes";
import type { Nullishable } from "@/utils/nullishUtils";
import { RestrictedFeatures } from "@/auth/featureFlags";
import type { ModInstance } from "@/types/modInstanceTypes";
import { type AppDispatch } from "@/extensionConsole/store";

type UseAutoDeployReturn = {
/**
Expand All @@ -45,7 +45,7 @@ function useAutoDeploy({
modInstances: ModInstance[];
extensionUpdateRequired: boolean;
}): UseAutoDeployReturn {
const dispatch = useDispatch<Dispatch>();
const dispatch = useDispatch<AppDispatch>();
// `true` until deployments have been fetched and activated
const [
isFetchingAndActivatingDeployments,
Expand Down Expand Up @@ -88,12 +88,13 @@ function useAutoDeploy({
// Attempt to automatically deploy the deployments
try {
setIsActivationInProgress(true);
await activateDeployments({
dispatch,
activatableDeployments,
modInstances,
reloadMode: "queue",
});
await dispatch(
activateDeployments({
activatableDeployments,
modInstances,
reloadMode: "queue",
}),
);
notify.success("Updated team deployments");
} catch (error) {
notify.error({ message: "Error updating team deployments", error });
Expand Down
1 change: 0 additions & 1 deletion src/extensionConsole/pages/mods/ModsPageActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ describe("ModsPageActions", () => {
expect(deactivateMod).toHaveBeenCalledWith(
modViewItem.modId,
expect.toBeArrayOfSize(3),
expect.toBeFunction(),
);
});

Expand Down
Loading
Loading