Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GEN-1668]: add handling for "dirty forms" #1704

Merged
merged 24 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f97a5a1
Merge pull request #4 from odigos-io/new-ui
BenElferink Oct 27, 2024
afa1268
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 27, 2024
536f32f
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 28, 2024
fa352be
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 28, 2024
a4f03d8
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 28, 2024
b3fa682
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 29, 2024
f0a397f
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 29, 2024
2c13a75
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 29, 2024
3ab5c0c
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 29, 2024
977c57b
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 30, 2024
d95b6fe
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 30, 2024
cde9bdd
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 31, 2024
7ca44f7
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Oct 31, 2024
29d2e0d
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 3, 2024
5647431
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 3, 2024
8d22beb
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 3, 2024
e53746d
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 3, 2024
6fa1955
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 4, 2024
a6b6ec2
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 5, 2024
746f560
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 5, 2024
5c55719
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 5, 2024
834e5ed
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 6, 2024
8f1cd3e
Merge branch 'new-ui' of https://github.com/odigos-io/odigos
BenElferink Nov 7, 2024
49fd025
feat: handle "dirty form"
BenElferink Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Props {}
const ActionDrawer: React.FC<Props> = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useActionFormData();
const { updateAction, deleteAction } = useActionCRUD();
Expand Down Expand Up @@ -82,14 +83,23 @@ const ActionDrawer: React.FC<Props> = () => {
title={(item as ActionDataParsed).spec.actionName}
imageUri={getActionIcon((item as ActionDataParsed).type)}
isEdit={isEditing}
clickEdit={handleEdit}
clickSave={handleSave}
clickDelete={handleDelete}
clickCancel={handleCancel}
isFormDirty={isFormDirty}
onEdit={handleEdit}
onSave={handleSave}
onDelete={handleDelete}
onCancel={handleCancel}
>
{isEditing && thisAction ? (
<FormContainer>
<ChooseActionBody isUpdate action={thisAction} formData={formData} handleFormChange={handleFormChange} />
<ChooseActionBody
isUpdate
action={thisAction}
formData={formData}
handleFormChange={(...params) => {
setIsFormDirty(true);
handleFormChange(...params);
}}
/>
</FormContainer>
) : (
<CardDetails data={cardData} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Props {}
const DestinationDrawer: React.FC<Props> = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

const { cardData, dynamicFields, exportedSignals, supportedSignals, destinationType, resetFormData, setDynamicFields, setExportedSignals } =
useDestinationFormData();
Expand Down Expand Up @@ -53,19 +54,26 @@ const DestinationDrawer: React.FC<Props> = () => {
title={(item as ActualDestination).name}
imageUri={(item as ActualDestination).destinationType.imageUrl}
isEdit={isEditing}
clickEdit={handleEdit}
clickSave={handleSave}
clickDelete={handleDelete}
clickCancel={handleCancel}
isFormDirty={isFormDirty}
onEdit={handleEdit}
onSave={handleSave}
onDelete={handleDelete}
onCancel={handleCancel}
>
{isEditing ? (
<FormContainer>
<EditDestinationForm
dynamicFields={dynamicFields}
exportedSignals={exportedSignals}
supportedSignals={supportedSignals}
handleSignalChange={handleSignalChange}
handleDynamicFieldChange={handleDynamicFieldChange}
handleSignalChange={(...params) => {
setIsFormDirty(true);
handleSignalChange(...params);
}}
handleDynamicFieldChange={(...params) => {
setIsFormDirty(true);
handleDynamicFieldChange(...params);
}}
/>
</FormContainer>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Props {}
const RuleDrawer: React.FC<Props> = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

const { formData, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useInstrumentationRuleFormData();
const { updateInstrumentationRule, deleteInstrumentationRule } = useInstrumentationRuleCRUD();
Expand Down Expand Up @@ -80,14 +81,23 @@ const RuleDrawer: React.FC<Props> = () => {
title={(item as InstrumentationRuleSpec).ruleName}
imageUri={getRuleIcon((item as InstrumentationRuleSpec).type)}
isEdit={isEditing}
clickEdit={handleEdit}
clickSave={handleSave}
clickDelete={handleDelete}
clickCancel={handleCancel}
isFormDirty={isFormDirty}
onEdit={handleEdit}
onSave={handleSave}
onDelete={handleDelete}
onCancel={handleCancel}
>
{isEditing && thisRule ? (
<FormContainer>
<ChooseRuleBody isUpdate rule={thisRule} formData={formData} handleFormChange={handleFormChange} />
<ChooseRuleBody
isUpdate
rule={thisRule}
formData={formData}
handleFormChange={(...params) => {
setIsFormDirty(true);
handleFormChange(...params);
}}
/>
</FormContainer>
) : (
<CardDetails data={cardData} />
Expand Down
73 changes: 44 additions & 29 deletions frontend/webapp/containers/main/overview/overview-drawer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ interface Props {
title: string;
imageUri: string;
isEdit: boolean;
clickEdit: (bool?: boolean) => void;
clickSave: (newTitle: string) => void;
clickDelete: () => void;
clickCancel: () => void;
isFormDirty: boolean;
onEdit: (bool?: boolean) => void;
onSave: (newTitle: string) => void;
onDelete: () => void;
onCancel: () => void;
}

const DrawerContent = styled.div`
Expand All @@ -35,10 +36,11 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({
title,
imageUri,
isEdit,
clickEdit,
clickSave,
clickDelete,
clickCancel,
isFormDirty,
onEdit,
onSave,
onDelete,
onCancel,
}) => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const setSelectedItem = useDrawerStore(({ setSelectedItem }) => setSelectedItem);
Expand All @@ -50,7 +52,7 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({

const closeDrawer = () => {
setSelectedItem(null);
clickEdit(false);
onEdit(false);
setIsDeleteModalOpen(false);
setIsCancelModalOpen(false);
};
Expand All @@ -60,48 +62,61 @@ const OverviewDrawer: React.FC<Props & PropsWithChildren> = ({
setIsCancelModalOpen(false);
};

const handleCancel = () => setIsCancelModalOpen(true);
const handleDelete = () => setIsDeleteModalOpen(true);
const handleCancel = () => {
titleRef.current?.clearTitle();
onCancel();
closeWarningModals();
};

const clickCancel = () => {
const isTitleDirty = titleRef.current?.getTitle() !== title;
if (isFormDirty || isTitleDirty) {
setIsCancelModalOpen(true);
} else {
handleCancel();
}
};

const handleDelete = () => {
onDelete();
closeWarningModals();
};

const clickDelete = () => {
setIsDeleteModalOpen(true);
};

const clickSave = () => {
onSave(titleRef.current?.getTitle() || '');
};

return (
<>
<Drawer isOpen onClose={closeDrawer} width={DRAWER_WIDTH} closeOnEscape={!isDeleteModalOpen && !isCancelModalOpen}>
<Drawer isOpen onClose={isEdit ? clickCancel : closeDrawer} width={DRAWER_WIDTH} closeOnEscape={!isDeleteModalOpen && !isCancelModalOpen}>
<DrawerContent>
<DrawerHeader
ref={titleRef}
title={title}
imageUri={imageUri}
isEdit={isEdit}
onEdit={() => clickEdit(true)}
onClose={isEdit ? handleCancel : closeDrawer}
onEdit={() => onEdit(true)}
onClose={isEdit ? clickCancel : closeDrawer}
/>

<ContentArea>{children}</ContentArea>

{isEdit && <DrawerFooter onSave={() => clickSave(titleRef.current?.getTitle() || '')} onCancel={handleCancel} onDelete={handleDelete} />}
{isEdit && <DrawerFooter onSave={clickSave} onCancel={clickCancel} onDelete={clickDelete} />}
</DrawerContent>
</Drawer>

<DeleteWarning
isOpen={isDeleteModalOpen}
name={`${selectedItem?.type}${title ? ` (${title})` : ''}`}
onApprove={() => {
clickDelete();
closeWarningModals();
}}
onApprove={handleDelete}
onDeny={closeWarningModals}
/>

<CancelWarning
isOpen={isCancelModalOpen}
name='edit mode'
onApprove={() => {
titleRef.current?.clearTitle();
clickCancel();
closeWarningModals();
}}
onDeny={closeWarningModals}
/>
<CancelWarning isOpen={isCancelModalOpen} name='edit mode' onApprove={handleCancel} onDeny={closeWarningModals} />
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { K8sActualSource, PatchSourceRequestInput, WorkloadId } from '@/types';
import { getMainContainerLanguageLogo } from '@/utils/constants/programming-languages';

const SourceDrawer: React.FC = () => {
const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem);
const setSelectedItem = useDrawerStore(({ setSelectedItem }) => setSelectedItem);
const { selectedItem, setSelectedItem } = useDrawerStore((store) => store);
const [isEditing, setIsEditing] = useState(false);
const [isFormDirty, setIsFormDirty] = useState(false);

const { updateActualSource, deleteSourcesForNamespace } = useActualSources();

Expand Down Expand Up @@ -89,10 +89,11 @@ const SourceDrawer: React.FC = () => {
title={(item as K8sActualSource).reportedName}
imageUri={getMainContainerLanguageLogo(item as K8sActualSource)}
isEdit={isEditing}
clickEdit={handleEdit}
clickSave={handleSave}
clickDelete={handleDelete}
clickCancel={handleCancel}
isFormDirty={isFormDirty}
onEdit={handleEdit}
onSave={handleSave}
onDelete={handleDelete}
onCancel={handleCancel}
>
<CardDetails data={cardData} />
</OverviewDrawer>
Expand Down
36 changes: 18 additions & 18 deletions frontend/webapp/reuseable-components/input-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from '../input';
import { Button } from '../button';
import styled from 'styled-components';
import { FieldLabel } from '../field-label';
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';

interface InputListProps {
initialValues?: string[];
Expand Down Expand Up @@ -57,54 +57,54 @@ const ButtonText = styled(Text)`
const INITIAL = [''];

const InputList: React.FC<InputListProps> = ({ initialValues = INITIAL, value = INITIAL, onChange, title, tooltip, required }) => {
const [inputs, setInputs] = useState<string[]>(value || initialValues);
const [rows, setRows] = useState<string[]>(value || initialValues);

useEffect(() => {
if (!inputs.length) setInputs(INITIAL);
if (!rows.length) setRows(INITIAL);
}, []);

const recordedValues = useRef('');
// Filter out rows where either key or value is empty
const validRows = useMemo(() => rows.filter((str) => !!str.trim()), [rows]);
const recordedRows = useRef(JSON.stringify(validRows));

useEffect(() => {
// Filter out rows where either key or value is empty
const validValues = inputs.filter((val) => val.trim() !== '');
const stringified = JSON.stringify(validValues);
const stringified = JSON.stringify(validRows);

// Only trigger onChange if valid key-value pairs have changed
if (recordedValues.current !== stringified) {
recordedValues.current = stringified;
if (recordedRows.current !== stringified) {
recordedRows.current = stringified;

if (onChange) onChange(validValues);
if (onChange) onChange(validRows);
}
}, [inputs, onChange]);
}, [validRows, onChange]);

const handleAddInput = () => {
setInputs((prev) => [...prev, '']);
setRows((prev) => [...prev, '']);
};

const handleDeleteInput = (idx: number) => {
setInputs((prev) => prev.filter((_, i) => i !== idx));
setRows((prev) => prev.filter((_, i) => i !== idx));
};

const handleInputChange = (val: string, idx: number) => {
setInputs((prev) => {
setRows((prev) => {
const payload = [...prev];
payload[idx] = val;
return payload;
});
};

// Check if any input field is empty
const isAddButtonDisabled = inputs.some((input) => input.trim() === '');
const isDelButtonDisabled = inputs.length <= 1;
const isAddButtonDisabled = rows.some((input) => input.trim() === '');
const isDelButtonDisabled = rows.length <= 1;

return (
<Container>
<FieldLabel title={title} required={required} tooltip={tooltip} />

{inputs.map((val, idx) => (
{rows.map((val, idx) => (
<InputRow key={`input-list-${idx}`}>
<Input value={val} onChange={(e) => handleInputChange(e.target.value, idx)} autoFocus={inputs.length > 1 && idx === inputs.length - 1} />
<Input value={val} onChange={(e) => handleInputChange(e.target.value, idx)} autoFocus={rows.length > 1 && idx === rows.length - 1} />
<DeleteButton disabled={isDelButtonDisabled} onClick={() => handleDeleteInput(idx)}>
<Image src='/icons/common/trash.svg' alt='Delete' width={16} height={16} />
</DeleteButton>
Expand Down
20 changes: 10 additions & 10 deletions frontend/webapp/reuseable-components/input-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Input } from '../input';
import { Button } from '../button';
import styled from 'styled-components';
import { FieldLabel } from '../field-label';
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useMemo } from 'react';

interface Props {
columns: {
Expand Down Expand Up @@ -65,20 +65,20 @@ export const InputTable: React.FC<Props> = ({ columns, initialValues = [], value
if (!rows.length) setRows([{ ...init }]);
}, []);

const recordedPairs = useRef('');
// Filter out rows where either key or value is empty
const validRows = useMemo(() => rows.filter((row) => !Object.values(row).filter((val) => !val).length), [rows]);
const recordedRows = useRef(JSON.stringify(validRows));

useEffect(() => {
// Filter out rows where any values are empty
const validKeyValuePairs = rows.filter((row) => !Object.values(row).filter((val) => !val).length);
const stringified = JSON.stringify(validKeyValuePairs);
const stringified = JSON.stringify(validRows);

// Only trigger onChange if valid pairs have changed
if (recordedPairs.current !== stringified) {
recordedPairs.current = stringified;
// Only trigger onChange if valid key-value pairs have changed
if (recordedRows.current !== stringified) {
recordedRows.current = stringified;

if (onChange) onChange(validKeyValuePairs);
if (onChange) onChange(validRows);
}
}, [rows, onChange]);
}, [validRows, onChange]);

const handleAddRow = () => {
setRows((prev) => {
Expand Down
Loading
Loading