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

STSMACOM-796 Show successful toast notifications for Create and Edit actions in <ControlledVocab> #1426

Merged
merged 3 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* `<EditCustomFieldsSettings>` now passes the `entityType` when making PUT requests to `/custom-fields`. Refs FCFIELDS-44.
* Added `tenant` prop to `<ControlledVocab>`. Refs STSMACOM-794.
* Use the default match and search option in Advanced search when they are not entered. Refs STSMACOM-793.
* Show successful toast notifications for Create and Edit actions in `<ControlledVocab>`. Refs STSMACOM-796.

## [9.0.0](https://github.com/folio-org/stripes-smart-components/tree/v9.0.0) (2023-10-11)
[Full Changelog](https://github.com/folio-org/stripes-smart-components/compare/v8.0.0...v9.0.0)
Expand Down
85 changes: 57 additions & 28 deletions lib/ControlledVocab/ControlledVocab.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import EditableList from '../EditableList';
import css from './ControlledVocab.css';
import makeRefdataActuatorsBoundTo from './actuators-refdata';

const ACTIONS = {
CREATE: 'termCreated',
EDIT: 'termUpdated',
DELETE: 'termDeleted',
};

const getTenantFromRESTResource = (queryParams, pathComponents, resourceValues, logger, props) => {
const {
tenant,
Expand Down Expand Up @@ -114,9 +120,6 @@ class ControlledVocab extends React.Component {
GET: PropTypes.func,
reset: PropTypes.func,
}),
tenant: PropTypes.shape({
replace: PropTypes.func,
}),
values: PropTypes.shape({
DELETE: PropTypes.func,
GET: PropTypes.func,
Expand Down Expand Up @@ -159,7 +162,9 @@ class ControlledVocab extends React.Component {
cannotDeleteTermHeader: "Cannot delete patron group",
cannotDeleteTermMessage: "This patron group cannot be deleted, as it is in use by one or more records.",
deleteEntry: "Delete patron group",
termCreated: "The patron group <b>{term}</b> was successfully <b>created</b>",
termDeleted: "The patron group <b>{term}</b> was successfully <b>deleted</b>",
termUpdated: "The patron group <b>{term}</b> was successfully <b>updated</b>",
termWillBeDeleted: "The patron group <b>{term}</b> will be <b>deleted.</b>"
}
*/
Expand All @@ -169,6 +174,8 @@ class ControlledVocab extends React.Component {
cannotDeleteTermMessage: PropTypes.string,
deleteEntry: PropTypes.string,
termDeleted: PropTypes.string,
termCreated: PropTypes.string,
termUpdated: PropTypes.string,
termWillBeDeleted: PropTypes.string,
}),
/*
Expand Down Expand Up @@ -211,7 +218,8 @@ class ControlledVocab extends React.Component {
// !{limitParam:-limit}
// in the manifest above.
actuatorType: 'rest',
canCreate: true
canCreate: true,
translations: {},
};

constructor(props) {
Expand Down Expand Up @@ -274,7 +282,10 @@ class ControlledVocab extends React.Component {
}

onCreateItem(item) {
return this.props.mutator.values.POST(this.props.preCreateHook(item));
return this.props.mutator.values.POST(this.props.preCreateHook(item))
.then(() => {
this.showSuccessCallout(item, ACTIONS.CREATE);
});
}

onDeleteItem() {
Expand All @@ -284,7 +295,7 @@ class ControlledVocab extends React.Component {

return this.props.mutator.values.DELETE({ id: selectedItem.id })
.then(() => {
this.showDeletionSuccessCallout(selectedItem);
this.showSuccessCallout(selectedItem, ACTIONS.DELETE);
this.deleteItemResolve();
})
.catch(() => {
Expand All @@ -296,7 +307,10 @@ class ControlledVocab extends React.Component {

onUpdateItem(item) {
this.props.mutator.activeRecord.update({ id: item.id });
return this.props.mutator.values.PUT(this.props.preUpdateHook(item));
return this.props.mutator.values.PUT(this.props.preUpdateHook(item))
.then(() => {
this.showSuccessCallout(item, ACTIONS.EDIT);
});
}

filteredRows(rows) {
Expand Down Expand Up @@ -343,28 +357,43 @@ class ControlledVocab extends React.Component {
});
}

showDeletionSuccessCallout(item) {
if (this.callout) {
const { termDeleted } = this.props.translations || {};
const message = (
termDeleted ?
<FormattedMessage
id={termDeleted}
values={{
term: item[this.state.primaryField],
}}
/> :
<FormattedMessage
id="stripes-smart-components.cv.termDeleted"
values={{
type: this.props.labelSingular,
term: item[this.state.primaryField],
}}
/>
);

this.callout.sendCallout({ message });
showSuccessCallout(item, action) {
if (!this.callout) {
return;
}

const {
termCreated,
termDeleted,
termUpdated,
} = this.props.translations;

const translationByAction = {
[ACTIONS.CREATE]: termCreated,
[ACTIONS.DELETE]: termDeleted,
[ACTIONS.EDIT]: termUpdated,
};

const translation = translationByAction[action];

const message = (
translation ?
<FormattedMessage
id={translation}
values={{
term: item[this.state.primaryField],
}}
/> :
<FormattedMessage
id={`stripes-smart-components.cv.${action}`}
values={{
type: this.props.labelSingular,
term: item[this.state.primaryField],
}}
/>
);

this.callout.sendCallout({ message });
}

handlePaneFocus({ paneTitleRef }) {
Expand Down
40 changes: 38 additions & 2 deletions lib/ControlledVocab/tests/ControlledVocab-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
including,
MultiColumnList,
MultiColumnListCell,
Callout,
} from '@folio/stripes-testing';

import mountComponent from './mountComponent';
Expand Down Expand Up @@ -59,7 +60,9 @@ describe('ControlledVocab', () => {
it('should display Delete button', () => cm.find(Button('Delete')).exists());

describe('click delete on confirmation modal', () => {
beforeEach(async () => {
beforeEach(async function () {
this.server.delete('location-units/institutions/:id', {}, 500);

await Button('Delete').click();
});

Expand All @@ -79,7 +82,9 @@ describe('ControlledVocab', () => {
it('should have row count 5', () => cv.has({ rowCount: 5 }));

describe('clicking Delete icon on first row', () => {
beforeEach(async () => {
beforeEach(async function () {
this.server.delete('location-units/institutions/:id', {}, 500);

await firstRow.delete();
});

Expand All @@ -99,6 +104,15 @@ describe('ControlledVocab', () => {
it('cannot delete modal title', () => mo.has({ title: 'Cannot delete Institution' }));
});
});

describe('when deleting is successful', () => {
beforeEach(async () => {
await firstRow.delete();
await Button('Delete').click();
});

it('should display successful callout message', () => Callout('The Institution Bowdoin College was successfully deleted').exists());
});
});

describe('User can edit EditableListForm', () => {
Expand Down Expand Up @@ -167,6 +181,14 @@ describe('ControlledVocab', () => {
it('should not display the error message', () => firstRow.find(TextField(including('name'))).is({ valid: true }));

it('should enable Save button', () => firstRow.has({ saveDisabled: false }));

describe('when creating is successful', () => {
beforeEach(async () => {
await firstRow.save();
});

it('should display successful callout message', () => Callout('The Institution test was successfully created').exists());
});
});
});

Expand Down Expand Up @@ -200,6 +222,20 @@ describe('ControlledVocab', () => {

it('should enable Delete button', () => cv.has({ deleteDisabled: false }));
});

describe('when editing an item', () => {
beforeEach(async () => {
await firstRow.find(TextField(including('name'))).fillIn('Bowdoin College edit');
});

describe('when creating is successful', () => {
beforeEach(async () => {
await firstRow.save();
});

it('should display successful callout message', () => Callout('The Institution Bowdoin College edit was successfully updated').exists());
});
});
});
});

Expand Down
22 changes: 21 additions & 1 deletion lib/ControlledVocab/tests/ControlledVocabErrors-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,27 @@ describe('ControlledVocabErrors', () => {

function mount() {
// eslint-disable-next-line no-undef
beforeEach(() => mountComponent(true, server));
beforeEach(function () {
mountComponent(true, this.server);
this.server.post('location-units/institutions', {
'errors': [{
'message': 'Cannot create entity; name is not unique',
'code': 'name.duplicate',
'parameters': [{
'key': 'fieldLabel',
'value': 'name'
}]
},
{
'message': 'test',
'code': '-1',
'parameters': [{
'key': 'test',
'value': 'test'
}]
}]
}, 422);
});
beforeEach(async () => {
await cv.newButton().click();
await cv.fillInputField('test');
Expand Down
Loading
Loading