From 6c441b826fcb2f7a301c5122ebc0fed2b6a0e94b Mon Sep 17 00:00:00 2001 From: Graham Langford <30706330+grahamlangford@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:30:22 -0500 Subject: [PATCH 1/2] Create a transformer to enforce current terminology in reportEvent without breaking Mixpanel events (#8774) * wip * fix remaining ts errors * block/service -> brick/integration * fix merge issues * renaming * renaming * fix tests * remove unused export * improve typing * refactoring * bump logging database number * add telemetryTypes to tsconfig.strictNullChecks * pr review related changes --- src/__snapshots__/Storyshots.test.js.snap | 6 +- src/activation/useActivateMod.ts | 4 +- src/background/executor.test.ts | 4 +- src/background/messenger/api.ts | 2 +- src/background/messenger/registration.ts | 6 +- .../removeModComponentForEveryTab.ts | 2 +- src/background/requests.ts | 4 +- src/background/telemetry.test.ts | 8 +-- src/background/telemetry.ts | 4 +- .../effects/AddDynamicTextSnippet.test.ts | 14 ++-- src/bricks/effects/AddDynamicTextSnippet.ts | 4 +- src/bricks/effects/AddQuickBarAction.tsx | 4 +- src/bricks/effects/AddTextSnippets.test.ts | 2 +- src/bricks/effects/AddTextSnippets.ts | 4 +- src/bricks/effects/assignModVariable.test.ts | 4 +- src/bricks/effects/assignModVariable.ts | 2 +- src/bricks/effects/attachAutocomplete.test.ts | 2 +- src/bricks/effects/customEvent.test.ts | 2 +- src/bricks/effects/pageState.test.ts | 18 +++--- src/bricks/effects/pageState.ts | 8 +-- src/bricks/effects/sidebarEffects.ts | 2 +- src/bricks/effects/tourEffect.ts | 7 +- src/bricks/renderers/customForm.test.tsx | 4 +- src/bricks/renderers/customForm.tsx | 6 +- .../transformers/RunBrickByIdTransformer.ts | 2 +- .../RunMetadataTransformer.test.ts | 36 +++++------ .../transformers/RunMetadataTransformer.ts | 8 +-- .../controlFlow/WithAsyncModVariable.test.ts | 4 +- .../controlFlow/WithAsyncModVariable.ts | 2 +- .../DisplayTemporaryInfo.test.ts | 10 +-- .../temporaryInfo/EphemeralPanel.tsx | 7 +- .../IntegrationDependencyWidget.tsx | 10 ++- src/components/logViewer/EntryRow.tsx | 2 +- src/components/logViewer/LogTable.stories.tsx | 10 +-- src/components/logViewer/LogTable.tsx | 2 +- .../__snapshots__/LogCard.test.tsx.snap | 2 +- src/contentScript/sidebarActivation.ts | 2 +- .../google/sheets/bricks/append.test.ts | 4 +- src/data/service/errorService.ts | 24 +++---- .../pages/integrations/IntegrationsPage.tsx | 2 +- .../pages/mods/hooks/useActivateAction.ts | 4 +- .../pages/mods/hooks/useReactivateAction.ts | 13 ++-- .../pages/mods/hooks/useViewLogsAction.ts | 2 +- .../pages/packageEditor/useLogContext.ts | 8 +-- src/hooks/useUserAction.ts | 4 +- src/mods/hooks/useDeactivateAction.ts | 4 +- .../documentBuilder/render/ListElement.tsx | 2 +- src/pageEditor/hooks/useDeactivateMod.tsx | 2 +- .../useRemoveModComponentFromStorage.tsx | 2 +- src/pageEditor/hooks/useResetModComponent.ts | 2 +- .../hooks/useSaveStandaloneModComponent.ts | 2 +- .../ActivatedModComponentListItem.tsx | 2 +- .../DraftModComponentListItem.tsx | 2 +- .../modals/addBrickModal/useAddBrick.ts | 2 +- src/pageEditor/panes/ModEditorPane.tsx | 2 +- .../tabs/editTab/useReportTraceError.ts | 2 +- src/pageEditor/toolbar/ReloadToolbar.tsx | 2 +- src/runtime/pipelineTests/trace.test.ts | 12 +++- src/runtime/reducePipeline.ts | 18 +++--- src/sidebar/PanelBody.test.tsx | 28 ++++---- src/sidebar/PanelBody.tsx | 2 +- src/sidebar/RendererComponent.test.tsx | 2 +- src/sidebar/Tabs.tsx | 6 +- src/sidebar/TemporaryPanelTabPane.tsx | 2 +- src/sidebar/activateMod/ActivateModInputs.tsx | 4 +- .../button/buttonStarterBrick.ts | 4 +- .../contextMenu/contextMenuStarterBrick.ts | 4 +- .../sidebar/sidebarStarterBrick.ts | 4 +- .../trigger/triggerStarterBrick.test.ts | 2 +- src/starterBricks/types.ts | 2 +- src/store/extensionsSlice.ts | 4 +- src/telemetry/deployments.ts | 16 ++--- src/telemetry/events.ts | 2 - src/telemetry/initErrorReporter.ts | 2 +- src/telemetry/logging.test.ts | 18 +++--- src/telemetry/logging.ts | 40 ++++++------ src/telemetry/reportEvent.ts | 14 ++-- src/telemetry/telemetryHelpers.test.ts | 12 ++-- src/telemetry/telemetryHelpers.ts | 64 +++++++++++++++---- src/telemetry/telemetryTypes.ts | 54 ++++++++++++++++ src/testUtils/factories/logFactories.ts | 2 +- src/testUtils/factories/runtimeFactories.ts | 12 ++-- src/tinyPages/offscreen.ts | 2 +- src/tsconfig.strictNullChecks.json | 1 + src/types/loggerTypes.ts | 20 +++--- src/types/sidebarTypes.ts | 3 +- src/utils/modUtils.ts | 35 +++++----- 87 files changed, 408 insertions(+), 290 deletions(-) create mode 100644 src/telemetry/telemetryTypes.ts diff --git a/src/__snapshots__/Storyshots.test.js.snap b/src/__snapshots__/Storyshots.test.js.snap index 21fd613bbe..5f91f2f3ee 100644 --- a/src/__snapshots__/Storyshots.test.js.snap +++ b/src/__snapshots__/Storyshots.test.js.snap @@ -2976,7 +2976,7 @@ exports[`Storyshots Editor/LogTable No Entries 1`] = ` Label - Block/Service + Brick/Integration Message/Error @@ -3038,7 +3038,7 @@ exports[`Storyshots Editor/LogTable No Entries For Level 1`] = ` Label - Block/Service + Brick/Integration Message/Error @@ -3100,7 +3100,7 @@ exports[`Storyshots Editor/LogTable Populated 1`] = ` Label - Block/Service + Brick/Integration Message/Error diff --git a/src/activation/useActivateMod.ts b/src/activation/useActivateMod.ts index e3fdf93dcf..fff10a40d2 100644 --- a/src/activation/useActivateMod.ts +++ b/src/activation/useActivateMod.ts @@ -53,8 +53,8 @@ type ActivationSource = "marketplace" | "extensionConsole"; function selectActivateEventData(modDefinition: ModDefinition) { return { - blueprintId: modDefinition.metadata.id, - extensions: modDefinition.extensionPoints.map((x) => x.label), + modId: modDefinition.metadata.id, + modComponents: modDefinition.extensionPoints.map((x) => x.label), }; } diff --git a/src/background/executor.test.ts b/src/background/executor.test.ts index 11880684db..ae343d1640 100644 --- a/src/background/executor.test.ts +++ b/src/background/executor.test.ts @@ -43,11 +43,11 @@ const runBrickMock = jest.mocked(runBrick); const optionsFactory = define({ ctxt: () => ({}), messageContext: (i: number) => ({ - extensionId: uuidSequence(i), + modComponentId: uuidSequence(i), }), meta: derive( (options) => ({ - extensionId: options.messageContext!.extensionId, + extensionId: options.messageContext!.modComponentId, runId: null, branches: [], }), diff --git a/src/background/messenger/api.ts b/src/background/messenger/api.ts index 153c4c1b02..e5f53f1d9d 100644 --- a/src/background/messenger/api.ts +++ b/src/background/messenger/api.ts @@ -70,7 +70,7 @@ export const recordLog = getNotifier("RECORD_LOG", bg); export const clearLogs = getMethod("CLEAR_LOGS", bg); export const clearLog = getMethod("CLEAR_LOG", bg); export const clearExtensionDebugLogs = getMethod( - "CLEAR_EXTENSION_DEBUG_LOGS", + "CLEAR_MOD_COMPONENT_DEBUG_LOGS", bg, ); diff --git a/src/background/messenger/registration.ts b/src/background/messenger/registration.ts index f63cbad8ba..857ea76d46 100644 --- a/src/background/messenger/registration.ts +++ b/src/background/messenger/registration.ts @@ -41,7 +41,7 @@ import * as packageRegistry from "@/registry/packageRegistry"; import integrationRegistry from "@/integrations/registry"; import { getUserData } from "@/auth/authStorage"; import { - clearExtensionDebugLogs, + clearModComponentDebugLogs, clearLog, clearLogs, recordError, @@ -110,7 +110,7 @@ declare global { RECORD_ERROR: typeof recordError; CLEAR_LOGS: typeof clearLogs; CLEAR_LOG: typeof clearLog; - CLEAR_EXTENSION_DEBUG_LOGS: typeof clearExtensionDebugLogs; + CLEAR_MOD_COMPONENT_DEBUG_LOGS: typeof clearModComponentDebugLogs; INTEGRATION_REGISTRY_CLEAR: typeof integrationRegistry.clear; LOCATOR_FIND_ALL_SANITIZED_CONFIGS_FOR_INTEGRATION: typeof integrationConfigLocator.findAllSanitizedConfigsForIntegration; @@ -183,7 +183,7 @@ export default function registerMessenger(): void { RECORD_ERROR: recordError, CLEAR_LOGS: clearLogs, CLEAR_LOG: clearLog, - CLEAR_EXTENSION_DEBUG_LOGS: clearExtensionDebugLogs, + CLEAR_MOD_COMPONENT_DEBUG_LOGS: clearModComponentDebugLogs, INTEGRATION_REGISTRY_CLEAR: integrationRegistry.clear.bind(integrationRegistry), diff --git a/src/background/removeModComponentForEveryTab.ts b/src/background/removeModComponentForEveryTab.ts index 2ecca0de29..e414a3b70b 100644 --- a/src/background/removeModComponentForEveryTab.ts +++ b/src/background/removeModComponentForEveryTab.ts @@ -39,5 +39,5 @@ export async function removeModComponentForEveryTab( }); await uninstallContextMenu({ extensionId: modComponentId }); await clearModComponentTraces(modComponentId); - await clearLog({ extensionId: modComponentId }); + await clearLog({ modComponentId }); } diff --git a/src/background/requests.ts b/src/background/requests.ts index 2180b1eeb3..a2064aec36 100644 --- a/src/background/requests.ts +++ b/src/background/requests.ts @@ -349,8 +349,8 @@ async function getIntegrationMessageContext( } return { - serviceId: config.serviceId, - serviceVersion: resolvedIntegration?.version, + integrationId: config.serviceId, + integrationVersion: resolvedIntegration?.version, authId: config.id, }; } diff --git a/src/background/telemetry.test.ts b/src/background/telemetry.test.ts index db43b94de7..771afb6008 100644 --- a/src/background/telemetry.test.ts +++ b/src/background/telemetry.test.ts @@ -21,7 +21,7 @@ import { TEST_flushAll, } from "@/background/telemetry"; import { appApiMock } from "@/testUtils/appApiMock"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; const EXPECTED_RUNTIME_ID = "abc123"; const expectedManifestValues = { @@ -53,13 +53,13 @@ beforeEach(async () => { describe("recordEvent", () => { test("runs", async () => { - await recordEvent({ event: "TestEvent" as Event, data: {} }); + await recordEvent({ event: "TestEvent" as TelemetryEvent, data: {} }); const events = await flushEvents(); expect(events).toHaveLength(1); }); test("includes expected default properties", async () => { - const testEvent = { event: "TestEvent" as Event, data: {} }; + const testEvent = { event: "TestEvent" as TelemetryEvent, data: {} }; await recordEvent(testEvent); const events = await flushEvents(); expect(events[0]).toMatchObject({ @@ -76,7 +76,7 @@ describe("recordEvent", () => { test("successfully persists concurrent telemetry events to local storage", async () => { // Easiest way to test race condition without having to mock const recordTestEvents = Array.from({ length: 100 }, async () => - recordEvent({ event: "TestEvent" as Event, data: {} }), + recordEvent({ event: "TestEvent" as TelemetryEvent, data: {} }), ); await Promise.all(recordTestEvents); diff --git a/src/background/telemetry.ts b/src/background/telemetry.ts index 797cee2082..4e1018dac4 100644 --- a/src/background/telemetry.ts +++ b/src/background/telemetry.ts @@ -30,7 +30,7 @@ import { count as logSize } from "@/telemetry/logging"; import { count as traceSize } from "@/telemetry/trace"; import { getUUID } from "@/telemetry/telemetryHelpers"; import { getExtensionVersion, getTabsWithAccess } from "@/utils/extensionUtils"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; const EVENT_BUFFER_DEBOUNCE_MS = 2000; const EVENT_BUFFER_MAX_MS = 10_000; @@ -333,7 +333,7 @@ export async function recordEvent({ event, data = {}, }: { - event: Event; + event: TelemetryEvent; data: UnknownObject | undefined; }): Promise { if (await allowsTrack()) { diff --git a/src/bricks/effects/AddDynamicTextSnippet.test.ts b/src/bricks/effects/AddDynamicTextSnippet.test.ts index 58e5c2fa0b..13f66c009c 100644 --- a/src/bricks/effects/AddDynamicTextSnippet.test.ts +++ b/src/bricks/effects/AddDynamicTextSnippet.test.ts @@ -49,8 +49,8 @@ describe("AddDynamicTextSnippet", () => { async (shortcut) => { const extensionId = uuidv4(); const logger = new ConsoleLogger({ - extensionId, - blueprintId: registryIdFactory(), + modComponentId: extensionId, + modId: registryIdFactory(), }); const pipeline = { @@ -81,8 +81,8 @@ describe("AddDynamicTextSnippet", () => { componentId: extensionId, context: { ...logger.context, - blockId: brick.id, - blockVersion: expect.toBeString(), + brickId: brick.id, + brickVersion: expect.toBeString(), label: brick.name, }, }, @@ -98,7 +98,7 @@ describe("AddDynamicTextSnippet", () => { "passes preview directly: %s", async (preview) => { const extensionId = uuidv4(); - const logger = new ConsoleLogger({ extensionId }); + const logger = new ConsoleLogger({ modComponentId: extensionId }); const pipeline = { id: brick.id, @@ -127,8 +127,8 @@ describe("AddDynamicTextSnippet", () => { componentId: extensionId, context: { ...logger.context, - blockId: brick.id, - blockVersion: expect.toBeString(), + brickId: brick.id, + brickVersion: expect.toBeString(), label: brick.name, }, }, diff --git a/src/bricks/effects/AddDynamicTextSnippet.ts b/src/bricks/effects/AddDynamicTextSnippet.ts index d21dd84765..4ba067f37e 100644 --- a/src/bricks/effects/AddDynamicTextSnippet.ts +++ b/src/bricks/effects/AddDynamicTextSnippet.ts @@ -114,7 +114,7 @@ class AddDynamicTextSnippet extends EffectABC { return; } - if (logger.context.extensionId == null) { + if (logger.context.modComponentId == null) { throw new Error("Must be run in the context of a mod"); } @@ -122,7 +122,7 @@ class AddDynamicTextSnippet extends EffectABC { let counter = 0; platform.snippetShortcutMenu.register({ - componentId: logger.context.extensionId, + componentId: logger.context.modComponentId, context: logger.context, // Trim leading command key in shortcut to be resilient to user input shortcut: normalizeShortcut(shortcut), diff --git a/src/bricks/effects/AddQuickBarAction.tsx b/src/bricks/effects/AddQuickBarAction.tsx index 7fbba633e0..6cafe66bdc 100644 --- a/src/bricks/effects/AddQuickBarAction.tsx +++ b/src/bricks/effects/AddQuickBarAction.tsx @@ -151,11 +151,11 @@ class AddQuickBarAction extends EffectABC { let counter = 0; // Expected parent id from QuickBarProviderExtensionPoint - const parentId = `provider-${logger.context.extensionId}`; + const parentId = `provider-${logger.context.modComponentId}`; const action: CustomAction = { // XXX: old actions will still appear in the quick bar unless the extension point clears out the old actions - id: `${logger.context.extensionId}-${title}`, + id: `${logger.context.modComponentId}-${title}`, // Additional metadata, for enabling clearing out old actions modComponentRef: mapMessageContextToModComponentRef(logger.context), // Can only provide a parent if the parent exists diff --git a/src/bricks/effects/AddTextSnippets.test.ts b/src/bricks/effects/AddTextSnippets.test.ts index d9b9df2239..33bb172ccc 100644 --- a/src/bricks/effects/AddTextSnippets.test.ts +++ b/src/bricks/effects/AddTextSnippets.test.ts @@ -53,7 +53,7 @@ describe("AddTextSnippets", () => { title: "Test", preview: "test text", handler: expect.toBeFunction(), - componentId: options.logger.context.extensionId, + componentId: options.logger.context.modComponentId, context: options.logger.context, }, ]); diff --git a/src/bricks/effects/AddTextSnippets.ts b/src/bricks/effects/AddTextSnippets.ts index b085210439..4059dca9b9 100644 --- a/src/bricks/effects/AddTextSnippets.ts +++ b/src/bricks/effects/AddTextSnippets.ts @@ -115,13 +115,13 @@ class AddTextSnippets extends EffectABC { return; } - if (logger.context.extensionId == null) { + if (logger.context.modComponentId == null) { throw new Error("Must be run in the context of a mod component"); } for (const { shortcut, title, text } of snippets) { platform.snippetShortcutMenu.register({ - componentId: logger.context.extensionId, + componentId: logger.context.modComponentId, context: logger.context, shortcut: normalizeShortcut(shortcut), title, diff --git a/src/bricks/effects/assignModVariable.test.ts b/src/bricks/effects/assignModVariable.test.ts index c4b2363f5b..778ce1b70a 100644 --- a/src/bricks/effects/assignModVariable.test.ts +++ b/src/bricks/effects/assignModVariable.test.ts @@ -35,8 +35,8 @@ const modId = validateRegistryId("test/123"); const brick = new AssignModVariable(); const logger = new ConsoleLogger({ - extensionId: modComponentId, - blueprintId: modId, + modComponentId, + modId, }); const brickOptions = brickOptionsFactory({ logger }); diff --git a/src/bricks/effects/assignModVariable.ts b/src/bricks/effects/assignModVariable.ts index 922df3c7a5..15a319a6e0 100644 --- a/src/bricks/effects/assignModVariable.ts +++ b/src/bricks/effects/assignModVariable.ts @@ -96,7 +96,7 @@ class AssignModVariable extends EffectABC { }>, { logger }: BrickOptions, ): Promise { - const { blueprintId: modId, extensionId: modComponentId } = logger.context; + const { modId, modComponentId } = logger.context; setState({ namespace: StateNamespaces.MOD, diff --git a/src/bricks/effects/attachAutocomplete.test.ts b/src/bricks/effects/attachAutocomplete.test.ts index 23b62827f9..4b223d196f 100644 --- a/src/bricks/effects/attachAutocomplete.test.ts +++ b/src/bricks/effects/attachAutocomplete.test.ts @@ -24,7 +24,7 @@ import { brickOptionsFactory } from "@/testUtils/factories/runtimeFactories"; const brick = new AttachAutocomplete(); const logger = new ConsoleLogger({ - extensionId: uuidSequence(0), + modComponentId: uuidSequence(0), }); describe("AttachAutocomplete", () => { diff --git a/src/bricks/effects/customEvent.test.ts b/src/bricks/effects/customEvent.test.ts index 19baad147e..17db0f9be4 100644 --- a/src/bricks/effects/customEvent.test.ts +++ b/src/bricks/effects/customEvent.test.ts @@ -24,7 +24,7 @@ import { brickOptionsFactory } from "@/testUtils/factories/runtimeFactories"; const brick = new CustomEventEffect(); const logger = new ConsoleLogger({ - extensionId: uuidSequence(0), + modComponentId: uuidSequence(0), }); describe("CustomEventEffect", () => { diff --git a/src/bricks/effects/pageState.test.ts b/src/bricks/effects/pageState.test.ts index 1e1935abfe..c7fecce72d 100644 --- a/src/bricks/effects/pageState.test.ts +++ b/src/bricks/effects/pageState.test.ts @@ -34,8 +34,8 @@ describe("@pixiebrix/state/get", () => { test("default to blueprint state", async () => { const brick = new GetPageState(); const logger = new ConsoleLogger({ - extensionId: uuidv4(), - blueprintId: validateRegistryId("test/123"), + modComponentId: uuidv4(), + modId: validateRegistryId("test/123"), }); await brick.transform( unsafeAssumeValidArg({}), @@ -53,8 +53,8 @@ describe("@pixiebrix/state/set", () => { test("shallow merge", async () => { const brick = new SetPageState(); const logger = new ConsoleLogger({ - extensionId: uuidv4(), - blueprintId: validateRegistryId("test/123"), + modComponentId: uuidv4(), + modId: validateRegistryId("test/123"), }); let result = await brick.transform( @@ -78,8 +78,8 @@ describe("@pixiebrix/state/set", () => { const brick = new SetPageState(); const logger = new ConsoleLogger({ - extensionId: uuidv4(), - blueprintId: validateRegistryId("test/123"), + modComponentId: uuidv4(), + modId: validateRegistryId("test/123"), }); const original = { @@ -164,8 +164,8 @@ describe("set and get", () => { const setState = new SetPageState(); const getState = new GetPageState(); const logger = new ConsoleLogger({ - extensionId: uuidv4(), - blueprintId: validateRegistryId("test/123"), + modComponentId: uuidv4(), + modId: validateRegistryId("test/123"), }); await setState.transform( @@ -196,7 +196,7 @@ describe("set and get", () => { const setState = new SetPageState(); const getState = new GetPageState(); const logger = new ConsoleLogger({ - extensionId: uuidv4(), + modComponentId: uuidv4(), }); await setState.transform( diff --git a/src/bricks/effects/pageState.ts b/src/bricks/effects/pageState.ts index 85cfa2324c..ad61a7ade0 100644 --- a/src/bricks/effects/pageState.ts +++ b/src/bricks/effects/pageState.ts @@ -150,15 +150,15 @@ export class SetPageState extends TransformerABC { }>, { logger, platform }: BrickOptions, ): Promise { - const { blueprintId, extensionId } = logger.context; + const { modId, modComponentId } = logger.context; return platform.state.setState({ namespace, data, mergeStrategy, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- TODO: https://github.com/pixiebrix/pixiebrix-extension/issues/7891 - modComponentId: extensionId!, - modId: blueprintId, + modComponentId: modComponentId!, + modId, }); } } @@ -205,7 +205,7 @@ export class GetPageState extends TransformerABC { }: BrickArgs<{ namespace?: StateNamespace }>, { logger, platform }: BrickOptions, ): Promise { - const { blueprintId: modId, extensionId: modComponentId } = logger.context; + const { modId, modComponentId } = logger.context; return platform.state.getState({ namespace, diff --git a/src/bricks/effects/sidebarEffects.ts b/src/bricks/effects/sidebarEffects.ts index e4b755aa92..6b4677ac33 100644 --- a/src/bricks/effects/sidebarEffects.ts +++ b/src/bricks/effects/sidebarEffects.ts @@ -79,7 +79,7 @@ export class ShowSidebar extends EffectABC { sidebarController.updateSidebar({ force: forcePanel, panelHeading, - blueprintId: logger.context.blueprintId, + blueprintId: logger.context.modId, }), ); } diff --git a/src/bricks/effects/tourEffect.ts b/src/bricks/effects/tourEffect.ts index c98aa3d3be..4a3b0b1816 100644 --- a/src/bricks/effects/tourEffect.ts +++ b/src/bricks/effects/tourEffect.ts @@ -133,7 +133,7 @@ export class TourEffect extends EffectABC { ); } - const { extensionId } = logger.context; + const { modComponentId } = logger.context; const abortController = new AbortController(); const stylesheetLink = await injectStylesheet(stylesheetUrl); @@ -166,7 +166,10 @@ export class TourEffect extends EffectABC { ); } - assertNotNullish(extensionId, "extensionId is required to run a tour"); + assertNotNullish( + modComponentId, + "modComponentId is required to run a tour", + ); const tour = introJs() .setOptions({ diff --git a/src/bricks/renderers/customForm.test.tsx b/src/bricks/renderers/customForm.test.tsx index 58b0b3bc25..95da28e61e 100644 --- a/src/bricks/renderers/customForm.test.tsx +++ b/src/bricks/renderers/customForm.test.tsx @@ -352,8 +352,8 @@ describe("CustomFormRenderer", () => { expect( getState({ namespace: StateNamespaces.MOD, - modComponentId: options.logger.context.extensionId, - modId: options.logger.context.blueprintId, + modComponentId: options.logger.context.modComponentId, + modId: options.logger.context.modId, }), ).toStrictEqual({ name: value, diff --git a/src/bricks/renderers/customForm.tsx b/src/bricks/renderers/customForm.tsx index 07374f2344..421e3e610b 100644 --- a/src/bricks/renderers/customForm.tsx +++ b/src/bricks/renderers/customForm.tsx @@ -284,8 +284,8 @@ export class CustomFormRenderer extends RendererABC { }>, { logger, runPipeline, platform }: BrickOptions, ): Promise { - if (logger.context.extensionId == null) { - throw new Error("extensionId is required"); + if (logger.context.modComponentId == null) { + throw new Error("modComponentId is required"); } // Redundant with the JSON Schema input validation for `required`. But keeping here for clarity @@ -311,7 +311,7 @@ export class CustomFormRenderer extends RendererABC { assumeNotNullish_UNSAFE(recordId); } - const { blueprintId, extensionId } = logger.context; + const { modId: blueprintId, modComponentId: extensionId } = logger.context; const initialData = await getInitialData(storage, recordId, { blueprintId, diff --git a/src/bricks/transformers/RunBrickByIdTransformer.ts b/src/bricks/transformers/RunBrickByIdTransformer.ts index 799cf716a8..fd3e4b44f7 100644 --- a/src/bricks/transformers/RunBrickByIdTransformer.ts +++ b/src/bricks/transformers/RunBrickByIdTransformer.ts @@ -95,7 +95,7 @@ class RunBrickByIdTransformer extends TransformerABC { const { logger, ...otherOptions } = options; return brick.run(rawArguments as BrickArgs, { - logger: logger.childLogger({ blockId: brick.id }), + logger: logger.childLogger({ brickId: brick.id }), ...otherOptions, }); } diff --git a/src/bricks/transformers/RunMetadataTransformer.test.ts b/src/bricks/transformers/RunMetadataTransformer.test.ts index 73b794f5a5..7a6e5e7f23 100644 --- a/src/bricks/transformers/RunMetadataTransformer.test.ts +++ b/src/bricks/transformers/RunMetadataTransformer.test.ts @@ -29,9 +29,9 @@ const brick = new RunMetadataTransformer(); describe("RunMetadataTransformer", () => { it("returns standalone mod metadata", async () => { - const extensionId = autoUUIDSequence(); + const modComponentId = autoUUIDSequence(); const logger = new ConsoleLogger({ - extensionId, + modComponentId, }); const result = await brick.run( @@ -42,7 +42,7 @@ describe("RunMetadataTransformer", () => { ); expect(result).toEqual({ - modComponentId: extensionId, + modComponentId, deploymentId: null, mod: null, runId: null, @@ -50,12 +50,12 @@ describe("RunMetadataTransformer", () => { }); it("returns packaged mod metadata", async () => { - const extensionId = autoUUIDSequence(); - const registryId = registryIdFactory(); + const modComponentId = autoUUIDSequence(); + const modId = registryIdFactory(); const logger = new ConsoleLogger({ - extensionId, - blueprintId: registryId, - blueprintVersion: "1.0.0" as SemVerString, + modComponentId, + modId, + modVersion: "1.0.0" as SemVerString, }); const result = await brick.run( @@ -66,10 +66,10 @@ describe("RunMetadataTransformer", () => { ); expect(result).toEqual({ - modComponentId: extensionId, + modComponentId, deploymentId: null, mod: { - id: registryId, + id: modId, version: "1.0.0", }, runId: null, @@ -77,15 +77,15 @@ describe("RunMetadataTransformer", () => { }); it("returns deployed mod metadata", async () => { - const extensionId = autoUUIDSequence(); + const modComponentId = autoUUIDSequence(); const deploymentId = autoUUIDSequence(); - const registryId = registryIdFactory(); + const modId = registryIdFactory(); const runId = autoUUIDSequence(); const logger = new ConsoleLogger({ - extensionId, - blueprintId: registryId, - blueprintVersion: "1.0.0" as SemVerString, + modComponentId, + modId, + modVersion: "1.0.0" as SemVerString, deploymentId, }); @@ -95,17 +95,17 @@ describe("RunMetadataTransformer", () => { logger, meta: { runId, - extensionId, + extensionId: modComponentId, branches: [], }, }), ); expect(result).toEqual({ - modComponentId: extensionId, + modComponentId, deploymentId, mod: { - id: registryId, + id: modId, version: "1.0.0", }, runId, diff --git a/src/bricks/transformers/RunMetadataTransformer.ts b/src/bricks/transformers/RunMetadataTransformer.ts index a1264d2eff..34cdf865a8 100644 --- a/src/bricks/transformers/RunMetadataTransformer.ts +++ b/src/bricks/transformers/RunMetadataTransformer.ts @@ -110,14 +110,14 @@ class RunMetadataTransformer extends TransformerABC { return { mod: - context.blueprintId == null + context.modId == null ? null : { - id: context.blueprintId, - version: context.blueprintVersion, + id: context.modId, + version: context.modVersion, }, deploymentId: context.deploymentId ?? null, - modComponentId: context.extensionId ?? null, + modComponentId: context.modComponentId ?? null, runId: meta.runId, }; } diff --git a/src/bricks/transformers/controlFlow/WithAsyncModVariable.test.ts b/src/bricks/transformers/controlFlow/WithAsyncModVariable.test.ts index 3e275a63e2..b4557030ed 100644 --- a/src/bricks/transformers/controlFlow/WithAsyncModVariable.test.ts +++ b/src/bricks/transformers/controlFlow/WithAsyncModVariable.test.ts @@ -67,8 +67,8 @@ const extensionId = autoUUIDSequence(); const blueprintId = registryIdFactory(); const logger = new ConsoleLogger({ - extensionId, - blueprintId, + modComponentId: extensionId, + modId: blueprintId, }); function expectPageState(expectedState: UnknownObject) { diff --git a/src/bricks/transformers/controlFlow/WithAsyncModVariable.ts b/src/bricks/transformers/controlFlow/WithAsyncModVariable.ts index e1026d941a..f575b7af85 100644 --- a/src/bricks/transformers/controlFlow/WithAsyncModVariable.ts +++ b/src/bricks/transformers/controlFlow/WithAsyncModVariable.ts @@ -180,7 +180,7 @@ export class WithAsyncModVariable extends TransformerABC { { logger, runPipeline }: BrickOptions, ) { const requestId = uuidv4(); - const { blueprintId, extensionId } = logger.context; + const { modId: blueprintId, modComponentId: extensionId } = logger.context; if (isNullOrBlank(stateKey)) { throw new PropError( diff --git a/src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.test.ts b/src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.test.ts index f1a91a9564..526b7f6a11 100644 --- a/src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.test.ts +++ b/src/bricks/transformers/temporaryInfo/DisplayTemporaryInfo.test.ts @@ -352,10 +352,12 @@ describe("DisplayTemporaryInfo", () => { const options = { ...testOptions("v3"), logger: new ConsoleLogger( - modComponentRefFactory({ - extensionId, - blueprintId: null, - }), + mapModComponentRefToMessageContext( + modComponentRefFactory({ + extensionId, + blueprintId: null, + }), + ), ), }; diff --git a/src/bricks/transformers/temporaryInfo/EphemeralPanel.tsx b/src/bricks/transformers/temporaryInfo/EphemeralPanel.tsx index c8b26f8d40..453ccd8c35 100644 --- a/src/bricks/transformers/temporaryInfo/EphemeralPanel.tsx +++ b/src/bricks/transformers/temporaryInfo/EphemeralPanel.tsx @@ -35,6 +35,7 @@ import { type PanelButton } from "@/types/sidebarTypes"; import { ClosePanelAction } from "@/bricks/errors"; import styles from "./EphemeralPanel.module.scss"; import useReportError from "@/hooks/useReportError"; +import { mapModComponentRefToMessageContext } from "@/utils/modUtils"; type Mode = "modal" | "popover"; @@ -174,7 +175,9 @@ const EphemeralPanel: React.FC = () => { { resolveTemporaryPanel(target, panelNonce, action); }} @@ -216,7 +219,7 @@ const EphemeralPanel: React.FC = () => { { resolveTemporaryPanel(target, panelNonce, action); }} diff --git a/src/components/fields/schemaFields/integrations/IntegrationDependencyWidget.tsx b/src/components/fields/schemaFields/integrations/IntegrationDependencyWidget.tsx index dccafaab18..2babbb2aa9 100644 --- a/src/components/fields/schemaFields/integrations/IntegrationDependencyWidget.tsx +++ b/src/components/fields/schemaFields/integrations/IntegrationDependencyWidget.tsx @@ -177,12 +177,20 @@ function clearIntegrationSelection( const NO_AUTH_OPTIONS = freeze([]); +type SelectedEventPayload = { + integration_id?: RegistryId; + is_user_action?: boolean; + auth_label?: string; + auth_sharing_type?: string; + auth_is_local?: boolean; +}; + // The only reason these inputs are optional is for tests, need to investigate better mocking instead // @see BotOptions.test.ts const makeSelectedEventPayload = ( authOption?: AuthOption, isUserAction?: boolean, -) => { +): SelectedEventPayload => { if (!authOption) { return {}; } diff --git a/src/components/logViewer/EntryRow.tsx b/src/components/logViewer/EntryRow.tsx index 312c05f665..54b74c6e9a 100644 --- a/src/components/logViewer/EntryRow.tsx +++ b/src/components/logViewer/EntryRow.tsx @@ -87,7 +87,7 @@ const EntryRow: React.FunctionComponent<{ entry: LogEntry }> = ({ entry }) => { {dateFormat.format(new Date(Number(entry.timestamp)))} {entry.level.toUpperCase()} {entry.context?.label} - {entry.context?.blockId ?? entry.context?.serviceId ?? ""} + {entry.context?.brickId ?? entry.context?.integrationId ?? ""} {entry.message} {expanded && expandable && ( diff --git a/src/components/logViewer/LogTable.stories.tsx b/src/components/logViewer/LogTable.stories.tsx index 11feae1a7c..1450618242 100644 --- a/src/components/logViewer/LogTable.stories.tsx +++ b/src/components/logViewer/LogTable.stories.tsx @@ -63,7 +63,7 @@ const DEBUG_MESSAGE: LogEntry = { message: "Sample debug message", level: "debug", context: { - blockId, + brickId: blockId, }, }; @@ -74,7 +74,7 @@ const ERROR_MESSAGE: LogEntry = { level: "error", context: { // Just the context that will show up in the table - blockId, + brickId: blockId, }, error: serializeError(new Error("Simple error")), }; @@ -86,7 +86,7 @@ const NESTED_ERROR_MESSAGE: LogEntry = { level: "error", context: { // Just the context that will show up in the table - blockId, + brickId: blockId, }, error: serializeError( new Error("Simple error", { @@ -125,13 +125,13 @@ const CONTEXT_ERROR_MESSAGE: LogEntry = { level: "error", context: { // Just the context that will show up in the table - blockId, + brickId: blockId, }, error: serializeError( new ContextError("Invalid inputs for brick", { cause: validationError, context: { - blockId, + brickId: blockId, }, }), ), diff --git a/src/components/logViewer/LogTable.tsx b/src/components/logViewer/LogTable.tsx index 4e3b8cb973..42d2b02575 100644 --- a/src/components/logViewer/LogTable.tsx +++ b/src/components/logViewer/LogTable.tsx @@ -32,7 +32,7 @@ const LogTable: React.FunctionComponent<{ Timestamp Level Label - Block/Service + Brick/Integration Message/Error diff --git a/src/components/logViewer/__snapshots__/LogCard.test.tsx.snap b/src/components/logViewer/__snapshots__/LogCard.test.tsx.snap index 03b1d6bd29..afc73526c2 100644 --- a/src/components/logViewer/__snapshots__/LogCard.test.tsx.snap +++ b/src/components/logViewer/__snapshots__/LogCard.test.tsx.snap @@ -117,7 +117,7 @@ exports[`renders empty table 1`] = ` Label - Block/Service + Brick/Integration Message/Error diff --git a/src/contentScript/sidebarActivation.ts b/src/contentScript/sidebarActivation.ts index 30ef8c040e..21d6646900 100644 --- a/src/contentScript/sidebarActivation.ts +++ b/src/contentScript/sidebarActivation.ts @@ -88,7 +88,7 @@ function addActivateModsListener(): void { reportEvent(Events.START_MOD_ACTIVATE, { // For legacy, report the first mod id - blueprintId: modIds, + modId: modIds[0], modIds, screen: "marketplace", reinstall: modIds.some((x) => activatedModIds.has(x)), diff --git a/src/contrib/google/sheets/bricks/append.test.ts b/src/contrib/google/sheets/bricks/append.test.ts index a503efb399..9e30c23d37 100644 --- a/src/contrib/google/sheets/bricks/append.test.ts +++ b/src/contrib/google/sheets/bricks/append.test.ts @@ -183,8 +183,8 @@ describe("checkAllValueHeadersExist", () => { }); const logger = new ConsoleLogger({ - extensionId: uuidv4(), - extensionPointId: validateRegistryId("test/test"), + modComponentId: uuidv4(), + starterBrickId: validateRegistryId("test/test"), }); const GOOGLE_PKCE_SERVICE_ID = validateRegistryId("google/oauth2-pkce"); diff --git a/src/data/service/errorService.ts b/src/data/service/errorService.ts index 7e622fe6e6..8357fb01b6 100644 --- a/src/data/service/errorService.ts +++ b/src/data/service/errorService.ts @@ -135,7 +135,7 @@ export async function reportToErrorService( "reportToErrorService should only be called from the background page", ); - if (flatContext.extensionId == null) { + if (flatContext.modComponentId == null) { // Only report errors that occurred within a user-defined extension/blueprint. Other errors only go to Application error telemetry. // (They're problems with our software.) return; @@ -159,8 +159,8 @@ export async function reportToErrorService( class_name: error.name, message, deployment: flatContext.deploymentId, - extension_uuid: flatContext.extensionId, - extension_label: flatContext.extensionLabel, + extension_uuid: flatContext.modComponentId, + extension_label: flatContext.modComponentLabel, step_label: flatContext.label, user_agent: window.navigator.userAgent, user_agent_extension_version: extensionVersion, @@ -172,24 +172,24 @@ export async function reportToErrorService( // For blueprint_version/service_version/brick_version the server can't handle null value. Must leave the property // off completely. - if (flatContext.blueprintId && flatContext.blueprintVersion) { + if (flatContext.modId && flatContext.modVersion) { payload.blueprint_version = { - id: flatContext.blueprintId, - version: flatContext.blueprintVersion, + id: flatContext.modId, + version: flatContext.modVersion, }; } - if (flatContext.serviceId && flatContext.serviceVersion) { + if (flatContext.integrationId && flatContext.integrationVersion) { payload.service_version = { - id: flatContext.serviceId, - version: flatContext.serviceVersion, + id: flatContext.integrationId, + version: flatContext.integrationVersion, }; } - if (flatContext.blockId && flatContext.blockVersion) { + if (flatContext.brickId && flatContext.brickVersion) { payload.brick_version = { - id: flatContext.blockId, - version: flatContext.blockVersion, + id: flatContext.brickId, + version: flatContext.brickVersion, }; } diff --git a/src/extensionConsole/pages/integrations/IntegrationsPage.tsx b/src/extensionConsole/pages/integrations/IntegrationsPage.tsx index efb01420f8..c6ebdbdc67 100644 --- a/src/extensionConsole/pages/integrations/IntegrationsPage.tsx +++ b/src/extensionConsole/pages/integrations/IntegrationsPage.tsx @@ -331,7 +331,7 @@ const IntegrationsPage: React.VFC = () => { (integration: Integration) => { // TODO: This is possibly being reported in the wrong place? Should this be when a new config is SAVED instead? reportEvent(Events.INTEGRATION_ADD, { - serviceId: integration.id, + integrationId: integration.id, }); if (integration.isAuthorizationGrant) { diff --git a/src/extensionConsole/pages/mods/hooks/useActivateAction.ts b/src/extensionConsole/pages/mods/hooks/useActivateAction.ts index 6e3feb3dcc..914049a6e2 100644 --- a/src/extensionConsole/pages/mods/hooks/useActivateAction.ts +++ b/src/extensionConsole/pages/mods/hooks/useActivateAction.ts @@ -29,7 +29,7 @@ function useActivateAction(modViewItem: ModViewItem): (() => void) | null { const activate = () => { if (isModDefinition(mod)) { reportEvent(Events.START_MOD_ACTIVATE, { - blueprintId: mod.metadata.id, + modId: mod.metadata.id, screen: "extensionConsole", reinstall: false, }); @@ -37,7 +37,7 @@ function useActivateAction(modViewItem: ModViewItem): (() => void) | null { dispatch(push(getActivateModHashRoute(mod.metadata.id))); } else { reportEvent(Events.START_MOD_ACTIVATE, { - blueprintId: null, + modId: null, screen: "extensionConsole", reinstall: false, }); diff --git a/src/extensionConsole/pages/mods/hooks/useReactivateAction.ts b/src/extensionConsole/pages/mods/hooks/useReactivateAction.ts index 8589b06e0e..fa936a3374 100644 --- a/src/extensionConsole/pages/mods/hooks/useReactivateAction.ts +++ b/src/extensionConsole/pages/mods/hooks/useReactivateAction.ts @@ -36,23 +36,18 @@ const useReactivateAction = (modViewItem: ModViewItem): (() => void) | null => { const reactivate = () => { if (hasModDefinition) { - const blueprintId = isModDefinition(mod) - ? mod.metadata.id - : mod._recipe?.id; + const modId = isModDefinition(mod) ? mod.metadata.id : mod._recipe?.id; - assertNotNullish( - blueprintId, - "blueprintId is required to reactivate mod", - ); + assertNotNullish(modId, "modId is required to reactivate mod"); reportEvent(Events.START_MOD_ACTIVATE, { - blueprintId, + modId, screen: "extensionConsole", reinstall: true, }); const reactivatePath = `marketplace/activate/${encodeURIComponent( - blueprintId, + modId, )}?reinstall=1`; dispatch(push(reactivatePath)); diff --git a/src/extensionConsole/pages/mods/hooks/useViewLogsAction.ts b/src/extensionConsole/pages/mods/hooks/useViewLogsAction.ts index 2179c0a735..ec5d1657da 100644 --- a/src/extensionConsole/pages/mods/hooks/useViewLogsAction.ts +++ b/src/extensionConsole/pages/mods/hooks/useViewLogsAction.ts @@ -35,7 +35,7 @@ function useViewLogsAction(modViewItem: ModViewItem): (() => void) | null { messageContext: isModDefinition(mod) ? { label: getLabel(mod), - blueprintId: mod.metadata.id, + modId: mod.metadata.id, } : mapModComponentToMessageContext(mod), }), diff --git a/src/extensionConsole/pages/packageEditor/useLogContext.ts b/src/extensionConsole/pages/packageEditor/useLogContext.ts index 136ba276d2..3e1a4fccf8 100644 --- a/src/extensionConsole/pages/packageEditor/useLogContext.ts +++ b/src/extensionConsole/pages/packageEditor/useLogContext.ts @@ -55,23 +55,23 @@ function useLogContext(config: string | null) { let messageContext: MessageContext | null; switch (json.kind) { case DefinitionKinds.INTEGRATION: { - messageContext = { serviceId: id }; + messageContext = { integrationId: id }; break; } case DefinitionKinds.STARTER_BRICK: { - messageContext = { extensionPointId: id }; + messageContext = { starterBrickId: id }; break; } case DefinitionKinds.BRICK: case DefinitionKinds.READER: { - messageContext = { blockId: id }; + messageContext = { brickId: id }; break; } case DefinitionKinds.MOD: { - messageContext = { blueprintId: id }; + messageContext = { modId: id }; break; } diff --git a/src/hooks/useUserAction.ts b/src/hooks/useUserAction.ts index 7636474575..668e5f1b35 100644 --- a/src/hooks/useUserAction.ts +++ b/src/hooks/useUserAction.ts @@ -17,12 +17,12 @@ import { type DependencyList, useCallback } from "react"; import notify from "@/utils/notify"; -import { type Event } from "@/telemetry/events"; +import { type TelemetryEvent } from "@/telemetry/telemetryTypes"; import { CancelError } from "@/errors/businessErrors"; import reportEvent from "@/telemetry/reportEvent"; type Options = { - event?: Event; + event?: TelemetryEvent; errorMessage?: string; successMessage?: string; }; diff --git a/src/mods/hooks/useDeactivateAction.ts b/src/mods/hooks/useDeactivateAction.ts index 1b2595b3b0..a7ef950487 100644 --- a/src/mods/hooks/useDeactivateAction.ts +++ b/src/mods/hooks/useDeactivateAction.ts @@ -62,7 +62,7 @@ function useDeactivateAction(modViewItem: ModViewItem): (() => void) | null { await deactivateMod(modId, modComponentsFromMod, dispatch); reportEvent(Events.MOD_REMOVE, { - blueprintId: modId, + modId, }); } else { await deactivateModComponents( @@ -72,7 +72,7 @@ function useDeactivateAction(modViewItem: ModViewItem): (() => void) | null { for (const modComponent of modComponentsFromMod) { reportEvent(Events.MOD_COMPONENT_REMOVE, { - extensionId: modComponent.id, + modComponentId: modComponent.id, }); } } diff --git a/src/pageEditor/documentBuilder/render/ListElement.tsx b/src/pageEditor/documentBuilder/render/ListElement.tsx index 913d6182f8..3b20df119f 100644 --- a/src/pageEditor/documentBuilder/render/ListElement.tsx +++ b/src/pageEditor/documentBuilder/render/ListElement.tsx @@ -93,7 +93,7 @@ const ListElementInternal: React.FC = ({ config: config.__value__, context: elementContext.options.ctxt, options: apiVersionOptions("v3"), - modId: documentContext.options.logger.context.blueprintId, + modId: documentContext.options.logger.context.modId, }, )) as DocumentBuilderElement; } else { diff --git a/src/pageEditor/hooks/useDeactivateMod.tsx b/src/pageEditor/hooks/useDeactivateMod.tsx index f7f8744cdf..319d7dc375 100644 --- a/src/pageEditor/hooks/useDeactivateMod.tsx +++ b/src/pageEditor/hooks/useDeactivateMod.tsx @@ -69,7 +69,7 @@ function useDeactivateMod(): (useDeactivateConfig: Config) => Promise { ); void clearLog({ - blueprintId: modId, + modId, }); dispatch(actions.removeModData(modId)); diff --git a/src/pageEditor/hooks/useRemoveModComponentFromStorage.tsx b/src/pageEditor/hooks/useRemoveModComponentFromStorage.tsx index 379023533a..dfb38d1ff8 100644 --- a/src/pageEditor/hooks/useRemoveModComponentFromStorage.tsx +++ b/src/pageEditor/hooks/useRemoveModComponentFromStorage.tsx @@ -97,7 +97,7 @@ export function useRemoveModComponentFromStorage(): ( reportEvent(Events.PAGE_EDITOR_REMOVE, { sessionId, - extensionId: modComponentId, + modComponentId, }); try { diff --git a/src/pageEditor/hooks/useResetModComponent.ts b/src/pageEditor/hooks/useResetModComponent.ts index 14648e7c8a..56bba4ba7a 100644 --- a/src/pageEditor/hooks/useResetModComponent.ts +++ b/src/pageEditor/hooks/useResetModComponent.ts @@ -58,7 +58,7 @@ function useResetModComponent(): (useResetConfig: Config) => Promise { reportEvent(Events.PAGE_EDITOR_RESET, { sessionId, - extensionId: modComponentId, + modComponentId, }); try { diff --git a/src/pageEditor/hooks/useSaveStandaloneModComponent.ts b/src/pageEditor/hooks/useSaveStandaloneModComponent.ts index 1e86d1f28f..cc61d9d452 100644 --- a/src/pageEditor/hooks/useSaveStandaloneModComponent.ts +++ b/src/pageEditor/hooks/useSaveStandaloneModComponent.ts @@ -55,7 +55,7 @@ function useSaveStandaloneModComponent(): ModComponentSaver { } else { reportEvent(Events.PAGE_EDITOR_STANDALONE_MOD_COMPONENT_UPDATE, { sessionId, - extensionId: modComponentFormState.uuid, + modComponentId: modComponentFormState.uuid, }); } } finally { diff --git a/src/pageEditor/modListingPanel/ActivatedModComponentListItem.tsx b/src/pageEditor/modListingPanel/ActivatedModComponentListItem.tsx index e4744836eb..531a851302 100644 --- a/src/pageEditor/modListingPanel/ActivatedModComponentListItem.tsx +++ b/src/pageEditor/modListingPanel/ActivatedModComponentListItem.tsx @@ -87,7 +87,7 @@ const ActivatedModComponentListItem: React.FunctionComponent<{ try { reportEvent(Events.PAGE_EDITOR_OPEN, { sessionId, - extensionId: modComponent.id, + modComponentId: modComponent.id, }); const modComponentFormState = diff --git a/src/pageEditor/modListingPanel/DraftModComponentListItem.tsx b/src/pageEditor/modListingPanel/DraftModComponentListItem.tsx index f87d456b4e..a34f5fefef 100644 --- a/src/pageEditor/modListingPanel/DraftModComponentListItem.tsx +++ b/src/pageEditor/modListingPanel/DraftModComponentListItem.tsx @@ -164,7 +164,7 @@ const DraftModComponentListItem: React.FunctionComponent< onClick={async () => { reportEvent(Events.PAGE_EDITOR_OPEN, { sessionId, - extensionId: modComponentFormState.uuid, + modComponentId: modComponentFormState.uuid, }); dispatch(actions.setActiveModComponentId(modComponentFormState.uuid)); diff --git a/src/pageEditor/modals/addBrickModal/useAddBrick.ts b/src/pageEditor/modals/addBrickModal/useAddBrick.ts index 2eb7f60be4..794d10f191 100644 --- a/src/pageEditor/modals/addBrickModal/useAddBrick.ts +++ b/src/pageEditor/modals/addBrickModal/useAddBrick.ts @@ -158,7 +158,7 @@ function useAddBrick(): AddBrick { reportEvent(Events.BRICK_ADD, { brickId: brick.id, sessionId, - extensionId: activeModComponent.uuid, + modComponentId: activeModComponent.uuid, source: "PageEditor-BrickSearchModal", }); }, diff --git a/src/pageEditor/panes/ModEditorPane.tsx b/src/pageEditor/panes/ModEditorPane.tsx index afa32260d2..6e40c6aba6 100644 --- a/src/pageEditor/panes/ModEditorPane.tsx +++ b/src/pageEditor/panes/ModEditorPane.tsx @@ -45,7 +45,7 @@ const ModEditorPane: React.VFC = () => { useEffect(() => { const messageContext: MessageContext = { - blueprintId: activeModId, + modId: activeModId, }; dispatch(logActions.setContext(messageContext)); }, [dispatch, activeModId]); diff --git a/src/pageEditor/tabs/editTab/useReportTraceError.ts b/src/pageEditor/tabs/editTab/useReportTraceError.ts index 3989d410bb..8177815df2 100644 --- a/src/pageEditor/tabs/editTab/useReportTraceError.ts +++ b/src/pageEditor/tabs/editTab/useReportTraceError.ts @@ -37,7 +37,7 @@ function useReportTraceError(): void { if (traceError && runId && runId !== prevRunId) { reportEvent(Events.PAGE_EDITOR_MOD_COMPONENT_ERROR, { sessionId, - extensionId: traceError.extensionId, + modComponentId: traceError.extensionId, }); } } diff --git a/src/pageEditor/toolbar/ReloadToolbar.tsx b/src/pageEditor/toolbar/ReloadToolbar.tsx index dd7a2b9292..5f88f92d91 100644 --- a/src/pageEditor/toolbar/ReloadToolbar.tsx +++ b/src/pageEditor/toolbar/ReloadToolbar.tsx @@ -123,7 +123,7 @@ const ReloadToolbar: React.FunctionComponent<{ // Report before the run to report even if the run errors reportEvent(Events.PAGE_EDITOR_MANUAL_RUN, { sessionId, - extensionId: modComponentFormState.uuid, + modComponentId: modComponentFormState.uuid, }); await run(); diff --git a/src/runtime/pipelineTests/trace.test.ts b/src/runtime/pipelineTests/trace.test.ts index c8750301d9..5ede82ed19 100644 --- a/src/runtime/pipelineTests/trace.test.ts +++ b/src/runtime/pipelineTests/trace.test.ts @@ -235,7 +235,9 @@ describe("Trace normal execution", () => { instanceId, }; - const logger = new ConsoleLogger().childLogger({ extensionId }); + const logger = new ConsoleLogger().childLogger({ + modComponentId: extensionId, + }); await reducePipeline(blockConfig, simpleInput({ inputArg: "hello" }), { ...testOptions("v2"), @@ -300,7 +302,9 @@ describe("Trace normal execution", () => { }, ]; - const logger = new ConsoleLogger().childLogger({ extensionId }); + const logger = new ConsoleLogger().childLogger({ + modComponentId: extensionId, + }); await reducePipeline(blockConfig, simpleInput({ inputArg: "hello" }), { ...testOptions("v2"), @@ -353,7 +357,9 @@ describe("Trace normal execution", () => { }, ]; - const logger = new ConsoleLogger().childLogger({ extensionId }); + const logger = new ConsoleLogger().childLogger({ + modComponentId: extensionId, + }); await expect(async () => { await reducePipeline(blockConfig, simpleInput({ inputArg: "hello" }), { diff --git a/src/runtime/reducePipeline.ts b/src/runtime/reducePipeline.ts index cbebecd482..715ef0e0f1 100644 --- a/src/runtime/reducePipeline.ts +++ b/src/runtime/reducePipeline.ts @@ -570,7 +570,7 @@ async function runBrick( if (selectTraceEnabled(trace)) { getPlatform().debugger.traces.exit({ ...trace, - extensionId: logger.context.extensionId, + extensionId: logger.context.modComponentId, blockId: brick.id, isFinal: true, isRenderer: true, @@ -615,7 +615,7 @@ async function applyReduceDefaults({ const logger = providedLogger ?? new ConsoleLogger(); return { - extensionId: extensionId ?? logger.context.extensionId, + extensionId: extensionId ?? logger.context.modComponentId, validateInput: true, headless: false, // Default to the `apiVersion: v1, v2` data passing behavior and renderer behavior @@ -662,7 +662,7 @@ export async function brickReducer( runId, // Be defensive if the call site doesn't provide an extensionId // See: https://github.com/pixiebrix/pixiebrix-extension/issues/3751 - extensionId: extensionId ?? logger.context.extensionId, + extensionId: extensionId ?? logger.context.modComponentId, blockInstanceId: brickConfig.instanceId, branches, }, @@ -833,7 +833,7 @@ function throwBrickError( getPlatform().debugger.traces.exit({ runId, branches, - extensionId: logger.context.extensionId, + extensionId: logger.context.modComponentId, blockId: brickConfig.id, blockInstanceId: brickConfig.instanceId, error: serializeError(error), @@ -888,8 +888,8 @@ async function getBrickLogger( } return pipelineLogger.childLogger({ - blockId: brickConfig.id, - blockVersion: version, + brickId: brickConfig.id, + brickVersion: version, // Use the most customized name for the step label: brickConfig.label ?? @@ -909,7 +909,7 @@ export async function reduceModComponentPipeline( if (platform.capabilities.includes("debugger")) { try { - const { extensionId } = pipelineLogger.context; + const { modComponentId: extensionId } = pipelineLogger.context; if (extensionId) { // `await` promise to avoid race condition where the calls here delete entries from this call to reducePipeline @@ -962,7 +962,7 @@ export async function reducePipeline( isLastBlock: index === pipelineArray.length - 1, previousOutput: output, context: extendModVariableContext(localVariableContext, { - modId: pipelineLogger.context.blueprintId, + modId: pipelineLogger.context.modId, options, // Mod variable is updated when each block is run update: true, @@ -1035,7 +1035,7 @@ async function reducePipelineExpression( previousOutput: legacyOutput, // Assume @input and @options are present context: extendModVariableContext(context as BrickArgsContext, { - modId: pipelineLogger.context.blueprintId, + modId: pipelineLogger.context.modId, options, // Update mod variable when each block is run update: true, diff --git a/src/sidebar/PanelBody.test.tsx b/src/sidebar/PanelBody.test.tsx index 0de4c159de..01b7c0421e 100644 --- a/src/sidebar/PanelBody.test.tsx +++ b/src/sidebar/PanelBody.test.tsx @@ -30,8 +30,8 @@ import { } from "@/types/rendererTypes"; import { screen } from "shadow-dom-testing-library"; -const extensionId = uuidv4(); -const blueprintId = registryIdFactory(); +const modComponentId = uuidv4(); +const modId = registryIdFactory(); describe("PanelBody", () => { beforeAll(() => { @@ -43,14 +43,14 @@ describe("PanelBody", () => { key: uuidv4(), error: serializeError(new Error("test error")), runId: uuidv4(), - extensionId, + extensionId: modComponentId, }; const { asFragment } = render( , ); @@ -63,14 +63,14 @@ describe("PanelBody", () => { key: uuidv4(), error: serializeError(new BusinessError("test error")), runId: uuidv4(), - extensionId, + extensionId: modComponentId, }; const { asFragment } = render( , ); @@ -83,14 +83,14 @@ describe("PanelBody", () => { key: uuidv4(), error: serializeError(new CancelError("test error")), runId: uuidv4(), - extensionId, + extensionId: modComponentId, }; const { asFragment } = render( , ); @@ -102,7 +102,7 @@ describe("PanelBody", () => { const payload: RendererRunPayload = { key: uuidv4(), runId: uuidv4(), - extensionId, + extensionId: modComponentId, blockId: validateRegistryId("@pixiebrix/html"), args: { html: "

Test

", @@ -114,7 +114,7 @@ describe("PanelBody", () => { , ); @@ -129,7 +129,7 @@ describe("PanelBody", () => { const payload: RendererRunPayload = { key: uuidv4(), runId: uuidv4(), - extensionId, + extensionId: modComponentId, blockId: validateRegistryId("@pixiebrix/html"), args: { html: "

Test

", @@ -141,7 +141,7 @@ describe("PanelBody", () => { , ); @@ -157,7 +157,7 @@ describe("PanelBody", () => { const payload: RendererRunPayload = { key: uuidv4(), runId: uuidv4(), - extensionId, + extensionId: modComponentId, blockId: validateRegistryId("@pixiebrix/document"), args: { body: [ @@ -215,7 +215,7 @@ describe("PanelBody", () => { , ); diff --git a/src/sidebar/PanelBody.tsx b/src/sidebar/PanelBody.tsx index 12fb4a602f..d436d51ee4 100644 --- a/src/sidebar/PanelBody.tsx +++ b/src/sidebar/PanelBody.tsx @@ -182,7 +182,7 @@ const PanelBody: React.FunctionComponent<{ const logger = platform.logger.childLogger({ ...context, - blockId, + brickId: blockId, }); const branches = tracePath ? mapPathToTraceBranches(tracePath) : []; diff --git a/src/sidebar/RendererComponent.test.tsx b/src/sidebar/RendererComponent.test.tsx index 02335da2c0..db92d3bb3c 100644 --- a/src/sidebar/RendererComponent.test.tsx +++ b/src/sidebar/RendererComponent.test.tsx @@ -63,7 +63,7 @@ describe("RendererComponent", () => { const props = { body: [config], options: brickOptionsFactory({ - logger: new ConsoleLogger({ extensionId }), + logger: new ConsoleLogger({ modComponentId: extensionId }), meta: { runId, extensionId, diff --git a/src/sidebar/Tabs.tsx b/src/sidebar/Tabs.tsx index 6f7ddc071a..0f1c7ec0f5 100644 --- a/src/sidebar/Tabs.tsx +++ b/src/sidebar/Tabs.tsx @@ -338,7 +338,9 @@ const Tabs: React.FC = () => { isRootPanel payload={panel.payload} onAction={permanentSidebarPanelAction} - context={panel.modComponentRef} + context={mapModComponentRefToMessageContext( + panel.modComponentRef, + )} /> @@ -390,7 +392,7 @@ const Tabs: React.FC = () => { reportEvent(Events.VIEW_ERROR, { panelType: "activate", // For backward compatability, provide a single modId to the recipeToActivate property - recipeToActivate: modActivationPanel.mods[0].modId, + modToActivate: modActivationPanel.mods[0].modId, modCount: modActivationPanel.mods.length, modIds: modActivationPanel.mods.map((x) => x.modId), }); diff --git a/src/sidebar/TemporaryPanelTabPane.tsx b/src/sidebar/TemporaryPanelTabPane.tsx index 4a1c14dedc..bb583be2d1 100644 --- a/src/sidebar/TemporaryPanelTabPane.tsx +++ b/src/sidebar/TemporaryPanelTabPane.tsx @@ -75,7 +75,7 @@ export const TemporaryPanelTabPane: React.FC<{ diff --git a/src/sidebar/activateMod/ActivateModInputs.tsx b/src/sidebar/activateMod/ActivateModInputs.tsx index b815c26ac5..71a716bf11 100644 --- a/src/sidebar/activateMod/ActivateModInputs.tsx +++ b/src/sidebar/activateMod/ActivateModInputs.tsx @@ -149,7 +149,7 @@ const ActivateModInputs: React.FC = ({ variant="outline-danger" onClick={() => { reportEvent(Events.MOD_ACTIVATION_CANCEL, { - recipeId: normalizedMod?.metadata?.id, + modId: normalizedMod?.metadata?.id, }); onClickCancel(); }} @@ -160,7 +160,7 @@ const ActivateModInputs: React.FC = ({ type="submit" onClick={() => { reportEvent(Events.MOD_ACTIVATION_SUBMIT, { - recipeId: normalizedMod?.metadata?.id, + modId: normalizedMod?.metadata?.id, }); return true; }} diff --git a/src/starterBricks/button/buttonStarterBrick.ts b/src/starterBricks/button/buttonStarterBrick.ts index a83b693bc1..da836a4351 100644 --- a/src/starterBricks/button/buttonStarterBrick.ts +++ b/src/starterBricks/button/buttonStarterBrick.ts @@ -759,8 +759,8 @@ export abstract class ButtonStarterBrickABC extends StarterBrickABC { // Does not report successful event only once expect(reportEventMock).toHaveBeenCalledExactlyOnceWith("TriggerRun", { - extensionId: modComponent.id, + modComponentId: modComponent.id, }); // Reports an error once diff --git a/src/starterBricks/types.ts b/src/starterBricks/types.ts index e51f5a0ebf..dc47457afd 100644 --- a/src/starterBricks/types.ts +++ b/src/starterBricks/types.ts @@ -226,7 +226,7 @@ export abstract class StarterBrickABC this.description = metadata.description; this.instanceNonce = uuidv4(); this.logger = this.platform.logger.childLogger({ - extensionPointId: this.id, + starterBrickId: this.id, }); } diff --git a/src/store/extensionsSlice.ts b/src/store/extensionsSlice.ts index a98d8d40ef..19cdd5e85c 100644 --- a/src/store/extensionsSlice.ts +++ b/src/store/extensionsSlice.ts @@ -174,8 +174,8 @@ const extensionsSlice = createSlice({ } reportEvent(Events.MOD_ACTIVATE, { - blueprintId: modDefinition.metadata.id, - blueprintVersion: modDefinition.metadata.version, + modId: modDefinition.metadata.id, + modVersion: modDefinition.metadata.version, deploymentId: deployment?.id, screen, reinstall: isReactivate, diff --git a/src/telemetry/deployments.ts b/src/telemetry/deployments.ts index b3a374cb52..b7c28d4351 100644 --- a/src/telemetry/deployments.ts +++ b/src/telemetry/deployments.ts @@ -19,26 +19,26 @@ export function selectEventData( if (modComponent._deployment) { return { label: modComponent.label, - extensionId: modComponent.id, + modComponentId: modComponent.id, deploymentId: modComponent._deployment?.id, - extensionPointId: isRegistryId(modComponent.extensionPointId) + starterBrickId: isRegistryId(modComponent.extensionPointId) ? modComponent.extensionPointId : undefined, - blueprintId: modComponent._recipe?.id, - blueprintVersion: modComponent._recipe?.version, + modId: modComponent._recipe?.id, + modVersion: modComponent._recipe?.version, }; } if (modComponent._recipe) { return { label: modComponent.label, - extensionId: modComponent.id, - blueprintId: modComponent._recipe?.id, - blueprintVersion: modComponent._recipe?.version, + modComponentId: modComponent.id, + modId: modComponent._recipe?.id, + modVersion: modComponent._recipe?.version, }; } return { - extensionId: modComponent.id, + modComponentId: modComponent.id, }; } diff --git a/src/telemetry/events.ts b/src/telemetry/events.ts index 9428dd7f46..15bd4ab1cb 100644 --- a/src/telemetry/events.ts +++ b/src/telemetry/events.ts @@ -166,5 +166,3 @@ export const Events = { SHORTCUT_SNIPPET_RUN: "TextCommandRun", } as const; - -export type Event = (typeof Events)[keyof typeof Events]; diff --git a/src/telemetry/initErrorReporter.ts b/src/telemetry/initErrorReporter.ts index 47e1ffb7ac..047ccc7867 100644 --- a/src/telemetry/initErrorReporter.ts +++ b/src/telemetry/initErrorReporter.ts @@ -25,8 +25,8 @@ import type { LogsEvent } from "@datadog/browser-logs/src/logsEvent.types"; import { cleanDatadogVersionName, mapAppUserToTelemetryUser, - type TelemetryUser, } from "@/telemetry/telemetryHelpers"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; // eslint-disable-next-line prefer-destructuring -- process.env const ENVIRONMENT = process.env.ENVIRONMENT; diff --git a/src/telemetry/logging.test.ts b/src/telemetry/logging.test.ts index f6f199d0dc..7f191cd8f9 100644 --- a/src/telemetry/logging.test.ts +++ b/src/telemetry/logging.test.ts @@ -90,18 +90,18 @@ describe("logging", () => { await expect(count()).resolves.toBe(0); }); - test("clearLog by blueprint id", async () => { - const blueprintId = registryIdFactory(); + test("clearLog by mod id", async () => { + const modId = registryIdFactory(); await appendEntry( logEntryFactory({ - context: messageContextFactory({ blueprintId }), + context: messageContextFactory({ modId }), }), ); await appendEntry(logEntryFactory()); - await clearLog({ blueprintId }); + await clearLog({ modId }); await expect(count()).resolves.toBe(1); }); @@ -119,20 +119,20 @@ describe("logging", () => { // Increase timeout so test isn't flakey on CI due to slow append operation }, 20_000); - test("getLogEntries by blueprintId", async () => { - const blueprintId = registryIdFactory(); + test("getLogEntries by modId", async () => { + const modId = registryIdFactory(); await appendEntry( logEntryFactory({ - context: messageContextFactory({ blueprintId }), + context: messageContextFactory({ modId }), }), ); await appendEntry(logEntryFactory()); - await expect(getLogEntries({ blueprintId })).resolves.toStrictEqual([ + await expect(getLogEntries({ modId })).resolves.toStrictEqual([ expect.objectContaining({ - context: expect.objectContaining({ blueprintId }), + context: expect.objectContaining({ modId }), }), ]); }); diff --git a/src/telemetry/logging.ts b/src/telemetry/logging.ts index 199b4b1232..41e379d12d 100644 --- a/src/telemetry/logging.ts +++ b/src/telemetry/logging.ts @@ -51,7 +51,7 @@ import { const DATABASE_NAME = "LOG"; const ENTRY_OBJECT_STORE = "entries"; -const DB_VERSION_NUMBER = 3; +const DB_VERSION_NUMBER = 4; /** * Maximum number of most recent logs to keep in the database. A low-enough number that performance should not be * impacted due to the number of entries. @@ -88,11 +88,11 @@ interface LogDB extends DBSchema { value: LogEntry; key: string; indexes: { - extensionId: string; - blueprintId: string; - blockId: string; - extensionPointId: string; - serviceId: string; + modComponentId: string; + modId: string; + brickId: string; + starterBrickId: string; + integrationId: string; authId: string; }; }; @@ -103,10 +103,10 @@ type IndexKey = keyof Except< | "deploymentId" | "label" | "pageName" - | "blueprintVersion" - | "blockVersion" - | "serviceVersion" - | "extensionLabel" + | "modVersion" + | "brickVersion" + | "integrationVersion" + | "modComponentLabel" | "platformName" | "url" | "connectionType" @@ -114,11 +114,11 @@ type IndexKey = keyof Except< >; const INDEX_KEYS = [ - "extensionId", - "blueprintId", - "blockId", - "extensionPointId", - "serviceId", + "modComponentId", + "modId", + "brickId", + "starterBrickId", + "integrationId", "authId", ] as const satisfies IndexKey[]; @@ -496,17 +496,17 @@ export async function setLoggingConfig(config: LoggingConfig): Promise { } /** - * Clear all debug and trace level logs for the given extension. + * Clear all debug and trace level logs for the given mod component. */ -export async function clearExtensionDebugLogs( - extensionId: UUID, +export async function clearModComponentDebugLogs( + modComponentId: UUID, ): Promise { const db = await openLoggingDB(); try { const tx = db.transaction(ENTRY_OBJECT_STORE, "readwrite"); - const index = tx.store.index("extensionId"); - for await (const cursor of index.iterate(extensionId)) { + const index = tx.store.index("modComponentId"); + for await (const cursor of index.iterate(modComponentId)) { if (cursor.value.level === "debug" || cursor.value.level === "trace") { await cursor.delete(); } diff --git a/src/telemetry/reportEvent.ts b/src/telemetry/reportEvent.ts index f0fb6fa4d0..632928c98b 100644 --- a/src/telemetry/reportEvent.ts +++ b/src/telemetry/reportEvent.ts @@ -16,8 +16,12 @@ */ import { backgroundTarget as bg, getNotifier } from "webext-messenger"; -import { type Event } from "@/telemetry/events"; import { expectContext } from "@/utils/expectContext"; +import { + type TelemetryEvent, + type ReportEventData, +} from "@/telemetry/telemetryTypes"; +import { mapEventDataToDeprecatedTerminology } from "@/telemetry/telemetryHelpers"; expectContext( "extension", @@ -31,11 +35,11 @@ const _record = getNotifier("RECORD_EVENT", bg); * Report an event to the PixieBrix telemetry service, if the user doesn't have DNT set. * @see selectEventData */ -export default function reportEvent( - event: Event, - data: UnknownObject = {}, +export default function reportEvent( + event: TelemetryEvent, + data: ReportEventData = {}, ): void { // eslint-disable-next-line prefer-rest-params -- Needs `arguments` to avoid printing the default console.debug(...arguments); - _record({ event, data }); + _record({ event, data: mapEventDataToDeprecatedTerminology(data) }); } diff --git a/src/telemetry/telemetryHelpers.test.ts b/src/telemetry/telemetryHelpers.test.ts index c4e63903cc..50a3c3a829 100644 --- a/src/telemetry/telemetryHelpers.test.ts +++ b/src/telemetry/telemetryHelpers.test.ts @@ -45,9 +45,9 @@ describe("mapModComponentRefToEventData", () => { it("maps fields", () => { const value = modComponentRefFactory(); expect(mapModComponentRefToMessageContext(value)).toStrictEqual({ - extensionId: value.extensionId, - blueprintId: value.blueprintId, - extensionPointId: value.extensionPointId, + modComponentId: value.extensionId, + modId: value.blueprintId, + starterBrickId: value.extensionPointId, }); }); @@ -56,9 +56,9 @@ describe("mapModComponentRefToEventData", () => { blueprintId: null, }); expect(mapModComponentRefToMessageContext(value)).toStrictEqual({ - extensionId: value.extensionId, - blueprintId: undefined, - extensionPointId: value.extensionPointId, + modComponentId: value.extensionId, + modId: undefined, + starterBrickId: value.extensionPointId, }); }); diff --git a/src/telemetry/telemetryHelpers.ts b/src/telemetry/telemetryHelpers.ts index dca63053a8..11113af186 100644 --- a/src/telemetry/telemetryHelpers.ts +++ b/src/telemetry/telemetryHelpers.ts @@ -16,23 +16,12 @@ */ import type { UserData } from "@/auth/authTypes"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; import { uuidv4 } from "@/types/helpers"; import type { UUID } from "@/types/stringTypes"; import { once } from "lodash"; import { StorageItem } from "webext-storage"; -/** - * The Person model for application error telemetry. - */ -export type TelemetryUser = { - /** - * User id or browser distinct id, if the user is anonymous. - */ - id: UUID; - email?: string; - organizationId?: UUID | null; -}; - export const uuidStorage = new StorageItem("USER_UUID"); /** @@ -97,3 +86,54 @@ export async function mapAppUserToTelemetryUser( organizationId: telemetryOrganizationId ?? organizationId, }; } + +export function mapEventDataToDeprecatedTerminology( + data: UnknownObject, +): UnknownObject { + if (data.brickId) { + data.blockId = data.brickId; + } + + if (data.brickVersion) { + data.blockVersion = data.brickVersion; + } + + if (data.integrationId) { + data.serviceId = data.integrationId; + } + + if (data.integrationVersion) { + data.serviceVersion = data.integrationVersion; + } + + if (data.modId) { + data.blueprintId = data.modId; + data.recipeId = data.modId; + } + + if (data.modComponentId) { + data.extensionId = data.modComponentId; + } + + if (data.modComponentLabel) { + data.extensionLabel = data.modComponentLabel; + } + + if (data.modComponents) { + data.extensions = data.modComponents; + } + + if (data.modToActivate) { + data.recipeToActivate = data.modToActivate; + } + + if (data.modVersion) { + data.blueprintVersion = data.modVersion; + } + + if (data.starterBrickId) { + data.extensionPointId = data.starterBrickId; + } + + return data; +} diff --git a/src/telemetry/telemetryTypes.ts b/src/telemetry/telemetryTypes.ts new file mode 100644 index 0000000000..1a0ea3dc43 --- /dev/null +++ b/src/telemetry/telemetryTypes.ts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { type Events } from "@/telemetry/events"; +import { type UUID } from "@/types/stringTypes"; + +/** + * The Person model for application error telemetry. + */ +export type TelemetryUser = { + /** + * User id or browser distinct id, if the user is anonymous. + */ + id: UUID; + email?: string; + organizationId?: UUID | null; +}; + +export type TelemetryEvent = (typeof Events)[keyof typeof Events]; + +const RESERVED_KEYS = [ + "blockId", + "blockVersion", + "blueprintId", + "blueprintVersion", + "extensionId", + "extensionLabel", + "extensionPointId", + "extensions", + "recipeId", + "recipeToActivate", + "serviceId", + "serviceVersion", +] as const; + +type BanReservedKeys = { + [K in (typeof RESERVED_KEYS)[number]]?: never; +}; + +export type ReportEventData = UnknownObject & BanReservedKeys; diff --git a/src/testUtils/factories/logFactories.ts b/src/testUtils/factories/logFactories.ts index ede6d243a1..df5df91521 100644 --- a/src/testUtils/factories/logFactories.ts +++ b/src/testUtils/factories/logFactories.ts @@ -24,7 +24,7 @@ import { import { type MessageContext } from "@/types/loggerTypes"; export const messageContextFactory = define({ - extensionId: uuidSequence, + modComponentId: uuidSequence, }); export const logEntryFactory = define({ diff --git a/src/testUtils/factories/runtimeFactories.ts b/src/testUtils/factories/runtimeFactories.ts index cab6f92a12..a093b4a774 100644 --- a/src/testUtils/factories/runtimeFactories.ts +++ b/src/testUtils/factories/runtimeFactories.ts @@ -20,6 +20,7 @@ import { define, derive } from "cooky-cutter"; import ConsoleLogger from "@/utils/ConsoleLogger"; import contentScriptPlatform from "@/contentScript/contentScriptPlatform"; import { modComponentRefFactory } from "@/testUtils/factories/modComponentFactories"; +import { mapModComponentRefToMessageContext } from "@/utils/modUtils"; /** * Factory for BrickOptions to pass to Brick.run method. @@ -34,12 +35,11 @@ export const brickOptionsFactory = define({ }, platform: (_i: number) => contentScriptPlatform, logger(_i: number) { - const { blueprintId, ...rest } = modComponentRefFactory(); + const modComponentRef = modComponentRefFactory(); // MessageContext expects undefined instead of null for blueprintId - return new ConsoleLogger({ - ...rest, - blueprintId: blueprintId ?? undefined, - }); + return new ConsoleLogger( + mapModComponentRefToMessageContext(modComponentRef), + ); }, root: (_i: number) => document, runPipeline: (_i: number) => @@ -51,7 +51,7 @@ export const brickOptionsFactory = define({ meta: derive( (options) => ({ runId: null, - extensionId: options.logger?.context.extensionId, + extensionId: options.logger?.context.modComponentId, branches: [], }), "logger", diff --git a/src/tinyPages/offscreen.ts b/src/tinyPages/offscreen.ts index f5e4c57243..581c587d3f 100644 --- a/src/tinyPages/offscreen.ts +++ b/src/tinyPages/offscreen.ts @@ -16,7 +16,7 @@ */ import type { MessageContext } from "@/types/loggerTypes"; -import { type TelemetryUser } from "@/telemetry/telemetryHelpers"; +import { type TelemetryUser } from "@/telemetry/telemetryTypes"; import { type SemVerString } from "@/types/registryTypes"; import { type SerializedError } from "@/types/messengerTypes"; import { deserializeError } from "serialize-error"; diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 2a6da97ca1..1a921ff3b1 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -1431,6 +1431,7 @@ "./telemetry/reportUncaughtErrors.ts", "./telemetry/telemetryHelpers.test.ts", "./telemetry/telemetryHelpers.ts", + "./telemetry/telemetryTypes.ts", "./telemetry/trace.ts", "./telemetry/traceHelpers.test.ts", "./telemetry/traceHelpers.ts", diff --git a/src/types/loggerTypes.ts b/src/types/loggerTypes.ts index 93c46dc56a..c0812d7561 100644 --- a/src/types/loggerTypes.ts +++ b/src/types/loggerTypes.ts @@ -28,24 +28,24 @@ export type MessageContext = { * A human-readable label, e.g., provided via a `label:` directive to help identify the step context when there's * multiple blocks with the same id being used. * - * @see MessageContext.extensionLabel + * @see MessageContext.modComponentLabel */ readonly label?: string; readonly deploymentId?: UUID; - readonly blueprintId?: RegistryId; - readonly blueprintVersion?: SemVerString; - readonly extensionPointId?: RegistryId; - readonly blockId?: RegistryId; - readonly blockVersion?: SemVerString; - readonly extensionId?: UUID; + readonly modId?: RegistryId; + readonly modVersion?: SemVerString; + readonly starterBrickId?: RegistryId; + readonly brickId?: RegistryId; + readonly brickVersion?: SemVerString; + readonly modComponentId?: UUID; /** * The human-readable label for the extension. Used to identify the extension when reporting telemetry from a * blueprint. (Each extension install has a different UUID) * @since 1.6.2 */ - readonly extensionLabel?: string; - readonly serviceId?: RegistryId; - readonly serviceVersion?: SemVerString; + readonly modComponentLabel?: string; + readonly integrationId?: RegistryId; + readonly integrationVersion?: SemVerString; readonly authId?: UUID; readonly pageName?: ContextName | "unknown"; readonly url?: string; diff --git a/src/types/sidebarTypes.ts b/src/types/sidebarTypes.ts index 7db3b67ba2..f9b104938e 100644 --- a/src/types/sidebarTypes.ts +++ b/src/types/sidebarTypes.ts @@ -72,8 +72,7 @@ export function isRendererErrorPayload( * Context for panel, with fields required for functionality marked as required. */ export type PanelContext = MessageContext & { - extensionId: UUID; - blueprintId: RegistryId | null; + modComponentId: UUID; }; /** diff --git a/src/utils/modUtils.ts b/src/utils/modUtils.ts index bef5f98f86..1c990234df 100644 --- a/src/utils/modUtils.ts +++ b/src/utils/modUtils.ts @@ -56,6 +56,7 @@ import { produce } from "immer"; import { isStarterBrickDefinitionLike } from "@/starterBricks/types"; import { normalizeStarterBrickDefinitionProp } from "@/starterBricks/starterBrickUtils"; import { type MessageContext } from "@/types/loggerTypes"; +import { type SetRequired } from "type-fest"; /** * Returns the ModComponentRef for a given mod component. @@ -81,28 +82,30 @@ export function mapModComponentToMessageContext( return { // The step label will be re-assigned later in reducePipeline label: modComponent.label ?? undefined, - extensionLabel: modComponent.label ?? undefined, - extensionId: modComponent.id, - extensionPointId: modComponent.extensionPointId, + modComponentLabel: modComponent.label ?? undefined, + modComponentId: modComponent.id, + starterBrickId: modComponent.extensionPointId, deploymentId: modComponent._deployment?.id, - blueprintId: modComponent._recipe?.id, - blueprintVersion: modComponent._recipe?.version, + modId: modComponent._recipe?.id, + modVersion: modComponent._recipe?.version, }; } /** * Returns the message context for a ModComponentRef. For use with passing to reportEvent * @see selectEventData + * + * TODO: Once we update the shape of ModComponentRef, we need to audit unnecessary usage of this function */ export function mapModComponentRefToMessageContext( modComponentRef: ModComponentRef, -): MessageContext { +): SetRequired { // Fields are currently named the same. In the future, the fields might temporarily diverge. return { - extensionId: modComponentRef.extensionId, - extensionPointId: modComponentRef.extensionPointId, + modComponentId: modComponentRef.extensionId, + starterBrickId: modComponentRef.extensionPointId, // MessageContext expects undefined instead of null/undefined - blueprintId: modComponentRef.blueprintId ?? undefined, + modId: modComponentRef.blueprintId ?? undefined, }; } @@ -118,18 +121,18 @@ export function mapMessageContextToModComponentRef( context: MessageContext, ): ModComponentRef { assertNotNullish( - context.extensionId, - "extensionId is required for ModComponentRef", + context.modComponentId, + "modComponentId is required for ModComponentRef", ); assertNotNullish( - context.extensionPointId, - "extensionPointId is required for ModComponentRef", + context.starterBrickId, + "starterBrickId is required for ModComponentRef", ); return { - extensionId: context.extensionId, - blueprintId: context.blueprintId, - extensionPointId: context.extensionPointId, + extensionId: context.modComponentId, + blueprintId: context.modId, + extensionPointId: context.starterBrickId, }; } From 2404e74a446d6224dfce7ccd2917a9a72c95c8fa Mon Sep 17 00:00:00 2001 From: Graham Langford <30706330+grahamlangford@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:23:50 -0500 Subject: [PATCH 2/2] #8781: strict null checks for BrickConfiguration.tsx (#8782) * add BrickConfiguration.tsx to strictNullChecks and fix errors * fix missing prop * add already checked files * adds more files --- src/bricks/effects/CommentOptions.tsx | 6 +- .../IdentityTransformerOptions.tsx | 6 +- .../jquery/JQueryReaderOptions.tsx | 4 +- src/components/fields/optionsRegistry.ts | 4 +- .../fields/schemaFields/BasicSchemaField.tsx | 10 ++- .../fields/schemaFields/SchemaField.tsx | 2 +- .../fields/schemaFields/fieldTypeCheckers.ts | 2 +- .../schemaFields/genericOptionsFactory.tsx | 10 +-- .../fields/schemaFields/schemaFieldUtils.ts | 4 +- .../widgets/FixedInnerObjectWidget.tsx | 4 +- .../schemaFields/widgets/ObjectWidget.tsx | 2 +- .../widgets/TemplateToggleWidget.tsx | 28 +++++-- .../schemaFields/widgets/varPopup/VarMenu.tsx | 17 ++-- .../varPopup/VariablesTree.stories.tsx | 9 ++- .../widgets/varPopup/VariablesTree.test.tsx | 5 +- .../widgets/varPopup/VariablesTree.tsx | 17 ++-- .../widgets/varPopup/getMenuOptions.ts | 7 +- .../widgets/varPopup/likelyVariableUtils.ts | 2 +- .../widgets/varPopup/menuFilters.ts | 81 +++++++++++++------ .../widgets/varPopup/useKeyboardNavigation.ts | 12 +-- .../widgets/varPopup/useTreeRow.ts | 2 +- .../IntegrationConfigEditorModal.tsx | 4 +- .../automationanywhere/ApiTaskOptions.tsx | 4 +- src/contrib/automationanywhere/BotOptions.tsx | 4 +- .../sheets/ui/AppendSpreadsheetOptions.tsx | 4 +- .../sheets/ui/LookupSpreadsheetOptions.tsx | 4 +- src/contrib/uipath/LocalProcessOptions.tsx | 4 +- src/contrib/uipath/ProcessOptions.tsx | 4 +- src/contrib/zapier/PushOptions.tsx | 4 +- src/hooks/useBrickOptions.ts | 44 +++++----- src/pageEditor/fields/AlertOptions.tsx | 4 +- .../fields/AssignModVariableOptions.tsx | 4 +- .../fields/CollapsibleFieldSection.tsx | 2 +- .../ConnectedCollapsibleFieldSection.tsx | 2 +- .../tabs/effect/BrickConfiguration.tsx | 14 ++-- src/runtime/getType.ts | 7 +- src/tsconfig.strictNullChecks.json | 22 +++++ src/utils/schemaUtils.ts | 5 +- 38 files changed, 230 insertions(+), 140 deletions(-) diff --git a/src/bricks/effects/CommentOptions.tsx b/src/bricks/effects/CommentOptions.tsx index 9ebe97b812..dee225934e 100644 --- a/src/bricks/effects/CommentOptions.tsx +++ b/src/bricks/effects/CommentOptions.tsx @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import ConnectedFieldTemplate from "@/components/form/ConnectedFieldTemplate"; import { useField } from "formik"; import { joinName } from "@/utils/formUtils"; @@ -15,8 +15,8 @@ import { type Expression } from "@/types/runtimeTypes"; * * Shows as a textarea with no toggle/exclude. */ -const CommentOptions: React.FunctionComponent = (props) => { - const { name, configKey } = props; +const CommentOptions: React.FunctionComponent = (props) => { + const { name, configKey = null } = props; const context = useContext(FieldRuntimeContext); diff --git a/src/bricks/transformers/IdentityTransformerOptions.tsx b/src/bricks/transformers/IdentityTransformerOptions.tsx index 4e3446558f..4f2cc8c6d2 100644 --- a/src/bricks/transformers/IdentityTransformerOptions.tsx +++ b/src/bricks/transformers/IdentityTransformerOptions.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { type Schema } from "@/types/schemaTypes"; import SchemaField from "@/components/fields/schemaFields/SchemaField"; import { joinName } from "@/utils/formUtils"; @@ -12,9 +12,9 @@ const ANY_SCHEMA: Schema = { /** * Page Editor fields for the @pixiebrix/identity brick. */ -const IdentityTransformerOptions: React.FunctionComponent = ({ +const IdentityTransformerOptions: React.FunctionComponent = ({ name, - configKey, + configKey = null, }) => ( = ({ name, configKey }) => { const basePath = joinName(name, configKey); const configName = partial(joinName, basePath); diff --git a/src/components/fields/optionsRegistry.ts b/src/components/fields/optionsRegistry.ts index c240ecd34d..305dcea7da 100644 --- a/src/components/fields/optionsRegistry.ts +++ b/src/components/fields/optionsRegistry.ts @@ -16,7 +16,7 @@ */ import type React from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { type RegistryId } from "@/types/registryTypes"; /** @@ -25,7 +25,7 @@ import { type RegistryId } from "@/types/registryTypes"; */ const optionsRegistry = new Map< RegistryId, - React.FunctionComponent + React.FunctionComponent >(); export default optionsRegistry; diff --git a/src/components/fields/schemaFields/BasicSchemaField.tsx b/src/components/fields/schemaFields/BasicSchemaField.tsx index edb93ddf8a..f6bb82d879 100644 --- a/src/components/fields/schemaFields/BasicSchemaField.tsx +++ b/src/components/fields/schemaFields/BasicSchemaField.tsx @@ -66,14 +66,16 @@ function useSetInitialValueForField({ }, []); useAsyncEffect(async () => { + const [inputModeOption] = inputModeOptions; // Initialize any undefined required fields to prevent inferring an "omit" input if ( value === undefined && isRequired && - !isEmpty(inputModeOptions) && - renderRef.current + inputModeOption && + renderRef.current && + inputModeOption.interpretValue ) { - await setValue(inputModeOptions[0].interpretValue(value)); + await setValue(inputModeOption.interpretValue(value)); } // We include setValue in the dependencies because sometimes the formik // helpers reference (setValue) changes, so we need to account for that in the dependencies @@ -91,7 +93,7 @@ const BasicSchemaField: SchemaFieldComponent = ({ name, schema, validationSchema, - isRequired, + isRequired = false, description, isObjectProperty = false, isArrayItem = false, diff --git a/src/components/fields/schemaFields/SchemaField.tsx b/src/components/fields/schemaFields/SchemaField.tsx index db3ccc0c01..601d695422 100644 --- a/src/components/fields/schemaFields/SchemaField.tsx +++ b/src/components/fields/schemaFields/SchemaField.tsx @@ -49,7 +49,7 @@ const SchemaField: SchemaFieldComponent = (props) => { return ; } - if (hasCustomWidget(uiSchema)) { + if (uiSchema && hasCustomWidget(uiSchema)) { const widget = get( customWidgets, uiSchema["ui:widget"] as keyof CustomWidgetRegistry, diff --git a/src/components/fields/schemaFields/fieldTypeCheckers.ts b/src/components/fields/schemaFields/fieldTypeCheckers.ts index 77fab41b61..291a8d60a3 100644 --- a/src/components/fields/schemaFields/fieldTypeCheckers.ts +++ b/src/components/fields/schemaFields/fieldTypeCheckers.ts @@ -63,7 +63,7 @@ export const isHeadingStyleField = (fieldDefinition: Schema) => fieldDefinition.type === "string" && fieldDefinition.format === "heading-style"; -export const hasCustomWidget = (uiSchema?: UiSchema) => +export const hasCustomWidget = (uiSchema: UiSchema) => typeof get(uiSchema, ["ui:widget"]) === "string"; /** diff --git a/src/components/fields/schemaFields/genericOptionsFactory.tsx b/src/components/fields/schemaFields/genericOptionsFactory.tsx index e0853f6e65..ad1412d1fb 100644 --- a/src/components/fields/schemaFields/genericOptionsFactory.tsx +++ b/src/components/fields/schemaFields/genericOptionsFactory.tsx @@ -23,7 +23,7 @@ import { sortedFields } from "@/components/fields/schemaFields/schemaFieldUtils" import { joinName } from "@/utils/formUtils"; import { inputProperties } from "@/utils/schemaUtils"; -export type BlockOptionProps = { +export type BrickOptionProps = { /** * The root field name for the block configuration. */ @@ -56,9 +56,9 @@ function genericOptionsFactory( NoOptionsComponent = NoOptions, }: { preserveSchemaOrder?: boolean; - NoOptionsComponent?: React.FunctionComponent; + NoOptionsComponent?: React.FunctionComponent; } = {}, -): React.FunctionComponent { +): React.FunctionComponent { const optionSchema = inputProperties(schema); if (isEmpty(optionSchema)) { return NoOptionsComponent; @@ -68,13 +68,13 @@ function genericOptionsFactory( preserveSchemaOrder, }); - const OptionsFields = ({ name, configKey }: BlockOptionProps) => ( + const OptionsFields = ({ name, configKey }: BrickOptionProps) => ( <> {sortedFieldsConfig.map( ({ prop, fieldSchema, propUiSchema, isRequired }) => ( { // eslint-disable-next-line security/detect-object-injection -- Fine because coming from Object.entries for the schema - const propUiSchema = uiSchema?.[prop]; + const propUiSchema = uiSchema?.[prop] as UiSchema; return { prop, diff --git a/src/components/fields/schemaFields/widgets/FixedInnerObjectWidget.tsx b/src/components/fields/schemaFields/widgets/FixedInnerObjectWidget.tsx index 5952285774..c17a6d4301 100644 --- a/src/components/fields/schemaFields/widgets/FixedInnerObjectWidget.tsx +++ b/src/components/fields/schemaFields/widgets/FixedInnerObjectWidget.tsx @@ -31,7 +31,7 @@ const FixedInnerObjectWidget: React.FC = (props) => { const Fields = useMemo(() => { let objectSchema = schema; - if (schema.oneOf) { + if (objectSchema.oneOf) { const matches = objectSchema.oneOf.filter( (x) => typeof x !== "boolean" && x.type === "object", ); @@ -52,7 +52,7 @@ const FixedInnerObjectWidget: React.FC = (props) => { return WorkshopMessageWidget; } - return genericOptionsFactory(objectSchema, null, { + return genericOptionsFactory(objectSchema, undefined, { preserveSchemaOrder: true, }); }, [schema]); diff --git a/src/components/fields/schemaFields/widgets/ObjectWidget.tsx b/src/components/fields/schemaFields/widgets/ObjectWidget.tsx index 87711653d7..ef0b4f4047 100644 --- a/src/components/fields/schemaFields/widgets/ObjectWidget.tsx +++ b/src/components/fields/schemaFields/widgets/ObjectWidget.tsx @@ -121,7 +121,7 @@ const ObjectFieldRow: React.FunctionComponent = ({ // As of v3, we allow object props of any type, not just string const defaultSchema: Schema = {}; const rawSchema = defined - ? parentSchema.properties[property] + ? parentSchema.properties?.[property] ?? defaultSchema : parentSchema.additionalProperties ?? defaultSchema; return typeof rawSchema === "boolean" ? defaultSchema : rawSchema; diff --git a/src/components/fields/schemaFields/widgets/TemplateToggleWidget.tsx b/src/components/fields/schemaFields/widgets/TemplateToggleWidget.tsx index cbbff910ec..c878c2bf7d 100644 --- a/src/components/fields/schemaFields/widgets/TemplateToggleWidget.tsx +++ b/src/components/fields/schemaFields/widgets/TemplateToggleWidget.tsx @@ -36,11 +36,16 @@ import { import VarPopup from "./varPopup/VarPopup"; import { isTemplateExpression } from "@/utils/expressionUtils"; +import { assertNotNullish } from "@/utils/nullishUtils"; export function getOptionForInputMode( options: InputModeOption[], - inputMode: FieldInputMode, + inputMode: FieldInputMode | undefined, ): InputModeOption | null { + if (inputMode == null) { + return null; + } + return options.find((option) => option.value === inputMode) ?? null; } @@ -62,7 +67,7 @@ const TemplateToggleWidget: React.VFC = ({ const { inputMode, onOmitField } = useToggleFormField( schemaFieldProps.name, schemaFieldProps.schema, - schemaFieldProps.isRequired, + schemaFieldProps.isRequired ?? false, ); const defaultInputRef = useRef(); @@ -97,13 +102,17 @@ const TemplateToggleWidget: React.VFC = ({ return; } - const { interpretValue } = getOptionForInputMode( - inputModeOptions, - newInputMode, + const option = getOptionForInputMode(inputModeOptions, newInputMode); + + assertNotNullish( + option, + `Option not found for input mode: ${newInputMode}`, ); - // Already handled "omit" and returned above - await setValue(interpretValue(value)); + const { interpretValue } = option; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- Already handled "omit" and returned above + await setValue(interpretValue!(value)); setFocusInput(true); }, [inputMode, inputModeOptions, setValue, value, onOmitField], @@ -151,7 +160,10 @@ const TemplateToggleWidget: React.VFC = ({ return; } - await setValue(selectedOption.interpretValue(newValue)); + assertNotNullish(selectedOption, `Option not found for ${inputMode}`); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- Already handled "omit" by limiting to var or string + await setValue(selectedOption.interpretValue!(newValue)); }, [inputMode, selectedOption, setValue], ); diff --git a/src/components/fields/schemaFields/widgets/varPopup/VarMenu.tsx b/src/components/fields/schemaFields/widgets/varPopup/VarMenu.tsx index 7f4181511f..c5fb5235c4 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/VarMenu.tsx +++ b/src/components/fields/schemaFields/widgets/varPopup/VarMenu.tsx @@ -46,6 +46,7 @@ import { getSelectedLineVirtualElement } from "@/components/fields/schemaFields/ import { inspectedTab } from "@/pageEditor/context/connection"; import useEventListener from "@/hooks/useEventListener"; import { StateNamespaces } from "@/platform/state/stateController"; +import { assertNotNullish, type Nullishable } from "@/utils/nullishUtils"; const emptyVarMap = new VarMap(); @@ -80,9 +81,9 @@ function usePositionVarPopup({ inputElementRef, variablePosition, }: { - knownVars: VarMap; + knownVars: Nullishable; inputElementRef: VarMenuProps["inputElementRef"]; - variablePosition: number; + variablePosition: number | null; }) { const dispatch = useDispatch(); const rootElementRef = useRef(null); @@ -98,11 +99,14 @@ function usePositionVarPopup({ const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { if (entry.contentBoxSize) { - setResize(entry.contentBoxSize[0].blockSize); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- contentBoxSize is defined + setResize(entry.contentBoxSize[0]!.blockSize); } } }); + assertNotNullish(element, "Element not found"); + resizeObserver.observe(element); return () => { @@ -175,7 +179,7 @@ const VarMenu: React.FunctionComponent = ({ getPageState(inspectedTab, { namespace: StateNamespaces.MOD, modComponentId: null, - modId: activeModComponentFormState.modMetadata?.id, + modId: activeModComponentFormState?.modMetadata?.id, }), [], ); @@ -196,7 +200,8 @@ const VarMenu: React.FunctionComponent = ({ }, [dispatch]); const starterBrickLabel = activeModComponentFormState?.type - ? ADAPTERS.get(activeModComponentFormState.type).label + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- checked above + ADAPTERS.get(activeModComponentFormState.type)!.label : ""; const { allOptions, filteredOptions } = useMemo(() => { @@ -215,7 +220,7 @@ const VarMenu: React.FunctionComponent = ({ const blocksInfo = Object.values(pipelineMap); - const { activeKeyPath } = useKeyboardNavigation({ + const { activeKeyPath = null } = useKeyboardNavigation({ inputElementRef, isVisible: Boolean(rootElementRef.current), likelyVariable, diff --git a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.stories.tsx b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.stories.tsx index 7b30c1edf1..81e8ac03f4 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.stories.tsx +++ b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.stories.tsx @@ -73,7 +73,14 @@ const knownVars = { const Template: Story = () => { const source = "root:Array Composite Reader"; const vars = knownVars[source]; - return ; + return ( + + ); }; export const Default = Template.bind({}); diff --git a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.test.tsx b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.test.tsx index 6d906bf65e..00a1d9892a 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.test.tsx +++ b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.test.tsx @@ -23,6 +23,7 @@ import VarMap, { ALLOW_ANY_CHILD, } from "@/analysis/analysisVisitors/varAnalysis/varMap"; import { render, screen } from "@testing-library/react"; +import { type UnknownRecord } from "type-fest"; testItRenders({ testName: "Renders the tree", @@ -41,6 +42,7 @@ testItRenders({ } as any, onVarSelect: noop, likelyVariable: null, + activeKeyPath: null, }, }); @@ -66,9 +68,10 @@ describe("VariablesTree", () => { render( , ); diff --git a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.tsx b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.tsx index e0ba25c37b..649a8c0011 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.tsx +++ b/src/components/fields/schemaFields/widgets/varPopup/VariablesTree.tsx @@ -40,7 +40,7 @@ const getItemString: GetItemString = (type, data, itemType, itemString) => { return null; } - return defaultItemString(type, data, itemType, itemString, null); + return defaultItemString(type, data, itemType, itemString, []); }; const NodeLabel: React.FunctionComponent<{ @@ -58,7 +58,7 @@ const NodeLabel: React.FunctionComponent<{ */ onSelect: (path: string[]) => void; }> = ({ isActive, path, onSelect }) => { - const buttonRef = useRef(null); + const buttonRef = useRef(null); const select = () => { // JSONTree tracks key path in reverse order @@ -77,14 +77,7 @@ const NodeLabel: React.FunctionComponent<{ return ( // Use native button to avoid extra bootstrap variant styling - ); @@ -93,8 +86,8 @@ const NodeLabel: React.FunctionComponent<{ const VariablesTree: React.FunctionComponent<{ vars: UnknownRecord; onVarSelect: (selectedPath: string[]) => void; - likelyVariable: string; - activeKeyPath?: KeyPath; + likelyVariable: string | null; + activeKeyPath: KeyPath | null; }> = ({ vars, onVarSelect, likelyVariable, activeKeyPath }) => ( = 0; index--) { // eslint-disable-next-line security/detect-object-injection -- accessing array item by index - const [, existenceTree] = varMapEntries[index]; + const varMapEntry = varMapEntries[index]; + + assertNotNullish(varMapEntry, "var map entry not found"); + + const [, existenceTree] = varMapEntry; for (const [outputKey] of Object.entries(existenceTree)) { // eslint-disable-next-line security/detect-object-injection -- access via Object.entries diff --git a/src/components/fields/schemaFields/widgets/varPopup/likelyVariableUtils.ts b/src/components/fields/schemaFields/widgets/varPopup/likelyVariableUtils.ts index 065cd8ec47..5a3c54de3e 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/likelyVariableUtils.ts +++ b/src/components/fields/schemaFields/widgets/varPopup/likelyVariableUtils.ts @@ -197,7 +197,7 @@ export function replaceLikelyVariable( * Select the full variable name based on the selected path and user's expression so far. */ export function getFullVariableName( - likelyVariable: string, + likelyVariable: string | null, selectedPath: string[], ): string { // `toPath` will create a separate element for the ? symbol. So we need to merge them back. Eventually we need to diff --git a/src/components/fields/schemaFields/widgets/varPopup/menuFilters.ts b/src/components/fields/schemaFields/widgets/varPopup/menuFilters.ts index 347d789da4..a106bbd08b 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/menuFilters.ts +++ b/src/components/fields/schemaFields/widgets/varPopup/menuFilters.ts @@ -25,6 +25,7 @@ import { } from "@/analysis/analysisVisitors/varAnalysis/varMap"; import { type KeyPath, type ShouldExpandNodeInitially } from "react-json-tree"; import { getIn } from "formik"; +import { assertNotNullish } from "@/utils/nullishUtils"; /** * Array of [source, varMap] tuples @@ -71,13 +72,16 @@ export function excludeIntegrationVariables(options: MenuOptions): MenuOptions { */ export function filterOptionsByVariable( options: MenuOptions, - likelyVariable: string, + likelyVariable: string | null, ): MenuOptions { if (isTextOrNullVar(likelyVariable)) { return options; } const [base, ...rest] = toVarPath(likelyVariable); + + assertNotNullish(base, `Expected base to be non-null: ${likelyVariable}`); + return options.filter(([source, vars]) => { if (rest.length === 0) { return Object.keys(vars).some((x) => x.startsWith(base)); @@ -98,6 +102,8 @@ function filterVarMapByPath( const [head, ...rest] = path; + assertNotNullish(head, "Expected head to be non-null"); + const entries: UnknownRecord = { // eslint-disable-next-line security/detect-object-injection -- Symbol [IS_ARRAY]: vars[IS_ARRAY], @@ -127,7 +133,7 @@ function filterVarMapByPath( */ export function filterVarMapByVariable( varMap: UnknownRecord, - likelyVariable: string, + likelyVariable: string | null, ): UnknownRecord { if (isTextOrNullVar(likelyVariable)) { return varMap; @@ -143,7 +149,7 @@ export function filterVarMapByVariable( */ export function expandCurrentVariableLevel( varMap: UnknownRecord, - likelyVariable: string, + likelyVariable: string | null, ): ShouldExpandNodeInitially { if (isTextOrNullVar(likelyVariable)) { return () => false; @@ -240,7 +246,7 @@ export function sortVarMapKeys(value: unknown): unknown { */ export function defaultMenuOption( options: MenuOptions, - likelyVariable: string, + likelyVariable: string | null, ): KeyPath | null { const reversedOptions = reverse([...options]); @@ -254,13 +260,19 @@ export function defaultMenuOption( // Must always have at least one option (e.g., the `@input`) // Prefer the last option, because that's the latest output - const vars = reversedOptions[0][1]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- length check above + const vars = reversedOptions[0]![1]; const first = Object.keys(vars)[0]; + + assertNotNullish(first, "Expected first to be non-null"); + return [first]; } const [head, ...rest] = parts; + assertNotNullish(head, "Expected head to be non-null"); + // Reverse options to find the last source that matches. (To account for shadowing) const sourceMatch = reversedOptions.find(([source, vars]) => Object.hasOwn(vars, head), @@ -271,7 +283,15 @@ export function defaultMenuOption( const partialMatch = reversedOptions.find(([, vars]) => Object.keys(vars).some((x) => x.startsWith(head)), ); - return partialMatch ? [Object.keys(partialMatch[1])[0]] : null; + + if (partialMatch) { + const key = Object.keys(partialMatch[1])[0]; + assertNotNullish(key, "Expected key to exist"); + + return [key]; + } + + return null; } const [, vars] = sourceMatch; @@ -284,13 +304,17 @@ export function defaultMenuOption( ] as UnknownRecord; for (const part of rest) { + assertNotNullish(part, "Expected part to be non-null"); + if (!Object.hasOwn(currentVars, part)) { + const match = Object.keys( + sortVarMapKeys(currentVars) as UnknownRecord, + ).find((x) => x.startsWith(part)); + + assertNotNullish(match, "Expected match to exist"); + // No exact match, return first partial match as default. - result.unshift( - Object.keys(sortVarMapKeys(currentVars)).find((x) => - x.startsWith(part), - ), - ); + result.unshift(match); break; } @@ -318,10 +342,10 @@ export function moveMenuOption({ offset, }: { options: MenuOptions; - likelyVariable: string; - keyPath: KeyPath; + likelyVariable: string | null; + keyPath: KeyPath | null; offset: number; -}): KeyPath { +}): KeyPath | null { if (keyPath == null) { return keyPath; } @@ -332,6 +356,8 @@ export function moveMenuOption({ const [head, ...rest] = keyPathParts; + assertNotNullish(head, "Expected head to be non-null"); + // Reverse options to find the last source that matches. (To account for shadowing) const sourceMatch = reversedOptions.find(([source, vars]) => Object.hasOwn(vars, head), @@ -352,23 +378,30 @@ export function moveMenuOption({ // User is switching between top-level variables if (rest.length === 0) { const sourceIndex = options.findIndex(([x]) => x === source); - const [, nextSourceVars] = options.at( - (sourceIndex + offset) % options.length, - ); - return [Object.keys(nextSourceVars)[0]]; + const option = options.at((sourceIndex + offset) % options.length); + + assertNotNullish(option, "Expected option to exist"); + const [, nextSourceVars] = option; + + const key = Object.keys(nextSourceVars)[0]; + assertNotNullish(key, "Expected key to exist"); + + return [key]; } const lastProperty = rest.pop(); const lastVars = getIn(varMap, rest.map(String)) as UnknownRecord; - const sortedVarNames = Object.keys(sortVarMapKeys(lastVars)); + const sortedVarNames = Object.keys(sortVarMapKeys(lastVars) as UnknownRecord); const varIndex = sortedVarNames.indexOf(String(lastProperty)); - return reverse([ - head, - ...rest, - sortedVarNames.at((varIndex + offset) % sortedVarNames.length), - ]); + return compact( + reverse([ + head, + ...rest, + sortedVarNames.at((varIndex + offset) % sortedVarNames.length), + ]), + ); } diff --git a/src/components/fields/schemaFields/widgets/varPopup/useKeyboardNavigation.ts b/src/components/fields/schemaFields/widgets/varPopup/useKeyboardNavigation.ts index 91dbf128f4..9bbd393d17 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/useKeyboardNavigation.ts +++ b/src/components/fields/schemaFields/widgets/varPopup/useKeyboardNavigation.ts @@ -42,9 +42,9 @@ function useKeyboardNavigation({ }: { inputElementRef: MutableRefObject; isVisible: boolean; - likelyVariable: string; + likelyVariable: string | null; menuOptions: MenuOptions; - onSelect: (keyPath: string[]) => void; + onSelect: (keyPath: string[] | null) => void; }) { // User's current selection in the variable menu const [activeKeyPath, setActiveKeyPath] = useState(); @@ -52,7 +52,7 @@ function useKeyboardNavigation({ const move = useCallback( (offset: number) => { - setActiveKeyPath((activeKeyPath) => + setActiveKeyPath((activeKeyPath = null) => moveMenuOption({ options: menuOptions, likelyVariable, @@ -84,7 +84,7 @@ function useKeyboardNavigation({ if (event.key === "Tab" || event.key === "Enter") { event.preventDefault(); - onSelect(activeKeyPath.map(String).reverse()); + onSelect(activeKeyPath?.map(String).reverse() ?? null); } }; @@ -112,8 +112,8 @@ function useResetActiveKeyPath({ setActiveKeyPath, }: { menuOptions: MenuOptions; - likelyVariable: string; - setActiveKeyPath: React.Dispatch>; + likelyVariable: string | null; + setActiveKeyPath: React.Dispatch>; }) { const prevMenuOptions = usePreviousValue(menuOptions); const prevLikelyVariable = usePreviousValue(likelyVariable); diff --git a/src/components/fields/schemaFields/widgets/varPopup/useTreeRow.ts b/src/components/fields/schemaFields/widgets/varPopup/useTreeRow.ts index 755bda3f2a..f3288d32e3 100644 --- a/src/components/fields/schemaFields/widgets/varPopup/useTreeRow.ts +++ b/src/components/fields/schemaFields/widgets/varPopup/useTreeRow.ts @@ -29,7 +29,7 @@ function useTreeRow({ onSelect, isActive, }: { - buttonRef: MutableRefObject; + buttonRef: MutableRefObject; onSelect: () => void; isActive: boolean; }) { diff --git a/src/components/integrations/IntegrationConfigEditorModal.tsx b/src/components/integrations/IntegrationConfigEditorModal.tsx index aeaa742159..817b220546 100644 --- a/src/components/integrations/IntegrationConfigEditorModal.tsx +++ b/src/components/integrations/IntegrationConfigEditorModal.tsx @@ -23,7 +23,7 @@ import { Button, Modal } from "react-bootstrap"; import AsyncButton from "@/components/AsyncButton"; import { truncate } from "lodash"; import genericOptionsFactory, { - type BlockOptionProps, + type BrickOptionProps, } from "@/components/fields/schemaFields/genericOptionsFactory"; import useSetDocumentTitle from "@/hooks/useSetDocumentTitle"; import ConnectedFieldTemplate from "@/components/form/ConnectedFieldTemplate"; @@ -113,7 +113,7 @@ const ModalContent: React.FC = ({ [onSave, onClose], ); - const Editor = useMemo>(() => { + const Editor = useMemo>(() => { if (optionsRegistry.has(integration.id)) { return optionsRegistry.get(integration.id); } diff --git a/src/contrib/automationanywhere/ApiTaskOptions.tsx b/src/contrib/automationanywhere/ApiTaskOptions.tsx index ac93bcdc39..8fe1af22ab 100644 --- a/src/contrib/automationanywhere/ApiTaskOptions.tsx +++ b/src/contrib/automationanywhere/ApiTaskOptions.tsx @@ -16,7 +16,7 @@ */ import React from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { partial } from "lodash"; import { joinName } from "@/utils/formUtils"; import RequireIntegrationConfig from "@/integrations/components/RequireIntegrationConfig"; @@ -120,7 +120,7 @@ const ApiTaskOptionsContent: React.FC<{ ); }; -const ApiTaskOptions: React.FC = ({ name, configKey }) => { +const ApiTaskOptions: React.FC = ({ name, configKey }) => { const configName = partial(joinName, name, configKey); return ( = ({ name, configKey }) => { +const BotOptions: React.FC = ({ name, configKey }) => { const configName = partial(joinName, name, configKey); return ( = ({ +const AppendSpreadsheetOptions: React.FunctionComponent = ({ name, configKey, }) => { diff --git a/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.tsx b/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.tsx index 6f5cc353db..af16cd6508 100644 --- a/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.tsx +++ b/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.tsx @@ -16,7 +16,7 @@ */ import React, { useState } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { useField } from "formik"; import { type Expression } from "@/types/runtimeTypes"; import { type Schema } from "@/types/schemaTypes"; @@ -110,7 +110,7 @@ const HeaderField: React.FunctionComponent<{ ); }; -const LookupSpreadsheetOptions: React.FunctionComponent = ({ +const LookupSpreadsheetOptions: React.FunctionComponent = ({ name, configKey, }) => { diff --git a/src/contrib/uipath/LocalProcessOptions.tsx b/src/contrib/uipath/LocalProcessOptions.tsx index f827a2c42f..2bb19b52a2 100644 --- a/src/contrib/uipath/LocalProcessOptions.tsx +++ b/src/contrib/uipath/LocalProcessOptions.tsx @@ -19,7 +19,7 @@ import React, { useMemo } from "react"; import { partial } from "lodash"; import { UIPATH_PROPERTIES as REMOTE_UIPATH_PROPERTIES } from "@/contrib/uipath/process"; import RemoteSchemaObjectField from "@/components/fields/schemaFields/RemoteSchemaObjectField"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { useSelectedRelease } from "@/contrib/uipath/uipathHooks"; import RequireIntegrationConfig from "@/integrations/components/RequireIntegrationConfig"; import ConnectedFieldTemplate from "@/components/form/ConnectedFieldTemplate"; @@ -40,7 +40,7 @@ import { import type { Option } from "@/components/form/widgets/SelectWidget"; import { inspectedTab } from "@/pageEditor/context/connection"; -const LocalProcessOptions: React.FunctionComponent = ({ +const LocalProcessOptions: React.FunctionComponent = ({ name, configKey, }) => { diff --git a/src/contrib/uipath/ProcessOptions.tsx b/src/contrib/uipath/ProcessOptions.tsx index dba0686db8..1a71e0a8eb 100644 --- a/src/contrib/uipath/ProcessOptions.tsx +++ b/src/contrib/uipath/ProcessOptions.tsx @@ -16,7 +16,7 @@ */ import React, { useCallback } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { partial } from "lodash"; import { UIPATH_PROPERTIES } from "@/contrib/uipath/process"; import { useField } from "formik"; @@ -57,7 +57,7 @@ async function fetchRobots( return (robots ?? []).map((x) => ({ value: x.Id, label: String(x.Name) })); } -const ProcessOptions: React.FunctionComponent = ({ +const ProcessOptions: React.FunctionComponent = ({ name, configKey, }) => { diff --git a/src/contrib/zapier/PushOptions.tsx b/src/contrib/zapier/PushOptions.tsx index de8e351e9b..290bd61300 100644 --- a/src/contrib/zapier/PushOptions.tsx +++ b/src/contrib/zapier/PushOptions.tsx @@ -16,7 +16,7 @@ */ import React, { useMemo } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { useField } from "formik"; import { type SchemaFieldProps } from "@/components/fields/schemaFields/propTypes"; import { type Webhook } from "@/contrib/zapier/contract"; @@ -83,7 +83,7 @@ const ZapField: React.FunctionComponent< const ObjectField = defaultFieldFactory(ObjectWidget); -const PushOptions: React.FunctionComponent = ({ +const PushOptions: React.FunctionComponent = ({ name, configKey, }) => { diff --git a/src/hooks/useBrickOptions.ts b/src/hooks/useBrickOptions.ts index 247cf6d212..0595734aef 100644 --- a/src/hooks/useBrickOptions.ts +++ b/src/hooks/useBrickOptions.ts @@ -18,69 +18,69 @@ import type React from "react"; import { useMemo, useState } from "react"; import genericOptionsFactory, { - type BlockOptionProps, + type BrickOptionProps, } from "@/components/fields/schemaFields/genericOptionsFactory"; -import blockRegistry from "@/bricks/registry"; +import brickRegistry from "@/bricks/registry"; import { useAsyncEffect } from "use-async-effect"; import reportError from "@/telemetry/reportError"; import optionsRegistry from "@/components/fields/optionsRegistry"; import { type RegistryId } from "@/types/registryTypes"; import { type Brick, isUserDefinedBrick } from "@/types/brickTypes"; -interface BlockState { - block?: Brick | null; +interface BrickState { + brick?: Brick | null; error?: string | null; } function useBrickOptions( id: RegistryId, -): [BlockState, React.FunctionComponent] { - const [{ block, error }, setBlock] = useState({ - block: null, +): [BrickState, React.FunctionComponent | null] { + const [{ brick, error }, setBrick] = useState({ + brick: null, error: null, }); useAsyncEffect( async (isMounted) => { - setBlock({ block: null, error: null }); + setBrick({ brick: null, error: null }); try { - const block = await blockRegistry.lookup(id); + const brick = await brickRegistry.lookup(id); if (!isMounted()) { return; } - setBlock({ block }); + setBrick({ brick }); } catch (error) { reportError(error); if (!isMounted()) { return; } - setBlock({ error: String(error) }); + setBrick({ error: String(error) }); } }, - [id, setBlock], + [id, setBrick], ); - const BlockOptions = useMemo(() => { - // Only return the BrickOptions if 1) the block is available, 2) and it is actually the block with the requested id. - // Must not return the BrickOptions for the previous block (when id has changed but the state hasn't been updated yet), - // or the config parameters of the past block will become part of the configuration of the new block. - if (id === block?.id) { - const registered = optionsRegistry.get(block.id); + const BrickOptions = useMemo(() => { + // Only return the BrickOptions if 1) the brick is available, 2) and it is actually the brick with the requested id. + // Must not return the BrickOptions for the previous brick (when id has changed but the state hasn't been updated yet), + // or the config parameters of the past brick will become part of the configuration of the new brick. + if (id === brick?.id) { + const registered = optionsRegistry.get(brick.id); return ( registered ?? - genericOptionsFactory(block.inputSchema, block.uiSchema, { + genericOptionsFactory(brick.inputSchema, brick.uiSchema, { // Preserve order for JS-based bricks. We can trust the order because JS literals preserve dictionary order - preserveSchemaOrder: !isUserDefinedBrick(block), + preserveSchemaOrder: !isUserDefinedBrick(brick), }) ); } return null; - }, [id, block]); + }, [id, brick]); - return [{ block, error }, BlockOptions]; + return [{ brick, error }, BrickOptions]; } export default useBrickOptions; diff --git a/src/pageEditor/fields/AlertOptions.tsx b/src/pageEditor/fields/AlertOptions.tsx index f494b5b964..cd2f59cd6e 100644 --- a/src/pageEditor/fields/AlertOptions.tsx +++ b/src/pageEditor/fields/AlertOptions.tsx @@ -16,7 +16,7 @@ */ import React, { useMemo } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { partial } from "lodash"; import { useField } from "formik"; import SchemaField from "@/components/fields/schemaFields/SchemaField"; @@ -24,7 +24,7 @@ import { ALERT_PERSISTENT_OPTION, AlertEffect } from "@/bricks/effects/alert"; import { type Schema } from "@/types/schemaTypes"; import { joinName } from "@/utils/formUtils"; -const AlertOptions: React.FC = ({ name, configKey }) => { +const AlertOptions: React.FC = ({ name, configKey }) => { const basePath = joinName(name, configKey); const configName = partial(joinName, basePath); diff --git a/src/pageEditor/fields/AssignModVariableOptions.tsx b/src/pageEditor/fields/AssignModVariableOptions.tsx index b0ecb73d20..c1b4f4a72c 100644 --- a/src/pageEditor/fields/AssignModVariableOptions.tsx +++ b/src/pageEditor/fields/AssignModVariableOptions.tsx @@ -16,7 +16,7 @@ */ import React, { useMemo } from "react"; -import { type BlockOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; +import { type BrickOptionProps } from "@/components/fields/schemaFields/genericOptionsFactory"; import { partial } from "lodash"; import SchemaField from "@/components/fields/schemaFields/SchemaField"; import { type Schema } from "@/types/schemaTypes"; @@ -50,7 +50,7 @@ function schemaWithKnownVariableNames(varMap: VarMap | null): Schema { }; } -const AssignModVariableOptions: React.FC = ({ +const AssignModVariableOptions: React.FC = ({ name, configKey, }) => { diff --git a/src/pageEditor/fields/CollapsibleFieldSection.tsx b/src/pageEditor/fields/CollapsibleFieldSection.tsx index 411cda34c9..03f4b909e9 100644 --- a/src/pageEditor/fields/CollapsibleFieldSection.tsx +++ b/src/pageEditor/fields/CollapsibleFieldSection.tsx @@ -35,7 +35,7 @@ const CollapsibleFieldSection: React.FC<{ title: React.ReactNode; toggleExpanded: () => void; expanded?: boolean; - bodyRef?: React.MutableRefObject; + bodyRef?: React.MutableRefObject; }> = ({ title, toggleExpanded, expanded, children, bodyRef }) => { const headerRef = useRef(null); diff --git a/src/pageEditor/fields/ConnectedCollapsibleFieldSection.tsx b/src/pageEditor/fields/ConnectedCollapsibleFieldSection.tsx index 4ab6695e12..fab24ca265 100644 --- a/src/pageEditor/fields/ConnectedCollapsibleFieldSection.tsx +++ b/src/pageEditor/fields/ConnectedCollapsibleFieldSection.tsx @@ -11,7 +11,7 @@ const ConnectedCollapsibleFieldSection = ({ }: { children: React.ReactNode; title: string; - bodyRef?: React.MutableRefObject; + bodyRef?: React.MutableRefObject; initialExpanded?: boolean; }) => { const dispatch = useDispatch(); diff --git a/src/pageEditor/tabs/effect/BrickConfiguration.tsx b/src/pageEditor/tabs/effect/BrickConfiguration.tsx index e6ad776354..4fef851d57 100644 --- a/src/pageEditor/tabs/effect/BrickConfiguration.tsx +++ b/src/pageEditor/tabs/effect/BrickConfiguration.tsx @@ -56,13 +56,12 @@ const BrickConfiguration: React.FunctionComponent<{ const context = useFormikContext(); const [config] = useField(name); - const [_rootField, _rootFieldMeta, rootFieldHelpers] = useField( - configName("root"), - ); + const [_rootField, _rootFieldMeta, rootFieldHelpers] = + useField(configName("root")); const brickErrors = getIn(context.errors, name); const isComment = brickId === CommentEffect.BRICK_ID; - const [{ block: brick, error }, BrickOptions] = useBrickOptions(brickId); + const [{ brick, error }, BrickOptions] = useBrickOptions(brickId); // Conditionally show Advanced Options "Condition" and "Target" depending on the value of brickType. // If brickType is undefined, don't show the options. @@ -73,16 +72,17 @@ const BrickConfiguration: React.FunctionComponent<{ ); const { data: isRootAware } = useAsyncState(async () => { - const inputSchema = inputProperties(brick.inputSchema); + const inputSchema = inputProperties(brick?.inputSchema); + // Handle DOM bricks that were upgraded to be root-aware if ("isRootAware" in inputSchema) { return Boolean(config.value.config.isRootAware); } - return brick.isRootAware(); + return brick?.isRootAware() ?? false; }, [brick, config.value.config.isRootAware]); - const advancedOptionsRef = useRef(); + const advancedOptionsRef = useRef(null); useAsyncEffect( async () => { diff --git a/src/runtime/getType.ts b/src/runtime/getType.ts index 87d95ac6ea..dba0fd6227 100644 --- a/src/runtime/getType.ts +++ b/src/runtime/getType.ts @@ -17,6 +17,7 @@ import { type BrickType } from "@/runtime/runtimeTypes"; import { type PackageInstance } from "@/types/registryTypes"; +import { type Nullishable } from "@/utils/nullishUtils"; import { isObject } from "@/utils/objectUtils"; function canInferType( @@ -29,8 +30,12 @@ function canInferType( * Returns the type of the brick, or `null` if the type cannot be determined. */ export default async function getType( - packageInstance: PackageInstance, + packageInstance: Nullishable, ): Promise { + if (packageInstance == null) { + return null; + } + if (canInferType(packageInstance)) { // HACK: including Integration and StarterBrick here is a hack to fix some call-sites. This method can only return // brick types. diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 1a921ff3b1..2ecf9905d1 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -4,6 +4,28 @@ "strictNullChecks": true }, "files": [ + "./components/fields/schemaFields/CssClassField.tsx", + "./components/fields/schemaFields/widgets/ObjectWidget.tsx", + "./components/fields/schemaFields/widgets/FixedInnerObjectWidget.tsx", + "./components/fields/schemaFields/widgets/ArrayWidget.tsx", + "./components/fields/schemaFields/widgets/varPopup/VarPopup.tsx", + "./components/fields/schemaFields/widgets/cssClassWidgets/CssClassWidget.tsx", + "./components/fields/schemaFields/getToggleOptions.tsx", + "./components/fields/schemaFields/widgets/varPopup/useKeyboardNavigation.ts", + "./components/fields/schemaFields/widgets/varPopup/menuFilters.ts", + "./components/fields/schemaFields/widgets/varPopup/getMenuOptions.ts", + "./components/fields/schemaFields/widgets/varPopup/VariablesTree.tsx", + "./components/fields/schemaFields/widgets/varPopup/VariablesTree.test.tsx", + "./components/fields/schemaFields/widgets/varPopup/VariablesTree.stories.tsx", + "./components/fields/schemaFields/widgets/varPopup/VarMenu.tsx", + "./components/fields/schemaFields/widgets/TemplateToggleWidget.tsx", + "./components/fields/schemaFields/genericOptionsFactory.tsx", + "./components/fields/schemaFields/SchemaField.tsx", + "./components/fields/schemaFields/BasicSchemaField.tsx", + "./components/fields/optionsRegistry.ts", + "./bricks/transformers/IdentityTransformerOptions.tsx", + "./bricks/effects/CommentOptions.tsx", + "./pageEditor/tabs/effect/BrickConfiguration.tsx", "../end-to-end-tests/env.ts", "../end-to-end-tests/fixtures/environmentCheck.ts", "../end-to-end-tests/fixtures/testBase.ts", diff --git a/src/utils/schemaUtils.ts b/src/utils/schemaUtils.ts index 62ef6f44a2..f4f1a162ab 100644 --- a/src/utils/schemaUtils.ts +++ b/src/utils/schemaUtils.ts @@ -24,6 +24,7 @@ import { import { castArray, intersection, isEmpty, uniq } from "lodash"; import { isNullOrBlank } from "@/utils/stringUtils"; import { UI_ORDER } from "@/components/formBuilder/schemaFieldNames"; +import { type Nullishable } from "@/utils/nullishUtils"; /** * Return the names of top-level required properties that are missing or blank in an object. @@ -68,7 +69,9 @@ export function propertiesToSchema( * Return the names of top-level properties in a JSON Schema or object. * @see propertiesToSchema */ -export function inputProperties(inputSchema: Schema): SchemaProperties { +export function inputProperties( + inputSchema: Nullishable, +): SchemaProperties { // NOTE: returning the argument is UNSAFE and is not consistent with the parameter type `Schema`. // In the past, PixieBrix definitions have supported shorthand of providing the properties directly.