Skip to content

Commit 2891fe2

Browse files
authored
Merge branch 'dev' into feature/TKN/OGUI-1788/vault-services
2 parents fa733dc + 1da5d94 commit 2891fe2

File tree

80 files changed

+3534
-526
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+3534
-526
lines changed

Configuration/webapp/app/api/axiosInstance.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const axiosInstance = axios.create({
2121
baseURL: API_URL,
2222
headers: {
2323
'Content-Type': 'application/json',
24-
'User-Agent': 'axios 0.21.1',
2524
},
2625
withCredentials: false,
2726
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license
3+
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
4+
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
5+
* All rights not expressly granted are reserved.
6+
*
7+
* This software is distributed under the terms of the GNU General Public
8+
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
9+
*
10+
* In applying this license CERN does not waive the privileges and immunities
11+
* granted to it by virtue of its status as an Intergovernmental Organization
12+
* or submit itself to any jurisdiction.
13+
*/
14+
15+
import { useMutation, useQueryClient } from '@tanstack/react-query';
16+
import axiosInstance from '../axiosInstance';
17+
import { CONFIGURATION_QUERY_KEY } from '../query/useConfigurationQuery';
18+
import { CONFIGURATION_RESTRICTIONS_QUERY_KEY } from '../query/useConfigurationRestrictionsQuery';
19+
import type { FormValue } from '~/components/form/types';
20+
21+
/**
22+
* useConfigurationMutation hook
23+
* Provides a mutation to save configuration data.
24+
* @param {string} configurationName - The name of the configuration to save.
25+
* @returns {UseMutationResult} The mutation result.
26+
*/
27+
export const useConfigurationMutation = (configurationName: string) => {
28+
const queryClient = useQueryClient();
29+
30+
return useMutation({
31+
mutationFn: async (configuration: FormValue) => {
32+
const response = await axiosInstance.put<FormValue>(
33+
`configurations/${configurationName}`,
34+
JSON.stringify({ configuration }),
35+
);
36+
return response.data;
37+
},
38+
onSuccess: () => {
39+
void queryClient.invalidateQueries({
40+
queryKey: [CONFIGURATION_QUERY_KEY, configurationName],
41+
});
42+
void queryClient.invalidateQueries({
43+
queryKey: [CONFIGURATION_RESTRICTIONS_QUERY_KEY, configurationName],
44+
});
45+
},
46+
});
47+
};

Configuration/webapp/app/api/query/useConfigurationQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import { useQuery } from '@tanstack/react-query';
1616
import axiosInstance from '../axiosInstance';
17-
import type { FormItem } from '~/components/form/Form';
17+
import type { FormValue } from '~/components/form/types';
1818

1919
export const CONFIGURATION_QUERY_KEY = 'configuration';
2020

@@ -23,6 +23,6 @@ export const useConfigurationQuery = (configuration: string) =>
2323
queryKey: [CONFIGURATION_QUERY_KEY, configuration],
2424
queryFn: async () =>
2525
axiosInstance
26-
.get<FormItem>(`configurations/${configuration}`)
26+
.get<FormValue>(`configurations/${configuration}`)
2727
.then((response) => response.data),
2828
});

Configuration/webapp/app/api/query/useConfigurationRestrictionsQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import { useQuery } from '@tanstack/react-query';
1616
import axiosInstance from '../axiosInstance';
17-
import type { FormRestrictions } from '~/components/form/Form';
17+
import type { ObjectRestrictions } from '~/components/form/types';
1818

1919
export const CONFIGURATION_RESTRICTIONS_QUERY_KEY = 'configuration-restrictions';
2020

@@ -23,6 +23,6 @@ export const useConfigurationRestrictionsQuery = (configuration: string) =>
2323
queryKey: [CONFIGURATION_RESTRICTIONS_QUERY_KEY, configuration],
2424
queryFn: async () =>
2525
axiosInstance
26-
.get<FormRestrictions>(`configurations/restrictions/${configuration}`)
26+
.get<ObjectRestrictions>(`configurations/restrictions/${configuration}`)
2727
.then((response) => response.data),
2828
});

Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
import { List } from '@mui/material';
1616
import ConfigNavigatorItem from './ConfigNavigatorItem';
1717
import { useConfigurationKeysQuery } from '~/api/query/useConfigurationKeysQuery';
18-
import { useEffect, useMemo, useState } from 'react';
18+
import { useDeferredValue, useEffect, useMemo, useState } from 'react';
1919
import { ROUTE_PREFIX } from '~/config';
2020
import { useLocation } from 'react-router';
2121
import { ConfigNavigatorLoader } from './ConfigNavigatorLoader';
2222
import { useConfigurationNavigate } from '~/hooks/useConfigurationNavigate';
2323
import { buildTree } from '~/utils/configuration-tree-builder';
24+
import { useDrawer } from '~/contexts/DrawerContext';
2425

2526
/**
2627
* ConfigNavigator component
@@ -41,6 +42,9 @@ export const ConfigNavigator = () => {
4142
const { pathname } = useLocation();
4243
const navigate = useConfigurationNavigate();
4344

45+
const { searchTerm } = useDrawer();
46+
const deferredSearch = useDeferredValue(searchTerm);
47+
4448
useEffect(() => {
4549
if (configKeys && configKeys.length > 0) {
4650
const pathToCheck = pathname.startsWith(ROUTE_PREFIX)
@@ -56,12 +60,31 @@ export const ConfigNavigator = () => {
5660
}
5761
}, [configKeys, areConfigKeysLoading, pathname]);
5862

63+
const searchableData = useMemo(() => {
64+
if (!configKeys) {
65+
return [];
66+
}
67+
return configKeys.map((key) => ({
68+
original: key,
69+
searchable: key.slice(ROUTE_PREFIX.length).toLowerCase(),
70+
}));
71+
}, [configKeys]);
72+
5973
const treeData = useMemo(() => {
6074
if (!configKeys) {
6175
return {};
6276
}
63-
return buildTree(configKeys);
64-
}, [configKeys]);
77+
78+
let keysToProcess = configKeys;
79+
if (deferredSearch.trim() !== '') {
80+
const lowerTerm = deferredSearch.toLowerCase();
81+
keysToProcess = searchableData
82+
.filter((item) => item.searchable.includes(lowerTerm))
83+
.map((item) => item.original);
84+
}
85+
86+
return buildTree(keysToProcess);
87+
}, [configKeys, deferredSearch]);
6588

6689
if (areConfigKeysLoading) {
6790
return <ConfigNavigatorLoader />;

Configuration/webapp/app/components/form/Form.tsx

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,73 +12,72 @@
1212
* or submit itself to any jurisdiction.
1313
*/
1414

15-
import { useCallback, useState, type FC, type PropsWithChildren } from 'react';
16-
import Accordion from '@mui/material/Accordion';
17-
import AccordionDetails from '@mui/material/AccordionDetails';
18-
import Stack from '@mui/material/Stack';
19-
import { Widget } from './Widget';
20-
import { AccordionHeader } from './AccordionHeader';
21-
import { Typography } from '@mui/material';
15+
import type { FC } from 'react';
16+
import { Widget } from './components/Widget';
17+
import { ArrayWidget } from './components/widgets/ArrayWidget';
18+
import type {
19+
ArrayRestrictions,
20+
FormArrayValue,
21+
FormObjectValue,
22+
FormPrimitiveValue,
23+
FormValue,
24+
ObjectRestrictions,
25+
Restrictions,
26+
} from './types';
27+
import { isArrayRestrictions, isObjectRestrictions } from './types/helpers';
28+
import { ObjectWidget } from './components/widgets/ObjectWidget';
29+
import type { Control } from 'react-hook-form';
30+
import type { InputsType } from '~/routes/configuration';
2231

23-
export type FormItem = { [key: string]: string | object | FormItem };
24-
25-
export type FormRestrictions = {
26-
[key: string]: 'string' | 'number' | 'boolean' | 'array' | FormRestrictions;
27-
};
28-
29-
interface FormProps extends PropsWithChildren {
32+
interface FormProps {
3033
sectionTitle: string;
31-
items: FormItem;
32-
itemsRestrictions: FormRestrictions;
34+
sectionPrefix: string;
35+
value: FormValue;
36+
restrictions: Restrictions | ObjectRestrictions | ArrayRestrictions;
37+
control: Control<InputsType>;
3338
}
3439

35-
/**
36-
* Function which returns false if the given object
37-
* which describes restrictions is the leaf (string, number, bool, array)
38-
* or returns true if the given object describes restrictions recursively
39-
* @param {'string' | 'number' | 'boolean' | 'array' | FormRestrictions} obj
40-
* the object which describes restrictions
41-
* @returns {boolean} value which indicates if the restrictions are recursive
42-
* or if this is the leaf of the FormRestrictions tree
43-
*/
44-
function isFormRestrictions(obj: FormRestrictions[string]): obj is FormRestrictions {
45-
return obj instanceof Object && !(obj instanceof Array);
46-
}
47-
48-
export const Form: FC<FormProps> = ({ sectionTitle, items, itemsRestrictions }) => {
49-
const [viewForm, setViewForm] = useState<boolean>(true);
40+
export const Form: FC<FormProps> = ({
41+
sectionTitle,
42+
sectionPrefix,
43+
value,
44+
restrictions,
45+
control,
46+
}) => {
47+
if (isObjectRestrictions(restrictions)) {
48+
return (
49+
<ObjectWidget
50+
key={sectionTitle}
51+
sectionTitle={sectionTitle}
52+
sectionPrefix={sectionPrefix}
53+
items={value as FormObjectValue}
54+
itemsRestrictions={restrictions}
55+
control={control}
56+
/>
57+
);
58+
}
5059

51-
const renderItem = useCallback(
52-
(key: string, value: FormRestrictions[string]) =>
53-
isFormRestrictions(value) ? (
54-
<Form
55-
key={key}
56-
sectionTitle={key}
57-
items={items[key] as FormItem}
58-
itemsRestrictions={itemsRestrictions[key] as FormRestrictions}
59-
/>
60-
) : (
61-
<Widget key={key} title={key} type={value} value={items[key]} />
62-
),
63-
[items, itemsRestrictions],
64-
);
60+
if (isArrayRestrictions(restrictions)) {
61+
return (
62+
<ArrayWidget
63+
key={sectionTitle}
64+
sectionTitle={sectionTitle}
65+
sectionPrefix={sectionPrefix}
66+
items={value as Array<FormArrayValue>}
67+
itemsRestrictions={restrictions}
68+
control={control}
69+
/>
70+
);
71+
}
6572

6673
return (
67-
<Accordion defaultExpanded>
68-
<AccordionHeader
69-
title={sectionTitle}
70-
viewForm={viewForm}
71-
viewFormToggle={() => setViewForm((v) => !v)}
72-
/>
73-
<AccordionDetails>
74-
{viewForm ? (
75-
<Stack spacing={2}>
76-
{Object.entries(itemsRestrictions).map(([key, value]) => renderItem(key, value))}
77-
</Stack>
78-
) : (
79-
<Typography component="pre">{JSON.stringify(items, null, 2)}</Typography>
80-
)}
81-
</AccordionDetails>
82-
</Accordion>
74+
<Widget
75+
key={sectionTitle}
76+
label={sectionTitle}
77+
sectionPrefix={sectionPrefix}
78+
type={restrictions}
79+
value={value as FormPrimitiveValue}
80+
control={control}
81+
/>
8382
);
8483
};

Configuration/webapp/app/components/form/Widget.tsx

Lines changed: 0 additions & 39 deletions
This file was deleted.

Configuration/webapp/app/components/form/AccordionHeader.tsx renamed to Configuration/webapp/app/components/form/components/AccordionHeader.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,32 @@
1515
import { useCallback, type FC, type PropsWithChildren, type ReactElement } from 'react';
1616
import AccordionSummary from '@mui/material/AccordionSummary';
1717
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
18-
import FormData from '@mui/icons-material/ListAlt';
19-
import RawData from '@mui/icons-material/EditNote';
18+
import RawData from '@mui/icons-material/DataObject';
2019
import { IconButton, Typography } from '@mui/material';
2120

2221
interface AccordionHeaderProps extends PropsWithChildren {
2322
title: string;
24-
viewForm: boolean;
25-
viewFormToggle: () => void;
23+
showRawViewModal: () => void;
2624
}
2725

26+
/**
27+
* Accordion header component.
28+
* @param {AccordionHeaderProps} props - The props of the accordion header.
29+
* @param {string} props.title - The title of the accordion header.
30+
* @param {boolean} props.viewForm - Whether the form is visible.
31+
* @param {() => void} props.viewFormToggle - The callback to toggle the form visibility.
32+
* @returns {ReactElement} The accordion header component.
33+
*/
2834
export const AccordionHeader: FC<AccordionHeaderProps> = ({
2935
title,
30-
viewForm,
31-
viewFormToggle,
36+
showRawViewModal,
3237
}): ReactElement => {
33-
const viewFormToggleCallback = useCallback(
38+
const showRawViewModalCallback = useCallback(
3439
(e: React.MouseEvent<HTMLButtonElement>) => {
3540
e.stopPropagation();
36-
viewFormToggle();
41+
showRawViewModal();
3742
},
38-
[viewFormToggle],
43+
[showRawViewModal],
3944
);
4045

4146
return (
@@ -49,8 +54,8 @@ export const AccordionHeader: FC<AccordionHeaderProps> = ({
4954
}}
5055
>
5156
<Typography sx={{ marginRight: 'auto', alignContent: 'center' }}>{title}</Typography>
52-
<IconButton onClick={viewFormToggleCallback}>
53-
{viewForm ? <RawData /> : <FormData />}
57+
<IconButton onClick={showRawViewModalCallback}>
58+
<RawData />
5459
</IconButton>
5560
</AccordionSummary>
5661
);

0 commit comments

Comments
 (0)