Skip to content

Commit

Permalink
[GEN-1797]: keep drawer open after update (#1859)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenElferink authored Nov 26, 2024
1 parent a7a7dfa commit 2eae7e9
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ActionDataParsed } from '@/types';

const buildCardFromActionSpec = (action: ActionDataParsed) => {
const buildCard = (action: ActionDataParsed) => {
const {
type,
spec: {
Expand All @@ -17,14 +17,14 @@ const buildCardFromActionSpec = (action: ActionDataParsed) => {
sampling_percentage,
endpoints_filters,
},
} = action as ActionDataParsed;
} = action;

const arr = [
{ title: 'Type', value: type || 'N/A' },
{ title: 'Type', value: type },
{ title: 'Status', value: String(!disabled) },
{ title: 'Monitors', value: signals.map((str) => str.toLowerCase()).join(', ') },
{ title: 'Name', value: actionName || 'N/A' },
{ title: 'Notes', value: notes || 'N/A' },
{ title: 'Monitors', value: signals.map((str) => str.toLowerCase()).join(', ') },
];

if (clusterAttributes) {
Expand Down Expand Up @@ -91,4 +91,4 @@ const buildCardFromActionSpec = (action: ActionDataParsed) => {
return arr;
};

export default buildCardFromActionSpec;
export default buildCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { safeJsonParse } from '@/utils';
import type { ActionDataParsed, ActionInput } from '@/types';

const buildDrawerItem = (id: string, formData: ActionInput, drawerItem: ActionDataParsed): ActionDataParsed => {
const { type, name, notes, signals, disable, details } = formData;
const {} = drawerItem;

return {
id,
type,
spec: {
actionName: name,
notes: notes,
signals: signals,
disabled: disable,
...safeJsonParse(details, {}),
},
};
};

export default buildDrawerItem;
73 changes: 41 additions & 32 deletions frontend/webapp/containers/main/actions/action-drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
import React, { useMemo, useState } from 'react';
import buildCard from './build-card';
import { ActionFormBody } from '../';
import styled from 'styled-components';
import { getActionIcon } from '@/utils';
import { useDrawerStore } from '@/store';
import { CardDetails } from '@/components';
import type { ActionDataParsed } from '@/types';
import { ACTION, getActionIcon } from '@/utils';
import buildDrawerItem from './build-drawer-item';
import { useActionCRUD, useActionFormData } from '@/hooks';
import OverviewDrawer from '../../overview/overview-drawer';
import { ACTION_OPTIONS } from '../action-modal/action-options';
import buildCardFromActionSpec from './build-card-from-action-spec';
import { OVERVIEW_ENTITY_TYPES, type ActionDataParsed } from '@/types';

interface Props {}

const ActionDrawer: React.FC<Props> = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);
const FormContainer = styled.div`
width: 100%;
height: 100%;
max-height: calc(100vh - 220px);
overflow: overlay;
overflow-y: auto;
`;

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

const { updateAction, deleteAction } = useActionCRUD({
onSuccess: (type) => {
setIsEditing(false);
setIsFormDirty(false);

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

const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

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

const { item } = selectedItem as { item: ActionDataParsed };
const arr = buildCardFromActionSpec(item);
const arr = buildCard(item);

return arr;
}, [selectedItem]);
Expand All @@ -49,37 +72,33 @@ const ActionDrawer: React.FC<Props> = () => {
}, [selectedItem, isEditing]);

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

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 deleteAction(id as string, (item as ActionDataParsed).type);
await deleteAction(id, item.type);
};

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

await updateAction(id as string, { ...formData, name: title });
const title = newTitle !== item.type ? newTitle : '';
handleFormChange('name', title);
await updateAction(id, { ...formData, name: title });
}
};

return (
<OverviewDrawer
title={(item as ActionDataParsed).spec.actionName || (item as ActionDataParsed).type}
imageUri={getActionIcon((item as ActionDataParsed).type)}
title={item.spec.actionName || item.type}
imageUri={getActionIcon(item.type)}
isEdit={isEditing}
isFormDirty={isFormDirty}
onEdit={handleEdit}
Expand All @@ -105,13 +124,3 @@ const ActionDrawer: React.FC<Props> = () => {
</OverviewDrawer>
);
};

export { ActionDrawer };

const FormContainer = styled.div`
width: 100%;
height: 100%;
max-height: calc(100vh - 220px);
overflow: overlay;
overflow-y: auto;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ActualDestination, DestinationDetailsResponse, ExportedSignals } from '@/types';
import { safeJsonParse } from '@/utils';

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

const buildCard = (destination: ActualDestination, destinationTypeDetails: DestinationDetailsResponse['destinationTypeDetails']) => {
const { exportedSignals, destinationType, fields } = destination;

const arr = [
{ title: 'Destination', value: destinationType.displayName },
{ title: 'Monitors', value: buildMonitorsList(exportedSignals) },
];

Object.entries(safeJsonParse<Record<string, string>>(fields, {})).map(([key, value]) => {
const found = destinationTypeDetails?.fields?.find((field) => field.name === key);

const { type } = safeJsonParse(found?.componentProperties, { type: '' });
const secret = type === 'password' ? new Array(value.length).fill('•').join('') : '';

arr.push({
title: found?.displayName || key,
value: secret || value || 'N/A',
});
});

return arr;
};

export default buildCard;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { ActualDestination, DestinationInput } from '@/types';

const buildDrawerItem = (id: string, formData: DestinationInput, drawerItem: ActualDestination): ActualDestination => {
const { name, exportedSignals, fields } = formData;
const { destinationType, conditions } = drawerItem || {};

let fieldsStringified: string | Record<string, any> = {};
fields.forEach(({ key, value }) => (fieldsStringified[key] = value));
fieldsStringified = JSON.stringify(fieldsStringified);

return {
id,
name,
exportedSignals,
fields: fieldsStringified,
destinationType,
conditions,
};
};

export default buildDrawerItem;
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({
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 });
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 Down
Loading

0 comments on commit 2eae7e9

Please sign in to comment.