Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(subform): Add SubFormMissingContentWarning Component #13813

Merged
merged 29 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b8cbb01
feat: Implementing support for full config of subform component
wrt95 Oct 14, 2024
0ba8449
adding required
wrt95 Oct 14, 2024
0727ea7
Merge branch 'main' into 13476-support-full-config-subform-component
wrt95 Oct 14, 2024
3b82880
adding another test
wrt95 Oct 14, 2024
ffbcba9
Merge branch 'main' into 13476-support-full-config-subform-component
Jondyr Oct 16, 2024
c2669dc
fixing comment from PR
wrt95 Oct 16, 2024
e136e4c
Merge branch 'main' into 13476-support-full-config-subform-component
wrt95 Oct 16, 2024
f40810d
Merge branch 'main' into 13476-support-full-config-subform-component
wrt95 Oct 16, 2024
b31daff
chore: Add StudioAlert component
Jondyr Oct 17, 2024
451746e
Remove size prop override
Jondyr Oct 17, 2024
310ca64
Add tests
Jondyr Oct 17, 2024
0ed30d8
Fix file extension
Jondyr Oct 17, 2024
e4c3af6
Merge branch 'main' into 13476-support-full-config-subform-component
Jondyr Oct 17, 2024
ba4d06a
Add SubFormMissingContentWarning Component
Jondyr Oct 17, 2024
cb4aded
Merge branch 'main' into 10-17-chore_add_studioalert_component
Jondyr Oct 17, 2024
7ae5b1e
Change test to use img role and custom text
Jondyr Oct 21, 2024
29943a7
Merge branch 'main' into 10-17-chore_add_studioalert_component
Jondyr Oct 21, 2024
a6dddee
Merge branch '10-17-chore_add_studioalert_component' into 10-17-add_s…
Jondyr Oct 21, 2024
5f355e3
Fix StudioAlert export
Jondyr Oct 21, 2024
51ce6ed
Merge branch '10-17-chore_add_studioalert_component' into 10-17-add_s…
Jondyr Oct 21, 2024
fd3a672
feat(subform): texts to inform user invalid subform layout (#13834)
Jondyr Oct 24, 2024
b33c775
Merge branch 'main' into 10-17-add_subformmissingcontentwarning_compo…
Jondyr Oct 30, 2024
b2bf825
Fix Subform naming
Jondyr Oct 30, 2024
6130a34
Revert accidental renaming
Jondyr Oct 30, 2024
ea8dd78
Rename directories to correct subform naming
Jondyr Oct 30, 2024
be1fb66
Remove no longer needed test
Jondyr Oct 30, 2024
45e1460
Merge branch 'main' into 10-17-add_subformmissingcontentwarning_compo…
Jondyr Oct 30, 2024
d8f781b
Update deploy designer workflow to replace underscore with dash
Jondyr Oct 30, 2024
7e20b2c
Merge branch 'main' into 10-17-add_subformmissingcontentwarning_compo…
lassopicasso Oct 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-designer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "tag=${{ needs.get-short-sha.outputs.short-sha }}" >> $GITHUB_OUTPUT
else
sanitized_branch_name=$(echo "${{ github.ref_name }}" | tr -d '()' | tr '/' '-')
sanitized_branch_name=$(echo "${{ github.ref_name }}" | tr -d '()' | tr '/' '-' | tr '_' '-')
echo "tag=${sanitized_branch_name}-${{ needs.get-short-sha.outputs.short-sha }}" >> $GITHUB_OUTPUT
fi

Expand Down
2 changes: 2 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,8 @@
"ux_editor.component_properties.subform.create_layout_set_description": "Hvis du velger å lage et nytt underskjema, oppretter vi et tomt underskjema for deg. Det må du selv utforme, før du kan sette opp tabellen.",
"ux_editor.component_properties.subform.created_layout_set_name": "Navn på underskjema",
"ux_editor.component_properties.subform.go_to_layout_set": "Gå til utforming av underskjemaet",
"ux_editor.component_properties.subform.layout_set_is_missing_content_heading": "Underskjemaet ditt mangler innhold.",
"ux_editor.component_properties.subform.layout_set_is_missing_content_paragraph": "Denne tabellen bruker underskjemaet for å hente feltene og tekstene som skal vises i tabellen. Velg Utform underskjemaet for å legge inn innhold.",
"ux_editor.component_properties.subform.no_layout_sets_acting_as_subform": "Det finnes ingen sidegrupper i løsningen som kan brukes som et underskjema",
"ux_editor.component_properties.subform.selected_layout_set_label": "Underskjema",
"ux_editor.component_properties.subform.selected_layout_set_title": "Endre underskjemakobling til {{subform}}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
padding: var(--fds-spacing-3);
}

.content-wrapper {
margin-top: var(--fds-spacing-4);
}

.addColumnButton {
margin-top: var(--fds-spacing-3);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import {
type EditSubformTableColumnsProps,
} from './EditSubformTableColumns';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { renderWithProviders } from 'dashboard/testing/mocks';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import userEvent from '@testing-library/user-event';
import { ComponentType } from 'app-shared/types/ComponentType';
import { componentMocks } from '@altinn/ux-editor/testing/componentMocks';
import { renderWithProviders } from '@altinn/ux-editor/testing/mocks';

const subformComponentMock = componentMocks[ComponentType.Subform];

const mockSubformLayoutValidation = jest.fn();
jest.mock('./hooks/useSubformLayoutValidation', () => ({
useSubformLayoutValidation: () => mockSubformLayoutValidation(),
}));

const defaultProps: EditSubformTableColumnsProps = {
component: subformComponentMock,
handleComponentChange: jest.fn(),
Expand All @@ -29,8 +34,10 @@ describe('EditSubformTableColumns', () => {
const user = userEvent.setup();

renderEditSubformTableColumns({
component: { ...subformComponentMock, tableColumns: undefined },
handleComponentChange: handleComponentChangeMock,
props: {
component: { ...subformComponentMock, tableColumns: undefined },
handleComponentChange: handleComponentChangeMock,
},
});

const addColumnButton = screen.getByRole('button', {
Expand All @@ -49,7 +56,7 @@ describe('EditSubformTableColumns', () => {
const user = userEvent.setup();

renderEditSubformTableColumns({
handleComponentChange: handleComponentChangeMock,
props: { handleComponentChange: handleComponentChangeMock },
});

const addColumnButton = screen.getByRole('button', {
Expand All @@ -68,7 +75,7 @@ describe('EditSubformTableColumns', () => {
const user = userEvent.setup();

renderEditSubformTableColumns({
handleComponentChange: handleComponentChangeMock,
props: { handleComponentChange: handleComponentChangeMock },
});

const headerInputbutton = screen.getByRole('button', {
Expand Down Expand Up @@ -96,7 +103,7 @@ describe('EditSubformTableColumns', () => {
const user = userEvent.setup();

renderEditSubformTableColumns({
handleComponentChange: handleComponentChangeMock,
props: { handleComponentChange: handleComponentChangeMock },
});

const deleteButton = screen.getByRole('button', {
Expand All @@ -111,9 +118,34 @@ describe('EditSubformTableColumns', () => {
const updatedComponent = handleComponentChangeMock.mock.calls[0][0];
expect(updatedComponent.tableColumns.length).toBe(0);
});

it('should show warning if subform validation is false', () => {
renderEditSubformTableColumns({ isSubformLayoutConfigured: false });
expect(
screen.getByText(
textMock('ux_editor.component_properties.subform.layout_set_is_missing_content_heading'),
),
).toBeInTheDocument();
expect(
screen.getByText(
textMock('ux_editor.component_properties.subform.layout_set_is_missing_content_paragraph'),
),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: textMock('top_menu.create') })).toBeInTheDocument();
});
});

const renderEditSubformTableColumns = (props: Partial<EditSubformTableColumnsProps> = {}) => {
type renderEditSubformTableColumnsParameters = {
props?: Partial<EditSubformTableColumnsProps>;
isSubformLayoutConfigured?: boolean;
};

const renderEditSubformTableColumns = (
{ props, isSubformLayoutConfigured }: renderEditSubformTableColumnsParameters = {
isSubformLayoutConfigured: true,
},
) => {
mockSubformLayoutValidation.mockReturnValue(isSubformLayoutConfigured);
const queryClient = createQueryClientMock();
return renderWithProviders(<EditSubformTableColumns {...defaultProps} {...props} />, {
...queriesMock,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { type ReactElement } from 'react';
import React, { type ReactElement, type ReactNode } from 'react';
import classes from './EditSubformTableColumns.module.css';
import { StudioButton, StudioHeading } from '@studio/components';
import { useTranslation } from 'react-i18next';
Expand All @@ -8,6 +8,8 @@ import { type TableColumn } from './types/TableColumn';
import { filterOutTableColumn, updateComponentWithSubform } from './utils';
import { useUniqueKeys } from '@studio/hooks';
import { ColumnElement } from './ColumnElement';
import { useSubformLayoutValidation } from './hooks/useSubformLayoutValidation';
import { SubformMissingContentWarning } from './SubformMissingContentWarning/SubformMissingContentWarning';

export type EditSubformTableColumnsProps = IGenericEditComponent<ComponentType.Subform>;

Expand All @@ -16,6 +18,7 @@ export const EditSubformTableColumns = ({
handleComponentChange,
}: EditSubformTableColumnsProps): ReactElement => {
const { t } = useTranslation();
var subformLayoutIsConfigured = useSubformLayoutValidation(component.layoutSet);

const tableColumns: TableColumn[] = component?.tableColumns ?? [];
const { getUniqueKey, addUniqueKey, removeUniqueKey } = useUniqueKeys({
Expand All @@ -42,11 +45,16 @@ export const EditSubformTableColumns = ({
handleComponentChange({ ...component, tableColumns: updatedColumns });
};

if (subformLayoutIsConfigured === false) {
return (
<EditSubformTableColumnsWrapper>
<SubformMissingContentWarning subformLayoutSetName={component.layoutSet} />
</EditSubformTableColumnsWrapper>
);
}

return (
<div className={classes.wrapper}>
<StudioHeading size='2xs' level={2}>
{t('ux_editor.properties_panel.subform_table_columns.heading')}
</StudioHeading>
<EditSubformTableColumnsWrapper>
{tableColumns.length > 0 &&
tableColumns.map((tableColum: TableColumn, index: number) => (
<ColumnElement
Expand All @@ -60,6 +68,22 @@ export const EditSubformTableColumns = ({
<StudioButton color='second' className={classes.addColumnButton} onClick={handleAddColumn}>
{t('ux_editor.properties_panel.subform_table_columns.add_column')}
</StudioButton>
</EditSubformTableColumnsWrapper>
);
};

type EditSubformTableColumnsWrapperProps = {
children: ReactNode;
};

const EditSubformTableColumnsWrapper = ({ children }: EditSubformTableColumnsWrapperProps) => {
const { t } = useTranslation();
return (
<div className={classes.wrapper}>
<StudioHeading size='2xs' level={2}>
{t('ux_editor.properties_panel.subform_table_columns.heading')}
</StudioHeading>
<div className={classes.contentWrapper}>{children}</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.redirectButton {
margin-top: var(--fds-spacing-3);
max-width: var(--fds-sizing-30);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { renderWithProviders } from '@altinn/ux-editor/testing/mocks';
import { SubformMissingContentWarning } from './SubformMissingContentWarning';
import React from 'react';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { textMock } from '@studio/testing/mocks/i18nMock';

const setSelectedFormLayoutName = jest.fn();
const setSelectedFormLayoutSetName = jest.fn();
jest.mock('@altinn/ux-editor/hooks', () => ({
useAppContext: () => ({
setSelectedFormLayoutName,
setSelectedFormLayoutSetName,
}),
}));

describe('SubformMissingContentWarning', () => {
it('renders without crashing', () => {
renderWithProviders(<SubformMissingContentWarning subformLayoutSetName='' />);
expect(
screen.getByText(
textMock('ux_editor.component_properties.subform.layout_set_is_missing_content_heading'),
),
).toBeInTheDocument();
expect(
screen.getByText(
textMock('ux_editor.component_properties.subform.layout_set_is_missing_content_paragraph'),
),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: textMock('top_menu.create') })).toBeInTheDocument();
});
it('calls redirect/state change functions on redirect button click', async () => {
const user = userEvent.setup();
const subformLayoutSetName = 'test';
renderWithProviders(
<SubformMissingContentWarning subformLayoutSetName={subformLayoutSetName} />,
);

await user.click(screen.getByRole('button', { name: textMock('top_menu.create') }));

expect(setSelectedFormLayoutName).toHaveBeenCalledTimes(1);
expect(setSelectedFormLayoutName).toHaveBeenCalledWith(undefined);
expect(setSelectedFormLayoutSetName).toHaveBeenCalledTimes(1);
expect(setSelectedFormLayoutSetName).toHaveBeenCalledWith(subformLayoutSetName);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { PencilIcon } from '@studio/icons';
import { StudioAlert, StudioButton, StudioHeading, StudioParagraph } from '@studio/components';
import type { ReactElement } from 'react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '@altinn/ux-editor/hooks';
import classes from './SubformMissingContentWarning.module.css';

type SubformMissingContentWarningProps = {
subformLayoutSetName: string;
};

export const SubformMissingContentWarning = ({
subformLayoutSetName: subformLayoutSetName,
}: SubformMissingContentWarningProps): ReactElement => {
const { setSelectedFormLayoutName, setSelectedFormLayoutSetName } = useAppContext();
const { t } = useTranslation();

const handleOnRedirectClick = (): void => {
setSelectedFormLayoutSetName(subformLayoutSetName);
setSelectedFormLayoutName(undefined);
};

return (
<StudioAlert severity='warning'>
<StudioHeading size='2xs' level={2}>
{t('ux_editor.component_properties.subform.layout_set_is_missing_content_heading')}
</StudioHeading>
<StudioParagraph size='sm'>
{t('ux_editor.component_properties.subform.layout_set_is_missing_content_paragraph')}
</StudioParagraph>
<StudioButton
onClick={handleOnRedirectClick}
variant='primary'
color='second'
icon={<PencilIcon />}
iconPlacement='left'
disabled={!subformLayoutSetName}
className={classes.redirectButton}
>
{t('top_menu.create')}
</StudioButton>
</StudioAlert>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useSubformLayoutValidation } from './useSubformLayoutValidation';
import { renderHookWithProviders } from '@altinn/ux-editor/testing/mocks';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { app, org } from '@studio/testing/testids';
import type { IFormLayouts } from '@altinn/ux-editor/types/global';
import { ComponentType } from 'app-shared/types/ComponentType';

const emptyLayout: IFormLayouts = {
page1: {
order: {
section1: ['component2'],
},
components: {},
containers: {},
customRootProperties: {},
customDataProperties: {},
},
};

const nonEmptyLayout: IFormLayouts = {
...emptyLayout,
page1: {
...emptyLayout.page1,
components: {
component2: {
type: ComponentType.Input,
id: 'component2',
itemType: 'COMPONENT',
dataModelBindings: { simpleBinding: 'simpleBinding' },
},
},
},
};

describe('useSubformLayoutValidation', () => {
it('should return true if form layout has components', () => {
const { result } = renderHook({
layout: nonEmptyLayout,
});
expect(result.current).toBe(true);
});
it('should return false if form layout has no components', () => {
const { result } = renderHook({
layout: emptyLayout,
});
expect(result.current).toBe(false);
});
});

type renderHookArgs = {
layout: IFormLayouts;
};

const renderHook = ({ layout }: renderHookArgs) => {
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.FormLayouts, org, app, ''], layout);
return renderHookWithProviders(() => useSubformLayoutValidation(''), {
queryClient: queryClient,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useFormLayoutsQuery } from '@altinn/ux-editor/hooks/queries/useFormLayoutsQuery';
import { getAllLayoutComponents } from '@altinn/ux-editor/utils/formLayoutUtils';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';

export const useSubformLayoutValidation = (subformLayoutSetName: string): boolean => {
const { org, app } = useStudioEnvironmentParams();
const { data: formLayout } = useFormLayoutsQuery(org, app, subformLayoutSetName);

if (formLayout) {
return Object.values(formLayout).some((value) => getAllLayoutComponents(value).length > 0);
}
return false;
};
Loading