diff --git a/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.test.tsx b/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.test.tsx
index 80cd830252f..579ffcadc0b 100644
--- a/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.test.tsx
+++ b/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.test.tsx
@@ -16,16 +16,27 @@ import { queriesMock } from 'app-shared/mocks/queriesMock';
import { SelectedContextType } from '../../context/HeaderContext';
import { Route, Routes } from 'react-router-dom';
-const deleteCodeListButtonTextMock = 'Delete Code List';
-const codeListNameMock = 'codeListNameMock';
+const uploadCodeListButtonTextMock: string = 'Upload Code List';
+const deleteCodeListButtonTextMock: string = 'Delete Code List';
+const codeListNameMock: string = 'codeListNameMock';
const codeListMock: CodeList = [{ value: '', label: '' }];
const codeListsDataMock: CodeListData[] = [{ title: codeListNameMock, data: codeListMock }];
+const mockOrgPath: string = '/testOrg';
jest.mock(
'../../../libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage',
() => ({
- CodeListPage: ({ onDeleteCodeList }: any) => (
+ CodeListPage: ({ onDeleteCodeList, onUploadCodeList }: any) => (
+
@@ -84,6 +95,58 @@ describe('OrgContentLibrary', () => {
expect(codeListMenuElement).toBeInTheDocument();
});
+ it('calls onUploadCodeList when onUploadCodeList is triggered', async () => {
+ const user = userEvent.setup();
+ renderOrgContentLibraryWithCodeLists({ initialEntries: [mockOrgPath] });
+ await goToLibraryPage(user, 'code_lists');
+ const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock });
+ await user.click(uploadCodeListButton);
+ expect(queriesMock.uploadCodeListForOrg).toHaveBeenCalledTimes(1);
+ expect(queriesMock.uploadCodeListForOrg).toHaveBeenCalledWith(org, expect.any(FormData));
+ });
+
+ it('renders success toast when onUploadCodeList is called successfully', async () => {
+ const user = userEvent.setup();
+ renderOrgContentLibraryWithCodeLists({ initialEntries: [mockOrgPath] });
+ await goToLibraryPage(user, 'code_lists');
+ const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock });
+ await user.click(uploadCodeListButton);
+ const successToastMessage = screen.getByText(
+ textMock('dashboard.org_library.code_list_upload_success'),
+ );
+ expect(successToastMessage).toBeInTheDocument();
+ });
+
+ it('renders error toast when onUploadCodeList is rejected with unknown error code', async () => {
+ const user = userEvent.setup();
+ const uploadCodeListForOrg = jest
+ .fn()
+ .mockImplementation(() => Promise.reject({ response: {} }));
+ renderOrgContentLibraryWithCodeLists({
+ initialEntries: [mockOrgPath],
+ queries: { uploadCodeListForOrg },
+ });
+ await goToLibraryPage(user, 'code_lists');
+ const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock });
+ await user.click(uploadCodeListButton);
+ const errorToastMessage = screen.getByText(
+ textMock('dashboard.org_library.code_list_upload_generic_error'),
+ );
+ expect(errorToastMessage).toBeInTheDocument();
+ });
+
+ it('calls onUploadCodeList and hides default error when handleUpload is triggered', async () => {
+ const user = userEvent.setup();
+ renderOrgContentLibraryWithCodeLists({ initialEntries: [mockOrgPath] });
+ await goToLibraryPage(user, 'code_lists');
+ const uploadCodeListButton = screen.getByRole('button', { name: uploadCodeListButtonTextMock });
+ await user.click(uploadCodeListButton);
+ expect(queriesMock.uploadCodeListForOrg).toHaveBeenCalledTimes(1);
+ expect(queriesMock.uploadCodeListForOrg).toHaveBeenCalledWith(org, expect.any(FormData));
+ const hideDefaultError = screen.queryByText(textMock('dashboard.org_library.default_error'));
+ expect(hideDefaultError).not.toBeInTheDocument();
+ });
+
it('calls deleteCodeListForOrg when onDeleteCodeList is triggered', async () => {
const user = userEvent.setup();
renderOrgContentLibraryWithCodeLists({ initialEntries: ['/testOrg'] });
diff --git a/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.tsx b/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.tsx
index 1fb4a3fc497..78fbbc36843 100644
--- a/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.tsx
+++ b/frontend/dashboard/pages/OrgContentLibrary/OrgContentLibrary.tsx
@@ -1,10 +1,15 @@
import type { ReactElement } from 'react';
-import React from 'react';
+import React, { useCallback } from 'react';
import { ResourceContentLibraryImpl } from '@studio/content-library';
+import { useTranslation } from 'react-i18next';
+import { isErrorUnknown } from 'app-shared/utils/ApiErrorUtils';
+import type { ApiError } from 'app-shared/types/api/ApiError';
+import { useUploadOrgCodeListMutation } from 'app-shared/hooks/mutations/useUploadOrgCodeListMutation';
+import { toast } from 'react-toastify';
+import type { AxiosError } from 'axios';
import { useSelectedContext } from '../../hooks/useSelectedContext';
import { useDeleteOrgCodeListMutation } from 'app-shared/hooks/mutations/useDeleteOrgCodeListMutation';
import { StudioAlert, StudioCenter, StudioParagraph } from '@studio/components';
-import { useTranslation } from 'react-i18next';
import { isOrg } from './utils';
export function OrgContentLibrary(): ReactElement {
@@ -19,6 +24,9 @@ export function OrgContentLibrary(): ReactElement {
function OrgContentLibraryWithContext(): ReactElement {
const selectedContext = useSelectedContext();
+
+ const handleUpload = useUploadCodeList(selectedContext);
+
const { mutate: deleteCodeList } = useDeleteOrgCodeListMutation(selectedContext);
const { getContentResourceLibrary } = new ResourceContentLibraryImpl({
@@ -29,7 +37,7 @@ function OrgContentLibraryWithContext(): ReactElement {
onDeleteCodeList: deleteCodeList,
onUpdateCodeListId: () => {},
onUpdateCodeList: () => {},
- onUploadCodeList: () => {},
+ onUploadCodeList: handleUpload,
},
},
},
@@ -51,3 +59,25 @@ function ContextWithoutLibraryAccess(): ReactElement {
);
}
+
+function useUploadCodeList(org: string): (file: File) => void {
+ const { mutate: uploadCodeList } = useUploadOrgCodeListMutation(org, {
+ hideDefaultError: (error: AxiosError
) => isErrorUnknown(error),
+ });
+ const { t } = useTranslation();
+
+ return useCallback(
+ (file: File) =>
+ uploadCodeList(file, {
+ onSuccess: () => {
+ toast.success(t('dashboard.org_library.code_list_upload_success'));
+ },
+ onError: (error: AxiosError) => {
+ if (isErrorUnknown(error)) {
+ toast.error(t('dashboard.org_library.code_list_upload_generic_error'));
+ }
+ },
+ }),
+ [uploadCodeList, t],
+ );
+}
diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index d37dd640fb4..5bfc04d5b9f 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -234,6 +234,8 @@
"dashboard.org_data_models": "{{orgName}}s datamodeller",
"dashboard.org_library.alert_no_org_selected": "For å vise biblioteket, må du velge en organisasjon i menyen øverst til høyre.",
"dashboard.org_library.alert_no_org_selected_no_access": "Hvis du ikke har tilgang til noen organisasjoner, må du be om tilgang fra en tjenesteeier.",
+ "dashboard.org_library.code_list_upload_success": "Filen ble lastet opp.",
+ "dashboard.org_library.fetch_error": "Det har oppstått et problem ved henting av data til biblioteket.",
"dashboard.org_resources": "{{orgName}}s ressurser",
"dashboard.resource_contact_description": "Se oversikt over hvordan du kommer i kontakt med oss i Altinn Studio.",
"dashboard.resource_contact_label": "Kontakt oss",
diff --git a/frontend/packages/shared/src/hooks/mutations/useUploadOrgCodeListMutation.ts b/frontend/packages/shared/src/hooks/mutations/useUploadOrgCodeListMutation.ts
index 4333880bb50..13197f015cb 100644
--- a/frontend/packages/shared/src/hooks/mutations/useUploadOrgCodeListMutation.ts
+++ b/frontend/packages/shared/src/hooks/mutations/useUploadOrgCodeListMutation.ts
@@ -1,10 +1,11 @@
+import type { MutationMeta } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useServicesContext } from '../../contexts/ServicesContext';
import { QueryKey } from '../../types/QueryKey';
import type { CodeListsResponse } from '../../types/api/CodeListsResponse';
import { FileUtils } from '@studio/pure-functions';
-export const useUploadOrgCodeListMutation = (org: string) => {
+export const useUploadOrgCodeListMutation = (org: string, meta?: MutationMeta) => {
const queryClient = useQueryClient();
const { uploadCodeListForOrg } = useServicesContext();
@@ -18,5 +19,6 @@ export const useUploadOrgCodeListMutation = (org: string) => {
onSuccess: (newData: CodeListsResponse) => {
queryClient.setQueryData([QueryKey.OrgCodeLists, org], newData);
},
+ meta,
});
};