Skip to content

Commit

Permalink
adapt getAllOptionsLists to return also faulty options lists if readi…
Browse files Browse the repository at this point in the history
…ng them fails
  • Loading branch information
standeren committed Dec 11, 2024
1 parent 066b2f2 commit 0f7a0f4
Show file tree
Hide file tree
Showing 37 changed files with 366 additions and 355 deletions.
33 changes: 28 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,41 @@ 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 All @@ -31,22 +31,21 @@ jest.mock(
>
{uploadCodeListButtonTextMock}
</button>
<button
onClick={() => onUpdateCodeList({ title: codeListNameMock, codeList: codeListMock })}
>
<button onClick={() => onUpdateCodeList({ title: codeListNameMock, data: codeListMock })}>
{updateCodeListButtonTextMock}
</button>
</div>
),
}),
);

const optionListIdsMock: string[] = ['list1'];
const optionsListsMock: OptionsListsResponse = [{ title: codeListNameMock }];

Check failure on line 42 in frontend/app-development/features/appContentLibrary/AppContentLibrary.test.tsx

View workflow job for this annotation

GitHub Actions / Typechecking and linting

'optionsListsMock' is declared but its value is never read.

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

it('renders the AppContentLibrary with codeLists and images resources available in the content menu', () => {

renderAppContentLibrary();
const libraryTitle = screen.getByRole('heading', {
name: textMock('app_content_library.landing_page.title'),
Expand Down Expand Up @@ -125,16 +124,16 @@ const goToLibraryPage = async (user: UserEvent, libraryPage: string) => {

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

const renderAppContentLibrary = ({
queries = {},
optionLists = optionListsMock,
optionsLists = [],
}: renderAppContentLibraryProps = {}) => {
const queryClientMock = createQueryClientMock();
if (optionLists.length) {
queryClientMock.setQueryData([QueryKey.OptionListIds, org, app], optionLists);
if (optionsLists.length) {
queryClientMock.setQueryData([QueryKey.OptionLists, org, app], optionsLists);
}
renderWithProviders(queries, queryClientMock)(<AppContentLibrary />);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,22 @@ import type { ApiError } from 'app-shared/types/api/ApiError';
import { toast } from 'react-toastify';
import type { AxiosError } from 'axios';
import { isErrorUnknown } from 'app-shared/utils/ApiErrorUtils';
import { useGetOptionListQuery } from 'app-shared/hooks/queries';
import { useOptionListsQuery } from 'app-shared/hooks/queries';
import { convertOptionsListsDataToCodeListsData } from './utils/convertOptionsListsDataToCodeListsData';

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 { data: optionListIds, isPending: optionListIdsPending } = useOptionListIdsQuery(org, app);
const getOptionList = useGetOptionListQuery(org, app);
const { mutate: uploadOptionList } = useAddOptionListMutation(org, app);
const { mutate: updateOptionList } = useUpdateOptionListMutation(org, app);

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

const handleUpload = (file: File) => {
Expand All @@ -43,16 +40,17 @@ export function AppContentLibrary(): React.ReactElement {
});
};

const handleUpdate = ({ title, codeList }: CodeListWithMetadata) => {
updateOptionList({ optionListId: title, optionsList: codeList });
const handleUpdate = ({ title, data }: CodeListWithMetadata) => {
updateOptionList({ optionListId: title, optionsList: data });
};

const codeListsData = convertOptionsListsDataToCodeListsData(optionListsData);

const { getContentResourceLibrary } = new ResourceContentLibraryImpl({
pages: {
codeList: {
props: {
codeListIds: optionListIds,
getCodeList: getOptionList,
codeListsData,
onUpdateCodeList: handleUpdate,
onUploadCodeList: handleUpload,
},
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;
};
13 changes: 7 additions & 6 deletions frontend/libs/studio-content-library/mocks/mockPagesConfig.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { PagesConfig } from '../src/types/PagesProps';
import type { UseLibraryQuery } from '../src/types/useLibraryQuery';
import type { CodeList } from '@studio/components';
import type { CodeListData } from '../src';

const getCodeListMock: UseLibraryQuery<CodeList, string> = (optionListId: string) => {
return { data: [], isError: false };
export const codeListData: CodeListData = {
title: 'CodeList1',
data: [{ value: 'value', label: 'label' }],
hasError: false,
};
export const codeListsDataMock: CodeListData[] = [codeListData];

export const mockPagesConfig: PagesConfig = {
codeList: {
props: {
codeListIds: ['CodeList1', 'CodeList2'],
getCodeList: getCodeListMock,
codeListsData: codeListsDataMock,
onUpdateCodeList: () => {},
onUploadCodeList: () => {},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { CodeListProps, OnGetCodeListResult } from './CodeList';
import type { CodeListProps } from './CodeList';
import { CodeList } from './CodeList';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { codeListsDataMock } from '../../../../../mocks/mockPagesConfig';

const onUpdateCodeListMock = jest.fn();
const onUploadCodeListMock = jest.fn();
const codeListName = 'codeList';
const codeListMock = [{ value: 'value', label: 'label' }];
const getCodeListMock: jest.Mock<OnGetCodeListResult, [codeListId: string]> = jest.fn(
(codeListId: string) => {
return { codeListWithMetadata: { title: codeListId, codeList: codeListMock }, isError: false };
},
);

describe('CodeList', () => {
it('renders the codeList heading', () => {
Expand Down Expand Up @@ -47,14 +41,13 @@ describe('CodeList', () => {

it('renders the code list as a clickable element', () => {
renderCodeList();
const codeListAccordion = screen.getByRole('button', { name: codeListName });
const codeListAccordion = screen.getByRole('button', { name: codeListsDataMock[0].title });
expect(codeListAccordion).toBeInTheDocument();
});
});

const defaultCodeListProps: CodeListProps = {
codeListIds: [codeListName],
getCodeList: getCodeListMock,
codeListsData: codeListsDataMock,
onUpdateCodeList: onUpdateCodeListMock,
onUploadCodeList: onUploadCodeListMock,
};
Expand Down
Loading

0 comments on commit 0f7a0f4

Please sign in to comment.