Skip to content

Commit

Permalink
Merge branch 'main' into support-code-lists-seacrh-in-library
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Jan 16, 2025
2 parents 797b0b5 + 9f902c6 commit c194cbd
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useAddOptionListMutation,
useUpdateOptionListMutation,
useUpdateOptionListIdMutation,
useDeleteOptionListMutation,
} from 'app-shared/hooks/mutations';
import { mapToCodeListsUsage } from './utils/mapToCodeListsUsage';

Expand All @@ -24,6 +25,7 @@ export function AppContentLibrary(): React.ReactElement {
org,
app,
);
const { mutate: deleteOptionList } = useDeleteOptionListMutation(org, app);
const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, {
hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error),
});
Expand Down Expand Up @@ -65,6 +67,7 @@ export function AppContentLibrary(): React.ReactElement {
codeList: {
props: {
codeListsData,
onDeleteCodeList: deleteOptionList,
onUpdateCodeListId: handleUpdateCodeListId,
onUpdateCodeList: handleUpdate,
onUploadCodeList: handleUpload,
Expand Down
3 changes: 3 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"app_content_library.code_lists.code_list_accordion_title": "Kodelistenavn: {{codeListTitle}}",
"app_content_library.code_lists.code_list_accordion_usage_sub_title_plural": "Kodelisten brukes i {{codeListUsagesCount}} komponenter.",
"app_content_library.code_lists.code_list_accordion_usage_sub_title_single": "Kodelisten brukes i {{codeListUsagesCount}} komponent.",
"app_content_library.code_lists.code_list_delete": "Slett kodeliste",
"app_content_library.code_lists.code_list_delete_disabled_title": "Før du kan å slette kodelisten, må du fjerne den fra der den er brukt i appen.",
"app_content_library.code_lists.code_list_delete_enabled_title": "Slett kodelisten fra biblioteket.",
"app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste",
"app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}",
"app_content_library.code_lists.code_list_show_usage": "Se hvor kodelisten er tatt i bruk",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const mockPagesConfig: PagesConfig = {
codeList: {
props: {
codeListsData: codeListsDataMock,
onDeleteCodeList: () => {},
onUpdateCodeListId: () => {},
onUpdateCodeList: () => {},
onUploadCodeList: () => {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock';
import type { CodeList as StudioComponentCodeList } from '@studio/components';
import { codeListsDataMock } from '../../../../../mocks/mockPagesConfig';

const onDeleteCodeListMock = jest.fn();
const onUpdateCodeListIdMock = jest.fn();
const onUpdateCodeListMock = jest.fn();
const onUploadCodeListMock = jest.fn();
Expand Down Expand Up @@ -190,6 +191,7 @@ const getCodeListAccordion = (codeListTitle: string) => {

const defaultCodeListPageProps: CodeListPageProps = {
codeListsData: codeListsDataMock,
onDeleteCodeList: onDeleteCodeListMock,
onUpdateCodeListId: onUpdateCodeListIdMock,
onUpdateCodeList: onUpdateCodeListMock,
onUploadCodeList: onUploadCodeListMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type CodeListData = {

export type CodeListPageProps = {
codeListsData: CodeListData[];
onDeleteCodeList: (codeListId: string) => void;
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
onUploadCodeList: (uploadedCodeList: File) => void;
Expand All @@ -31,6 +32,7 @@ export type CodeListPageProps = {

export function CodeListPage({
codeListsData,
onDeleteCodeList,
onUpdateCodeListId,
onUpdateCodeList,
onUploadCodeList,
Expand Down Expand Up @@ -69,6 +71,7 @@ export function CodeListPage({
/>
<CodeLists
codeListsData={filteredCodeLists}
onDeleteCodeList={onDeleteCodeList}
onUpdateCodeListId={handleUpdateCodeListId}
onUpdateCodeList={onUpdateCodeList}
codeListInEditMode={codeListInEditMode}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { CodeList as StudioComponentsCodeList } from '@studio/components';
import { codeListsDataMock } from '../../../../../../mocks/mockPagesConfig';

const codeListName = codeListsDataMock[0].title;
const onDeleteCodeListMock = jest.fn();
const onUpdateCodeListIdMock = jest.fn();
const onUpdateCodeListMock = jest.fn();

Expand Down Expand Up @@ -137,6 +138,26 @@ describe('CodeLists', () => {
expect(codeListUsagesModalTitle).toBeInTheDocument();
});

it('renders button to delete code list as disabled when code list is used', async () => {
renderCodeLists({
codeListsUsages: [
{
codeListId: codeListName,
codeListIdSources: [
{ layoutSetId: 'layoutSetId', layoutName: 'layoutName', componentIds: ['componentId'] },
],
},
],
});
const deleteCodeListButton = screen.getByRole('button', {
name: textMock('app_content_library.code_lists.code_list_delete'),
});
expect(deleteCodeListButton).toBeDisabled();
expect(deleteCodeListButton.title).toBe(
textMock('app_content_library.code_lists.code_list_delete_disabled_title'),
);
});

it('renders the code list editor', () => {
renderCodeLists();
const codeListEditor = screen.getByText(textMock('code_list_editor.legend'));
Expand Down Expand Up @@ -206,6 +227,20 @@ describe('CodeLists', () => {
const errorMessage = screen.getByText(textMock('app_content_library.code_lists.fetch_error'));
expect(errorMessage).toBeInTheDocument();
});

it('calls onDeleteCodeList when clicking delete button', async () => {
const user = userEvent.setup();
renderCodeLists();
const deleteCodeListButton = screen.getByRole('button', {
name: textMock('app_content_library.code_lists.code_list_delete'),
});
expect(deleteCodeListButton.title).toBe(
textMock('app_content_library.code_lists.code_list_delete_enabled_title'),
);
await user.click(deleteCodeListButton);
expect(onDeleteCodeListMock).toHaveBeenCalledTimes(1);
expect(onDeleteCodeListMock).toHaveBeenLastCalledWith(codeListName);
});
});

const changeCodeListId = async (user: UserEvent, oldCodeListId: string, newCodeListId: string) => {
Expand All @@ -227,6 +262,7 @@ const changeCodeListId = async (user: UserEvent, oldCodeListId: string, newCodeL

const defaultProps: CodeListsProps = {
codeListsData: codeListsDataMock,
onDeleteCodeList: onDeleteCodeListMock,
onUpdateCodeListId: onUpdateCodeListIdMock,
onUpdateCodeList: onUpdateCodeListMock,
codeListInEditMode: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getCodeListSourcesById, getCodeListUsageCount } from '../utils/codeList

export type CodeListsProps = {
codeListsData: CodeListData[];
onDeleteCodeList: (codeListId: string) => void;
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
codeListInEditMode: string | undefined;
Expand All @@ -19,22 +20,16 @@ export type CodeListsProps = {

export function CodeLists({
codeListsData,
onUpdateCodeListId,
onUpdateCodeList,
codeListInEditMode,
codeListNames,
codeListsUsages,
...rest
}: CodeListsProps): React.ReactElement[] {
return codeListsData.map((codeListData) => {
const codeListSources = getCodeListSourcesById(codeListsUsages, codeListData.title);
return (
<CodeList
key={codeListData.title}
codeListData={codeListData}
onUpdateCodeListId={onUpdateCodeListId}
onUpdateCodeList={onUpdateCodeList}
codeListInEditMode={codeListInEditMode}
codeListNames={codeListNames}
{...rest}
codeListSources={codeListSources}
/>
);
Expand All @@ -48,11 +43,9 @@ type CodeListProps = Omit<CodeListsProps, 'codeListsData' | 'codeListsUsages'> &

function CodeList({
codeListData,
onUpdateCodeListId,
onUpdateCodeList,
codeListInEditMode,
codeListNames,
codeListSources,
...rest
}: CodeListProps): React.ReactElement {
return (
<Accordion border>
Expand All @@ -63,10 +56,8 @@ function CodeList({
/>
<CodeListAccordionContent
codeListData={codeListData}
onUpdateCodeListId={onUpdateCodeListId}
onUpdateCodeList={onUpdateCodeList}
codeListNames={codeListNames}
codeListSources={codeListSources}
{...rest}
/>
</Accordion.Item>
</Accordion>
Expand Down Expand Up @@ -120,10 +111,7 @@ type CodeListAccordionContentProps = Omit<CodeListProps, 'codeListInEditMode'>;

function CodeListAccordionContent({
codeListData,
onUpdateCodeListId,
onUpdateCodeList,
codeListNames,
codeListSources,
...rest
}: CodeListAccordionContentProps): React.ReactElement {
const { t } = useTranslation();

Expand All @@ -134,14 +122,7 @@ function CodeListAccordionContent({
{t('app_content_library.code_lists.fetch_error')}
</StudioAlert>
) : (
<EditCodeList
codeList={codeListData.data}
codeListTitle={codeListData.title}
onUpdateCodeListId={onUpdateCodeListId}
onUpdateCodeList={onUpdateCodeList}
codeListNames={codeListNames}
codeListSources={codeListSources}
/>
<EditCodeList codeList={codeListData.data} codeListTitle={codeListData.title} {...rest} />
)}
</Accordion.Content>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.editCodeList {
display: flex;
flex-direction: column;
gap: var(--fds-spacing-2);
gap: var(--fds-spacing-3);
}

.codeListUsageButton {
Expand All @@ -11,3 +11,9 @@
.seeUsageIcon {
font-size: var(--fds-sizing-5);
}

.buttons {
display: flex;
flex-direction: row;
gap: var(--fds-spacing-3);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type {
CodeList as StudioComponentsCodeList,
CodeList,
CodeListEditorTexts,
import type { CodeList, CodeListEditorTexts } from '@studio/components';
import {
StudioDeleteButton,
StudioModal,
StudioCodeListEditor,
StudioToggleableTextfield,
} from '@studio/components';
import { StudioModal, StudioCodeListEditor, StudioToggleableTextfield } from '@studio/components';
import React from 'react';
import { useTranslation } from 'react-i18next';
import type { CodeListWithMetadata } from '../../CodeListPage';
Expand All @@ -18,6 +19,7 @@ import { CodeListUsages } from './CodeListUsages/CodeListUsages';
export type EditCodeListProps = {
codeList: CodeList;
codeListTitle: string;
onDeleteCodeList: (codeListId: string) => void;
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
codeListNames: string[];
Expand All @@ -27,6 +29,7 @@ export type EditCodeListProps = {
export function EditCodeList({
codeList,
codeListTitle,
onDeleteCodeList,
onUpdateCodeListId,
onUpdateCodeList,
codeListNames,
Expand Down Expand Up @@ -54,6 +57,8 @@ export function EditCodeList({
return getInvalidInputFileNameErrorMessage(fileNameError);
};

const handleDeleteCodeList = (): void => onDeleteCodeList(codeListTitle);

const codeListHasUsages = codeListSources.length > 0;

return (
Expand Down Expand Up @@ -85,18 +90,52 @@ export function EditCodeList({
onBlurAny={handleCodeListChange}
texts={editorTexts}
/>
{codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />}
<CodeListButtons
codeListHasUsages={codeListHasUsages}
codeListSources={codeListSources}
onDeleteCodeList={handleDeleteCodeList}
/>
</div>
);
}

export const updateCodeListWithMetadata = (
currentCodeListWithMetadata: CodeListWithMetadata,
updatedCodeList: StudioComponentsCodeList,
updatedCodeList: CodeList,
): CodeListWithMetadata => {
return { ...currentCodeListWithMetadata, codeList: updatedCodeList };
};

type CodeListButtonsProps = {
codeListHasUsages: boolean;
codeListSources: CodeListIdSource[];
onDeleteCodeList: (codeListId: string) => void;
};

function CodeListButtons({
codeListHasUsages,
codeListSources,
onDeleteCodeList,
}: CodeListButtonsProps): React.ReactElement {
const { t } = useTranslation();
const deleteButtonTitle = codeListHasUsages
? t('app_content_library.code_lists.code_list_delete_disabled_title')
: t('app_content_library.code_lists.code_list_delete_enabled_title');

return (
<div className={classes.buttons}>
<StudioDeleteButton
onDelete={onDeleteCodeList}
title={deleteButtonTitle}
disabled={codeListHasUsages}
>
{t('app_content_library.code_lists.code_list_delete')}
</StudioDeleteButton>
{codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />}
</div>
);
}

export type ShowCodeListUsagesSourcesModalProps = {
codeListSources: CodeListIdSource[];
};
Expand Down
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/api/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
selectedMaskinportenScopesPath,
createInstancePath,
dataTypePath,
optionListPath,
} from 'app-shared/api/paths';
import type { AddLanguagePayload } from 'app-shared/types/api/AddLanguagePayload';
import type { AddRepoParams } from 'app-shared/types/api';
Expand Down Expand Up @@ -86,6 +87,7 @@ export const addImage = (org: string, app: string, form: FormData) => post<FormD
export const deleteImage = (org: string, app: string, imageName: string) => del(imagePath(org, app, imageName));

export const deleteLayoutSet = (org: string, app: string, layoutSetIdToUpdate: string) => del(layoutSetPath(org, app, layoutSetIdToUpdate));
export const deleteOptionList = (org: string, app: string, optionListId: string) => del(optionListPath(org, app, optionListId));
export const updateLayoutSetId = (org: string, app: string, layoutSetIdToUpdate: string, newLayoutSetId: string) => put(layoutSetPath(org, app, layoutSetIdToUpdate), newLayoutSetId, { headers: { 'Content-Type': 'application/json' } });
export const addRepo = (repoToAdd: AddRepoParams) => post<Repository>(`${createRepoPath()}${buildQueryParams(repoToAdd)}`);
export const addXsdFromRepo = (org: string, app: string, modelPath: string) => post<JsonSchema>(dataModelAddXsdFromRepoPath(org, app, modelPath));
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const submitFeedbackPath = (org, app) => `${basePath}/${org}/${app}/feedb
// FormEditor
export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post
export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get
export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get
export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get, Delete
export const optionListsPath = (org, app) => `${basePath}/${org}/${app}/options/option-lists`; // Get
export const optionListReferencesPath = (org, app) => `${basePath}/${org}/${app}/options/usage`; // Get
export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/hooks/mutations/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useAddOptionListMutation } from './useAddOptionListMutation';
export { useDeleteOptionListMutation } from './useDeleteOptionListMutation';
export { useUpdateOptionListMutation } from './useUpdateOptionListMutation';
export { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation';
export { useUpsertTextResourcesMutation } from './useUpsertTextResourcesMutation';
Expand Down
Loading

0 comments on commit c194cbd

Please sign in to comment.