Skip to content

Commit

Permalink
Merge branch 'main' into change-button-form-in-convert-explicit-filter
Browse files Browse the repository at this point in the history
  • Loading branch information
ghazwarhili authored Jan 9, 2025
2 parents eec4717 + db1cdc3 commit 0fdd169
Show file tree
Hide file tree
Showing 14 changed files with 89 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { FieldConstants } from '../../../utils/constants/fieldConstants';
import { useSnackMessage } from '../../../hooks/useSnackMessage';
import { CustomMuiDialog } from '../customMuiDialog/CustomMuiDialog';
import { ExpandingTextField } from '../../inputs/reactHookForm/text/ExpandingTextField';
import { MAX_CHAR_DESCRIPTION } from '../../../utils/constants/uiConstants';

export interface DescriptionModificationDialogProps {
elementUuid: string;
Expand All @@ -24,7 +25,7 @@ export interface DescriptionModificationDialogProps {
}

const schema = yup.object().shape({
[FieldConstants.DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'),
[FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'),
});
type SchemaType = yup.InferType<typeof schema>;

Expand Down
3 changes: 2 additions & 1 deletion src/components/filter/FilterCreationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { FilterForm } from './FilterForm';
import { EXPERT_FILTER_QUERY, expertFilterSchema, getExpertFilterEmptyFormData } from './expert/ExpertFilterForm';
import { FilterType } from './constants/FilterConstants';
import { ElementExistsType } from '../../utils/types/elementType';
import { MAX_CHAR_DESCRIPTION } from '../../utils/constants/uiConstants';

const emptyFormData = {
[FieldConstants.NAME]: '',
Expand All @@ -38,7 +39,7 @@ const formSchema = yup
.object()
.shape({
[FieldConstants.NAME]: yup.string().trim().required('nameEmpty'),
[FieldConstants.DESCRIPTION]: yup.string().max(500, 'descriptionLimitError'),
[FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'),
[FieldConstants.FILTER_TYPE]: yup.string().required(),
[FieldConstants.EQUIPMENT_TYPE]: yup.string().required(),
...explicitNamingFilterSchema,
Expand Down
37 changes: 22 additions & 15 deletions src/components/filter/HeaderFilterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { UniqueNameInput } from '../inputs/reactHookForm/text/UniqueNameInput';
import { ElementExistsType, ElementType } from '../../utils/types/elementType';
import { DescriptionField } from '../inputs/reactHookForm/text/DescriptionField';
import { RadioInput } from '../inputs/reactHookForm/booleans/RadioInput';
import yup from '../../utils/yupConfig';
import { MAX_CHAR_DESCRIPTION } from '../../utils/constants/uiConstants';

export interface FilterFormProps {
creation?: boolean;
Expand All @@ -25,6 +27,13 @@ export interface FilterFormProps {
handleFilterTypeChange?: (event: React.ChangeEvent<HTMLInputElement>, value: string) => void;
}

export const HeaderFilterSchema = {
[FieldConstants.NAME]: yup.string().trim().required('nameEmpty'),
[FieldConstants.FILTER_TYPE]: yup.string().required(),
[FieldConstants.EQUIPMENT_TYPE]: yup.string().required(),
[FieldConstants.DESCRIPTION]: yup.string().max(MAX_CHAR_DESCRIPTION, 'descriptionLimitError'),
};

export function HeaderFilterForm({
sourceFilterForExplicitNamingConversion,
creation,
Expand All @@ -46,22 +55,20 @@ export function HeaderFilterForm({
elementExists={elementExists}
/>
</Grid>
{creation && (
<>
<Grid item xs={12}>
<DescriptionField />
<>
<Grid item xs={12}>
<DescriptionField />
</Grid>
{creation && !sourceFilterForExplicitNamingConversion && (
<Grid item>
<RadioInput
name={FieldConstants.FILTER_TYPE}
options={filterTypes}
formProps={{ onChange: handleFilterTypeChange }} // need to override this in order to do not activate the validate button when changing the filter type
/>
</Grid>
{!sourceFilterForExplicitNamingConversion && (
<Grid item>
<RadioInput
name={FieldConstants.FILTER_TYPE}
options={filterTypes}
formProps={{ onChange: handleFilterTypeChange }} // need to override this in order to do not activate the validate button when changing the filter type
/>
</Grid>
)}
</>
)}
)}
</>
</Grid>
);
}
32 changes: 8 additions & 24 deletions src/components/filter/expert/ExpertFilterEditionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,29 @@
*/

import { yupResolver } from '@hookform/resolvers/yup';
import { UUID } from 'crypto';
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useSnackMessage } from '../../../hooks/useSnackMessage';
import { FetchStatus } from '../../../utils/constants/fetchStatus';
import { FieldConstants } from '../../../utils/constants/fieldConstants';
import { ElementExistsType } from '../../../utils/types/elementType';
import yup from '../../../utils/yupConfig';
import { CustomMuiDialog } from '../../dialogs/customMuiDialog/CustomMuiDialog';
import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants';
import { ItemSelectionForCopy } from '../filter.type';
import { FilterEditionProps } from '../filter.type';
import { FilterForm } from '../FilterForm';
import { saveExpertFilter } from '../utils/filterApi';
import { EXPERT_FILTER_QUERY, expertFilterSchema } from './ExpertFilterForm';
import { importExpertRules } from './expertFilterUtils';
import { HeaderFilterSchema } from '../HeaderFilterForm';

const formSchema = yup
.object()
.shape({
[FieldConstants.NAME]: yup.string().trim().required('nameEmpty'),
[FieldConstants.FILTER_TYPE]: yup.string().required(),
[FieldConstants.EQUIPMENT_TYPE]: yup.string().required(),
...HeaderFilterSchema,
...expertFilterSchema,
})
.required();

export interface ExpertFilterEditionDialogProps {
id: string;
name: string;
titleId: string;
open: boolean;
onClose: () => void;
broadcastChannel: BroadcastChannel;
itemSelectionForCopy: ItemSelectionForCopy;
setItemSelectionForCopy: (selection: ItemSelectionForCopy) => void;
getFilterById: (id: string) => Promise<{ [prop: string]: any }>;
activeDirectory?: UUID;
elementExists?: ElementExistsType;
language?: string;
}

export function ExpertFilterEditionDialog({
id,
name,
Expand All @@ -60,7 +42,8 @@ export function ExpertFilterEditionDialog({
activeDirectory,
elementExists,
language,
}: Readonly<ExpertFilterEditionDialogProps>) {
description,
}: Readonly<FilterEditionProps>) {
const { snackError } = useSnackMessage();
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);

Expand All @@ -86,6 +69,7 @@ export function ExpertFilterEditionDialog({
setDataFetchStatus(FetchStatus.FETCH_SUCCESS);
reset({
[FieldConstants.NAME]: name,
[FieldConstants.DESCRIPTION]: description,
[FieldConstants.FILTER_TYPE]: FilterType.EXPERT.id,
[FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE],
[EXPERT_FILTER_QUERY]: importExpertRules(response[EXPERT_FILTER_QUERY]),
Expand All @@ -99,7 +83,7 @@ export function ExpertFilterEditionDialog({
});
});
}
}, [id, name, open, reset, snackError, getFilterById]);
}, [id, name, open, reset, snackError, getFilterById, description]);

const onSubmit = useCallback(
(filterForm: { [prop: string]: any }) => {
Expand All @@ -108,7 +92,7 @@ export function ExpertFilterEditionDialog({
filterForm[EXPERT_FILTER_QUERY],
filterForm[FieldConstants.EQUIPMENT_TYPE],
filterForm[FieldConstants.NAME],
'', // The description can not be edited from this dialog
filterForm[FieldConstants.DESCRIPTION] ?? '',
false,
null,
onClose,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { yupResolver } from '@hookform/resolvers/yup';
import { UUID } from 'crypto';
import PropTypes from 'prop-types';
import { useCallback, useEffect, useState } from 'react';
import { SubmitHandler, useForm, UseFormReturn } from 'react-hook-form';
Expand All @@ -19,36 +18,20 @@ import { saveExplicitNamingFilter } from '../utils/filterApi';
import { explicitNamingFilterSchema, FILTER_EQUIPMENTS_ATTRIBUTES } from './ExplicitNamingFilterForm';

import { FetchStatus } from '../../../utils/constants/fetchStatus';
import { ElementExistsType } from '../../../utils/types/elementType';
import { FilterForm } from '../FilterForm';
import { FilterType, NO_ITEM_SELECTION_FOR_COPY } from '../constants/FilterConstants';
import { ItemSelectionForCopy } from '../filter.type';
import { FilterEditionProps } from '../filter.type';
import { HeaderFilterSchema } from '../HeaderFilterForm';

const formSchema = yup
.object()
.shape({
[FieldConstants.NAME]: yup.string().trim().required('nameEmpty'),
[FieldConstants.FILTER_TYPE]: yup.string().required(),
[FieldConstants.EQUIPMENT_TYPE]: yup.string().required(),
...HeaderFilterSchema,
...explicitNamingFilterSchema,
})
.required();

type FormSchemaType = yup.InferType<typeof formSchema>;
export interface ExplicitNamingFilterEditionDialogProps {
id: string;
name: string;
titleId: string;
open: boolean;
onClose: () => void;
broadcastChannel: BroadcastChannel;
itemSelectionForCopy: ItemSelectionForCopy;
setItemSelectionForCopy: (selection: ItemSelectionForCopy) => void;
getFilterById: (id: string) => Promise<any>;
activeDirectory?: UUID;
elementExists?: ElementExistsType;
language?: string;
}

export function ExplicitNamingFilterEditionDialog({
id,
Expand All @@ -63,7 +46,8 @@ export function ExplicitNamingFilterEditionDialog({
activeDirectory,
elementExists,
language,
}: Readonly<ExplicitNamingFilterEditionDialogProps>) {
description,
}: Readonly<FilterEditionProps>) {
const { snackError } = useSnackMessage();
const [dataFetchStatus, setDataFetchStatus] = useState(FetchStatus.IDLE);

Expand All @@ -88,6 +72,7 @@ export function ExplicitNamingFilterEditionDialog({
setDataFetchStatus(FetchStatus.FETCH_SUCCESS);
reset({
[FieldConstants.NAME]: name,
[FieldConstants.DESCRIPTION]: description,
[FieldConstants.FILTER_TYPE]: FilterType.EXPLICIT_NAMING.id,
[FieldConstants.EQUIPMENT_TYPE]: response[FieldConstants.EQUIPMENT_TYPE],
[FILTER_EQUIPMENTS_ATTRIBUTES]: response[FILTER_EQUIPMENTS_ATTRIBUTES].map((row: any) => ({
Expand All @@ -104,7 +89,7 @@ export function ExplicitNamingFilterEditionDialog({
});
});
}
}, [id, name, open, reset, snackError, getFilterById]);
}, [id, name, open, reset, snackError, getFilterById, description]);

const onSubmit = useCallback<SubmitHandler<FormSchemaType>>(
(filterForm) => {
Expand All @@ -113,7 +98,7 @@ export function ExplicitNamingFilterEditionDialog({
false,
filterForm[FieldConstants.EQUIPMENT_TYPE],
filterForm[FieldConstants.NAME],
'', // The description can not be edited from this dialog
filterForm[FieldConstants.DESCRIPTION] ?? '',
id,
(error) => {
snackError({
Expand Down
17 changes: 17 additions & 0 deletions src/components/filter/filter.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { UUID } from 'crypto';
import { ElementExistsType } from '../../utils';

/**
* Represent an item/object in directories.
Expand All @@ -18,3 +19,19 @@ export type ItemSelectionForCopy = {
parentDirectoryUuid: UUID | null;
specificTypeItem: string | null;
};

export interface FilterEditionProps {
id: string;
name: string;
titleId: string;
open: boolean;
onClose: () => void;
broadcastChannel: BroadcastChannel;
itemSelectionForCopy: ItemSelectionForCopy;
setItemSelectionForCopy: (selection: ItemSelectionForCopy) => void;
getFilterById: (id: string) => Promise<{ [prop: string]: any }>;
activeDirectory?: UUID;
elementExists?: ElementExistsType;
language?: string;
description?: string;
}
2 changes: 2 additions & 0 deletions src/components/filter/utils/filterApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const saveExplicitNamingFilter = (
filterEquipmentsAttributes: cleanedTableValues,
},
name,
description,
token
)
.then(() => {
Expand Down Expand Up @@ -113,6 +114,7 @@ export const saveExpertFilter = (
rules: exportExpertRules(query),
},
name,
description,
token
)
.then(() => {
Expand Down
7 changes: 4 additions & 3 deletions src/components/inputs/reactHookForm/text/DescriptionField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import { FieldConstants } from '../../../../utils/constants/fieldConstants';
import { ExpandingTextField } from './ExpandingTextField';

export function DescriptionField() {
const [isDescriptionFieldVisible, setIsDescriptionFieldVisible] = useState(false);
const { setValue } = useFormContext();
const { setValue, getValues } = useFormContext();
const description = getValues(FieldConstants.DESCRIPTION);
const [isDescriptionFieldVisible, setIsDescriptionFieldVisible] = useState(!!description);

const handleOpenDescription = () => {
setIsDescriptionFieldVisible(true);
};

const handleCloseDescription = () => {
setIsDescriptionFieldVisible(false);
setValue(FieldConstants.DESCRIPTION, '');
setValue(FieldConstants.DESCRIPTION, '', { shouldDirty: true });
};

return (
Expand Down
18 changes: 10 additions & 8 deletions src/components/notifications/NotificationsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { useListenerManager } from './hooks/useListenerManager';
// the delay before we consider the WS truly connected
const DELAY_BEFORE_WEBSOCKET_CONNECTED = 12000;

export type NotificationsProviderProps = { urls: Record<string, string> };
function isUrlDefined(tuple: [string, string | undefined]): tuple is [string, string] {
return tuple[1] !== undefined;
}

export type NotificationsProviderProps = { urls: Record<string, string | undefined> };
export function NotificationsProvider({ urls, children }: PropsWithChildren<NotificationsProviderProps>) {
const {
broadcast: broadcastMessage,
Expand All @@ -28,10 +32,10 @@ export function NotificationsProvider({ urls, children }: PropsWithChildren<Noti
} = useListenerManager<ListenerOnReopen, never>(urls);

useEffect(() => {
const connections = Object.keys(urls)
.filter((u) => urls[u] != null)
.map((urlKey) => {
const rws = new ReconnectingWebSocket(() => urls[urlKey], [], {
const connections = Object.entries(urls)
.filter(isUrlDefined)
.map(([urlKey, url]) => {
const rws = new ReconnectingWebSocket(() => url, [], {
// this option set the minimum duration being connected before reset the retry count to 0
minUptime: DELAY_BEFORE_WEBSOCKET_CONNECTED,
});
Expand All @@ -52,9 +56,7 @@ export function NotificationsProvider({ urls, children }: PropsWithChildren<Noti
return rws;
});

return () => {
connections.forEach((c) => c.close());
};
return () => connections.forEach((c) => c.close());
}, [broadcastMessage, broadcastOnReopen, urls]);

const contextValue = useMemo(
Expand Down
2 changes: 1 addition & 1 deletion src/components/notifications/hooks/useListenerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useCallback, useEffect, useRef } from 'react';
import { ListenerEventWS, ListenerOnReopen } from '../contexts/NotificationsContext';

export const useListenerManager = <TListener extends ListenerEventWS | ListenerOnReopen, TMessage extends MessageEvent>(
urls: Record<string, string>
urls: Record<string, string | undefined>
) => {
const urlsListenersRef = useRef<Record<string, TListener[]>>({});

Expand Down
3 changes: 2 additions & 1 deletion src/services/appsMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { Metadata, StudyMetadata } from '../utils';

// https://github.com/gridsuite/deployment/blob/main/docker-compose/docker-compose.base.yml
// https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/apps-metadata.json
export type Url = string | URL;
export type UrlString = `${string}://${string}` | `/${string}` | `./${string}`;
export type Url = UrlString | URL;

export type Env = {
appsMetadataServerUrl?: Url;
Expand Down
3 changes: 2 additions & 1 deletion src/services/explore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ export function createFilter(
);
}

export function saveFilter(filter: any, name: string, token?: string) {
export function saveFilter(filter: any, name: string, description: string, token?: string) {
const urlSearchParams = new URLSearchParams();
urlSearchParams.append('name', name);
urlSearchParams.append('description', description);
return backendFetch(
`${PREFIX_EXPLORE_SERVER_QUERIES}/v1/explore/filters/${filter.id}?${urlSearchParams.toString()}`,
{
Expand Down
Loading

0 comments on commit 0fdd169

Please sign in to comment.