From 6806d3ac3bbfbd376e20c07238765d0a1a6e7768 Mon Sep 17 00:00:00 2001 From: Dawid Jankowiak Date: Fri, 23 Aug 2024 13:58:55 -0300 Subject: [PATCH] feat(metadata-sidebar): Add metadata add template dropdown menu (#3606) * feat(metadata-sidebar): Add AddMetadataTemplateDropdown and improve basic styling * feat(metadata-sidebar): Fix failing tests Extend Jest configuration to not transforming metadata-editor code * feat(metadata-sidebar): add AddMetadataTemplateDropdown To MetadataSidebarRedesign * feat(content-sidebar): Bring back changes to mockServiceWorker.js No idea why they got there in the first place * feat(metadata-sidebar): update storybook * feat(metadata-sidebar): PR comments * feat(metadata-sidebar): simplify storybook * feat(metadata-sidebar): enum status * feat(metadata-sidebar): global variables and enum upper case change * feat(metadata-sidebar): useSidebarMetadataFetcher tests * feat(metadata-sidebar): useSidebarMetadataFetcher tests * feat(metadata-sidebar): PR comments * feat(metadata-sidebar): PR comments * feat(metadata-sidebar): loading status test * feat(metadata-sidebar): use SidebarContent + tests * feat(metadata-sidebar): template dropdown menu nit fixes --------- Co-authored-by: Karolina Rusek-Bieniek Co-authored-by: Wiola --- scripts/jest/jest.config.js | 4 +- .../MetadataSidebarRedesign.scss | 9 +- .../MetadataSidebarRedesign.tsx | 85 ++++++++++-- .../MetadataSidebarRedesign.test.tsx | 116 ++++++++++++++++ .../MetadataSidebarRedesigned.test.tsx | 23 ---- .../useSidebarMetadataFetcher.test.tsx | 109 +++++++++++++++ .../hooks/useSidebarMetadataFetcher.ts | 125 ++++++++++++++++++ .../stories/MetadataSidebar.mdx | 5 + .../MetadataSidebarRedesign.stories.tsx | 35 +++++ ...MetadataSidebarRedesign-visual.stories.tsx | 52 ++++++++ 10 files changed, 526 insertions(+), 37 deletions(-) create mode 100644 src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx delete mode 100644 src/elements/content-sidebar/__tests__/MetadataSidebarRedesigned.test.tsx create mode 100644 src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx create mode 100644 src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts create mode 100644 src/elements/content-sidebar/stories/MetadataSidebar.mdx create mode 100644 src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx create mode 100644 src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx diff --git a/scripts/jest/jest.config.js b/scripts/jest/jest.config.js index bcd7a982fc..c21eb6d969 100644 --- a/scripts/jest/jest.config.js +++ b/scripts/jest/jest.config.js @@ -25,5 +25,7 @@ module.exports = { testEnvironment: 'jsdom', testMatch: ['**/__tests__/**/*.test.+(js|jsx|ts|tsx)'], testPathIgnorePatterns: ['stories.test.js$', 'stories.test.tsx$', 'stories.test.d.ts'], - transformIgnorePatterns: ['node_modules/(?!(@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets)/)'], + transformIgnorePatterns: [ + 'node_modules/(?!(@box/react-virtualized/dist/es|@box/cldr-data|@box/blueprint-web|@box/blueprint-web-assets|@box/metadata-editor)/)', + ], }; diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.scss b/src/elements/content-sidebar/MetadataSidebarRedesign.scss index 8a651e469b..4f300c8cd9 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.scss +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.scss @@ -1,5 +1,10 @@ -@import '../common/variables'; +@import '~@box/blueprint-web-assets/tokens/tokens.scss'; .bcs-MetadataSidebarRedesign { - padding-inline: 10px; + border-left: 1px solid $gray-10; + + .bcs-MetadataSidebarRedesign-content { + padding: $space-2; + background-color: $gray-02; + } } diff --git a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx index 02c4abdf14..c977dd7d67 100644 --- a/src/elements/content-sidebar/MetadataSidebarRedesign.tsx +++ b/src/elements/content-sidebar/MetadataSidebarRedesign.tsx @@ -4,30 +4,93 @@ */ import * as React from 'react'; import flow from 'lodash/flow'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { InlineError, LoadingIndicator } from '@box/blueprint-web'; +import { AddMetadataTemplateDropdown } from '@box/metadata-editor'; + +import API from '../../api'; +import SidebarContent from './SidebarContent'; import { withAPIContext } from '../common/api-context'; import { withErrorBoundary } from '../common/error-boundary'; import { withLogger } from '../common/logger'; -import { ORIGIN_METADATA_SIDEBAR_REDESIGN } from '../../constants'; +import { ORIGIN_METADATA_SIDEBAR_REDESIGN, SIDEBAR_VIEW_METADATA } from '../../constants'; import { EVENT_JS_READY } from '../common/logger/constants'; import { mark } from '../../utils/performance'; -import messages from '../common/messages'; +import useSidebarMetadataFetcher, { STATUS } from './hooks/useSidebarMetadataFetcher'; + +import { type ElementsXhrError } from '../../common/types/api'; +import { type ElementOrigin } from '../common/flowTypes'; +import { MetadataTemplate } from '../../common/types/metadata'; +import { type WithLoggerProps } from '../../common/types/logging'; +import messages from '../common/messages'; import './MetadataSidebarRedesign.scss'; const MARK_NAME_JS_READY = `${ORIGIN_METADATA_SIDEBAR_REDESIGN}_${EVENT_JS_READY}`; mark(MARK_NAME_JS_READY); -function MetadataSidebarRedesign() { +export interface ExternalProps { + isFeatureEnabled: boolean; +} + +interface PropsWithoutContext extends ExternalProps { + elementId: string; + fileId: string; + hasSidebarInitialized?: boolean; +} + +interface ContextInfo { + isErrorDisplayed: boolean; + error: ElementsXhrError | Error; +} + +export interface ErrorContextProps { + onError: (error: ElementsXhrError | Error, code: string, contextInfo?: ContextInfo, origin?: ElementOrigin) => void; +} + +export interface MetadataSidebarRedesignProps extends PropsWithoutContext, ErrorContextProps, WithLoggerProps { + api: API; +} + +function MetadataSidebarRedesign({ api, elementId, fileId, onError, isFeatureEnabled }: MetadataSidebarRedesignProps) { + const { formatMessage } = useIntl(); + + const [selectedTemplates, setSelectedTemplates] = React.useState>([]); + + const { templates, errorMessage, status } = useSidebarMetadataFetcher(api, fileId, onError, isFeatureEnabled); + + const metadataDropdown = status === STATUS.SUCCESS && templates && ( + { + setSelectedTemplates([...selectedTemplates, selectedTemplate]); + }} + /> + ); + + const errorMessageDisplay = status === STATUS.ERROR && errorMessage && ( + + + + ); + return ( -
-

- -

-
-

Hello from Metadata Sidebar redesign

-
+ +
+ {errorMessageDisplay} + {status === STATUS.LOADING && ( + + )} +
+
); } diff --git a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx new file mode 100644 index 0000000000..50ab231f55 --- /dev/null +++ b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesign.test.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { userEvent } from '@testing-library/user-event'; +import { FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; +import { screen, render } from '../../../test-utils/testing-library'; +import { + MetadataSidebarRedesignComponent as MetadataSidebarRedesign, + type MetadataSidebarRedesignProps, +} from '../MetadataSidebarRedesign'; +import useSidebarMetadataFetcher, { STATUS } from '../hooks/useSidebarMetadataFetcher'; + +jest.mock('../hooks/useSidebarMetadataFetcher'); +const mockUseSidebarMetadataFetcher = useSidebarMetadataFetcher as jest.MockedFunction< + typeof useSidebarMetadataFetcher +>; + +describe('elements/content-sidebar/Metadata/MetadataSidebarRedesign', () => { + const mockTemplates = [ + { + id: 'metadata_template_custom_1', + scope: 'global', + templateKey: 'properties', + hidden: false, + }, + ]; + + const mockFile = { + id: '123', + permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true }, + }; + + const renderComponent = (props = {}) => { + const defaultProps = { + api: {}, + fileId: 'test-file-id-1', + elementId: 'element-1', + isFeatureEnabled: true, + onError: jest.fn(), + } satisfies MetadataSidebarRedesignProps; + + render(); + }; + + beforeEach(() => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + templates: mockTemplates, + errorMessage: null, + status: STATUS.SUCCESS, + file: mockFile, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should render title', () => { + renderComponent(); + + expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeInTheDocument(); + }); + + test('should have accessible "Add template" button', () => { + renderComponent(); + + expect(screen.getByRole('button', { name: 'Add template' })).toBeInTheDocument(); + }); + + test('should have selectable "Custom Metadata" template in dropdown', async () => { + renderComponent(); + + const addTemplateButton = screen.getByRole('button', { name: 'Add template' }); + await userEvent.click(addTemplateButton); + + const customMetadataOption = screen.getByRole('option', { name: 'Custom Metadata' }); + expect(customMetadataOption).toBeInTheDocument(); + userEvent.click(customMetadataOption); + + // instead of below assertions check if template was added when MetadataInstanceList will be implemented + await userEvent.click(addTemplateButton); + + expect(customMetadataOption).toHaveAttribute('aria-disabled', 'true'); + }); + + test('should render metadata sidebar with error', async () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + templates: [], + errorMessage: { + id: 'error', + defaultMessage: 'error message', + }, + status: STATUS.ERROR, + file: mockFile, + }); + + const errorMessage = { id: 'error', defaultMessage: 'error message' }; + renderComponent(); + + expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeInTheDocument(); + expect(screen.getByText(errorMessage.defaultMessage)).toBeInTheDocument(); + }); + + test('should render metadata sidebar with loading indicator', async () => { + mockUseSidebarMetadataFetcher.mockReturnValue({ + templates: [], + errorMessage: null, + status: STATUS.LOADING, + file: mockFile, + }); + + renderComponent(); + + expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeInTheDocument(); + expect(screen.getByTestId('loading')).toBeInTheDocument(); + expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); + }); +}); diff --git a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesigned.test.tsx b/src/elements/content-sidebar/__tests__/MetadataSidebarRedesigned.test.tsx deleted file mode 100644 index dbbaf1ac7f..0000000000 --- a/src/elements/content-sidebar/__tests__/MetadataSidebarRedesigned.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { IntlProvider } from 'react-intl'; - -import { screen, render } from '@testing-library/react'; - -import { MetadataSidebarRedesignComponent as MetadataSidebar } from '../MetadataSidebarRedesign'; - -jest.unmock('react-intl'); - -describe('elements/content-sidebar/Metadata/MetadataSidebarRedesigned', () => { - const renderComponent = (props = {}) => - render(, { - wrapper: ({ children }: { children: React.ReactNode }) => ( - {children} - ), - }); - - test('should render title', () => { - renderComponent(); - - expect(screen.getByRole('heading', { level: 3, name: 'Metadata' })).toBeVisible(); - }); -}); diff --git a/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx b/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx new file mode 100644 index 0000000000..d4ad55db66 --- /dev/null +++ b/src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx @@ -0,0 +1,109 @@ +import { renderHook, waitFor } from '../../../test-utils/testing-library'; +import messages from '../../common/messages'; +import { FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; +import useSidebarMetadataFetcher, { STATUS } from '../hooks/useSidebarMetadataFetcher'; + +const mockError = { + status: 500, + message: 'Internal Server Error', +}; + +const mockFile = { + id: '123', + permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true }, +}; + +const mockTemplates = [ + { + id: 'metadata_template_custom_1', + scope: 'global', + templateKey: 'properties', + hidden: false, + }, +]; + +const mockAPI = { + getFile: jest.fn((id, successCallback, errorCallback) => { + try { + successCallback(mockFile); + } catch (error) { + errorCallback(error); + } + }), + getMetadata: jest.fn((_file, successCallback, errorCallback) => { + try { + successCallback({ + editors: [], + templates: mockTemplates, + }); + } catch (error) { + errorCallback(error); + } + }), +}; +const api = { + getFileAPI: jest.fn().mockReturnValue(mockAPI), + getMetadataAPI: jest.fn().mockReturnValue(mockAPI), +}; + +describe('useSidebarMetadataFetcher', () => { + const onErrorMock = jest.fn(); + const isFeatureEnabledMock = true; + + const setupHook = (fileId = '123') => + renderHook(() => useSidebarMetadataFetcher(api, fileId, onErrorMock, isFeatureEnabledMock)); + + test('should fetch the file and metadata successfully', async () => { + const { result } = setupHook(); + + await waitFor(() => expect(result.current.status).toBe(STATUS.SUCCESS)); + + expect(result.current.file).toEqual(mockFile); + expect(result.current.templates).toEqual(mockTemplates); + expect(result.current.errorMessage).toBeNull(); + }); + + test('should handle file fetching error', async () => { + mockAPI.getFile.mockImplementation((id, successCallback, errorCallback) => + errorCallback(mockError, 'file_fetch_error'), + ); + + const { result } = setupHook(); + + await waitFor(() => expect(result.current.status).toBe(STATUS.ERROR)); + + expect(result.current.file).toBeUndefined(); + expect(result.current.errorMessage).toBe(messages.sidebarMetadataEditingErrorContent); + expect(onErrorMock).toHaveBeenCalledWith( + mockError, + 'file_fetch_error', + expect.objectContaining({ + error: mockError, + isErrorDisplayed: true, + }), + ); + }); + + test('should handle metadata fetching error', async () => { + mockAPI.getFile.mockImplementation((id, successCallback) => { + successCallback(mockFile); + }); + mockAPI.getMetadata.mockImplementation((file, successCallback, errorCallback) => { + errorCallback(mockError, 'metadata_fetch_error'); + }); + const { result } = setupHook(); + + await waitFor(() => expect(result.current.status).toBe(STATUS.ERROR)); + + expect(result.current.templates).toBeNull(); + expect(result.current.errorMessage).toBe(messages.sidebarMetadataFetchingErrorContent); + expect(onErrorMock).toHaveBeenCalledWith( + mockError, + 'metadata_fetch_error', + expect.objectContaining({ + error: mockError, + isErrorDisplayed: true, + }), + ); + }); +}); diff --git a/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts new file mode 100644 index 0000000000..0fe877a566 --- /dev/null +++ b/src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts @@ -0,0 +1,125 @@ +import * as React from 'react'; +import getProp from 'lodash/get'; +import { type MessageDescriptor } from 'react-intl'; +import API from '../../../api'; +import { type ElementsXhrError } from '../../../common/types/api'; +import { isUserCorrectableError } from '../../../utils/error'; +import { FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS, FIELD_PERMISSIONS_CAN_UPLOAD } from '../../../constants'; + +import messages from '../../common/messages'; + +import { type BoxItem } from '../../../common/types/core'; +import { MetadataTemplate, type MetadataEditor } from '../../../common/types/metadata'; +import { type ErrorContextProps, type ExternalProps } from '../MetadataSidebarRedesign'; + +export enum STATUS { + IDLE = 'idle', + LOADING = 'loading', + ERROR = 'error', + SUCCESS = 'success', +} +interface DataFetcher { + status: STATUS; + file: BoxItem | null; + errorMessage: MessageDescriptor | null; + templates: Array; +} + +function useSidebarMetadataFetcher( + api: API, + fileId: string, + onError: ErrorContextProps['onError'], + isFeatureEnabled: ExternalProps['isFeatureEnabled'], +): DataFetcher { + const [status, setStatus] = React.useState(STATUS.IDLE); + const [file, setFile] = React.useState(null); + const [templates, setTemplates] = React.useState(null); + const [errorMessage, setErrorMessage] = React.useState(null); + + const onApiError = React.useCallback( + (error: ElementsXhrError, code: string, message: MessageDescriptor) => { + const { status: errorStatus } = error; + const isValidError = isUserCorrectableError(errorStatus); + setStatus(STATUS.ERROR); + setErrorMessage(message); + onError(error, code, { + error, + isErrorDisplayed: isValidError, + }); + }, + [onError], + ); + + const fetchMetadataSuccessCallback = React.useCallback( + ({ templates: fetchedTemplates }: { editors: Array; templates: Array }) => { + setErrorMessage(null); + setStatus(STATUS.SUCCESS); + setTemplates(fetchedTemplates); + }, + [], + ); + + const fetchMetadataErrorCallback = React.useCallback( + (e: ElementsXhrError, code: string) => { + setTemplates(null); + onApiError(e, code, messages.sidebarMetadataFetchingErrorContent); + }, + [onApiError], + ); + + const fetchMetadata = React.useCallback( + (fetchedFile: BoxItem) => { + api.getMetadataAPI(false).getMetadata( + fetchedFile, + fetchMetadataSuccessCallback, + fetchMetadataErrorCallback, + isFeatureEnabled, + { refreshCache: true }, + ); + }, + [api, fetchMetadataErrorCallback, fetchMetadataSuccessCallback, isFeatureEnabled], + ); + + const fetchFileSuccessCallback = React.useCallback( + (fetchedFile: BoxItem) => { + const { currentFile } = file ?? {}; + const currentFileCanUpload = getProp(currentFile, FIELD_PERMISSIONS_CAN_UPLOAD, false); + const newFileCanUpload = getProp(fetchedFile, FIELD_PERMISSIONS_CAN_UPLOAD, false); + const shouldFetchMetadata = !currentFile || currentFileCanUpload !== newFileCanUpload; + setFile(fetchedFile); + if (shouldFetchMetadata && fetchedFile) { + fetchMetadata(fetchedFile); + } else { + setStatus(STATUS.SUCCESS); + } + }, + [fetchMetadata, file], + ); + + const fetchFileErrorCallback = React.useCallback( + (e: ElementsXhrError, code: string) => { + setFile(undefined); + onApiError(e, code, messages.sidebarMetadataEditingErrorContent); + }, + [onApiError], + ); + + React.useEffect(() => { + if (status === STATUS.IDLE) { + setStatus(STATUS.LOADING); + api.getFileAPI().getFile(fileId, fetchFileSuccessCallback, fetchFileErrorCallback, { + fields: [FIELD_IS_EXTERNALLY_OWNED, FIELD_PERMISSIONS], + refreshCache: true, + }); + } + }, [api, fetchFileErrorCallback, fetchFileSuccessCallback, fileId, status]); + + return { + status, + file, + errorMessage, + templates, + }; +} + +export default useSidebarMetadataFetcher; diff --git a/src/elements/content-sidebar/stories/MetadataSidebar.mdx b/src/elements/content-sidebar/stories/MetadataSidebar.mdx new file mode 100644 index 0000000000..ccc9e51168 --- /dev/null +++ b/src/elements/content-sidebar/stories/MetadataSidebar.mdx @@ -0,0 +1,5 @@ +import { Meta } from '@storybook/blocks'; + +import MetadataSidebarStories from './MetadataSidebar.stories'; + + \ No newline at end of file diff --git a/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx new file mode 100644 index 0000000000..d6bb46a277 --- /dev/null +++ b/src/elements/content-sidebar/stories/MetadataSidebarRedesign.stories.tsx @@ -0,0 +1,35 @@ +import { type StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; +import { type ComponentProps } from 'react'; +import MetadataSidebarRedesign from '../MetadataSidebarRedesign'; +import ContentSidebar from '../ContentSidebar'; + +const fileIdWithMetadata = global.FILE_ID; +const mockFeatures = { + 'metadata.redesign.enabled': true, +}; +const mockLogger = { + onReadyMetric: ({ endMarkName }) => { + console.log(`Logger: onReadyMetric called with endMarkName: ${endMarkName}`); + }, +}; + +const defaultMetadataSidebarProps: ComponentProps = { + isFeatureEnabled: true, + onError: fn, +}; + +export default { + title: 'Elements/ContentSidebar/MetadataSidebarRedesign', + component: ContentSidebar, + args: { + fileId: fileIdWithMetadata, + features: mockFeatures, + logger: mockLogger, + hasMetadata: true, + token: global.TOKEN, + metadataSidebarProps: defaultMetadataSidebarProps, + }, +}; + +export const AddTemplateDropdownMenu: StoryObj = {}; diff --git a/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx new file mode 100644 index 0000000000..2707b3f94d --- /dev/null +++ b/src/elements/content-sidebar/stories/tests/MetadataSidebarRedesign-visual.stories.tsx @@ -0,0 +1,52 @@ +import { expect, userEvent, within, fn } from '@storybook/test'; +import { defaultVisualConfig } from '../../../../utils/storybook'; +import ContentSidebar from '../../ContentSidebar'; + +export const Basic = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByRole('heading', { name: 'Metadata' }); + const addTemplateButton = await canvas.findByRole('button', { name: 'Add template' }); + + expect(addTemplateButton).toBeInTheDocument(); + await userEvent.click(addTemplateButton); + + const customMetadataOption = canvas.getByRole('option', { name: 'Custom Metadata' }); + expect(customMetadataOption).toBeInTheDocument(); + }, +}; + +const fileIdWithMetadata = global.FILE_ID; +const token = global.TOKEN; +const mockFeatures = { + 'metadata.redesign.enabled': true, +}; +const mockLogger = { + onReadyMetric: ({ endMarkName }) => { + console.log(`Logger: onReadyMetric called with endMarkName: ${endMarkName}`); + }, +}; + +const defaultMetadataArgs = { + fileId: fileIdWithMetadata, + isFeatureEnabled: true, + onError: fn, +}; + +export default { + title: 'Elements/ContentSidebar/MetadataSidebarRedesign/tests/visual-regression-tests', + component: ContentSidebar, + args: { + token, + metadataSidebarProps: { + ...defaultMetadataArgs, + }, + hasMetadata: true, + features: mockFeatures, + fileId: fileIdWithMetadata, + logger: mockLogger, + }, + parameters: { + ...defaultVisualConfig.parameters, + }, +};