From a275205a5a38f8145a64f7302d712efd338e2eb7 Mon Sep 17 00:00:00 2001 From: hegeaal Date: Wed, 2 Oct 2024 11:31:20 +0200 Subject: [PATCH] feat: add tema section to dataset form --- apps/dataset-catalog/app/actions/actions.ts | 27 +++- .../datasets/datasets-page-client.tsx | 4 +- .../catalogs/[catalogId]/datasets/page.tsx | 2 +- .../app/context/themes/index.tsx | 57 ++++++++ apps/dataset-catalog/app/layout.tsx | 23 ++-- .../dataset-form-access-rights.section.tsx | 2 +- .../dataset-form-tema-section.tsx | 124 ++++++++++++++++++ .../dataset-form/dataset-form.module.css | 11 ++ .../dataset-form/dataset-initial-values.tsx | 4 + .../components/dataset-form/index.tsx | 2 + .../dataset-form/validation-schema.tsx | 3 + libs/data-access/src/index.ts | 1 + libs/data-access/src/lib/themes/api/index.tsx | 19 +++ libs/types/src/index.ts | 1 + libs/types/src/lib/dataset.ts | 3 + libs/types/src/lib/theme.ts | 24 ++++ .../utils/src/lib/language/dataset.form.nb.ts | 7 + 17 files changed, 297 insertions(+), 17 deletions(-) create mode 100644 apps/dataset-catalog/app/context/themes/index.tsx create mode 100644 apps/dataset-catalog/components/dataset-form/dataset-form-tema-section.tsx create mode 100644 libs/data-access/src/lib/themes/api/index.tsx create mode 100644 libs/types/src/lib/theme.ts diff --git a/apps/dataset-catalog/app/actions/actions.ts b/apps/dataset-catalog/app/actions/actions.ts index 564d456b1..90cde0ba6 100644 --- a/apps/dataset-catalog/app/actions/actions.ts +++ b/apps/dataset-catalog/app/actions/actions.ts @@ -36,13 +36,26 @@ export async function getDatasetById(catalogId: string, datasetId: string): Prom return jsonResponse; } +const convertListToObjectStructure = (uriList: string[]) => { + return uriList.map((uri) => ({ uri: uri })); +}; + export async function createDataset(values: DatasetToBeCreated, catalogId: string) { - const newDataset = removeEmptyValues(values); + console.log(values.losThemeList && convertListToObjectStructure(values.losThemeList)); + const newDataset = { + ...values, + theme: [ + ...(values.losThemeList ? convertListToObjectStructure(values.losThemeList) : []), + ...(values.euThemeList ? convertListToObjectStructure(values.euThemeList) : []), + ], + }; + const datasetNoEmptyValues = removeEmptyValues(newDataset); + const session = await getValidSession(); let success = false; let datasetId = undefined; try { - const response = await postDataset(newDataset, catalogId, `${session?.accessToken}`); + const response = await postDataset(datasetNoEmptyValues, catalogId, `${session?.accessToken}`); if (response.status !== 201) { throw new Error(); } @@ -79,7 +92,15 @@ export async function deleteDataset(catalogId: string, datasetId: string) { } export async function updateDataset(catalogId: string, initialDataset: Dataset, values: Dataset) { - const diff = compare(initialDataset, removeEmptyValues(values)); + const updatedDataset = removeEmptyValues({ + ...values, + theme: [ + ...(values.losThemeList ? convertListToObjectStructure(values.losThemeList) : []), + ...(values.euThemeList ? convertListToObjectStructure(values.euThemeList) : []), + ], + }); + + const diff = compare(initialDataset, updatedDataset); if (diff.length === 0) { throw new Error(localization.alert.noChanges); diff --git a/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/datasets-page-client.tsx b/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/datasets-page-client.tsx index be8dd4333..e99f6126c 100644 --- a/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/datasets-page-client.tsx +++ b/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/datasets-page-client.tsx @@ -90,9 +90,9 @@ const DatasetsPageClient = ({ datasets, catalogId }: Props) => {
- Legg til... + {`${localization.add}...`} diff --git a/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/page.tsx b/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/page.tsx index 2ebd91e8e..da316cac9 100644 --- a/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/page.tsx +++ b/apps/dataset-catalog/app/catalogs/[catalogId]/datasets/page.tsx @@ -18,7 +18,7 @@ export default async function DatasetSearchHitsPage({ params }: Params) { const datasets: Dataset[] = await getDatasets(catalogId); const organization: Organization = await getOrganization(catalogId).then((res) => res.json()); - const hasWritePermission = await hasOrganizationWritePermission(session.accessToken, catalogId); + const hasWritePermission = hasOrganizationWritePermission(session.accessToken, catalogId); const breadcrumbList = [ { diff --git a/apps/dataset-catalog/app/context/themes/index.tsx b/apps/dataset-catalog/app/context/themes/index.tsx new file mode 100644 index 000000000..c60b8a68a --- /dev/null +++ b/apps/dataset-catalog/app/context/themes/index.tsx @@ -0,0 +1,57 @@ +'use client'; +import { getLosThemes, getDataThemes } from '@catalog-frontend/data-access'; +import { LosTheme, DataTheme } from '@catalog-frontend/types'; +import React, { createContext, useEffect, useState, ReactNode } from 'react'; + +type ThemesContextType = { + losThemes: LosTheme[]; + dataThemes: DataTheme[]; + loading: boolean; + error: string | null; +}; + +const ThemesContext = createContext(undefined); + +export const ThemesProvider = ({ children }: { children: ReactNode }) => { + const [losThemes, setLosThemes] = useState([]); + const [dataThemes, setDataThemes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchThemes = async () => { + setLoading(true); + try { + const [los, data] = await Promise.all([getLosThemes(), getDataThemes()]); + const losThemesData = await los.json(); + const dataThemesData = await data.json(); + + setLosThemes(losThemesData.losNodes.flat()); + setDataThemes(dataThemesData.dataThemes); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch themes'); + } finally { + setLoading(false); + } + }; + + fetchThemes(); + }, []); + + const value = { + losThemes, + dataThemes, + loading, + error, + }; + + return {children}; +}; + +export const useThemes = () => { + const context = React.useContext(ThemesContext); + if (context === undefined) { + throw new Error('useThemes must be used within a ThemesProvider'); + } + return context; +}; diff --git a/apps/dataset-catalog/app/layout.tsx b/apps/dataset-catalog/app/layout.tsx index 788ed6d74..0045c4fb1 100644 --- a/apps/dataset-catalog/app/layout.tsx +++ b/apps/dataset-catalog/app/layout.tsx @@ -1,6 +1,7 @@ import { Layout, NextAuthProvider } from '@catalog-frontend/ui'; import { localization } from '@catalog-frontend/utils'; import { Metadata } from 'next'; +import { ThemesProvider } from './context/themes'; export const metadata: Metadata = { title: localization.catalogType.dataset, @@ -12,16 +13,18 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => { - - {children} - + + + {children} + + diff --git a/apps/dataset-catalog/components/dataset-form/dataset-form-access-rights.section.tsx b/apps/dataset-catalog/components/dataset-form/dataset-form-access-rights.section.tsx index 637365e5e..cdedc33c1 100644 --- a/apps/dataset-catalog/components/dataset-form/dataset-form-access-rights.section.tsx +++ b/apps/dataset-catalog/components/dataset-form/dataset-form-access-rights.section.tsx @@ -31,7 +31,7 @@ export const AccessRightsSection = ({ errors, values }: AccessRightsSectionProps label={ } diff --git a/apps/dataset-catalog/components/dataset-form/dataset-form-tema-section.tsx b/apps/dataset-catalog/components/dataset-form/dataset-form-tema-section.tsx new file mode 100644 index 000000000..120295a88 --- /dev/null +++ b/apps/dataset-catalog/components/dataset-form/dataset-form-tema-section.tsx @@ -0,0 +1,124 @@ +import { Dataset, Option } from '@catalog-frontend/types'; +import { FormContainer, TitleWithTag } from '@catalog-frontend/ui'; +import { useThemes } from '../../app/context/themes/index'; +import { Combobox, Spinner } from '@digdir/designsystemet-react'; +import { getTranslateText, localization } from '@catalog-frontend/utils'; +import { Field, FormikErrors, useFormikContext } from 'formik'; +import styles from './dataset-form.module.css'; + +export const TemaSection = () => { + const { losThemes, dataThemes, loading } = useThemes(); + const { setFieldValue, values, errors } = useFormikContext(); + + const getNameFromLosPath = (path: string): string | string[] => { + const obj = losThemes?.find((obj) => obj.losPaths.includes(path)); + return obj ? getTranslateText(obj.name) : []; + }; + + const getParentNames = (inputPaths: string[]): string => { + const results: string[] = []; + + inputPaths.forEach((path) => { + const parts = path.split('/').slice(0, -1); + const parentPath = parts.slice(0, -1).join('/'); + const childPath = parts.join('/'); + + const parentName = getNameFromLosPath(parentPath); + const childName = getNameFromLosPath(childPath); + + const formattedResult = `${parentName} - ${childName}`; + results.push(formattedResult); + }); + + return `${localization.datasetForm.helptext.parentTheme}: ${results.join('; ')}`; + }; + + const containsFilter = (inputValue: string, option: Option): boolean => { + return option.label.toLowerCase().includes(inputValue.toLowerCase()); + }; + + return ( + + + <> +
+ + {loading ? ( +
+ +
+ ) : ( + setFieldValue('losThemeList', values)} + > + {localization.search.noHits} + {losThemes?.map((theme) => ( + + {getTranslateText(theme.name)} + + ))} + + )} +
+ + + <> +
+ + {loading ? ( +
+ +
+ ) : ( + setFieldValue('euThemeList', values)} + > + {localization.search.noHits} + {dataThemes && + dataThemes.map((eutheme) => ( + + {getTranslateText(eutheme.label)} + + ))} + + )} +
+ +
+ ); +}; + +export default TemaSection; diff --git a/apps/dataset-catalog/components/dataset-form/dataset-form.module.css b/apps/dataset-catalog/components/dataset-form/dataset-form.module.css index e69de29bb..4f8944179 100644 --- a/apps/dataset-catalog/components/dataset-form/dataset-form.module.css +++ b/apps/dataset-catalog/components/dataset-form/dataset-form.module.css @@ -0,0 +1,11 @@ +.combobox { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.spinner { + display: flex; + justify-content: center; + align-items: center; +} diff --git a/apps/dataset-catalog/components/dataset-form/dataset-initial-values.tsx b/apps/dataset-catalog/components/dataset-form/dataset-initial-values.tsx index 8b16b07da..b0c1581da 100644 --- a/apps/dataset-catalog/components/dataset-form/dataset-initial-values.tsx +++ b/apps/dataset-catalog/components/dataset-form/dataset-initial-values.tsx @@ -24,6 +24,8 @@ export const datasetTemplate = (dataset: Dataset): Dataset => { dataset.landingPage && dataset?.landingPage?.length > 0 && dataset.landingPage.every((page) => page !== null) ? dataset.landingPage : [''], + losThemeList: dataset.theme ? dataset.theme.filter((t) => t.uri.includes('/los/')).map((t) => t.uri) : [], + euThemeList: dataset.theme ? dataset.theme.filter((t) => t.uri.includes('/data-theme/')).map((t) => t.uri) : [], }; }; @@ -45,5 +47,7 @@ export const datasetToBeCreatedTemplate = (): DatasetToBeCreated => { legalBasisForAccess: [{ uri: '', prefLabel: { nb: '' } }], legalBasisForProcessing: [{ uri: '', prefLabel: { nb: '' } }], legalBasisForRestriction: [{ uri: '', prefLabel: { nb: '' } }], + losThemeList: [], + euThemeList: [], }; }; diff --git a/apps/dataset-catalog/components/dataset-form/index.tsx b/apps/dataset-catalog/components/dataset-form/index.tsx index 9e176764f..175aa2edb 100644 --- a/apps/dataset-catalog/components/dataset-form/index.tsx +++ b/apps/dataset-catalog/components/dataset-form/index.tsx @@ -11,6 +11,7 @@ import { useState } from 'react'; import { datasetValidationSchema } from './validation-schema'; import { TitleSection } from './dataset-from-title-section'; import { AccessRightsSection } from './dataset-form-access-rights.section'; +import TemaSection from './dataset-form-tema-section'; type Props = { initialValues: DatasetToBeCreated | Dataset; @@ -92,6 +93,7 @@ export const DatasetForm = ({ initialValues, submitType }: Props) => { values={values} errors={errors} /> +
diff --git a/apps/dataset-catalog/components/dataset-form/validation-schema.tsx b/apps/dataset-catalog/components/dataset-form/validation-schema.tsx index 8b8fb398b..9a41e1744 100644 --- a/apps/dataset-catalog/components/dataset-form/validation-schema.tsx +++ b/apps/dataset-catalog/components/dataset-form/validation-schema.tsx @@ -39,4 +39,7 @@ export const datasetValidationSchema = Yup.object().shape({ .url(localization.validation.invalidUrl), }), ), + euThemeList: Yup.array() + .min(1, localization.datasetForm.validation.euTheme) + .required(localization.datasetForm.validation.euTheme), }); diff --git a/libs/data-access/src/index.ts b/libs/data-access/src/index.ts index 771484c43..2a1f520ec 100644 --- a/libs/data-access/src/index.ts +++ b/libs/data-access/src/index.ts @@ -18,3 +18,4 @@ export * from './lib/data-service/api'; export * from './lib/records-of-processing-activities/api'; export * from './lib/strapi/generated/graphql'; export * from './lib/strapi/service-messages'; +export * from './lib/themes/api'; diff --git a/libs/data-access/src/lib/themes/api/index.tsx b/libs/data-access/src/lib/themes/api/index.tsx new file mode 100644 index 000000000..05d0ce8f5 --- /dev/null +++ b/libs/data-access/src/lib/themes/api/index.tsx @@ -0,0 +1,19 @@ +export const getLosThemes = async () => { + const resource = `https://staging.fellesdatakatalog.digdir.no/reference-data/los/themes-and-words`; //env-variabel kommer i neste PR + const options = { + headers: { + 'Content-Type': 'application/json', + }, + }; + return await fetch(resource, options); +}; + +export const getDataThemes = async () => { + const resource = `https://staging.fellesdatakatalog.digdir.no/reference-data/eu/data-themes`; //env-variabel kommer i neste PR + const options = { + headers: { + 'Content-Type': 'application/json', + }, + }; + return await fetch(resource, options); +}; diff --git a/libs/types/src/index.ts b/libs/types/src/index.ts index 1b8de2154..4355bb410 100644 --- a/libs/types/src/index.ts +++ b/libs/types/src/index.ts @@ -19,3 +19,4 @@ export * from './lib/filters'; export * from './lib/dataset'; export * from './lib/enums'; export * from './lib/catalogs'; +export * from './lib/theme'; diff --git a/libs/types/src/lib/dataset.ts b/libs/types/src/lib/dataset.ts index a13cf757f..80f431d57 100644 --- a/libs/types/src/lib/dataset.ts +++ b/libs/types/src/lib/dataset.ts @@ -16,6 +16,9 @@ export interface DatasetToBeCreated { legalBasisForAccess?: UriWIthLabel[]; legalBasisForRestriction?: UriWIthLabel[]; landingPage?: string[]; + theme?: { uri: string }[]; + losThemeList?: string[]; // An array of los theme uris used as helper values for Formik. This property is not part of the db object. + euThemeList?: string[]; // An array of eu theme uris used as helper values for Formik. This property is not part of the db object. } export interface UriWIthLabel { diff --git a/libs/types/src/lib/theme.ts b/libs/types/src/lib/theme.ts new file mode 100644 index 000000000..1077842a6 --- /dev/null +++ b/libs/types/src/lib/theme.ts @@ -0,0 +1,24 @@ +import { UriWIthLabel } from './dataset'; +import { LocalizedStrings } from './localization'; + +export interface LosTheme { + children?: null; + parents?: string[]; + isTheme?: boolean; + losPaths: string[]; + name?: LocalizedStrings; + definition?: null; + uri: string; + synonyms?: string[]; + relatedTerms?: null; + theme?: boolean; + internalId: null; +} + +export interface DataTheme { + uri: string; + code?: string; + label: LocalizedStrings; + startUse?: string; + conceptSchema?: UriWIthLabel; +} diff --git a/libs/utils/src/lib/language/dataset.form.nb.ts b/libs/utils/src/lib/language/dataset.form.nb.ts index 7c019d227..e01e66925 100644 --- a/libs/utils/src/lib/language/dataset.form.nb.ts +++ b/libs/utils/src/lib/language/dataset.form.nb.ts @@ -9,6 +9,8 @@ export const datasetFormNb = { legalBasisForRestriction: 'Angi referanse til relevant lov eller forskrift. Helst til lovdata på paragrafnivå.', legalBasisForProcessing: 'Angi referanse til relevant lov eller forskrift, samtykke eller nødvendighetsvurdering.', legalBasisForAccess: 'Angi referanse til relevant lov eller forskrift. Helst til lovdata på paragrafnivå.', + theme: 'Velg tema(er) som beskriver inneholdet i datasettet.', + parentTheme: 'Overordnet tema', }, heading: { description: 'Beskrivelse av datasettet', @@ -18,6 +20,8 @@ export const datasetFormNb = { legalBasisForRestriction: 'Skjermingshjemmel', legalBasisForProcessing: 'Behandlingsgrunnlag', legalBasisForAccess: 'Utleveringshjemmel', + losTheme: 'LOS-tema og emner', + euTheme: 'EU-tema', }, accessRight: { public: 'Allmenn tilgang', @@ -27,6 +31,8 @@ export const datasetFormNb = { fieldLabel: { description: 'Beskrivelse av datasettet (Norsk bokmål)', title: 'Tittel (Norsk bokmål)', + losTheme: 'Velg tema, kategorier og emner', + euTheme: 'Velg EU-tema(er)', }, alert: { confirmDelete: 'Er du sikker på at du vil slette datasettbeskrivelsen?', @@ -38,5 +44,6 @@ export const datasetFormNb = { descriptionRequired: 'Beskrivelse er påkrevd.', description: 'Beskrivelsen må være minst 5 karakterer lang.', url: `Ugyldig lenke. Vennligst sørg for at lenken starter med ‘https://’ og inneholder et gyldig toppdomene (f.eks. ‘.no’).`, + euTheme: 'Minst et EU-tema må være valgt.', }, };