Skip to content

Commit e13b24d

Browse files
committed
feat: support deletion of code list from library
1 parent a514966 commit e13b24d

File tree

15 files changed

+211
-34
lines changed

15 files changed

+211
-34
lines changed

frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
useAddOptionListMutation,
1515
useUpdateOptionListMutation,
1616
useUpdateOptionListIdMutation,
17+
useDeleteOptionListMutation,
1718
} from 'app-shared/hooks/mutations';
1819
import { mapToCodeListsUsage } from './utils/mapToCodeListsUsage';
1920

@@ -24,6 +25,7 @@ export function AppContentLibrary(): React.ReactElement {
2425
org,
2526
app,
2627
);
28+
const { mutate: deleteOptionList } = useDeleteOptionListMutation(org, app);
2729
const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, {
2830
hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error),
2931
});
@@ -65,6 +67,7 @@ export function AppContentLibrary(): React.ReactElement {
6567
codeList: {
6668
props: {
6769
codeListsData,
70+
onDeleteCodeList: deleteOptionList,
6871
onUpdateCodeListId: handleUpdateCodeListId,
6972
onUpdateCodeList: handleUpdate,
7073
onUploadCodeList: handleUpload,

frontend/language/src/nb.json

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"app_content_library.code_lists.code_list_accordion_title": "Kodelistenavn: {{codeListTitle}}",
1616
"app_content_library.code_lists.code_list_accordion_usage_sub_title_plural": "Kodelisten brukes i {{codeListUsagesCount}} komponenter.",
1717
"app_content_library.code_lists.code_list_accordion_usage_sub_title_single": "Kodelisten brukes i {{codeListUsagesCount}} komponent.",
18+
"app_content_library.code_lists.code_list_delete": "Slett kodeliste",
19+
"app_content_library.code_lists.code_list_delete_disabled_title": "Dersom du ønsker å slette denne kodelisten, må du fjerne den fra der den er brukt i appen.",
20+
"app_content_library.code_lists.code_list_delete_enabled_title": "Slett kodelisten fra bibliotket.",
1821
"app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste",
1922
"app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}",
2023
"app_content_library.code_lists.code_list_show_usage": "Se hvor kodelisten er tatt i bruk",

frontend/libs/studio-content-library/mocks/mockPagesConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const mockPagesConfig: PagesConfig = {
1212
codeList: {
1313
props: {
1414
codeListsData: codeListsDataMock,
15+
onDeleteCodeList: () => {},
1516
onUpdateCodeListId: () => {},
1617
onUpdateCodeList: () => {},
1718
onUploadCodeList: () => {},

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock';
88
import type { CodeList as StudioComponentCodeList } from '@studio/components';
99
import { codeListsDataMock } from '../../../../../mocks/mockPagesConfig';
1010

11+
const onDeleteCodeListMock = jest.fn();
1112
const onUpdateCodeListIdMock = jest.fn();
1213
const onUpdateCodeListMock = jest.fn();
1314
const onUploadCodeListMock = jest.fn();
@@ -151,6 +152,7 @@ const uploadCodeList = async (user: UserEvent, fileName: string = uploadedCodeLi
151152

152153
const defaultCodeListPageProps: CodeListPageProps = {
153154
codeListsData: codeListsDataMock,
155+
onDeleteCodeList: onDeleteCodeListMock,
154156
onUpdateCodeListId: onUpdateCodeListIdMock,
155157
onUpdateCodeList: onUpdateCodeListMock,
156158
onUploadCodeList: onUploadCodeListMock,

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export type CodeListData = {
2222

2323
export type CodeListPageProps = {
2424
codeListsData: CodeListData[];
25+
onDeleteCodeList: (codeListId: string) => void;
2526
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
2627
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
2728
onUploadCodeList: (uploadedCodeList: File) => void;
@@ -30,6 +31,7 @@ export type CodeListPageProps = {
3031

3132
export function CodeListPage({
3233
codeListsData,
34+
onDeleteCodeList,
3335
onUpdateCodeListId,
3436
onUpdateCodeList,
3537
onUploadCodeList,
@@ -61,6 +63,7 @@ export function CodeListPage({
6163
/>
6264
<CodeLists
6365
codeListsData={codeListsData}
66+
onDeleteCodeList={onDeleteCodeList}
6467
onUpdateCodeListId={handleUpdateCodeListId}
6568
onUpdateCodeList={onUpdateCodeList}
6669
codeListInEditMode={codeListInEditMode}

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { CodeList as StudioComponentsCodeList } from '@studio/components';
1212
import { codeListsDataMock } from '../../../../../../mocks/mockPagesConfig';
1313

1414
const codeListName = codeListsDataMock[0].title;
15+
const onDeleteCodeListMock = jest.fn();
1516
const onUpdateCodeListIdMock = jest.fn();
1617
const onUpdateCodeListMock = jest.fn();
1718

@@ -137,6 +138,26 @@ describe('CodeLists', () => {
137138
expect(codeListUsagesModalTitle).toBeInTheDocument();
138139
});
139140

141+
it('renders button to delete code list as disabled when code list is used', async () => {
142+
renderCodeLists({
143+
codeListsUsages: [
144+
{
145+
codeListId: codeListName,
146+
codeListIdSources: [
147+
{ layoutSetId: 'layoutSetId', layoutName: 'layoutName', componentIds: ['componentId'] },
148+
],
149+
},
150+
],
151+
});
152+
const deleteCodeListButton = screen.getByRole('button', {
153+
name: textMock('app_content_library.code_lists.code_list_delete'),
154+
});
155+
expect(deleteCodeListButton).toBeDisabled();
156+
expect(deleteCodeListButton.title).toBe(
157+
textMock('app_content_library.code_lists.code_list_delete_disabled_title'),
158+
);
159+
});
160+
140161
it('renders the code list editor', () => {
141162
renderCodeLists();
142163
const codeListEditor = screen.getByText(textMock('code_list_editor.legend'));
@@ -206,6 +227,20 @@ describe('CodeLists', () => {
206227
const errorMessage = screen.getByText(textMock('app_content_library.code_lists.fetch_error'));
207228
expect(errorMessage).toBeInTheDocument();
208229
});
230+
231+
it('calls onDeleteCodeList when clicking delete button', async () => {
232+
const user = userEvent.setup();
233+
renderCodeLists();
234+
const deleteCodeListButton = screen.getByRole('button', {
235+
name: textMock('app_content_library.code_lists.code_list_delete'),
236+
});
237+
expect(deleteCodeListButton.title).toBe(
238+
textMock('app_content_library.code_lists.code_list_delete_enabled_title'),
239+
);
240+
await user.click(deleteCodeListButton);
241+
expect(onDeleteCodeListMock).toHaveBeenCalledTimes(1);
242+
expect(onDeleteCodeListMock).toHaveBeenLastCalledWith(codeListName);
243+
});
209244
});
210245

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

228263
const defaultProps: CodeListsProps = {
229264
codeListsData: codeListsDataMock,
265+
onDeleteCodeList: onDeleteCodeListMock,
230266
onUpdateCodeListId: onUpdateCodeListIdMock,
231267
onUpdateCodeList: onUpdateCodeListMock,
232268
codeListInEditMode: undefined,

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx

+7-26
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getCodeListSourcesById, getCodeListUsageCount } from '../utils';
1010

1111
export type CodeListsProps = {
1212
codeListsData: CodeListData[];
13+
onDeleteCodeList: (codeListId: string) => void;
1314
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
1415
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
1516
codeListInEditMode: string | undefined;
@@ -19,22 +20,16 @@ export type CodeListsProps = {
1920

2021
export function CodeLists({
2122
codeListsData,
22-
onUpdateCodeListId,
23-
onUpdateCodeList,
24-
codeListInEditMode,
25-
codeListNames,
2623
codeListsUsages,
24+
...rest
2725
}: CodeListsProps): React.ReactElement[] {
2826
return codeListsData.map((codeListData) => {
2927
const codeListSources = getCodeListSourcesById(codeListsUsages, codeListData.title);
3028
return (
3129
<CodeList
3230
key={codeListData.title}
3331
codeListData={codeListData}
34-
onUpdateCodeListId={onUpdateCodeListId}
35-
onUpdateCodeList={onUpdateCodeList}
36-
codeListInEditMode={codeListInEditMode}
37-
codeListNames={codeListNames}
32+
{...rest}
3833
codeListSources={codeListSources}
3934
/>
4035
);
@@ -48,11 +43,9 @@ type CodeListProps = Omit<CodeListsProps, 'codeListsData' | 'codeListsUsages'> &
4843

4944
function CodeList({
5045
codeListData,
51-
onUpdateCodeListId,
52-
onUpdateCodeList,
5346
codeListInEditMode,
54-
codeListNames,
5547
codeListSources,
48+
...rest
5649
}: CodeListProps): React.ReactElement {
5750
return (
5851
<Accordion border>
@@ -63,10 +56,8 @@ function CodeList({
6356
/>
6457
<CodeListAccordionContent
6558
codeListData={codeListData}
66-
onUpdateCodeListId={onUpdateCodeListId}
67-
onUpdateCodeList={onUpdateCodeList}
68-
codeListNames={codeListNames}
6959
codeListSources={codeListSources}
60+
{...rest}
7061
/>
7162
</Accordion.Item>
7263
</Accordion>
@@ -120,10 +111,7 @@ type CodeListAccordionContentProps = Omit<CodeListProps, 'codeListInEditMode'>;
120111

121112
function CodeListAccordionContent({
122113
codeListData,
123-
onUpdateCodeListId,
124-
onUpdateCodeList,
125-
codeListNames,
126-
codeListSources,
114+
...rest
127115
}: CodeListAccordionContentProps): React.ReactElement {
128116
const { t } = useTranslation();
129117

@@ -134,14 +122,7 @@ function CodeListAccordionContent({
134122
{t('app_content_library.code_lists.fetch_error')}
135123
</StudioAlert>
136124
) : (
137-
<EditCodeList
138-
codeList={codeListData.data}
139-
codeListTitle={codeListData.title}
140-
onUpdateCodeListId={onUpdateCodeListId}
141-
onUpdateCodeList={onUpdateCodeList}
142-
codeListNames={codeListNames}
143-
codeListSources={codeListSources}
144-
/>
125+
<EditCodeList codeList={codeListData.data} codeListTitle={codeListData.title} {...rest} />
145126
)}
146127
</Accordion.Content>
147128
);

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.module.css

+6
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@
1111
.seeUsageIcon {
1212
font-size: var(--fds-sizing-5);
1313
}
14+
15+
.buttons {
16+
display: flex;
17+
flex-direction: row;
18+
gap: var(--fds-spacing-2);
19+
}

frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx

+46-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import type {
2-
CodeList as StudioComponentsCodeList,
3-
CodeList,
4-
CodeListEditorTexts,
1+
import type { CodeList, CodeListEditorTexts } from '@studio/components';
2+
import {
3+
StudioDeleteButton,
4+
StudioModal,
5+
StudioCodeListEditor,
6+
StudioToggleableTextfield,
57
} from '@studio/components';
6-
import { StudioModal, StudioCodeListEditor, StudioToggleableTextfield } from '@studio/components';
78
import React from 'react';
89
import { useTranslation } from 'react-i18next';
910
import type { CodeListWithMetadata } from '../../CodeListPage';
@@ -18,6 +19,7 @@ import { CodeListUsages } from './CodeListUsages/CodeListUsages';
1819
export type EditCodeListProps = {
1920
codeList: CodeList;
2021
codeListTitle: string;
22+
onDeleteCodeList: (codeListId: string) => void;
2123
onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void;
2224
onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void;
2325
codeListNames: string[];
@@ -27,6 +29,7 @@ export type EditCodeListProps = {
2729
export function EditCodeList({
2830
codeList,
2931
codeListTitle,
32+
onDeleteCodeList,
3033
onUpdateCodeListId,
3134
onUpdateCodeList,
3235
codeListNames,
@@ -54,6 +57,8 @@ export function EditCodeList({
5457
return getInvalidInputFileNameErrorMessage(fileNameError);
5558
};
5659

60+
const handleDeleteCodeList = (): void => onDeleteCodeList(codeListTitle);
61+
5762
const codeListHasUsages = codeListSources.length > 0;
5863

5964
return (
@@ -85,18 +90,52 @@ export function EditCodeList({
8590
onBlurAny={handleCodeListChange}
8691
texts={editorTexts}
8792
/>
88-
{codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />}
93+
<CodeListButtons
94+
codeListHasUsages={codeListHasUsages}
95+
codeListSources={codeListSources}
96+
onDeleteCodeList={handleDeleteCodeList}
97+
/>
8998
</div>
9099
);
91100
}
92101

93102
export const updateCodeListWithMetadata = (
94103
currentCodeListWithMetadata: CodeListWithMetadata,
95-
updatedCodeList: StudioComponentsCodeList,
104+
updatedCodeList: CodeList,
96105
): CodeListWithMetadata => {
97106
return { ...currentCodeListWithMetadata, codeList: updatedCodeList };
98107
};
99108

109+
type CodeListButtonsProps = {
110+
codeListHasUsages: boolean;
111+
codeListSources: CodeListIdSource[];
112+
onDeleteCodeList: (codeListId: string) => void;
113+
};
114+
115+
function CodeListButtons({
116+
codeListHasUsages,
117+
codeListSources,
118+
onDeleteCodeList,
119+
}: CodeListButtonsProps): React.ReactElement {
120+
const { t } = useTranslation();
121+
const deleteButtonTitle = codeListHasUsages
122+
? t('app_content_library.code_lists.code_list_delete_disabled_title')
123+
: t('app_content_library.code_lists.code_list_delete_enabled_title');
124+
125+
return (
126+
<div className={classes.buttons}>
127+
<StudioDeleteButton
128+
onDelete={onDeleteCodeList}
129+
title={deleteButtonTitle}
130+
disabled={codeListHasUsages}
131+
>
132+
{t('app_content_library.code_lists.code_list_delete')}
133+
</StudioDeleteButton>
134+
{codeListHasUsages && <ShowCodeListUsagesSourcesModal codeListSources={codeListSources} />}
135+
</div>
136+
);
137+
}
138+
100139
export type ShowCodeListUsagesSourcesModalProps = {
101140
codeListSources: CodeListIdSource[];
102141
};

frontend/packages/shared/src/api/mutations.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
selectedMaskinportenScopesPath,
4848
createInstancePath,
4949
dataTypePath,
50+
optionListPath,
5051
} from 'app-shared/api/paths';
5152
import type { AddLanguagePayload } from 'app-shared/types/api/AddLanguagePayload';
5253
import type { AddRepoParams } from 'app-shared/types/api';
@@ -86,6 +87,7 @@ export const addImage = (org: string, app: string, form: FormData) => post<FormD
8687
export const deleteImage = (org: string, app: string, imageName: string) => del(imagePath(org, app, imageName));
8788

8889
export const deleteLayoutSet = (org: string, app: string, layoutSetIdToUpdate: string) => del(layoutSetPath(org, app, layoutSetIdToUpdate));
90+
export const deleteOptionList = (org: string, app: string, optionListId: string) => del(optionListPath(org, app, optionListId));
8991
export const updateLayoutSetId = (org: string, app: string, layoutSetIdToUpdate: string, newLayoutSetId: string) => put(layoutSetPath(org, app, layoutSetIdToUpdate), newLayoutSetId, { headers: { 'Content-Type': 'application/json' } });
9092
export const addRepo = (repoToAdd: AddRepoParams) => post<Repository>(`${createRepoPath()}${buildQueryParams(repoToAdd)}`);
9193
export const addXsdFromRepo = (org: string, app: string, modelPath: string) => post<JsonSchema>(dataModelAddXsdFromRepoPath(org, app, modelPath));

frontend/packages/shared/src/api/paths.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const submitFeedbackPath = (org, app) => `${basePath}/${org}/${app}/feedb
4242
// FormEditor
4343
export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post
4444
export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get
45-
export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get
45+
export const optionListPath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Get, Delete
4646
export const optionListsPath = (org, app) => `${basePath}/${org}/${app}/options/option-lists`; // Get
4747
export const optionListReferencesPath = (org, app) => `${basePath}/${org}/${app}/options/usage`; // Get
4848
export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get

frontend/packages/shared/src/hooks/mutations/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { useAddOptionListMutation } from './useAddOptionListMutation';
2+
export { useDeleteOptionListMutation } from './useDeleteOptionListMutation';
23
export { useUpdateOptionListMutation } from './useUpdateOptionListMutation';
34
export { useUpdateOptionListIdMutation } from './useUpdateOptionListIdMutation';
45
export { useUpsertTextResourcesMutation } from './useUpsertTextResourcesMutation';

0 commit comments

Comments
 (0)