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

#9137: types and migrations for mod variable declaration section (1/3) #9138

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { Deployment } from "@/types/contract";
import type { OptionsArgs } from "@/types/runtimeTypes";
import type { IntegrationDependency } from "@/integrations/integrationTypes";
import { nowTimestamp } from "@/utils/timeUtils";
import { emptyModVariablesDefinitionFactory } from "@/utils/modUtils";

export type ActivateModComponentParam = {
/**
Expand Down Expand Up @@ -75,6 +76,8 @@ export function mapModComponentDefinitionToActivatedModComponent<
// Definitions are pushed down into the mod components. That's OK because `resolveDefinitions` determines
// uniqueness based on the content of the definition. Therefore, bricks will be re-used as necessary
definitions: modDefinition.definitions ?? {},
// Mod variable definitions/declarations are pushed down into the mod components.
variables: modDefinition.variables ?? emptyModVariablesDefinitionFactory(),
optionsArgs,
label: modComponentDefinition.label,
extensionPointId: modComponentDefinition.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { validateOutputKey } from "@/runtime/runtimeTypes";
import { toExpression } from "@/utils/expressionUtils";
import { normalizeAvailability } from "@/bricks/available";
import { StarterBrickTypes } from "@/types/starterBrickTypes";
import { emptyModVariablesDefinitionFactory } from "@/utils/modUtils";

describe("selectVariables", () => {
test("selects nothing when no services used", () => {
Expand Down Expand Up @@ -222,6 +223,7 @@ describe("selectVariables", () => {
],
permissions: emptyPermissionsFactory(),
optionsArgs: {},
variablesDefinition: emptyModVariablesDefinitionFactory(),
modMetadata: undefined,
modComponent: {
brickPipeline: [
Expand Down
12 changes: 11 additions & 1 deletion src/pageEditor/starterBricks/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ import {
type BaseFormState,
type SingleLayerReaderConfig,
} from "@/pageEditor/store/editor/baseFormStateTypes";
import { emptyModOptionsDefinitionFactory } from "@/utils/modUtils";
import {
emptyModOptionsDefinitionFactory,
emptyModVariablesDefinitionFactory,
} from "@/utils/modUtils";
import {
type Availability,
type NormalizedAvailability,
Expand Down Expand Up @@ -123,6 +126,7 @@ export function baseFromModComponent<T extends StarterBrickType>(
| "integrationDependencies"
| "permissions"
| "optionsArgs"
| "variablesDefinition"
| "modMetadata"
> & { type: T } {
return {
Expand All @@ -134,6 +138,8 @@ export function baseFromModComponent<T extends StarterBrickType>(
integrationDependencies: config.integrationDependencies ?? [],
permissions: config.permissions ?? {},
optionsArgs: config.optionsArgs ?? {},
variablesDefinition:
config.variables ?? emptyModVariablesDefinitionFactory(),
type,
modMetadata: config._recipe,
};
Expand Down Expand Up @@ -173,6 +179,7 @@ export function baseSelectModComponent({
uuid,
label,
optionsArgs,
variablesDefinition,
integrationDependencies,
permissions,
starterBrick,
Expand All @@ -187,6 +194,7 @@ export function baseSelectModComponent({
| "integrationDependencies"
| "permissions"
| "optionsArgs"
| "variables"
> {
return {
id: uuid,
Expand All @@ -197,6 +205,7 @@ export function baseSelectModComponent({
integrationDependencies,
permissions,
optionsArgs,
variables: variablesDefinition,
};
}

Expand All @@ -209,6 +218,7 @@ export function makeInitialBaseState(
integrationDependencies: [],
permissions: emptyPermissionsFactory(),
optionsArgs: {},
variablesDefinition: emptyModVariablesDefinitionFactory(),
modComponent: {
brickPipeline: [],
},
Expand Down
35 changes: 30 additions & 5 deletions src/pageEditor/store/editor/baseFormStateTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import { type UUID } from "@/types/stringTypes";
import { type StarterBrickType } from "@/types/starterBrickTypes";
import { type Permissions } from "webextension-polyfill";
import { type ModComponentBase } from "@/types/modComponentTypes";
import { type ModOptionsDefinition } from "@/types/modDefinitionTypes";
import {
type ModOptionsDefinition,
type ModVariablesDefinition,
} from "@/types/modDefinitionTypes";
import { type BrickPipeline } from "@/bricks/types";
import { type Metadata, type RegistryId } from "@/types/registryTypes";
import { type NormalizedAvailability } from "@/types/availabilityTypes";
Expand Down Expand Up @@ -71,6 +74,7 @@ export type BaseModComponentState = BaseModComponentStateV2;

/**
* @deprecated - Do not use versioned state types directly
* @see BaseFormState
*/
export interface BaseFormStateV1<
TModComponent extends BaseModComponentStateV1 = BaseModComponentStateV1,
Expand Down Expand Up @@ -143,6 +147,7 @@ export interface BaseFormStateV1<

/**
* @deprecated - Do not use versioned state types directly
* @see BaseFormState
*/
export type BaseFormStateV2<
TModComponent extends BaseModComponentStateV1 = BaseModComponentStateV1,
Expand All @@ -159,6 +164,7 @@ export type BaseFormStateV2<

/**
* @deprecated - Do not use versioned state types directly
* @see BaseFormState
*/
export type BaseFormStateV3<
TModComponent extends BaseModComponentStateV2 = BaseModComponentStateV2,
Expand Down Expand Up @@ -194,6 +200,7 @@ export type BaseFormStateV3<

/**
* @deprecated - Do not use versioned state types directly
* @see BaseFormState
*/
export type BaseFormStateV4<
TModComponent extends BaseModComponentStateV2 = BaseModComponentStateV2,
Expand All @@ -207,15 +214,33 @@ export type BaseFormStateV4<
"type"
>;

/**
* Base form state version that introduces a variablesDefinition section for declaring mod variables.
* @deprecated - Do not use versioned state types directly
* @see BaseFormState
* @since 2.1.2
*/
export type BaseFormStateV5<
TModComponent extends BaseModComponentState = BaseModComponentState,
TStarterBrick extends BaseStarterBrickState = BaseStarterBrickState,
> = BaseFormStateV4<TModComponent, TStarterBrick> & {
/**
* The mod variable definitions/declarations
* @see ModDefinition.variables
* @since 2.1.2
*/
variablesDefinition: ModVariablesDefinition;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the mismatch between mod.variables and variablesDefinition on the formState?

Copy link
Contributor Author

@twschiller twschiller Sep 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matching the naming conventions for the 2 types:

options?: ModOptionsDefinition;

Mod definition:

export interface UnsavedModDefinition extends Definition {
  kind: typeof DefinitionKinds.MOD;
  extensionPoints: ModComponentDefinition[];
  definitions?: InnerDefinitions;
  options?: ModOptionsDefinition;
  variables?: ModVariablesDefinition;
}

Versus form state:

  /**
   * Information about the mod options or `undefined`
   * if the mod component is not part of a mod.
   * @see ModDefinition.options
   */
  optionsDefinition?: ModOptionsDefinition;
  
    /**
   * The mod variable definitions/declarations
   * @see ModDefinition.variables
   * @since 2.1.2
   */
  variablesDefinition: ModVariablesDefinition;

Personally, I'm indifferent. So if there's preference on where the team wants the naming to go, we should adopt it now to avoid having to write another migration in the future

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be migrating to a world where the formstate and the mod definition are as similar as possible (ideally a formstate would just be a mutable mod definition?). I would like to have @BLoe to weigh in though since he's spent the most time dealing with the form states in the Page Editor

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'd prefer to use the Definitions and Values suffixes everywhere possible to differentiate between the definitions and current values. I don't really care if we do that here/now or a DX change later that updates these all at once to add the suffix.

Copy link
Contributor Author

@twschiller twschiller Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'd prefer to use the Definitions and Values suffixes everywhere possible to differentiate between the definitions and current values

In ModDefinition, everything is definition. So using suffix on sub-properties would be redundant. Therefore, I wouldn't use the suffix there:

export interface UnsavedModDefinition extends Definition {
  kind: typeof DefinitionKinds.MOD;
  extensionPoints: ModComponentDefinition[];
  definitions?: InnerDefinitions;
  options?: ModOptionsDefinition;
  variables?: ModVariablesDefinition;
}

I've updated the change in ModComponent clarify that it's the definition and not the variables themselves

};

export type BaseFormState<
TModComponent extends BaseModComponentState = BaseModComponentState,
TStarterBrick extends BaseStarterBrickState = BaseStarterBrickState,
> = Except<
BaseFormStateV4<TModComponent, TStarterBrick>,
// On migration, re-point this type to the most recent BaseFormStateV<N> type name
BaseFormStateV5<TModComponent, TStarterBrick>,
// NOTE: this is not changing the type shape/structure. It's just cleaning up the type name/reference which makes
// types easier to work with for testing migrations.
"integrationDependencies"
> & {
/**
* Using the un-versioned type
*/
integrationDependencies: IntegrationDependency[];
};
2 changes: 1 addition & 1 deletion src/pageEditor/store/editor/editorSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ export const persistEditorConfig = {
// Change the type of localStorage to our overridden version so that it can be exported
// See: @/store/StorageInterface.ts
storage: localStorage as StorageInterface,
version: 7,
version: 8,
migrate: createMigrate(migrations, { debug: Boolean(process.env.DEBUG) }),
blacklist: [
"inserting",
Expand Down
31 changes: 29 additions & 2 deletions src/pageEditor/store/editor/pageEditorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
type BaseFormStateV2,
type BaseFormStateV3,
type BaseFormStateV4,
type BaseFormStateV5,
} from "@/pageEditor/store/editor/baseFormStateTypes";

export type AddBrickLocation = {
Expand Down Expand Up @@ -381,12 +382,38 @@ export type EditorStateV5 = Except<
*/
export type EditorStateV6 = Except<EditorStateV5, "insertingStarterBrickType">;

// Instead of maintaining old enums, just clearing data panel state on migration, see migrateEditorStateV5
/**
* Version bump to account for changes in DataPanelTabKeys.
*
* Same type as EditorStateV6, but bumped because there's an associated migration to clear out the Data Panel UI state.
*
* @deprecated - Do not use versioned state types directly, exported for testing
* @see migrateEditorStateV6
* @see DataPanelTabKey
*/
export type EditorStateV7 = EditorStateV6;

export type EditorState = Except<
/**
* Version bump to account for variableDefinition property added in BaseFormState
*
* @deprecated - Do not use versioned state types directly, exported for testing
* @see migrateEditorStateV7
*/
export type EditorStateV8 = Except<
EditorStateV7,
"modComponentFormStates" | "deletedModComponentFormStatesByModId"
> & {
modComponentFormStates: BaseFormStateV5[];
deletedModComponentFormStatesByModId: Record<string, BaseFormStateV5[]>;
};

export type EditorState = Except<
// On migration, re-point this type to the most recent EditorStateV<N> type name
EditorStateV8,
// Swap out any properties with versioned types for type references to the latest version.
// NOTE: this is not changing the type shape/structure. It's just cleaning up the type name/reference which makes
// types easier to work with for testing migrations.
twschiller marked this conversation as resolved.
Show resolved Hide resolved
"modComponentFormStates" | "deletedModComponentFormStatesByModId"
> & {
modComponentFormStates: ModComponentFormState[];
deletedModComponentFormStatesByModId: Record<string, ModComponentFormState[]>;
Expand Down
49 changes: 49 additions & 0 deletions src/store/editorMigrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
type EditorStateV5,
type EditorStateV6,
type EditorStateV7,
type EditorStateV8,
} from "@/pageEditor/store/editor/pageEditorTypes";
import { cloneDeep, mapValues, omit } from "lodash";
import {
Expand Down Expand Up @@ -56,13 +57,15 @@
migrateEditorStateV4,
migrateEditorStateV5,
migrateEditorStateV6,
migrateEditorStateV7,
} from "@/store/editorMigrations";
import { type FactoryConfig } from "cooky-cutter/dist/define";
import { StarterBrickTypes } from "@/types/starterBrickTypes";
import {
FOUNDATION_NODE_ID,
makeInitialBrickPipelineUIState,
} from "@/pageEditor/store/editor/uiState";
import { modComponentToFormState } from "@/pageEditor/starterBricks/adapter";

Check failure on line 68 in src/store/editorMigrations.test.ts

View workflow job for this annotation

GitHub Actions / types

'modComponentToFormState' is declared but its value is never read.

const initialStateV1: EditorStateV1 & PersistedState = {
selectionSeq: 0,
Expand Down Expand Up @@ -249,6 +252,40 @@
const initialStateV7: EditorStateV7 & PersistedState =
cloneDeep(initialStateV6);

const initialStateV8: EditorStateV8 & PersistedState = {
selectionSeq: 0,
activeModComponentId: null,
activeModId: null,
expandedModId: null,
error: null,
beta: false,
modComponentFormStates: [],
knownEditableBrickIds: [],
dirty: {},
isBetaUI: false,
copiedBrick: undefined,
brickPipelineUIStateById: {},
dirtyModOptionsById: {},
dirtyModMetadataById: {},
visibleModalKey: null,
addBrickLocation: undefined,
keepLocalCopyOnCreateMod: false,
deletedModComponentFormStatesByModId: {},
availableActivatedModComponentIds: [],
isPendingAvailableActivatedModComponents: false,
availableDraftModComponentIds: [],
isPendingDraftModComponents: false,
isModListExpanded: true,
isDataPanelExpanded: true,
isDimensionsWarningDismissed: false,
isVariablePopoverVisible: false,
// Function under test does not handle updating the persistence, this is handled by redux-persist
_persist: {
version: 1,
rehydrated: false,
},
};

function unmigrateServices(
integrationDependencies: IntegrationDependencyV2[] = [],
): IntegrationDependencyV1[] {
Expand Down Expand Up @@ -615,4 +652,16 @@
);
});
});

describe("migrateEditorState V7 to V8", () => {
it("migrates empty state", () => {
expect(migrateEditorStateV7(initialStateV7)).toStrictEqual(
initialStateV8,
);
});

it("add variable definitions section", () => {
throw Error("Not implemented");

Check failure on line 664 in src/store/editorMigrations.test.ts

View workflow job for this annotation

GitHub Actions / lint

Use `new` when throwing an error

Check failure on line 664 in src/store/editorMigrations.test.ts

View workflow job for this annotation

GitHub Actions / lint

Use `new Error()` instead of `Error()`

Check failure on line 664 in src/store/editorMigrations.test.ts

View workflow job for this annotation

GitHub Actions / test

editor state migrations › migrateEditorState V7 to V8 › add variable definitions section

Not implemented at Object.Error (src/store/editorMigrations.test.ts:664:13)
});
});
});
26 changes: 26 additions & 0 deletions src/store/editorMigrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
type BaseFormStateV2,
type BaseFormStateV3,
type BaseFormStateV4,
type BaseFormStateV5,
type BaseModComponentStateV1,
type BaseModComponentStateV2,
} from "@/pageEditor/store/editor/baseFormStateTypes";
Expand All @@ -30,19 +31,22 @@ import {
type IntegrationDependencyV2,
} from "@/integrations/integrationTypes";
import {
type EditorState,
type EditorStateV1,
type EditorStateV2,
type EditorStateV3,
type EditorStateV4,
type EditorStateV5,
type EditorStateV6,
type EditorStateV7,
type EditorStateV8,
} from "@/pageEditor/store/editor/pageEditorTypes";
import { type ModComponentFormState } from "@/pageEditor/starterBricks/formStateTypes";
import { produce } from "immer";
import { DataPanelTabKey } from "@/pageEditor/tabs/editTab/dataPanel/dataPanelTypes";
import { makeInitialDataTabState } from "@/pageEditor/store/editor/uiState";
import { type BrickConfigurationUIState } from "@/pageEditor/store/editor/uiStateTypes";
import { emptyModVariablesDefinitionFactory } from "@/utils/modUtils";

export const migrations: MigrationManifest = {
// Redux-persist defaults to version: -1; Initialize to positive-1-indexed
Expand All @@ -55,6 +59,7 @@ export const migrations: MigrationManifest = {
5: (state: EditorStateV4 & PersistedState) => migrateEditorStateV4(state),
6: (state: EditorStateV5 & PersistedState) => migrateEditorStateV5(state),
7: (state: EditorStateV6 & PersistedState) => migrateEditorStateV6(state),
8: (state: EditorStateV7 & PersistedState) => migrateEditorStateV7(state),
};

export function migrateIntegrationDependenciesV1toV2(
Expand Down Expand Up @@ -223,3 +228,24 @@ export function migrateEditorStateV6(
}
});
}

export function migrateEditorStateV7(
state: EditorStateV7 & PersistedState,
): EditorStateV8 & PersistedState {
// Reset the Data Panel state using the current set of DataPanelTabKeys
return produce(state, (draft) => {
for (const formState of draft.modComponentFormStates) {
(formState as BaseFormStateV5).variablesDefinition =
emptyModVariablesDefinitionFactory();
}

for (const formStates of Object.values(
draft.deletedModComponentFormStatesByModId,
)) {
for (const formState of formStates) {
(formState as BaseFormStateV5).variablesDefinition =
emptyModVariablesDefinitionFactory();
}
}
}) as EditorState & PersistedState;
}
2 changes: 2 additions & 0 deletions src/testUtils/factories/pageEditorFactories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { type BaseModComponentState } from "@/pageEditor/store/editor/baseFormSt
import { assertNotNullish } from "@/utils/nullishUtils";
import { type Permissions } from "webextension-polyfill";
import { validateOutputKey } from "@/runtime/runtimeTypes";
import { emptyModVariablesDefinitionFactory } from "@/utils/modUtils";

const baseModComponentStateFactory = define<BaseModComponentState>({
brickPipeline: () => pipelineFactory(),
Expand All @@ -75,6 +76,7 @@ const internalFormStateFactory = define<InternalFormStateOverride>({
uuid: uuidSequence,
installed: true,
optionsArgs: () => ({}) as OptionsArgs,
variablesDefinition: () => emptyModVariablesDefinitionFactory(),
integrationDependencies(): IntegrationDependency[] {
return [];
},
Expand Down
Loading
Loading