Skip to content

Commit

Permalink
Add page config panel (#12428) (#12442)
Browse files Browse the repository at this point in the history
* Add page config panel

* Add tests

* Fix parallell tests issue

* Add test for navigationMenu in page accordion

* Adapt playwright tests

* Use variable from fds for min-height in StudioSectionHeader

---------

Co-authored-by: Tomas Engebretsen <tomas.engebretsen@digdir.no>
  • Loading branch information
standeren and TomasEng authored Mar 15, 2024
1 parent 3da6d66 commit e72def6
Show file tree
Hide file tree
Showing 58 changed files with 890 additions and 659 deletions.
18 changes: 18 additions & 0 deletions backend/src/Designer/Controllers/AppDevelopmentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,24 @@ public async Task<IActionResult> GetLayoutSettings(string org, string app, [From
}
}

/// <summary>
/// Get all names of layouts across layoutSets
/// </summary>
/// <param name="org">Unique identifier of the organisation responsible for the app.</param>
/// <param name="app">Application identifier which is unique within an organisation.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is canceled.</param>
/// <returns>The layout-sets.json</returns>
[HttpGet]
[UseSystemTextJson]
[Route("layout-names")]
public async Task<IActionResult> GetLayoutNames(string org, string app, CancellationToken cancellationToken)
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
var editingContext = AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer);
string[] layoutNames = await _appDevelopmentService.GetLayoutNames(editingContext, cancellationToken);
return Ok(layoutNames);
}

/// <summary>
/// Return JSON presentation of the model
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,29 @@ public async Task SaveLayoutSettings(AltinnRepoEditingContext altinnRepoEditingC
await altinnAppGitRepository.SaveLayoutSettings(null, layoutSettings);
}

/// <inheritdoc />
public async Task<string[]> GetLayoutNames(AltinnRepoEditingContext altinnRepoEditingContext, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository =
_altinnGitRepositoryFactory.GetAltinnAppGitRepository(altinnRepoEditingContext.Org,
altinnRepoEditingContext.Repo, altinnRepoEditingContext.Developer);
bool appUsesLayoutSets = altinnAppGitRepository.AppUsesLayoutSets();
if (appUsesLayoutSets)
{
string[] layoutNames = [];
LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken);
foreach (LayoutSetConfig layoutSetConfig in layoutSets.Sets)
{
string[] layoutNamesForSet = altinnAppGitRepository.GetLayoutNames(layoutSetConfig.Id);
layoutNames = layoutNames.Concat(layoutNamesForSet).ToArray();
}
return layoutNames;
}

return altinnAppGitRepository.GetLayoutNames(null);
}

/// <inheritdoc />
public async Task<ModelMetadata> GetModelMetadata(AltinnRepoEditingContext altinnRepoEditingContext,
string layoutSetName, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -197,6 +220,7 @@ private async Task<string> GetTaskIdBasedOnLayoutSet(AltinnRepoEditingContext al
return layoutSets?.Sets?.Find(set => set.Id == layoutSetName)?.Tasks[0];
}

/// <inheritdoc />
public async Task<LayoutSets> GetLayoutSets(AltinnRepoEditingContext altinnRepoEditingContext,
CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ public interface IAppDevelopmentService
/// <param name="cancellationToken">An <see cref="CancellationToken"/> that observes if operation is cancelled.</param>
public Task SaveLayoutSettings(AltinnRepoEditingContext altinnRepoEditingContext, JsonNode layoutSettings, string layoutSetName, CancellationToken cancellationToken = default);

/// <summary>
/// Gets an array of names of all layouts in all layoutSets (if app uses sets)
/// </summary>
/// <param name="altinnRepoEditingContext">An <see cref="AltinnRepoEditingContext"/>.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that observes if operation is canceled.</param>
public Task<string[]> GetLayoutNames(AltinnRepoEditingContext altinnRepoEditingContext, CancellationToken cancellationToken = default);

/// <summary>
/// Returns the <see cref="ModelMetadata"/> for an app.
/// </summary>
Expand Down
31 changes: 15 additions & 16 deletions frontend/app-development/features/textEditor/TextEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import userEvent from '@testing-library/user-event';
import * as testids from '../../../testing/testids';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { queryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';

// Test data
const org = 'test-org';
Expand Down Expand Up @@ -43,16 +45,16 @@ describe('TextEditor', () => {
afterEach(jest.clearAllMocks);

it('renders the component', async () => {
await render();

renderTextEditor();
await waitForElementToBeRemoved(() => screen.queryByText(textMock('text_editor.loading_page')));
expect(screen.getByText(testTextResourceKey)).toBeInTheDocument();
expect(screen.getByText(testTextResourceValue)).toBeInTheDocument();
});

it('updates search query when searching text', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const search = '1';
const searchInput = screen.getByTestId('text-editor-search-default');
Expand All @@ -64,7 +66,7 @@ describe('TextEditor', () => {
it('adds new text resource when clicking add button', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const addButton = screen.getByRole('button', { name: textMock('text_editor.new_text') });
await act(() => user.click(addButton));
Expand All @@ -75,7 +77,7 @@ describe('TextEditor', () => {
it('updates text resource when editing text', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const textarea = screen.getByRole('textbox', {
name: textMock('text_editor.table_row_input_label', {
Expand All @@ -93,9 +95,9 @@ describe('TextEditor', () => {
});

it('updates text id when editing text id', async () => {
queryClientMock.setQueryData([QueryKey.LayoutNames, org, app], []);
const user = userEvent.setup();

await render();
renderTextEditor();

const editButton = screen.getByRole('button', {
name: textMock('text_editor.toggle_edit_mode', { textKey: testTextResourceKey }),
Expand All @@ -117,7 +119,7 @@ describe('TextEditor', () => {
it('deletes text id when clicking delete button', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const deleteButton = screen.getByRole('button', { name: textMock('schema_editor.delete') });
act(() => deleteButton.click());
Expand All @@ -135,7 +137,7 @@ describe('TextEditor', () => {
it('adds new language when clicking add button', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const addBtn = screen.getByRole('button', {
name: textMock('general.add'),
Expand All @@ -158,7 +160,7 @@ describe('TextEditor', () => {
it('deletes a language when clicking delete button', async () => {
const user = userEvent.setup();

await render();
renderTextEditor();

const deleteButton = screen.getByTestId(testids.deleteButton('en'));
await act(() => user.click(deleteButton));
Expand All @@ -181,17 +183,14 @@ describe('TextEditor', () => {
});
});

const render = async (queries: Partial<ServicesContextProps> = {}) => {
const view = renderWithProviders(<TextEditor />, {
const renderTextEditor = (queries: Partial<ServicesContextProps> = {}) => {
return renderWithProviders(<TextEditor />, {
queryClient: queryClientMock,
queries: {
getTextResources,
getTextLanguages,
...queries,
},
startUrl: `${APP_DEVELOPMENT_BASENAME}/${org}/${app}`,
});

await waitForElementToBeRemoved(() => screen.queryByText(textMock('text_editor.loading_page')));

return view;
};
7 changes: 6 additions & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@
"resourceadm.switch_toggle": "Toggle av eller på",
"right_menu.calculations": "Beregninger",
"right_menu.content": "Innhold",
"right_menu.content_empty": "Ingen komponent valgt...",
"right_menu.content_empty": "Ingen side valgt",
"right_menu.dataModelBindings": "Datamodellknytninger",
"right_menu.dataModelBindings_delete_confirm": "Er du sikker på at du vil slette denne datamodellknytningen?",
"right_menu.dataModelBindings_edit": "Rediger {{binding}}",
Expand Down Expand Up @@ -1122,6 +1122,7 @@
"right_menu.rules_empty": "Ingen regler lagt til...",
"right_menu.show_old_dynamics": "Vis gammelt verktøy for dynamikk",
"right_menu.text": "Tekst",
"right_menu.text_label": "Tekst accordion",
"right_menu.warning_dynamics_deprecated": "Denne funksjonaliteten utgår og vi kommer ikke til å vedlikeholde den. Vi anbefaler å bruke <0 href=\"{{expressionDocs}}\" >dynamiske uttrykk.</0>",
"schema_editor.active_languages": "Aktive språk:",
"schema_editor.add": "Legg til",
Expand Down Expand Up @@ -1641,6 +1642,7 @@
"ux_editor.file_upload_component.settings": "Innstillinger for filopplastingskomponent",
"ux_editor.file_upload_component.valid_file_endings": "Innstillinger for filopplastingskomponent",
"ux_editor.form_designer": "Skjemadesigner",
"ux_editor.id_identifier": "ID: {{item}}",
"ux_editor.image_component.settings": "Innstillinger for bilde",
"ux_editor.info": "Informasjon",
"ux_editor.information_altinn_library": "Dette er en komponent som inngår i Altinns faste bibliotek",
Expand Down Expand Up @@ -1803,6 +1805,9 @@
"ux_editor.modal_properties_textResourceBindings_leftColumnHeader_add": "Legg til tekst for overskrift i venstre kolonne",
"ux_editor.modal_properties_textResourceBindings_next": "Tekst på neste-knapp",
"ux_editor.modal_properties_textResourceBindings_next_add": "Legg til tekst på neste-knapp",
"ux_editor.modal_properties_textResourceBindings_page_id": "Side-ID",
"ux_editor.modal_properties_textResourceBindings_page_name": "Visningsnavn for side",
"ux_editor.modal_properties_textResourceBindings_page_name_add": "Legg til visningsnavn for side",
"ux_editor.modal_properties_textResourceBindings_questionDescriptions": "Beskrivelser av spørsmål",
"ux_editor.modal_properties_textResourceBindings_questionDescriptions_add": "Legg til tekst for beskrivelser av spørsmål",
"ux_editor.modal_properties_textResourceBindings_questionHelpTexts": "Hjelpetekster for spørsmål",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
.container {
display: flex;
justify-content: space-between;
height: max-content;
min-height: var(--fds-spacing-12);
box-sizing: border-box;
max-height: max-content;
gap: var(--fds-spacing-2);
padding: var(--fds-spacing-2) var(--fds-spacing-3);
background-color: var(--fds-semantic-surface-neutral-selected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type StudioSectionHeaderProps = {
text: string;
level?: HeadingProps['level'];
};
helpText: {
helpText?: {
text: string;
title: string;
};
Expand All @@ -30,9 +30,11 @@ const StudioSectionHeader = forwardRef<HTMLDivElement, StudioSectionHeaderProps>
{heading.text}
</Heading>
</div>
<HelpText size='medium' title={helpText.title}>
{helpText.text}
</HelpText>
{helpText && (
<HelpText size='medium' title={helpText.title}>
{helpText.text}
</HelpText>
)}
</div>
);
},
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-d
export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get
export const ruleConfigPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-config?${s({ layoutSetName })}`; // Get, Post
export const datamodelMetadataPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/model-metadata?${s({ layoutSetName })}`; // Get
export const layoutNamesPath = (org, app) => `${basePath}/${org}/${app}/app-development/layout-names`; // Get
export const layoutSetPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/layout-sets?${s({ layoutSetName })}`; // Put, Post
export const layoutSetsPath = (org, app) => `${basePath}/${org}/${app}/app-development/layout-sets`; // Get
export const layoutSettingsPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/layout-settings?${s({ layoutSetName })}`; // Get, Post
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 @@ -45,6 +45,7 @@ import {
userStarredListPath,
widgetSettingsPath,
resourceAccessListsPath,
layoutNamesPath,
} from './paths';
import type { AppDeploymentsResponse, AppReleasesResponse, DatamodelMetadataResponse, SearchRepoFilterParams, SearchRepositoryResponse } from 'app-shared/types/api';
import type { BranchStatus } from 'app-shared/types/BranchStatus';
Expand Down Expand Up @@ -88,6 +89,7 @@ export const getFormLayouts = (owner: string, app: string, layoutSetName: string
export const getFormLayoutsV3 = (owner: string, app: string, layoutSetName: string) => get<FormLayoutsResponseV3>(formLayoutsPath(owner, app, layoutSetName));
export const getFrontEndSettings = (owner: string, app: string) => get<IFrontEndSettings>(frontEndSettingsPath(owner, app));
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 getNewsList = (language: 'nb' | 'en') => get<NewsList>(newsListUrl(language));
export const getOptionListIds = (owner: string, app: string) => get<string[]>(optionListIdsPath(owner, app));
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/mocks/queriesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const queriesMock: ServicesContextProps = {
getFormLayoutsV3: jest.fn().mockImplementation(() => Promise.resolve<FormLayoutsResponseV3>({})),
getFrontEndSettings: jest.fn().mockImplementation(() => Promise.resolve<IFrontEndSettings>({})),
getInstanceIdForPreview: jest.fn().mockImplementation(() => Promise.resolve<string>('')),
getLayoutNames: jest.fn().mockImplementation(() => Promise.resolve<string[]>([])),
getLayoutSets: jest.fn().mockImplementation(() => Promise.resolve<LayoutSets>(layoutSets)),
getNewsList: jest.fn().mockImplementation(() => Promise.resolve<NewsList>(newsList)),
getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve<string[]>([])),
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 @@ -19,6 +19,7 @@ export enum QueryKey {
FrontEndSettings = 'FrontEndSettings',
InstanceId = 'InstanceId',
JsonSchema = 'JsonSchema',
LayoutNames = 'LayoutNames',
LayoutSchema = 'LayoutSchema',
LayoutSets = 'LayoutSets',
NewsList = 'NewsList',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';
import type { ComponentType } from 'app-shared/types/ComponentType';
import type { ComponentSpecificConfig } from 'app-shared/types/ComponentSpecificConfig';
import type { Expression } from '@studio/components';

export type FormLayoutsResponse = KeyValuePairs<ExternalFormLayout>;

Expand All @@ -12,7 +13,7 @@ export interface ExternalFormLayout {

export interface ExternalData {
layout: ExternalComponent[];
hidden?: boolean;
hidden?: Expression;
[key: string]: any;
}

Expand Down
11 changes: 10 additions & 1 deletion frontend/packages/text-editor/src/TextEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import userEvent from '@testing-library/user-event';
import { textMock } from '../../../testing/mocks/i18nMock';
import type { ITextResource, ITextResources } from 'app-shared/types/global';
import * as testids from '../../../testing/testids';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { QueryKey } from 'app-shared/types/QueryKey';
import { queryClientMock } from 'app-shared/mocks/queryClientMock';

const user = userEvent.setup();
let mockScrollIntoView = jest.fn();
Expand Down Expand Up @@ -40,7 +44,12 @@ describe('TextEditor', () => {
updateTextId: jest.fn(),
upsertTextResource: jest.fn(),
};
return rtlRender(<TextEditor {...defaultProps} {...props} />);
queryClientMock.setQueryData([QueryKey.LayoutNames, 'org', 'app'], []);
return rtlRender(
<ServicesContextProvider {...queriesMock} client={queryClientMock}>
<TextEditor {...defaultProps} {...props} />
</ServicesContextProvider>,
);
};
beforeEach(() => {
// Need to mock the scrollIntoView function
Expand Down
29 changes: 25 additions & 4 deletions frontend/packages/text-editor/src/TextList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { TextList } from './TextList';
import { screen, render as rtlRender, act } from '@testing-library/react';
import { textMock } from '../../../testing/mocks/i18nMock';
import type { TextTableRow } from './types';
import { ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { queryClientMock } from 'app-shared/mocks/queryClientMock';

const textKey1: string = 'a';

Expand Down Expand Up @@ -57,16 +61,28 @@ const renderTextList = (props: Partial<TextListProps> = {}) => {
selectedLanguages: ['nb', 'en', 'nn'],
...props,
};

return { initPros: allProps, ...rtlRender(<TextList {...allProps} />) };
queryClientMock.setQueryData([QueryKey.LayoutNames, 'org', 'app'], []);
return {
initPros: allProps,
...rtlRender(
<ServicesContextProvider {...queriesMock} client={queryClientMock}>
<TextList {...allProps} />
</ServicesContextProvider>,
),
};
};

describe('TextList', () => {
it('should call updateEntryId when the ID is changed in the edit mode', async () => {
const user = userEvent.setup();
const updateEntryId = jest.fn();
const { rerender, initPros } = renderTextList({ updateEntryId });
rerender(<TextList {...initPros} />);
queryClientMock.setQueryData([QueryKey.LayoutNames, 'org', 'app'], []);
rerender(
<ServicesContextProvider {...queriesMock} client={queryClientMock}>
<TextList {...initPros} />
</ServicesContextProvider>,
);

const toggleEditButton = screen.getAllByRole('button', {
name: textMock('text_editor.toggle_edit_mode', { textKey: textKey1 }),
Expand All @@ -90,7 +106,12 @@ describe('TextList', () => {
textMock('text_editor.key.error_empty'),
];
const { rerender, initPros } = renderTextList({ updateEntryId });
rerender(<TextList {...initPros} />);
queryClientMock.setQueryData([QueryKey.LayoutNames, 'org', 'app'], []);
rerender(
<ServicesContextProvider {...queriesMock} client={queryClientMock}>
<TextList {...initPros} />
</ServicesContextProvider>,
);

const toggleEditButton = screen.getAllByRole('button', {
name: textMock('text_editor.toggle_edit_mode', { textKey: textKey1 }),
Expand Down
Loading

0 comments on commit e72def6

Please sign in to comment.