From f20335bc74f38b643850b6154ef2412b69d9ec7a Mon Sep 17 00:00:00 2001 From: Tomas Engebretsen Date: Tue, 28 Jan 2025 12:36:29 +0100 Subject: [PATCH 01/52] Add mocks for code list endpoints (#14528) --- frontend/packages/shared/src/api/mutations.ts | 9 +++++++ frontend/packages/shared/src/api/queries.ts | 5 ++++ .../shared/src/mocks/codeListsResponse.ts | 26 +++++++++++++++++++ .../packages/shared/src/mocks/queriesMock.ts | 6 +++++ .../packages/shared/src/types/CodeList.ts | 1 + .../packages/shared/src/types/CodeListData.ts | 7 +++++ .../shared/src/types/api/CodeListsResponse.ts | 3 +++ 7 files changed, 57 insertions(+) create mode 100644 frontend/packages/shared/src/mocks/codeListsResponse.ts create mode 100644 frontend/packages/shared/src/types/CodeList.ts create mode 100644 frontend/packages/shared/src/types/CodeListData.ts create mode 100644 frontend/packages/shared/src/types/api/CodeListsResponse.ts diff --git a/frontend/packages/shared/src/api/mutations.ts b/frontend/packages/shared/src/api/mutations.ts index d4d5faf950d..465f8edc356 100644 --- a/frontend/packages/shared/src/api/mutations.ts +++ b/frontend/packages/shared/src/api/mutations.ts @@ -75,6 +75,8 @@ import type { FormLayoutRequest } from 'app-shared/types/api/FormLayoutRequest'; import type { Option } from 'app-shared/types/Option'; import type { MaskinportenScopes } from 'app-shared/types/MaskinportenScope'; import type { DataType } from '../types/DataType'; +import type { CodeListData } from 'app-shared/types/CodeListData'; +import type { CodeList } from 'app-shared/types/CodeList'; const headers = { Accept: 'application/json', @@ -167,3 +169,10 @@ export const updateProcessDataTypes = (org: string, app: string, dataTypesChange // Maskinporten export const updateSelectedMaskinportenScopes = (org: string, app: string, appScopesUpsertRequest: MaskinportenScopes) => put(selectedMaskinportenScopesPath(org, app), appScopesUpsertRequest); + +// Organisation library: +// Todo: Replace these with real API calls when endpoints are ready. https://github.com/Altinn/altinn-studio/issues/14505 +export const createCodeListForOrg = async (org: string, payload: CodeListData): Promise => Promise.resolve(); +export const updateCodeListForOrg = async (org: string, codeListId: string, payload: CodeList): Promise => Promise.resolve(); +export const deleteCodeListForOrg = async (org: string, codeListId: string): Promise => Promise.resolve(); +export const uploadCodeListForOrg = async (org: string, app: string, payload: FormData): Promise => Promise.resolve(); diff --git a/frontend/packages/shared/src/api/queries.ts b/frontend/packages/shared/src/api/queries.ts index 18941a258f3..eece7ba6c50 100644 --- a/frontend/packages/shared/src/api/queries.ts +++ b/frontend/packages/shared/src/api/queries.ts @@ -99,6 +99,8 @@ import type { OptionListReferences } from 'app-shared/types/OptionListReferences import type { LayoutSetsModel } from '../types/api/dto/LayoutSetsModel'; import type { AccessPackageResource, PolicyAccessPackageAreaGroup } from 'app-shared/types/PolicyAccessPackages'; import type { DataType } from '../types/DataType'; +import { codeListsResponse } from '../mocks/codeListsResponse'; +import type { CodeListsResponse } from '../types/api/CodeListsResponse'; export const getIsLoggedInWithAnsattporten = () => get<{ isLoggedIn: boolean }>(authStatusAnsattporten()); export const getMaskinportenScopes = (org: string, app: string) => get(availableMaskinportenScopesPath(org, app)); @@ -176,3 +178,6 @@ export const getProcessTaskType = (org: string, app: string, taskId: string) => // Contact Page export const fetchBelongsToGiteaOrg = () => get(belongsToOrg()); + +// Organisation library +export const getCodeListsForOrg = async (org: string): Promise => Promise.resolve(codeListsResponse); // Todo: Replace with real API call when endpoint is ready. https://github.com/Altinn/altinn-studio/issues/14505 diff --git a/frontend/packages/shared/src/mocks/codeListsResponse.ts b/frontend/packages/shared/src/mocks/codeListsResponse.ts new file mode 100644 index 00000000000..8304d94bb5a --- /dev/null +++ b/frontend/packages/shared/src/mocks/codeListsResponse.ts @@ -0,0 +1,26 @@ +import type { CodeListsResponse } from '../types/api/CodeListsResponse'; +import type { CodeListData } from '@studio/content-library'; + +const codeList1: CodeListData = { + title: 'codeList1', + data: [ + { label: 'Item 1', value: 'item1' }, + { label: 'Item 2', value: 'item2' }, + ], +}; + +const codeList2: CodeListData = { + title: 'codeList2', + data: [ + { label: 'A', value: 'a' }, + { label: 'B', value: 'b' }, + { label: 'C', value: 'c' }, + ], +}; + +const codeListWithError: CodeListData = { + title: 'codeListWithError', + hasError: true, +}; + +export const codeListsResponse: CodeListsResponse = [codeList1, codeList2, codeListWithError]; diff --git a/frontend/packages/shared/src/mocks/queriesMock.ts b/frontend/packages/shared/src/mocks/queriesMock.ts index 80ce56357c7..e01db32ee86 100644 --- a/frontend/packages/shared/src/mocks/queriesMock.ts +++ b/frontend/packages/shared/src/mocks/queriesMock.ts @@ -74,6 +74,7 @@ import type { OptionListReferences } from 'app-shared/types/OptionListReferences import type { LayoutSetsModel } from '../types/api/dto/LayoutSetsModel'; import { layoutSetsExtendedMock } from '@altinn/ux-editor/testing/layoutSetsMock'; import type { OptionListsResponse } from 'app-shared/types/api/OptionListsResponse'; +import type { CodeListsResponse } from 'app-shared/types/api/CodeListsResponse'; export const queriesMock: ServicesContextProps = { // Queries @@ -83,6 +84,7 @@ export const queriesMock: ServicesContextProps = { .mockImplementation(() => Promise.resolve(appReleasesResponse)), getAppVersion: jest.fn().mockImplementation(() => Promise.resolve(appVersion)), getBranchStatus: jest.fn().mockImplementation(() => Promise.resolve(branchStatus)), + getCodeListsForOrg: jest.fn().mockImplementation(() => Promise.resolve([])), getDataModel: jest.fn().mockImplementation(() => Promise.resolve({})), getDataModelMetadata: jest .fn() @@ -214,6 +216,7 @@ export const queriesMock: ServicesContextProps = { .fn() .mockImplementation(() => Promise.resolve(createRepoCommitPayload)), copyApp: jest.fn().mockImplementation(() => Promise.resolve()), + createCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), createDataModel: jest.fn().mockImplementation(() => Promise.resolve({})), updateDataType: jest.fn().mockImplementation(() => Promise.resolve({})), createDeployment: jest.fn().mockImplementation(() => Promise.resolve()), @@ -222,6 +225,7 @@ export const queriesMock: ServicesContextProps = { .fn() .mockImplementation(() => Promise.resolve(createRepoCommitPayload)), deleteAppAttachmentMetadata: jest.fn().mockImplementation(() => Promise.resolve()), + deleteCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), deleteDataModel: jest.fn().mockImplementation(() => Promise.resolve()), deleteDataTypeFromAppMetadata: jest.fn().mockImplementation(() => Promise.resolve()), deleteFormLayout: jest.fn().mockImplementation(() => Promise.resolve()), @@ -248,8 +252,10 @@ export const queriesMock: ServicesContextProps = { updateAppPolicy: jest.fn().mockImplementation(() => Promise.resolve()), updateAppMetadata: jest.fn().mockImplementation(() => Promise.resolve()), updateAppConfig: jest.fn().mockImplementation(() => Promise.resolve()), + updateCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), updateOptionList: jest.fn().mockImplementation(() => Promise.resolve()), updateOptionListId: jest.fn().mockImplementation(() => Promise.resolve()), + uploadCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), uploadDataModel: jest.fn().mockImplementation(() => Promise.resolve({})), uploadOptionList: jest.fn().mockImplementation(() => Promise.resolve()), upsertTextResources: jest diff --git a/frontend/packages/shared/src/types/CodeList.ts b/frontend/packages/shared/src/types/CodeList.ts new file mode 100644 index 00000000000..bd336bfa537 --- /dev/null +++ b/frontend/packages/shared/src/types/CodeList.ts @@ -0,0 +1 @@ +export type { CodeList } from '@studio/components'; diff --git a/frontend/packages/shared/src/types/CodeListData.ts b/frontend/packages/shared/src/types/CodeListData.ts new file mode 100644 index 00000000000..d09e909f0c4 --- /dev/null +++ b/frontend/packages/shared/src/types/CodeListData.ts @@ -0,0 +1,7 @@ +import type { CodeListItem } from '@studio/components'; + +export type CodeListData = { + title: string; + data?: CodeListItem[]; + hasError?: boolean; +}; diff --git a/frontend/packages/shared/src/types/api/CodeListsResponse.ts b/frontend/packages/shared/src/types/api/CodeListsResponse.ts new file mode 100644 index 00000000000..875fa3b26dd --- /dev/null +++ b/frontend/packages/shared/src/types/api/CodeListsResponse.ts @@ -0,0 +1,3 @@ +import type { CodeListData } from '../CodeListData'; + +export type CodeListsResponse = CodeListData[]; From fbc88860d0bceef39eaaca689ac22063f779e878 Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:50:34 +0100 Subject: [PATCH 02/52] feat: add paths for org-level code lists (#14530) --- frontend/packages/shared/src/api/paths.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/packages/shared/src/api/paths.js b/frontend/packages/shared/src/api/paths.js index d3859982f55..7b875a973d3 100644 --- a/frontend/packages/shared/src/api/paths.js +++ b/frontend/packages/shared/src/api/paths.js @@ -87,6 +87,11 @@ export const getImageFileNamesPath = (org, app) => `${basePath}/${org}/${app}/im // Languages - new text-format export const languagesPath = (org, app) => `${basePath}/${org}/${app}/languages`; // Get +// Library - org-level +export const orgCodeListsPath = (org) => `${basePath}/${org}/code-lists`; // Get +export const orgCodeListPath = (org, optionsListId) => `${basePath}/${org}/code-lists/${optionsListId}`; // Post, Put, Delete +export const orgCodeListUploadPath = (org) => `${basePath}/${org}/code-lists/upload`; // Post + // Organizations export const orgsListPath = () => `${basePath}/orgs`; // Get From ce1dff3423fe0d2ed9a6e35820097bdce24b900b Mon Sep 17 00:00:00 2001 From: Tomas Engebretsen Date: Tue, 28 Jan 2025 13:57:08 +0100 Subject: [PATCH 03/52] Org text resource mocks (#14534) --- frontend/packages/shared/src/api/mutations.ts | 9 +- frontend/packages/shared/src/api/queries.ts | 2 + .../shared/src/mocks/codeListsResponse.ts | 31 +++++-- .../packages/shared/src/mocks/queriesMock.ts | 7 ++ .../shared/src/mocks/textResourcesMock.ts | 87 +++++++++++++++++++ 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 frontend/packages/shared/src/mocks/textResourcesMock.ts diff --git a/frontend/packages/shared/src/api/mutations.ts b/frontend/packages/shared/src/api/mutations.ts index 465f8edc356..17eb0c5cfb2 100644 --- a/frontend/packages/shared/src/api/mutations.ts +++ b/frontend/packages/shared/src/api/mutations.ts @@ -57,7 +57,7 @@ import type { CreateDeploymentPayload } from 'app-shared/types/api/CreateDeploym import type { CreateReleasePayload } from 'app-shared/types/api/CreateReleasePayload'; import type { CreateRepoCommitPayload } from 'app-shared/types/api/CreateRepoCommitPayload'; import type { LayoutSetPayload } from 'app-shared/types/api/LayoutSetPayload'; -import type { ILayoutSettings, ITextResourcesObjectFormat } from 'app-shared/types/global'; +import type { ILayoutSettings, ITextResource, ITextResourcesObjectFormat } from 'app-shared/types/global'; import type { RuleConfig } from 'app-shared/types/RuleConfig'; import type { UpdateTextIdPayload } from 'app-shared/types/api/UpdateTextIdPayload'; import { buildQueryParams } from 'app-shared/utils/urlUtils'; @@ -170,9 +170,14 @@ export const updateProcessDataTypes = (org: string, app: string, dataTypesChange // Maskinporten export const updateSelectedMaskinportenScopes = (org: string, app: string, appScopesUpsertRequest: MaskinportenScopes) => put(selectedMaskinportenScopesPath(org, app), appScopesUpsertRequest); -// Organisation library: +// Organisation library code lists: // Todo: Replace these with real API calls when endpoints are ready. https://github.com/Altinn/altinn-studio/issues/14505 export const createCodeListForOrg = async (org: string, payload: CodeListData): Promise => Promise.resolve(); export const updateCodeListForOrg = async (org: string, codeListId: string, payload: CodeList): Promise => Promise.resolve(); export const deleteCodeListForOrg = async (org: string, codeListId: string): Promise => Promise.resolve(); export const uploadCodeListForOrg = async (org: string, app: string, payload: FormData): Promise => Promise.resolve(); + +// Organisation text resources: +// Todo: Replace these with real API calls when endpoints are ready. https://github.com/Altinn/altinn-studio/issues/14503 +export const createTextResourcesForOrg = async (org: string, language: string): Promise => Promise.resolve(); +export const updateTextResourcesForOrg = async (org: string, language: string, payload: ITextResource[]): Promise => Promise.resolve(); diff --git a/frontend/packages/shared/src/api/queries.ts b/frontend/packages/shared/src/api/queries.ts index eece7ba6c50..8f6d9bc5eef 100644 --- a/frontend/packages/shared/src/api/queries.ts +++ b/frontend/packages/shared/src/api/queries.ts @@ -101,6 +101,7 @@ import type { AccessPackageResource, PolicyAccessPackageAreaGroup } from 'app-sh import type { DataType } from '../types/DataType'; import { codeListsResponse } from '../mocks/codeListsResponse'; import type { CodeListsResponse } from '../types/api/CodeListsResponse'; +import { textResourcesMock } from '../mocks/textResourcesMock'; export const getIsLoggedInWithAnsattporten = () => get<{ isLoggedIn: boolean }>(authStatusAnsattporten()); export const getMaskinportenScopes = (org: string, app: string) => get(availableMaskinportenScopesPath(org, app)); @@ -181,3 +182,4 @@ export const fetchBelongsToGiteaOrg = () => get(belongsToOrg()); // Organisation library export const getCodeListsForOrg = async (org: string): Promise => Promise.resolve(codeListsResponse); // Todo: Replace with real API call when endpoint is ready. https://github.com/Altinn/altinn-studio/issues/14505 +export const getTextResourcesForOrg = async (org: string, language: string): Promise => Promise.resolve(textResourcesMock); // Todo: Replace with real API call when endpoint is ready. https://github.com/Altinn/altinn-studio/issues/14503 diff --git a/frontend/packages/shared/src/mocks/codeListsResponse.ts b/frontend/packages/shared/src/mocks/codeListsResponse.ts index 8304d94bb5a..63a9a94ef81 100644 --- a/frontend/packages/shared/src/mocks/codeListsResponse.ts +++ b/frontend/packages/shared/src/mocks/codeListsResponse.ts @@ -1,20 +1,41 @@ import type { CodeListsResponse } from '../types/api/CodeListsResponse'; import type { CodeListData } from '@studio/content-library'; +import { + description1TextResource, + description2TextResource, + helpText1TextResource, + helpText2TextResource, + label1TextResource, + label2TextResource, + label3TextResource, + label4TextResource, + label5TextResource, +} from 'app-shared/mocks/textResourcesMock'; const codeList1: CodeListData = { title: 'codeList1', data: [ - { label: 'Item 1', value: 'item1' }, - { label: 'Item 2', value: 'item2' }, + { + description: description1TextResource.id, + helpText: helpText1TextResource.id, + label: label1TextResource.id, + value: 'item1', + }, + { + description: description2TextResource.id, + helpText: helpText2TextResource.id, + label: label2TextResource.id, + value: 'item2', + }, ], }; const codeList2: CodeListData = { title: 'codeList2', data: [ - { label: 'A', value: 'a' }, - { label: 'B', value: 'b' }, - { label: 'C', value: 'c' }, + { label: label3TextResource.id, value: 'a' }, + { label: label4TextResource.id, value: 'b' }, + { label: label5TextResource.id, value: 'c' }, ], }; diff --git a/frontend/packages/shared/src/mocks/queriesMock.ts b/frontend/packages/shared/src/mocks/queriesMock.ts index e01db32ee86..ca4791bf3e0 100644 --- a/frontend/packages/shared/src/mocks/queriesMock.ts +++ b/frontend/packages/shared/src/mocks/queriesMock.ts @@ -197,6 +197,11 @@ export const queriesMock: ServicesContextProps = { getSelectedMaskinportenScopes: jest .fn() .mockImplementation(() => Promise.resolve([])), + getTextResourcesForOrg: jest + .fn() + .mockImplementation(() => + Promise.resolve(textResourcesWithLanguage), + ), updateSelectedMaskinportenScopes: jest.fn().mockImplementation(() => Promise.resolve()), // Queries - Contact @@ -224,6 +229,7 @@ export const queriesMock: ServicesContextProps = { createRepoCommit: jest .fn() .mockImplementation(() => Promise.resolve(createRepoCommitPayload)), + createTextResourcesForOrg: jest.fn().mockImplementation(() => Promise.resolve()), deleteAppAttachmentMetadata: jest.fn().mockImplementation(() => Promise.resolve()), deleteCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), deleteDataModel: jest.fn().mockImplementation(() => Promise.resolve()), @@ -255,6 +261,7 @@ export const queriesMock: ServicesContextProps = { updateCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), updateOptionList: jest.fn().mockImplementation(() => Promise.resolve()), updateOptionListId: jest.fn().mockImplementation(() => Promise.resolve()), + updateTextResourcesForOrg: jest.fn().mockImplementation(() => Promise.resolve()), uploadCodeListForOrg: jest.fn().mockImplementation(() => Promise.resolve()), uploadDataModel: jest.fn().mockImplementation(() => Promise.resolve({})), uploadOptionList: jest.fn().mockImplementation(() => Promise.resolve()), diff --git a/frontend/packages/shared/src/mocks/textResourcesMock.ts b/frontend/packages/shared/src/mocks/textResourcesMock.ts new file mode 100644 index 00000000000..422d756b113 --- /dev/null +++ b/frontend/packages/shared/src/mocks/textResourcesMock.ts @@ -0,0 +1,87 @@ +import type { ITextResource, ITextResourcesWithLanguage } from 'app-shared/types/global'; + +export const label1TextResource: ITextResource = { + id: 'label1', + value: 'Ledetekst 1', +}; +export const label2TextResource: ITextResource = { + id: 'label2', + value: 'Ledetekst 2', +}; +export const label3TextResource: ITextResource = { + id: 'label3', + value: 'Ledetekst 3', +}; +export const label4TextResource: ITextResource = { + id: 'label4', + value: 'Ledetekst 4', +}; +export const label5TextResource: ITextResource = { + id: 'label5', + value: 'Ledetekst 5', +}; + +export const description1TextResource: ITextResource = { + id: 'description1', + value: 'Beskrivelse 1', +}; +export const description2TextResource: ITextResource = { + id: 'description2', + value: 'Beskrivelse 2', +}; +export const description3TextResource: ITextResource = { + id: 'description3', + value: 'Beskrivelse 3', +}; +export const description4TextResource: ITextResource = { + id: 'description4', + value: 'Beskrivelse 4', +}; +export const description5TextResource: ITextResource = { + id: 'description5', + value: 'Beskrivelse 5', +}; + +export const helpText1TextResource: ITextResource = { + id: 'helpText1', + value: 'Hjelpetekst 1', +}; +export const helpText2TextResource: ITextResource = { + id: 'helpText2', + value: 'Hjelpetekst 2', +}; +export const helpText3TextResource: ITextResource = { + id: 'helpText3', + value: 'Hjelpetekst 3', +}; +export const helpText4TextResource: ITextResource = { + id: 'helpText4', + value: 'Hjelpetekst 4', +}; +export const helpText5TextResource: ITextResource = { + id: 'helpText5', + value: 'Hjelpetekst 5', +}; + +const textResources: ITextResource[] = [ + label1TextResource, + label2TextResource, + label3TextResource, + label4TextResource, + label5TextResource, + description1TextResource, + description2TextResource, + description3TextResource, + description4TextResource, + description5TextResource, + helpText1TextResource, + helpText2TextResource, + helpText3TextResource, + helpText4TextResource, + helpText5TextResource, +]; + +export const textResourcesMock: ITextResourcesWithLanguage = { + language: 'nb', + resources: textResources, +}; From d179445ea927f98a7101ae14849a75d3109fceb7 Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Tue, 28 Jan 2025 16:06:58 +0100 Subject: [PATCH 04/52] feat: create endpoints for codelists on org level (#14533) --- .../Organisation/CodeListController.cs | 151 +++++++++++++++ .../Preview/InstancesController.cs | 4 +- .../Preview/V3/OldInstancesController.cs | 4 +- .../Designer/Controllers/PreviewController.cs | 3 +- .../GitRepository/AltinnAppGitRepository.cs | 11 +- .../Infrastructure/ServiceRegistration.cs | 3 + .../Services/Implementation/OptionsService.cs | 5 +- .../Organisation/CodeListService.cs | 177 ++++++++++++++++++ .../Organisation/ICodeListService.cs | 96 ++++++++++ .../AltinnAppGitRepositoryTests.cs | 12 +- .../Services/TextsServiceTest.cs | 4 +- 11 files changed, 454 insertions(+), 16 deletions(-) create mode 100644 backend/src/Designer/Controllers/Organisation/CodeListController.cs create mode 100644 backend/src/Designer/Services/Implementation/Organisation/CodeListService.cs create mode 100644 backend/src/Designer/Services/Interfaces/Organisation/ICodeListService.cs diff --git a/backend/src/Designer/Controllers/Organisation/CodeListController.cs b/backend/src/Designer/Controllers/Organisation/CodeListController.cs new file mode 100644 index 00000000000..95b7b1c2b57 --- /dev/null +++ b/backend/src/Designer/Controllers/Organisation/CodeListController.cs @@ -0,0 +1,151 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Helpers; +using Altinn.Studio.Designer.Models; +using Altinn.Studio.Designer.Models.Dto; +using Altinn.Studio.Designer.Services.Interfaces.Organisation; +using LibGit2Sharp; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.Studio.Designer.Controllers.Organisation; + +/// +/// Controller containing actions related to options (code lists) on organisation level. +/// +[ApiController] +[Authorize] +[Route("designer/api/{org}/code-lists")] +public class CodeListController : ControllerBase +{ + private readonly ICodeListService _codeListService; + private const string Repo = "content"; + + /// + /// Initializes a new instance of the class. + /// + /// The options service for organisation level + public CodeListController(ICodeListService codeListService) + { + _codeListService = codeListService; + } + + /// + /// Fetches the contents of all the options lists belonging to the app. + /// + /// Unique identifier of the organisation responsible for the app. + /// List of objects with all option lists belonging to the app with data + /// set if option list is valid, or hasError set if option list is invalid. + [HttpGet] + public async Task>> GetCodeLists(string org) + { + try + { + string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext); + + List optionLists = await _codeListService.GetCodeLists(org, Repo, developer); + + return Ok(optionLists); + } + catch (NotFoundException) + { + return NoContent(); + } + } + + /// + /// Creates or overwrites an options list. + /// + /// Unique identifier of the organisation responsible for the app. + /// Name of the options list. + /// Contents of the options list. + /// A that observes if operation is cancelled. + [HttpPost] + [Produces("application/json")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Route("{optionsListId}")] + public async Task>>> CreateCodeList(string org, [FromRoute] string optionsListId, [FromBody] List