Skip to content

Commit

Permalink
feat: display layoutset type in FormDesignerToolbar combobox descript…
Browse files Browse the repository at this point in the history
…ion (#14115)
  • Loading branch information
Jondyr authored Nov 21, 2024
1 parent 66bdaae commit 4bcc03d
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 23 deletions.
5 changes: 5 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,11 @@
"process_editor.sync_error_layout_sets_data_type": "Det oppsto en feil da datatype skulle synkroniseres i filen 'layoutsets.json'. Kontroller at 'layoutsets.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
"process_editor.sync_error_layout_sets_task_id": "Det oppsto en feil da oppgave-ID skulle synkroniseres i filen 'layoutsets.json'. Kontroller at 'layoutsets.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
"process_editor.sync_error_policy_file_task_id": "Det oppsto en feil da oppgave-ID skulle synkroniseres i filen 'policy.json'. Kontroller at 'policy.json' kun inneholder gyldig JSON-struktur og prøv igjen.",
"process_editor.task_type.confirmation": "Bekreftelse",
"process_editor.task_type.data": "Utfylling",
"process_editor.task_type.feedback": "Tilbakemelding",
"process_editor.task_type.payment": "Betaling",
"process_editor.task_type.signing": "Signering",
"process_editor.too_old_version_helptext_content": "Du har nå versjon {{version}} av app-biblioteket vårt.\n\nVi lanserer muligheten til å redigere prosessen sammen med versjon 8 av biblioteket. Når du har oppgradert til versjon 8, får du funksjonalitet for å redigere prosessen.\n\nFør det kan du bare se prosessen og eventuelle oppsett som er knyttet til den.",
"process_editor.too_old_version_helptext_title": "Informasjon om hvorfor du ikke kan redigere prosessen",
"process_editor.too_old_version_title": "Du kan ikke redigere prosessen",
Expand Down
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import type { Policy } from 'app-shared/types/Policy';
import type { RepoDiffResponse } from 'app-shared/types/api/RepoDiffResponse';
import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse';
import type { OptionsLists } from 'app-shared/types/api/OptionsLists';
import type { LayoutSetsModel } from '../types/api/dto/LayoutSetsModel';

export const getIsLoggedInWithAnsattporten = async (): Promise<boolean> =>
// TODO: replace with endpoint when it's ready in the backend.
Expand Down Expand Up @@ -110,6 +111,7 @@ export const getImageFileNames = (owner: string, app: string) => get<string[]>(g
export const getInstanceIdForPreview = (owner: string, app: string) => get<string>(instanceIdForPreviewPath(owner, app));
export const getLayoutNames = (owner: string, app: string) => get<string[]>(layoutNamesPath(owner, app));
export const getLayoutSets = (owner: string, app: string) => get<LayoutSets>(layoutSetsPath(owner, app));
export const getLayoutSetsExtended = (owner: string, app: string) => get<LayoutSetsModel>(layoutSetsPath(owner, app) + '/extended');
export const getOptionLists = (owner: string, app: string) => get<OptionsLists>(optionListsPath(owner, app));
export const getOptionListIds = (owner: string, app: string) => get<string[]>(optionListIdsPath(owner, app));
export const getOrgList = () => get<OrgList>(orgListUrl());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery, type UseQueryResult } from '@tanstack/react-query';
import { useServicesContext } from '../../contexts/ServicesContext';
import type { LayoutSetsModel } from '../../types/api/dto/LayoutSetsModel';
import { QueryKey } from '../../types/QueryKey';

export const useLayoutSetsExtendedQuery = (
org: string,
app: string,
): UseQueryResult<LayoutSetsModel, Error> => {
const { getLayoutSetsExtended } = useServicesContext();
return useQuery<LayoutSetsModel>({
queryKey: [QueryKey.LayoutSetsExtended, org, app],
queryFn: () => getLayoutSetsExtended(org, app),
});
};
3 changes: 3 additions & 0 deletions frontend/packages/shared/src/mocks/queriesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export const queriesMock: ServicesContextProps = {
getInstanceIdForPreview: jest.fn().mockImplementation(() => Promise.resolve<string>('')),
getLayoutNames: jest.fn().mockImplementation(() => Promise.resolve<string[]>([])),
getLayoutSets: jest.fn().mockImplementation(() => Promise.resolve<LayoutSets>(layoutSets)),
getLayoutSetsExtended: jest
.fn()
.mockImplementation(() => Promise.resolve<LayoutSets>(layoutSets)),
getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve<string[]>([])),
getOptionLists: jest.fn().mockImplementation(() => Promise.resolve<OptionsLists>({})),
getOrgList: jest.fn().mockImplementation(() => Promise.resolve<OrgList>(orgList)),
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/types/QueryKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum QueryKey {
LayoutNames = 'LayoutNames',
LayoutSchema = 'LayoutSchema',
LayoutSets = 'LayoutSets',
LayoutSetsExtended = 'LayoutSetsExtended',
OptionLists = 'OptionLists',
OptionListIds = 'OptionListIds',
OrgList = 'OrgList',
Expand Down
8 changes: 8 additions & 0 deletions frontend/packages/shared/src/types/api/dto/LayoutSetModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { TaskModel } from './TaskModel';

export type LayoutSetModel = {
id: string;
dataType: string;
type: string;
task: TaskModel;
};
3 changes: 3 additions & 0 deletions frontend/packages/shared/src/types/api/dto/LayoutSetsModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import type { LayoutSetModel } from './LayoutSetModel';

export type LayoutSetsModel = { sets: LayoutSetModel[] };
4 changes: 4 additions & 0 deletions frontend/packages/shared/src/types/api/dto/TaskModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type TaskModel = {
id: string;
type: string;
};
10 changes: 10 additions & 0 deletions frontend/packages/shared/src/utils/layoutSetsUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PROTECTED_TASK_NAME_CUSTOM_RECEIPT } from 'app-shared/constants';
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
import { validateLayoutNameAndLayoutSetName } from 'app-shared/utils/LayoutAndLayoutSetNameValidationUtils/validateLayoutNameAndLayoutSetName';
import type { LayoutSetModel } from '../types/api/dto/LayoutSetModel';

export const getLayoutSetNameForCustomReceipt = (layoutSets: LayoutSets): string | undefined => {
return layoutSets?.sets?.find((set) => set.tasks?.includes(PROTECTED_TASK_NAME_CUSTOM_RECEIPT))
Expand All @@ -21,3 +22,12 @@ export const getLayoutSetIdValidationErrorKey = (
return 'process_editor.configuration_panel_layout_set_id_not_unique';
return null;
};

export const getLayoutSetTypeTranslationKey = (layoutSet: LayoutSetModel): string => {
if (layoutSet.type === 'subform') return 'ux_editor.subform';
if (layoutSet.task?.type) {
return `process_editor.task_type.${layoutSet.task.type}`;
}

return '';
};
5 changes: 4 additions & 1 deletion frontend/packages/ux-editor/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock';
import { typedLocalStorage } from '@studio/pure-functions';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import type { AppContextProps } from './AppContext';
import { layoutSetsMock } from './testing/layoutSetsMock';
import { layoutSetsExtendedMock, layoutSetsMock } from './testing/layoutSetsMock';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { user as userMock } from 'app-shared/mocks/mocks';
import { QueryKey } from 'app-shared/types/QueryKey';
Expand All @@ -15,6 +15,9 @@ import { PreviewContextProvider } from 'app-development/contexts/PreviewContext'
const mockQueries: Partial<ServicesContextProps> = {
getInstanceIdForPreview: jest.fn().mockImplementation(() => Promise.resolve('test')),
getLayoutSets: jest.fn().mockImplementation(() => Promise.resolve(layoutSetsMock)),
getLayoutSetsExtended: jest
.fn()
.mockImplementation(() => Promise.resolve(layoutSetsExtendedMock)),
getFormLayoutSettings: jest
.fn()
.mockImplementation(() => Promise.resolve(formLayoutSettingsMock)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
layoutSet1NameMock,
layoutSet2NameMock,
layoutSet3SubformNameMock,
layoutSetsExtendedMock,
layoutSetsMock,
} from '../../testing/layoutSetsMock';
import { QueryKey } from 'app-shared/types/QueryKey';
Expand All @@ -18,6 +19,8 @@ import {
removeFeatureFlagFromLocalStorage,
} from 'app-shared/utils/featureToggleUtils';
import { textMock } from '@studio/testing/mocks/i18nMock';
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';
import type { LayoutSetsModel } from 'app-shared/types/api/dto/LayoutSetsModel';

// Test data
const layoutSetName1 = layoutSet1NameMock;
Expand All @@ -31,12 +34,16 @@ describe('LayoutSetsContainer', () => {
const combobox = screen.getByRole('combobox');
await user.click(combobox);

expect(await screen.findByRole('option', { name: layoutSetName1 })).toBeInTheDocument();
expect(await screen.findByRole('option', { name: layoutSetName2 })).toBeInTheDocument();
expect(
await screen.findByRole('option', { name: new RegExp(layoutSetName1 + ' ') }),
).toBeInTheDocument();
expect(
await screen.findByRole('option', { name: new RegExp(layoutSetName2 + ' ') }),
).toBeInTheDocument();
});

it('should not render combobox when there are no layoutSets', async () => {
render({ sets: null });
render({ layoutSets: null, layoutSetsExtended: null });
expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
});

Expand All @@ -45,7 +52,7 @@ describe('LayoutSetsContainer', () => {
const user = userEvent.setup();
const combobox = screen.getByRole('combobox');
await user.click(combobox);
await user.click(screen.getByRole('option', { name: layoutSetName2 }));
await user.click(screen.getByRole('option', { name: new RegExp(layoutSetName2 + ' ') }));

await waitFor(() =>
expect(appContextMock.setSelectedFormLayoutSetName).toHaveBeenCalledTimes(1),
Expand All @@ -59,10 +66,10 @@ describe('LayoutSetsContainer', () => {

it('should render the delete subform button when feature is enabled and selected layoutset is a subform', () => {
addFeatureFlagToLocalStorage('subform');
render(
{ sets: [{ id: layoutSet3SubformNameMock, type: 'subform' }] },
{ selectedlayoutSet: layoutSet3SubformNameMock },
);
render({
layoutSets: { sets: [{ id: layoutSet3SubformNameMock, type: 'subform' }] },
selectedLayoutSet: layoutSet3SubformNameMock,
});
const deleteSubformButton = screen.getByRole('button', {
name: textMock('ux_editor.delete.subform'),
});
Expand All @@ -72,10 +79,10 @@ describe('LayoutSetsContainer', () => {

it('should not render the delete subform button when feature is enabled and selected layoutset is not a subform', () => {
addFeatureFlagToLocalStorage('subform');
render(
{ sets: [{ id: layoutSet1NameMock, dataType: 'data-model' }] },
{ selectedlayoutSet: layoutSet1NameMock },
);
render({
layoutSets: { sets: [{ id: layoutSet1NameMock, dataType: 'data-model' }] },
selectedLayoutSet: layoutSet1NameMock,
});
const deleteSubformButton = screen.queryByRole('button', {
name: textMock('ux_editor.delete.subform'),
});
Expand All @@ -92,8 +99,19 @@ describe('LayoutSetsContainer', () => {
});
});

const render = (layoutSetsData = layoutSetsMock, options: { selectedlayoutSet?: string } = {}) => {
queryClientMock.setQueryData([QueryKey.LayoutSets, org, app], layoutSetsData);
appContextMock.selectedFormLayoutSetName = options.selectedlayoutSet || layoutSetName1;
type renderProps = {
layoutSets?: LayoutSets;
layoutSetsExtended?: LayoutSetsModel;
selectedLayoutSet?: string;
};

const render = ({
layoutSets = layoutSetsMock,
layoutSetsExtended = layoutSetsExtendedMock,
selectedLayoutSet = layoutSetName1,
}: renderProps = {}) => {
queryClientMock.setQueryData([QueryKey.LayoutSets, org, app], layoutSets);
queryClientMock.setQueryData([QueryKey.LayoutSetsExtended, org, app], layoutSetsExtended);
appContextMock.selectedFormLayoutSetName = selectedLayoutSet;
return renderWithProviders(<LayoutSetsContainer />);
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import React, { useEffect } from 'react';
import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { useText, useAppContext } from '../../hooks';
import { useAppContext } from '../../hooks';
import classes from './LayoutSetsContainer.module.css';
import { ExportForm } from './ExportForm';
import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils';
import { StudioCombobox } from '@studio/components';
import { DeleteSubformWrapper } from './Subform/DeleteSubformWrapper';
import { useLayoutSetsExtendedQuery } from 'app-shared/hooks/queries/useLayoutSetsExtendedQuery';
import { getLayoutSetTypeTranslationKey } from 'app-shared/utils/layoutSetsUtils';
import { useTranslation } from 'react-i18next';
import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery';

export function LayoutSetsContainer() {
const { org, app } = useStudioEnvironmentParams();
const { data: layoutSetsResponse } = useLayoutSetsQuery(org, app);
const layoutSets = layoutSetsResponse?.sets;
const t = useText();
const { data: layoutSets } = useLayoutSetsExtendedQuery(org, app);
const { t } = useTranslation();
const {
selectedFormLayoutSetName,
setSelectedFormLayoutSetName,
Expand All @@ -26,7 +29,7 @@ export function LayoutSetsContainer() {
onLayoutSetNameChange(selectedFormLayoutSetName);
}, [onLayoutSetNameChange, selectedFormLayoutSetName]);

if (!layoutSets) return null;
if (!layoutSetsResponse || !layoutSets) return null;

const handleLayoutSetChange = async (layoutSetName: string) => {
if (selectedFormLayoutSetName !== layoutSetName && layoutSetName) {
Expand All @@ -47,11 +50,11 @@ export function LayoutSetsContainer() {
value={[selectedFormLayoutSetName]}
onValueChange={([value]) => handleLayoutSetChange(value)}
>
{layoutSets.map((layoutSet) => (
{layoutSets.sets.map((layoutSet) => (
<StudioCombobox.Option
value={layoutSet.id}
key={layoutSet.id}
description={layoutSet.type === 'subform' && t('ux_editor.subform')}
description={t(getLayoutSetTypeTranslationKey(layoutSet))}
>
{layoutSet.id}
</StudioCombobox.Option>
Expand Down
24 changes: 24 additions & 0 deletions frontend/packages/ux-editor/src/testing/layoutSetsMock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { LayoutSetsModel } from 'app-shared/types/api/dto/LayoutSetsModel';
import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse';

export const dataModelNameMock = 'test-data-model';
Expand All @@ -24,3 +25,26 @@ export const layoutSetsMock: LayoutSets = {
},
],
};

export const layoutSetsExtendedMock: LayoutSetsModel = {
sets: [
{
id: layoutSet1NameMock,
dataType: 'data-model',
type: null,
task: { id: 'Task_1', type: 'data' },
},
{
id: layoutSet2NameMock,
dataType: 'data-model-2',
type: null,
task: { id: 'Task_2', type: 'data' },
},
{
id: layoutSet3SubformNameMock,
dataType: 'data-model-3',
type: 'subform',
task: null,
},
],
};

0 comments on commit 4bcc03d

Please sign in to comment.