Skip to content

Commit

Permalink
Add reset button confirmation (#328)
Browse files Browse the repository at this point in the history
* Add reset values confirmation

* Hide table in update confirmation if no layout

* Update panel options

---------

Co-authored-by: Mikhail Volkov <mikhail@volkovlabs.io>
  • Loading branch information
asimonok and mikhail-vl authored Jan 4, 2024
1 parent 5fe915e commit c26e2ee
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 73 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Change Log

## 3.5.0 (IN PROGRESS)
## 3.5.0 (2023-01-04)

### Features / Enhancements

Expand All @@ -9,6 +9,8 @@
- Add code options source for select element (#323)
- Add value changed code (#324)
- Update to Node 20 (#326)
- Add suggestions for code editors (#327)
- Add reset button confirmation (#328)

## 3.4.0 (2023-12-14)

Expand Down
73 changes: 73 additions & 0 deletions src/components/FormPanel/FormPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,79 @@ describe('Panel', () => {

await waitFor(() => expect(selectors.errorMessage()).toBeInTheDocument());
});

it('Should ask to confirm', async () => {
/**
* Render
*/
const replaceVariables = jest.fn((code) => code);
const publish = jest.fn();
jest.mocked(getAppEvents).mockImplementation(
() =>
({
publish,
}) as any
);
const defaultOptions = {
initial: {
code: `notifySuccess("success");`,
},
update: {},
resetAction: {
mode: ResetActionMode.CUSTOM,
code: 'notifySuccess("success");',
confirm: true,
},
};

const { rerender } = await act(async () =>
render(
getComponent({
props: {
replaceVariables,
},
options: defaultOptions,
})
)
);

await act(async () =>
rerender(
getComponent({
props: {
replaceVariables,
},
options: {
...defaultOptions,
elements: [
{ id: 'test', type: FormElementType.STRING, value: '111' },
{ id: 'test2', type: FormElementType.NUMBER, value: 10 },
],
},
})
)
);

jest.mocked(replaceVariables).mockClear();
jest.mocked(publish).mockClear();

expect(selectors.buttonReset()).not.toBeDisabled();
await act(async () => {
fireEvent.click(selectors.buttonReset());
});

/**
* Check if confirmation shown
*/
expect(selectors.resetConfirmModal()).toBeInTheDocument();

/**
* Confirm reset
*/
await act(async () => fireEvent.click(selectors.buttonConfirmReset()));

expect(publish).toHaveBeenCalled();
});
});

describe('Confirm changes', () => {
Expand Down
149 changes: 87 additions & 62 deletions src/components/FormPanel/FormPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const FormPanel: React.FC<Props> = ({
const [title, setTitle] = useState('');
const [initial, setInitial] = useState<{ [id: string]: unknown }>({});
const [updateConfirmation, setUpdateConfirmation] = useState(false);
const [resetConfirmation, setResetConfirmation] = useState(false);
const [isInitialized, setInitialized] = useState(false);

/**
Expand Down Expand Up @@ -954,7 +955,14 @@ export const FormPanel: React.FC<Props> = ({
: {}
}
disabled={!!loading || !resetEnabled}
onClick={resetRequest}
onClick={() => {
if (options.resetAction.confirm) {
setResetConfirmation(true);
return;
}

resetRequest();
}}
size={options.buttonGroup.size}
data-testid={TEST_IDS.panel.buttonReset}
>
Expand Down Expand Up @@ -994,37 +1002,68 @@ export const FormPanel: React.FC<Props> = ({
body={
<div data-testid={TEST_IDS.panel.confirmModalContent}>
<h4>{options.confirmModal.body}</h4>
<table className={styles.confirmTable}>
<thead>
<tr className={styles.confirmTable}>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.name}</b>
</td>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.oldValue}</b>
</td>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.newValue}</b>
</td>
</tr>
</thead>
<tbody>
{elements.map((element: FormElement) => {
if (element.value === initial[element.id]) {
return;
}
{options.layout.variant !== LayoutVariant.NONE && (
<table className={styles.confirmTable}>
<thead>
<tr className={styles.confirmTable}>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.name}</b>
</td>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.oldValue}</b>
</td>
<td className={styles.confirmTableTd}>
<b>{options.confirmModal.columns.newValue}</b>
</td>
</tr>
</thead>
<tbody>
{elements.map((element: FormElement) => {
if (element.value === initial[element.id]) {
return;
}

/**
* Skip Disabled elements, which can be updated in the custom code as previous values
*/
if (element.type === FormElementType.DISABLED) {
return;
}
/**
* Skip Disabled elements, which can be updated in the custom code as previous values
*/
if (element.type === FormElementType.DISABLED) {
return;
}

/**
* Skip Password elements
*/
if (element.type === FormElementType.PASSWORD) {
return (
<tr
className={styles.confirmTable}
key={element.id}
data-testid={TEST_IDS.panel.confirmModalField(element.id)}
>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldTitle}>
{element.title || element.tooltip}
</td>
<td
className={styles.confirmTableTd}
data-testid={TEST_IDS.panel.confirmModalFieldPreviousValue}
>
*********
</td>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldValue}>
*********
</td>
</tr>
);
}

let currentValue = element.value;
/**
* Convert DateTime object to ISO string
*/
if (element.type === FormElementType.DATETIME) {
currentValue = dateTime(element.value).toISOString();
}

/**
* Skip Password elements
*/
if (element.type === FormElementType.PASSWORD) {
return (
<tr
className={styles.confirmTable}
Expand All @@ -1038,43 +1077,17 @@ export const FormPanel: React.FC<Props> = ({
className={styles.confirmTableTd}
data-testid={TEST_IDS.panel.confirmModalFieldPreviousValue}
>
*********
{initial[element.id] === undefined ? '' : String(initial[element.id])}
</td>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldValue}>
*********
{currentValue === undefined ? '' : String(currentValue)}
</td>
</tr>
);
}

let currentValue = element.value;
/**
* Convert DateTime object to ISO string
*/
if (element.type === FormElementType.DATETIME) {
currentValue = dateTime(element.value).toISOString();
}

return (
<tr
className={styles.confirmTable}
key={element.id}
data-testid={TEST_IDS.panel.confirmModalField(element.id)}
>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldTitle}>
{element.title || element.tooltip}
</td>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldPreviousValue}>
{initial[element.id] === undefined ? '' : String(initial[element.id])}
</td>
<td className={styles.confirmTableTd} data-testid={TEST_IDS.panel.confirmModalFieldValue}>
{currentValue === undefined ? '' : String(currentValue)}
</td>
</tr>
);
})}
</tbody>
</table>
})}
</tbody>
</table>
)}
</div>
}
confirmText={options.confirmModal.confirm}
Expand All @@ -1085,6 +1098,18 @@ export const FormPanel: React.FC<Props> = ({
onDismiss={() => setUpdateConfirmation(false)}
dismissText={options.confirmModal.cancel}
/>

<ConfirmModal
isOpen={resetConfirmation}
title="Confirm reset values"
body={<div data-testid={TEST_IDS.panel.resetConfirmModal}>Please confirm to reset values</div>}
confirmText="Confirm"
onConfirm={() => {
resetRequest();
setResetConfirmation(false);
}}
onDismiss={() => setResetConfirmation(false)}
/>
</div>
);
};
2 changes: 2 additions & 0 deletions src/constants/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const TEST_IDS = {
confirmModalFieldTitle: `data-testid panel confirm-modal-content field field-title`,
confirmModalFieldPreviousValue: `data-testid panel confirm-modal-content field field-previous-value`,
confirmModalFieldValue: `data-testid panel confirm-modal-content field field-value`,
resetConfirmModal: 'data-testid panel reset-confirm-modal',
buttonConfirmReset: selectors.pages.ConfirmModal.delete,
},
customCodeEditor: {
root: 'data-testid custom-code-editor',
Expand Down
29 changes: 19 additions & 10 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export const plugin = new PanelPlugin<PanelOptions>(FormPanel)
builder
.addRadio({
path: 'update.payloadMode',
name: 'Update Request Payload',
name: 'Request Payload',
description: 'Choose what values will be included in payload.',
category: ['Update Request Payload'],
settings: {
Expand Down Expand Up @@ -397,60 +397,59 @@ export const plugin = new PanelPlugin<PanelOptions>(FormPanel)
path: 'update.confirm',
name: 'Confirmation Window',
description: 'Ask to confirm updated values.',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
settings: {
options: BOOLEAN_OPTIONS,
},
defaultValue: false,
showIf: (config) => config.layout.variant !== LayoutVariant.NONE,
})
.addTextInput({
path: 'confirmModal.title',
name: 'Title',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
defaultValue: CONFIRM_MODAL_DEFAULT.title,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.body',
name: 'Text',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
defaultValue: CONFIRM_MODAL_DEFAULT.body,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.columns.name',
name: 'Label column',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
defaultValue: CONFIRM_MODAL_DEFAULT.columns.name,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.columns.oldValue',
name: 'Old value column',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
defaultValue: CONFIRM_MODAL_DEFAULT.columns.oldValue,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.columns.newValue',
name: 'New value column',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
defaultValue: CONFIRM_MODAL_DEFAULT.columns.newValue,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.confirm',
name: 'Confirm button',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
description: 'The text on the confirm button.',
defaultValue: CONFIRM_MODAL_DEFAULT.confirm,
showIf: (config) => config.update.confirm,
})
.addTextInput({
path: 'confirmModal.cancel',
name: 'Cancel button',
category: ['Confirmation Window'],
category: ['Update Confirmation Window'],
description: 'The text on the cancel button.',
defaultValue: CONFIRM_MODAL_DEFAULT.cancel,
showIf: (config) => config.update.confirm,
Expand Down Expand Up @@ -652,6 +651,16 @@ export const plugin = new PanelPlugin<PanelOptions>(FormPanel)
defaultValue: UPDATE_CODE_DEFAULT,
showIf: (config) =>
config.reset.variant !== ButtonVariant.HIDDEN && config.resetAction.mode === ResetActionMode.DATASOURCE,
})
.addRadio({
path: 'resetAction.confirm',
name: 'Confirmation Window',
description: 'Ask to reset values.',
category: ['Reset Request'],
settings: {
options: BOOLEAN_OPTIONS,
},
defaultValue: false,
});

/**
Expand Down
Loading

0 comments on commit c26e2ee

Please sign in to comment.