Skip to content

Commit

Permalink
feat: add tema section to dataset form (#832)
Browse files Browse the repository at this point in the history
  • Loading branch information
hegeaal authored Oct 10, 2024
1 parent b2d764a commit 4510cb7
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 17 deletions.
26 changes: 23 additions & 3 deletions apps/dataset-catalog/app/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,25 @@ 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);
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();
}
Expand Down Expand Up @@ -79,7 +91,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ const DatasetsPageClient = ({ datasets, catalogId }: Props) => {
<SearchHitsPageLayout.LeftColumn>
<div className={styles.leftColumn}>
<div>
<Paragraph>Legg til...</Paragraph>
<Paragraph>{`${localization.add}...`}</Paragraph>
<Select onChange={handleSelectChange}>
<option value='blank'>{`${localization.choose}...`}</option>
<option value=''>{`${localization.choose}...`}</option>
<option value='dataset'>{localization.resourceType.dataset}</option>
<option value='datasetSeries'>{localization.resourceType.datasetSeries}</option>
</Select>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand Down
57 changes: 57 additions & 0 deletions apps/dataset-catalog/app/context/themes/index.tsx
Original file line number Diff line number Diff line change
@@ -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<ThemesContextType | undefined>(undefined);

export const ThemesProvider = ({ children }: { children: ReactNode }) => {
const [losThemes, setLosThemes] = useState<LosTheme[]>([]);
const [dataThemes, setDataThemes] = useState<DataTheme[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(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) {
console.error(`Failed to fetch reference-data, ${err}`);
} finally {
setLoading(false);
}
};

fetchThemes();
}, []);

const value = {
losThemes,
dataThemes,
loading,
error,
};

return <ThemesContext.Provider value={value}>{children}</ThemesContext.Provider>;
};

export const useThemes = () => {
const context = React.useContext(ThemesContext);
if (context === undefined) {
throw new Error('useThemes must be used within a ThemesProvider');
}
return context;
};
23 changes: 13 additions & 10 deletions apps/dataset-catalog/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -12,16 +13,18 @@ const RootLayout = ({ children }: { children: React.ReactNode }) => {
<html lang={localization.getLanguage()}>
<body>
<NextAuthProvider>
<Layout
catalogAdminUrl={process.env.CATALOG_ADMIN_BASE_URI}
fdkRegistrationBaseUrl={`${process.env.NEXT_PUBLIC_CATALOG_PORTAL_BASE_URI}/catalogs`}
adminGuiBaseUrl={process.env.ADMIN_GUI_BASE_URI}
fdkCommunityBaseUrl={process.env.FDK_COMMUNITY_BASE_URI}
fdkBaseUrl={process.env.FDK_BASE_URI}
catalogTitle={localization.catalogType.service}
>
{children}
</Layout>
<ThemesProvider>
<Layout
catalogAdminUrl={process.env.CATALOG_ADMIN_BASE_URI}
fdkRegistrationBaseUrl={`${process.env.NEXT_PUBLIC_CATALOG_PORTAL_BASE_URI}/catalogs`}
adminGuiBaseUrl={process.env.ADMIN_GUI_BASE_URI}
fdkCommunityBaseUrl={process.env.FDK_COMMUNITY_BASE_URI}
fdkBaseUrl={process.env.FDK_BASE_URI}
catalogTitle={localization.catalogType.service}
>
{children}
</Layout>
</ThemesProvider>
</NextAuthProvider>
</body>
</html>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const AccessRightsSection = ({ errors, values }: AccessRightsSectionProps
label={
<TitleWithTag
title={localization.access}
tagColor='third'
tagColor='info'
tagTitle={localization.tag.recommended}
/>
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Dataset>();

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 (
<FormContainer>
<FormContainer.Header
title={localization.datasetForm.heading.losTheme}
subtitle={localization.datasetForm.helptext.theme}
/>
<>
<div className={styles.combobox}>
<TitleWithTag
title={localization.datasetForm.fieldLabel.losTheme}
tagTitle={localization.tag.recommended}
tagColor='info'
/>
{loading ? (
<div className={styles.spinner}>
<Spinner title={`${localization.loading}...`} />
</div>
) : (
<Field
as={Combobox}
name='losThemeList'
value={values.losThemeList}
multiple
virtual
filter={containsFilter}
placeholder={`${localization.search.search}...`}
onValueChange={(values: string[]) => setFieldValue('losThemeList', values)}
>
<Combobox.Empty>{localization.search.noHits}</Combobox.Empty>
{losThemes?.map((theme) => (
<Combobox.Option
key={theme.uri}
value={theme.uri}
description={getParentNames(theme.losPaths)}
>
{getTranslateText(theme.name)}
</Combobox.Option>
))}
</Field>
)}
</div>
</>
<FormContainer.Header
title={localization.datasetForm.heading.euTheme}
subtitle={localization.datasetForm.helptext.theme}
/>
<>
<div className={styles.combobox}>
<TitleWithTag
title={localization.datasetForm.fieldLabel.euTheme}
tagTitle={localization.tag.required}
/>
{loading ? (
<div className={styles.spinner}>
<Spinner title={`${localization.loading}...`} />
</div>
) : (
<Field
as={Combobox}
multiple
filter={containsFilter}
placeholder={`${localization.search.search}...`}
error={errors.euThemeList}
value={values.euThemeList}
onValueChange={(values: string[]) => setFieldValue('euThemeList', values)}
>
<Combobox.Empty>{localization.search.noHits}</Combobox.Empty>
{dataThemes &&
dataThemes.map((eutheme) => (
<Combobox.Option
key={eutheme.uri}
value={eutheme.uri}
>
{getTranslateText(eutheme.label)}
</Combobox.Option>
))}
</Field>
)}
</div>
</>
</FormContainer>
);
};

export default TemaSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.combobox {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.spinner {
display: flex;
justify-content: center;
align-items: center;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) : [],
};
};

Expand All @@ -45,5 +47,7 @@ export const datasetToBeCreatedTemplate = (): DatasetToBeCreated => {
legalBasisForAccess: [{ uri: '', prefLabel: { nb: '' } }],
legalBasisForProcessing: [{ uri: '', prefLabel: { nb: '' } }],
legalBasisForRestriction: [{ uri: '', prefLabel: { nb: '' } }],
losThemeList: [],
euThemeList: [],
};
};
2 changes: 2 additions & 0 deletions apps/dataset-catalog/components/dataset-form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -92,6 +93,7 @@ export const DatasetForm = ({ initialValues, submitType }: Props) => {
values={values}
errors={errors}
/>
<TemaSection />
</div>
</FormLayout>
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});
1 change: 1 addition & 0 deletions libs/data-access/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
19 changes: 19 additions & 0 deletions libs/data-access/src/lib/themes/api/index.tsx
Original file line number Diff line number Diff line change
@@ -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);
};
1 change: 1 addition & 0 deletions libs/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from './lib/filters';
export * from './lib/dataset';
export * from './lib/enums';
export * from './lib/catalogs';
export * from './lib/theme';
3 changes: 3 additions & 0 deletions libs/types/src/lib/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 4510cb7

Please sign in to comment.