Skip to content

Commit

Permalink
prepare code for form error handling (and fix wrongful-merge-conflict) (
Browse files Browse the repository at this point in the history
  • Loading branch information
BenElferink authored Nov 27, 2024
1 parent dd78911 commit e9958d6
Show file tree
Hide file tree
Showing 14 changed files with 143 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const FormContainer = styled.div`

export const ActionDrawer: React.FC<Props> = () => {
const { selectedItem, setSelectedItem } = useDrawerStore();
const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useActionFormData();
const { formData, formErrors, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useActionFormData();

const { updateAction, deleteAction } = useActionCRUD({
onSuccess: (type) => {
Expand Down Expand Up @@ -88,7 +88,7 @@ export const ActionDrawer: React.FC<Props> = () => {
};

const handleSave = async (newTitle: string) => {
if (validateForm({ withAlert: true })) {
if (validateForm({ withAlert: true, alertTitle: ACTION.UPDATE })) {
const title = newTitle !== item.type ? newTitle : '';
handleFormChange('name', title);
await updateAction(id, { ...formData, name: title });
Expand All @@ -112,6 +112,7 @@ export const ActionDrawer: React.FC<Props> = () => {
isUpdate
action={thisAction}
formData={formData}
formErrors={formErrors}
handleFormChange={(...params) => {
setIsFormDirty(true);
handleFormChange(...params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface Props {
isUpdate?: boolean;
action: ActionOption;
formData: ActionInput;
formErrors: Record<string, string>;
handleFormChange: (key: keyof ActionInput, val: any) => void;
}

Expand All @@ -23,7 +24,7 @@ const FieldTitle = styled(Text)`
margin-bottom: 12px;
`;

export const ActionFormBody: React.FC<Props> = ({ isUpdate, action, formData, handleFormChange }) => {
export const ActionFormBody: React.FC<Props> = ({ isUpdate, action, formData, formErrors, handleFormChange }) => {
return (
<Container>
{isUpdate && (
Expand Down
24 changes: 14 additions & 10 deletions frontend/webapp/containers/main/actions/action-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { ACTION } from '@/utils';
import { ActionFormBody } from '../';
import React, { useMemo, useState } from 'react';
import { CenterThis, ModalBody } from '@/styles';
import { useActionCRUD, useActionFormData } from '@/hooks/actions';
import { ACTION_OPTIONS, type ActionOption } from './action-options';
Expand All @@ -11,16 +12,10 @@ interface Props {
}

export const ActionModal: React.FC<Props> = ({ isOpen, onClose }) => {
const { formData, handleFormChange, resetFormData, validateForm } = useActionFormData();
const { formData, formErrors, handleFormChange, resetFormData, validateForm } = useActionFormData();
const { createAction, loading } = useActionCRUD({ onSuccess: handleClose });
const [selectedItem, setSelectedItem] = useState<ActionOption | undefined>(undefined);

const isFormOk = useMemo(() => !!selectedItem && validateForm(), [selectedItem, formData]);

const handleSubmit = async () => {
createAction(formData);
};

function handleClose() {
resetFormData();
setSelectedItem(undefined);
Expand All @@ -33,6 +28,15 @@ export const ActionModal: React.FC<Props> = ({ isOpen, onClose }) => {
setSelectedItem(item);
};

const handleSubmit = async () => {
const isFormOk = validateForm({ withAlert: true, alertTitle: ACTION.CREATE });
if (!isFormOk) return null;

await createAction(formData);

handleClose();
};

return (
<Modal
isOpen={isOpen}
Expand All @@ -45,7 +49,7 @@ export const ActionModal: React.FC<Props> = ({ isOpen, onClose }) => {
variant: 'primary',
label: 'DONE',
onClick: handleSubmit,
disabled: !isFormOk || loading,
disabled: !selectedItem || loading,
},
]}
/>
Expand All @@ -64,7 +68,7 @@ export const ActionModal: React.FC<Props> = ({ isOpen, onClose }) => {
<FadeLoader cssOverride={{ scale: 2 }} />
</CenterThis>
) : (
<ActionFormBody action={selectedItem} formData={formData} handleFormChange={handleFormChange} />
<ActionFormBody action={selectedItem} formData={formData} formErrors={formErrors} handleFormChange={handleFormChange} />
)}
</div>
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useMemo, useState } from 'react';
import { ACTION } from '@/utils';
import buildCard from './build-card';
import styled from 'styled-components';
import { safeJsonParse } from '@/utils';
import { useDrawerStore } from '@/store';
import { CardDetails } from '@/components';
import type { ActualDestination } from '@/types';
import buildDrawerItem from './build-drawer-item';
import OverviewDrawer from '../../overview/overview-drawer';
import { DestinationFormBody } from '../destination-form-body';
import { OVERVIEW_ENTITY_TYPES, type ActualDestination } from '@/types';
import { useDestinationCRUD, useDestinationFormData, useDestinationTypes } from '@/hooks';

interface Props {}
Expand All @@ -19,95 +21,87 @@ const FormContainer = styled.div`
`;

export const DestinationDrawer: React.FC<Props> = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);
const { selectedItem, setSelectedItem } = useDrawerStore();
const { destinations: destinationTypes } = useDestinationTypes();

const { updateDestination, deleteDestination } = useDestinationCRUD();
const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem, destinationTypeDetails, dynamicFields, setDynamicFields } = useDestinationFormData({
const { formData, formErrors, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem, destinationTypeDetails, dynamicFields, setDynamicFields } = useDestinationFormData({
destinationType: (selectedItem?.item as ActualDestination)?.destinationType?.type,
preLoadedFields: (selectedItem?.item as ActualDestination)?.fields,
// TODO: supportedSignals: thisDestination?.supportedSignals,
// currently, the real "supportedSignals" is being used by "destination" passed as prop to "DestinationFormBody"
});

const cardData = useMemo(() => {
if (!selectedItem) return [];

const buildMonitorsList = (exportedSignals: ActualDestination['exportedSignals']): string =>
Object.keys(exportedSignals)
.filter((key) => exportedSignals[key])
.join(', ') || 'N/A';

const buildDestinationFieldData = (parsedFields: Record<string, string>) =>
Object.entries(parsedFields).map(([key, value]) => {
const found = destinationTypeDetails?.fields?.find((field) => field.name === key);
const { updateDestination, deleteDestination } = useDestinationCRUD({
onSuccess: (type) => {
setIsEditing(false);
setIsFormDirty(false);

if (type === ACTION.DELETE) {
setSelectedItem(null);
} else {
const { item } = selectedItem as { item: ActualDestination };
const { id } = item;
setSelectedItem({ id, type: OVERVIEW_ENTITY_TYPES.DESTINATION, item: buildDrawerItem(id, formData, item) });
}
},
});

const { type } = safeJsonParse(found?.componentProperties, { type: '' });
const secret = type === 'password' ? new Array(value.length).fill('•').join('') : '';
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

return {
title: found?.displayName || key,
value: secret || value || 'N/A',
};
});
const cardData = useMemo(() => {
if (!selectedItem || !destinationTypeDetails) return [];

const { exportedSignals, destinationType, fields } = selectedItem.item as ActualDestination;
const parsedFields = safeJsonParse<Record<string, string>>(fields, {});
const fieldsData = buildDestinationFieldData(parsedFields);
const { item } = selectedItem as { item: ActualDestination };
const arr = buildCard(item, destinationTypeDetails);

return [{ title: 'Destination', value: destinationType.displayName || 'N/A' }, { title: 'Monitors', value: buildMonitorsList(exportedSignals) }, ...fieldsData];
return arr;
}, [selectedItem, destinationTypeDetails]);

const { destinations } = useDestinationTypes();
const thisDestination = useMemo(() => {
if (!destinations.length || !selectedItem || !isEditing) {
if (!destinationTypes.length || !selectedItem || !isEditing) {
resetFormData();
return undefined;
}

const { item } = selectedItem as { item: ActualDestination };
const found = destinations.map(({ items }) => items.filter(({ type }) => type === item.destinationType.type)).filter((arr) => !!arr.length)[0][0];
const found = destinationTypes.map(({ items }) => items.filter(({ type }) => type === item.destinationType.type)).filter((arr) => !!arr.length)[0][0];

if (!found) return undefined;

loadFormWithDrawerItem(selectedItem);

return found;
}, [destinations, selectedItem, isEditing]);
}, [destinationTypes, selectedItem, isEditing]);

if (!selectedItem?.item) return null;
const { id, item } = selectedItem;
const { id, item } = selectedItem as { id: string; item: ActualDestination };

const handleEdit = (bool?: boolean) => {
if (typeof bool === 'boolean') {
setIsEditing(bool);
} else {
setIsEditing(true);
}
setIsEditing(typeof bool === 'boolean' ? bool : true);
};

const handleCancel = () => {
resetFormData();
setIsEditing(false);
setIsFormDirty(false);
};

const handleDelete = async () => {
await deleteDestination(id as string);
await deleteDestination(id);
};

const handleSave = async (newTitle: string) => {
if (validateForm({ withAlert: true })) {
const title = newTitle !== (item as ActualDestination).destinationType.displayName ? newTitle : '';

await updateDestination(id as string, { ...formData, name: title });
if (validateForm({ withAlert: true, alertTitle: ACTION.UPDATE })) {
const title = newTitle !== item.destinationType.displayName ? newTitle : '';
handleFormChange('name', title);
await updateDestination(id, { ...formData, name: title });
}
};

return (
<OverviewDrawer
title={(item as ActualDestination).name || (item as ActualDestination).destinationType.displayName}
imageUri={(item as ActualDestination).destinationType.imageUrl}
title={item.name || item.destinationType.displayName}
imageUri={item.destinationType.imageUrl}
isEdit={isEditing}
isFormDirty={isFormDirty}
onEdit={handleEdit}
Expand All @@ -120,7 +114,7 @@ export const DestinationDrawer: React.FC<Props> = () => {
<DestinationFormBody
isUpdate
destination={thisDestination}
isFormOk={validateForm()}
formErrors={formErrors}
formData={formData}
handleFormChange={(...params) => {
setIsFormDirty(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { CheckboxList, Divider, Input, NotificationNote, SectionTitle } from '@/
interface Props {
isUpdate?: boolean;
destination?: DestinationTypeItem;
isFormOk: boolean;
formData: DestinationInput;
formErrors: Record<string, string>;
handleFormChange: (key: keyof DestinationInput | string, val: any) => void;
dynamicFields: DynamicField[];
setDynamicFields: Dispatch<SetStateAction<DynamicField[]>>;
Expand All @@ -22,8 +22,9 @@ const Container = styled.div`
padding: 0 4px;
`;

export function DestinationFormBody({ isUpdate, destination, isFormOk, formData, handleFormChange, dynamicFields, setDynamicFields }: Props) {
export function DestinationFormBody({ isUpdate, destination, formData, formErrors, handleFormChange, dynamicFields, setDynamicFields }: Props) {
const { supportedSignals, testConnectionSupported, displayName } = destination || {};
const isFormOk = useMemo(() => !Object.keys(formErrors).length, [formErrors]);

const [isFormDirty, setIsFormDirty] = useState(false);
const [showConnectionError, setShowConnectionError] = useState(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { ModalBody } from '@/styles';
import { useAppStore } from '@/store';
import { INPUT_TYPES } from '@/utils';
import styled from 'styled-components';
import { SideMenu } from '@/components';
import { ACTION, INPUT_TYPES } from '@/utils';
import { DestinationFormBody } from '../destination-form-body';
import { ChooseDestinationBody } from './choose-destination-body';
import { useDestinationCRUD, useDestinationFormData } from '@/hooks';
Expand Down Expand Up @@ -32,15 +32,13 @@ const SideMenuWrapper = styled.div`
export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboarding, isOpen, onClose }) => {
const [selectedItem, setSelectedItem] = useState<DestinationTypeItem | undefined>();

const { createDestination } = useDestinationCRUD();
const { createDestination, loading } = useDestinationCRUD();
const addConfiguredDestination = useAppStore(({ addConfiguredDestination }) => addConfiguredDestination);
const { formData, handleFormChange, resetFormData, validateForm, dynamicFields, setDynamicFields } = useDestinationFormData({
const { formData, formErrors, handleFormChange, resetFormData, validateForm, dynamicFields, setDynamicFields } = useDestinationFormData({
supportedSignals: selectedItem?.supportedSignals,
preLoadedFields: selectedItem?.fields,
});

const isFormOk = !!selectedItem && validateForm();

const handleClose = () => {
resetFormData();
setSelectedItem(undefined);
Expand All @@ -59,6 +57,9 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
};

const handleSubmit = async () => {
const isFormOk = validateForm({ withAlert: !isOnboarding, alertTitle: ACTION.CREATE });
if (!isFormOk) return null;

if (isOnboarding) {
const destinationTypeDetails = dynamicFields.map((field) => ({
title: field.title,
Expand All @@ -81,7 +82,7 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard

addConfiguredDestination({ stored: storedDestination, form: formData });
} else {
createDestination(formData);
await createDestination(formData);
}

handleClose();
Expand All @@ -93,7 +94,7 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
label: 'DONE',
variant: 'primary' as const,
onClick: handleSubmit,
disabled: !isFormOk,
disabled: !selectedItem || loading,
},
];

Expand All @@ -103,6 +104,7 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
iconSrc: '/icons/common/arrow-white.svg',
variant: 'secondary' as const,
onClick: handleBack,
disabled: loading,
});
}

Expand All @@ -126,8 +128,8 @@ export const DestinationModal: React.FC<AddDestinationModalProps> = ({ isOnboard
{!!selectedItem ? (
<DestinationFormBody
destination={selectedItem}
isFormOk={isFormOk}
formData={formData}
formErrors={formErrors}
handleFormChange={handleFormChange}
dynamicFields={dynamicFields}
setDynamicFields={setDynamicFields}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const FormContainer = styled.div`

export const RuleDrawer: React.FC<Props> = () => {
const { selectedItem, setSelectedItem } = useDrawerStore();
const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useInstrumentationRuleFormData();
const { formData, formErrors, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useInstrumentationRuleFormData();

const { updateInstrumentationRule, deleteInstrumentationRule } = useInstrumentationRuleCRUD({
onSuccess: (type) => {
Expand Down Expand Up @@ -85,7 +85,7 @@ export const RuleDrawer: React.FC<Props> = () => {
};

const handleSave = async (newTitle: string) => {
if (validateForm({ withAlert: true })) {
if (validateForm({ withAlert: true, alertTitle: ACTION.UPDATE })) {
const title = newTitle !== item.type ? newTitle : '';
handleFormChange('ruleName', title);
await updateInstrumentationRule(id, { ...formData, ruleName: title });
Expand All @@ -109,6 +109,7 @@ export const RuleDrawer: React.FC<Props> = () => {
isUpdate
rule={thisRule}
formData={formData}
formErrors={formErrors}
handleFormChange={(...params) => {
setIsFormDirty(true);
handleFormChange(...params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface Props {
isUpdate?: boolean;
rule: RuleOption;
formData: InstrumentationRuleInput;
formErrors: Record<string, string>;
handleFormChange: (key: keyof InstrumentationRuleInput, val: any) => void;
}

Expand All @@ -23,7 +24,7 @@ const FieldTitle = styled(Text)`
margin-bottom: 12px;
`;

export const RuleFormBody: React.FC<Props> = ({ isUpdate, rule, formData, handleFormChange }) => {
export const RuleFormBody: React.FC<Props> = ({ isUpdate, rule, formData, formErrors, handleFormChange }) => {
return (
<Container>
{isUpdate && (
Expand Down
Loading

0 comments on commit e9958d6

Please sign in to comment.