Skip to content

Commit

Permalink
refactor: adapt contract when fetching all option lists to visualise …
Browse files Browse the repository at this point in the history
…error per option list
  • Loading branch information
standeren committed Dec 17, 2024
1 parent 8783861 commit 0273996
Show file tree
Hide file tree
Showing 31 changed files with 306 additions and 257 deletions.
32 changes: 27 additions & 5 deletions backend/src/Designer/Controllers/OptionsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Altinn.Studio.Designer.Exceptions.Options;
using Altinn.Studio.Designer.Helpers;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Models.Dto;
using Altinn.Studio.Designer.Services.Interfaces;
using LibGit2Sharp;
using Microsoft.AspNetCore.Authorization;
Expand Down Expand Up @@ -56,20 +58,40 @@ public ActionResult<string[]> GetOptionsListIds(string org, string repo)
/// </summary>
/// <param name="org">Unique identifier of the organisation responsible for the app.</param>
/// <param name="repo">Application identifier which is unique within an organisation.</param>
/// <returns>Dictionary of all option lists belonging to the app</returns>
/// <returns>List of <see cref="OptionListData" /> objects with all option lists belonging to the app with data
/// set if option list is valid, or isError set if option list is invalid.</returns>
[HttpGet]
[Route("option-lists")]
public async Task<ActionResult<Dictionary<string, List<Option>>>> GetOptionLists(string org, string repo)
public async Task<ActionResult<List<OptionListData>>> GetOptionLists(string org, string repo)
{
try
{
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
string[] optionListIds = _optionsService.GetOptionsListIds(org, repo, developer);
Dictionary<string, List<Option>> optionLists = [];
List<OptionListData> optionLists = [];
foreach (string optionListId in optionListIds)
{
List<Option> optionList = await _optionsService.GetOptionsList(org, repo, developer, optionListId);
optionLists.Add(optionListId, optionList);
try
{
List<Option> optionList = await _optionsService.GetOptionsList(org, repo, developer, optionListId);
OptionListData optionListData = new()
{
Title = optionListId,
Data = optionList,
HasError = false
};
optionLists.Add(optionListData);
}
catch (InvalidOptionsFormatException)
{
OptionListData optionListData = new()
{
Title = optionListId,
Data = null,
HasError = true
};
optionLists.Add(optionListData);
}
}
return Ok(optionLists);
}
Expand Down
11 changes: 11 additions & 0 deletions backend/src/Designer/Models/Dto/OptionListData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using JetBrains.Annotations;

namespace Altinn.Studio.Designer.Models.Dto;

public class OptionListData
{
public string Title;
[CanBeNull] public List<Option> Data;
public bool? HasError;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { app, org } from '@studio/testing/testids';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import type { UserEvent } from '@testing-library/user-event';
import userEvent from '@testing-library/user-event';
import type { OptionsLists } from 'app-shared/types/api/OptionsLists';
import type { CodeList } from '@studio/components';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists';

const uploadCodeListButtonTextMock = 'Upload Code List';
const updateCodeListButtonTextMock = 'Update Code List';
Expand Down Expand Up @@ -41,10 +41,6 @@ jest.mock(
}),
);

const optionListsMock: OptionsLists = {
list1: [{ label: 'label', value: 'value' }],
};

describe('AppContentLibrary', () => {
afterEach(jest.clearAllMocks);

Expand All @@ -61,7 +57,7 @@ describe('AppContentLibrary', () => {
});

it('renders a spinner when waiting for option lists', () => {
renderAppContentLibrary({ optionLists: {} });
renderAppContentLibrary({ optionListsData: [] });
const spinner = screen.getByText(textMock('general.loading'));
expect(spinner).toBeInTheDocument();
});
Expand Down Expand Up @@ -127,16 +123,16 @@ const goToLibraryPage = async (user: UserEvent, libraryPage: string) => {

type renderAppContentLibraryProps = {
queries?: Partial<ServicesContextProps>;
optionLists?: OptionsLists;
optionListsData?: OptionsListsResponse;
};

const renderAppContentLibrary = ({
queries = {},
optionLists = optionListsMock,
optionListsData = [],
}: renderAppContentLibraryProps = {}) => {
const queryClientMock = createQueryClientMock();
if (Object.keys(optionLists).length) {
queryClientMock.setQueryData([QueryKey.OptionLists, org, app], optionLists);
if (optionListsData.length) {
queryClientMock.setQueryData([QueryKey.OptionLists, org, app], optionListsData);
}
renderWithProviders(queries, queryClientMock)(<AppContentLibrary />);
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { CodeListWithMetadata } from '@studio/content-library';
import { ResourceContentLibraryImpl } from '@studio/content-library';
import React from 'react';
import { useOptionListsQuery } from 'app-shared/hooks/queries/useOptionListsQuery';
import { useOptionListsQuery } from 'app-shared/hooks/queries';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { convertOptionListsToCodeLists } from './utils/convertOptionListsToCodeLists';
import { convertOptionsListsDataToCodeListsData } from './utils/convertOptionsListsDataToCodeListsData';
import { StudioPageSpinner } from '@studio/components';
import { useTranslation } from 'react-i18next';
import { useAddOptionListMutation, useUpdateOptionListMutation } from 'app-shared/hooks/mutations';
Expand All @@ -15,20 +15,19 @@ import { isErrorUnknown } from 'app-shared/utils/ApiErrorUtils';
export function AppContentLibrary(): React.ReactElement {
const { org, app } = useStudioEnvironmentParams();
const { t } = useTranslation();
const {
data: optionLists,
isPending: optionListsPending,
isError: optionListsError,
} = useOptionListsQuery(org, app);
const { data: optionListsData, isPending: optionListsDataPending } = useOptionListsQuery(
org,
app,
);
const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, {
hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error),
});
const { mutate: updateOptionList } = useUpdateOptionListMutation(org, app);

if (optionListsPending)
if (optionListsDataPending)
return <StudioPageSpinner spinnerTitle={t('general.loading')}></StudioPageSpinner>;

const codeLists = convertOptionListsToCodeLists(optionLists);
const codeListsData = convertOptionsListsDataToCodeListsData(optionListsData);

const handleUpload = (file: File) => {
uploadOptionList(file, {
Expand All @@ -51,10 +50,9 @@ export function AppContentLibrary(): React.ReactElement {
pages: {
codeList: {
props: {
codeLists: codeLists,
codeListsData,
onUpdateCodeList: handleUpdate,
onUploadCodeList: handleUpload,
fetchDataError: optionListsError,
},
},
images: {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { CodeListData } from '@studio/content-library';
import { convertOptionsListsDataToCodeListsData } from './convertOptionsListsDataToCodeListsData';
import type { OptionsListsResponse } from 'app-shared/types/api/OptionsLists';

describe('convertOptionsListsDataToCodeListsData', () => {
it('converts option lists data to code lists data correctly', () => {
const optionListId: string = 'optionListId';
const optionListsData: OptionsListsResponse = [
{
title: optionListId,
data: [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
],
hasError: false,
},
];
const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData);
expect(result).toEqual([
{
title: optionListId,
data: [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
],
hasError: false,
},
]);
});

it('sets hasError to true in result when optionListsResponse returns an option list with error', () => {
const optionListId: string = 'optionListId';
const optionListsData: OptionsListsResponse = [
{
title: optionListId,
data: null,
hasError: true,
},
];
const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData);
expect(result).toEqual([{ title: optionListId, data: null, hasError: true }]);
});

it('returns a result with empty code list data array when the input option list data is empty', () => {
const optionListsData: OptionsListsResponse = [];
const result: CodeListData[] = convertOptionsListsDataToCodeListsData(optionListsData);
expect(result).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { OptionsListData, OptionsListsResponse } from 'app-shared/types/api/OptionsLists';
import type { CodeListData } from '@studio/content-library';

export const convertOptionsListsDataToCodeListsData = (optionListsData: OptionsListsResponse) => {
const codeListsData = [];
optionListsData.map((optionListData) => {
const codeListData = convertOptionsListDataToCodeListData(optionListData);
codeListsData.push(codeListData);
});
return codeListsData;
};

const convertOptionsListDataToCodeListData = (optionListData: OptionsListData) => {
const codeListData: CodeListData = {
title: optionListData.title,
data: optionListData.data,
hasError: optionListData.hasError,
};
return codeListData;
};
2 changes: 1 addition & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"app_content_library.code_lists.create_new_code_list_modal_title": "Lag ny kodeliste",
"app_content_library.code_lists.create_new_code_list_name": "Navn",
"app_content_library.code_lists.edit_code_list_placeholder_text": "Her kommer det redigeringsmuligheter snart",
"app_content_library.code_lists.fetch_error": "Kunne ikke hente kodelister fra appen.",
"app_content_library.code_lists.fetch_error": "Kunne ikke hente kodelisten fra appen.",
"app_content_library.code_lists.info_box.description": "En kodeliste er en liste med strukturerte data. Den inneholder definerte alternativer som alle har en unik kode. For eksempel kan du ha en kodeliste med kommunenavn i skjemaet ditt, som brukerne kan velge fra en nedtrekksmeny. Brukerne ser bare navnet, ikke koden.",
"app_content_library.code_lists.info_box.title": "Hva er en kodeliste?",
"app_content_library.code_lists.no_content": "Dette biblioteket har ingen kodelister",
Expand Down
14 changes: 9 additions & 5 deletions frontend/libs/studio-content-library/mocks/mockPagesConfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import type { PagesConfig } from '../src/types/PagesProps';
import type { CodeListData } from '../src';

export const codeListData: CodeListData = {
title: 'CodeList1',
data: [{ value: 'value', label: 'label' }],
hasError: false,
};
export const codeListsDataMock: CodeListData[] = [codeListData];

export const mockPagesConfig: PagesConfig = {
codeList: {
props: {
codeLists: [
{ title: 'CodeList1', codeList: [] },
{ title: 'CodeList2', codeList: [] },
],
codeListsData: codeListsDataMock,
onUpdateCodeList: () => {},
onUploadCodeList: () => {},
fetchDataError: false,
},
},
images: {
Expand Down
Loading

0 comments on commit 0273996

Please sign in to comment.