From eeeab073caac081e9984a40f15ab71b2871c72e4 Mon Sep 17 00:00:00 2001 From: Aleksandr Voznesenskii <62722417+Fewwy@users.noreply.github.com> Date: Tue, 21 Feb 2023 12:38:14 +0100 Subject: [PATCH 1/2] chore(ESSNTL-3736): Implement Delete group modal (#1769) Implements https://issues.redhat.com/browse/ESSNTL-3736. --- .../Modals/DeleteGroupModal.cy.js | 43 ++++++++++ .../Modals/DeleteGroupModal.js | 81 +++++++++++++++++++ .../{ => __tests__}/CreateGroupModal.test.js | 4 +- .../Modals/__tests__/DeleteGroupModal.test.js | 31 +++++++ src/components/InventoryGroups/utils/api.js | 4 + 5 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 src/components/InventoryGroups/Modals/DeleteGroupModal.cy.js create mode 100644 src/components/InventoryGroups/Modals/DeleteGroupModal.js rename src/components/InventoryGroups/Modals/{ => __tests__}/CreateGroupModal.test.js (90%) create mode 100644 src/components/InventoryGroups/Modals/__tests__/DeleteGroupModal.test.js diff --git a/src/components/InventoryGroups/Modals/DeleteGroupModal.cy.js b/src/components/InventoryGroups/Modals/DeleteGroupModal.cy.js new file mode 100644 index 000000000..1739af5fa --- /dev/null +++ b/src/components/InventoryGroups/Modals/DeleteGroupModal.cy.js @@ -0,0 +1,43 @@ +/* eslint-disable camelcase */ +import React from 'react'; +import { mount } from '@cypress/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import { getStore } from '../../../store'; +import DeleteGroupModal from './DeleteGroupModal'; + +describe('Delete Group Modal', () => { + before(() => { + cy.window().then(window => window.insights = { + chrome: { + isProd: false, + auth: { + getUser: () => { + return Promise.resolve({}); + } + } + } + }); + }); + + beforeEach(() => { + cy.intercept('DELETE', '**/api/inventory/v1/groups/1', { + statusCode: 200, body: { + } + }).as('delete'); + + mount( + + + + + + ); + }); + + it('Input is fillable and firing a delete request', () => { + cy.get(`div[class="pf-c-check"]`).click(); + cy.get(`button[type="submit"]`).click(); + cy.wait('@delete'); + }); +}); diff --git a/src/components/InventoryGroups/Modals/DeleteGroupModal.js b/src/components/InventoryGroups/Modals/DeleteGroupModal.js new file mode 100644 index 000000000..3b3be9ce4 --- /dev/null +++ b/src/components/InventoryGroups/Modals/DeleteGroupModal.js @@ -0,0 +1,81 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; +import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; +import Modal from './Modal'; +import { deleteGroupById } from '../utils/api'; +import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon'; +import warningColor from '@patternfly/react-tokens/dist/esm/global_warning_color_100'; +import { Text } from '@patternfly/react-core'; +import apiWithToast from '../utils/apiWithToast'; +import { useDispatch } from 'react-redux'; + +const description = (name) => ( + + {name} and all its data will be permanently deleted. + Associated systems will be removed from the group but will not be deleted. + +); + +const schema = (name) => ({ + fields: [ + { + component: componentTypes.PLAIN_TEXT, + name: 'warning-message', + label: description(name) + }, + { + component: componentTypes.CHECKBOX, + name: 'confirmation', + label: 'I understand that this action cannot be undone.', + validate: [{ type: validatorTypes.REQUIRED }] + } + ] +}); + +const defaultValueToBeRemoved = () => console.log('data reloaded'); + +const DeleteGroupModal = ({ + isModalOpen, + setIsModalOpen, + reloadData = defaultValueToBeRemoved, + modalState +}) => { + const { id, name } = modalState; + const dispatch = useDispatch(); + + const handleDeleteGroup = () => { + const statusMessages = { + onSuccess: { + title: 'Success', + description: `${name} has been removed successfully` + }, + onError: { title: 'Error', description: 'Failed to delete group' } + }; + apiWithToast(dispatch, () => deleteGroupById(id), statusMessages); + }; + + return ( + setIsModalOpen(false)} + title="Delete group?" + titleIconVariant={() => ()} + variant="danger" + submitLabel="Delete" + schema={schema(name)} + onSubmit={handleDeleteGroup} + reloadData={reloadData} + /> + ); +}; + +DeleteGroupModal.propTypes = { + id: PropTypes.number, + name: PropTypes.string, + modalState: PropTypes.object, + isModalOpen: PropTypes.bool, + setIsModalOpen: PropTypes.func, + reloadData: PropTypes.func +}; +export default DeleteGroupModal; diff --git a/src/components/InventoryGroups/Modals/CreateGroupModal.test.js b/src/components/InventoryGroups/Modals/__tests__/CreateGroupModal.test.js similarity index 90% rename from src/components/InventoryGroups/Modals/CreateGroupModal.test.js rename to src/components/InventoryGroups/Modals/__tests__/CreateGroupModal.test.js index e72fe8f11..f3317a3b1 100644 --- a/src/components/InventoryGroups/Modals/CreateGroupModal.test.js +++ b/src/components/InventoryGroups/Modals/__tests__/CreateGroupModal.test.js @@ -1,11 +1,11 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; -import { getStore } from '../../../store'; +import { getStore } from '../../../../store'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; -import CreateGroupModal from './CreateGroupModal'; +import CreateGroupModal from '../CreateGroupModal'; describe('CreateGroupModal', () => { it('renders correctly', () => { diff --git a/src/components/InventoryGroups/Modals/__tests__/DeleteGroupModal.test.js b/src/components/InventoryGroups/Modals/__tests__/DeleteGroupModal.test.js new file mode 100644 index 000000000..bf68a86dd --- /dev/null +++ b/src/components/InventoryGroups/Modals/__tests__/DeleteGroupModal.test.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { getStore } from '../../../../store'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import DeleteGroupModal from '../DeleteGroupModal'; + +describe('DeleteGroupModal', () => { + it('renders correctly', () => { + render( + + + console.log('data reloaded')} + modalState={{ id: 1, name: 'test name' }} + /> + + + ); + expect(screen.getByRole('heading', { name: /Delete group/ })).toBeInTheDocument(); + expect(screen.getByText((content) => content.startsWith('test name'))).toBeInTheDocument(); + // eslint-disable-next-line max-len + expect(screen.getByText((content) => content.startsWith('and all its data will be permanently deleted. Associated systems will be removed from the group but will not be deleted.'))) + .toBeInTheDocument(); + expect(screen.getByRole('checkbox')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Delete/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Cancel/ })).toBeInTheDocument(); + }); +}); diff --git a/src/components/InventoryGroups/utils/api.js b/src/components/InventoryGroups/utils/api.js index 4d21a62f8..36e1e162d 100644 --- a/src/components/InventoryGroups/utils/api.js +++ b/src/components/InventoryGroups/utils/api.js @@ -25,6 +25,10 @@ export const validateGroupName = (name) => { .then((resp) => resp?.results.some((group) => group.name === name)); }; +export const deleteGroupById = (id) => { + return instance.delete(`${INVENTORY_API_BASE}/groups/${id}`); +}; + getGroups.propTypes = { search: PropTypes.shape({ // eslint-disable-next-line camelcase From fe4d2ed10ee095951347a34ca229345b5fab8a2e Mon Sep 17 00:00:00 2001 From: Aleksandr Voznesenskii <62722417+Fewwy@users.noreply.github.com> Date: Tue, 21 Feb 2023 13:28:51 +0100 Subject: [PATCH 2/2] chore(ESSNTL-3734): Implement Rename group modal (#1768) Implements https://issues.redhat.com/browse/ESSNTL-3734. --- package-lock.json | 86 +++++++-------- package.json | 6 +- .../Modals/RenameGroupModal.cy.js | 104 ++++++++++++++++++ .../Modals/RenameGroupModal.js | 88 +++++++++++++++ .../Modals/__tests__/RenameGroupModal.test.js | 28 +++++ src/components/InventoryGroups/utils/api.js | 7 ++ 6 files changed, 273 insertions(+), 46 deletions(-) create mode 100644 src/components/InventoryGroups/Modals/RenameGroupModal.cy.js create mode 100644 src/components/InventoryGroups/Modals/RenameGroupModal.js create mode 100644 src/components/InventoryGroups/Modals/__tests__/RenameGroupModal.test.js diff --git a/package-lock.json b/package-lock.json index 854b01429..f89ae595c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,9 @@ "name": "insights-inventory-frontend", "version": "1.3.0", "dependencies": { - "@data-driven-forms/common": "^3.19.3", - "@data-driven-forms/pf4-component-mapper": "^3.19.3", - "@data-driven-forms/react-form-renderer": "^3.19.3", + "@data-driven-forms/common": "^3.20.0", + "@data-driven-forms/pf4-component-mapper": "^3.20.0", + "@data-driven-forms/react-form-renderer": "^3.20.0", "@patternfly/react-core": "^4.265.2", "@patternfly/react-icons": "^4.93.6", "@patternfly/react-table": "^4.111.45", @@ -1940,6 +1940,12 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, "node_modules/@babel/runtime": { "version": "7.20.13", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", @@ -7592,9 +7598,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001451", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", - "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==", + "version": "1.0.30001452", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001452.tgz", + "integrity": "sha512-Lkp0vFjMkBB3GTpLR8zk4NwW5EdRdnitwYJHDOOKIU85x4ckYCPQ+9WlVvSVClHxVReefkUMtWZH2l9KGlD51w==", "funding": [ { "type": "opencollective", @@ -8653,12 +8659,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.2.tgz", - "integrity": "sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.28.0.tgz", + "integrity": "sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==", "dev": true, "dependencies": { - "browserslist": "^4.21.4" + "browserslist": "^4.21.5" }, "funding": { "type": "opencollective", @@ -9687,9 +9693,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.289", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.289.tgz", - "integrity": "sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg==" + "version": "1.4.295", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz", + "integrity": "sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw==" }, "node_modules/emittery": { "version": "0.8.1", @@ -22123,14 +22129,14 @@ } }, "node_modules/regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.0.tgz", + "integrity": "sha512-ZdhUQlng0RoscyW7jADnUZ25F5eVtHdMyXSb2PiwafvteRAOJUjFoUPEYZSIfP99fBIs3maLIRfpEddT78wAAQ==", "dev": true, "dependencies": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" @@ -22151,12 +22157,6 @@ "node": ">=14" } }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, "node_modules/regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -27538,6 +27538,12 @@ "@babel/plugin-transform-react-pure-annotations": "^7.16.0" } }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, "@babel/runtime": { "version": "7.20.13", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", @@ -31999,9 +32005,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001451", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", - "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==" + "version": "1.0.30001452", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001452.tgz", + "integrity": "sha512-Lkp0vFjMkBB3GTpLR8zk4NwW5EdRdnitwYJHDOOKIU85x4ckYCPQ+9WlVvSVClHxVReefkUMtWZH2l9KGlD51w==" }, "cardinal": { "version": "2.1.1", @@ -32790,12 +32796,12 @@ "dev": true }, "core-js-compat": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.2.tgz", - "integrity": "sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.28.0.tgz", + "integrity": "sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==", "dev": true, "requires": { - "browserslist": "^4.21.4" + "browserslist": "^4.21.5" } }, "core-js-pure": { @@ -33584,9 +33590,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.289", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.289.tgz", - "integrity": "sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg==" + "version": "1.4.295", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.295.tgz", + "integrity": "sha512-lEO94zqf1bDA3aepxwnWoHUjA8sZ+2owgcSZjYQy0+uOSEclJX0VieZC+r+wLpSxUHRd6gG32znTWmr+5iGzFw==" }, "emittery": { "version": "0.8.1", @@ -42816,14 +42822,14 @@ "dev": true }, "regexpu-core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", - "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.0.tgz", + "integrity": "sha512-ZdhUQlng0RoscyW7jADnUZ25F5eVtHdMyXSb2PiwafvteRAOJUjFoUPEYZSIfP99fBIs3maLIRfpEddT78wAAQ==", "dev": true, "requires": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" @@ -42838,12 +42844,6 @@ "@pnpm/npm-conf": "^1.0.4" } }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, "regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", diff --git a/package.json b/package.json index 615458bc6..ab6438538 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "npm": ">=7.0.0" }, "dependencies": { - "@data-driven-forms/common": "^3.19.3", - "@data-driven-forms/pf4-component-mapper": "^3.19.3", - "@data-driven-forms/react-form-renderer": "^3.19.3", + "@data-driven-forms/common": "^3.20.0", + "@data-driven-forms/pf4-component-mapper": "^3.20.0", + "@data-driven-forms/react-form-renderer": "^3.20.0", "@patternfly/react-core": "^4.265.2", "@patternfly/react-icons": "^4.93.6", "@patternfly/react-table": "^4.111.45", diff --git a/src/components/InventoryGroups/Modals/RenameGroupModal.cy.js b/src/components/InventoryGroups/Modals/RenameGroupModal.cy.js new file mode 100644 index 000000000..b0e270f81 --- /dev/null +++ b/src/components/InventoryGroups/Modals/RenameGroupModal.cy.js @@ -0,0 +1,104 @@ +/* eslint-disable camelcase */ +import React from 'react'; +import { mount } from '@cypress/react'; +import RenameGroupModal from './RenameGroupModal'; +import { + TEXT_INPUT +} from '@redhat-cloud-services/frontend-components-utilities'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import { getStore } from '../../../store'; + +const mockResponse = [ + { + count: 50, + page: 20, + per_page: 20, + total: 50, + results: [ + { + created_at: '2020-02-09T10:16:07.996Z', + host_ids: ['bA6deCFc19564430AB814bf8F70E8cEf'], + id: '3f01b55457674041b75e41829bcee1dca', + name: 'sre-group0', + updated_at: '2020-02-09T10:16:07.996Z' + }, + { + created_at: '2020-02-09T10:16:07.996Z', + host_ids: ['bA6deCFc19564430AB814bf8F70E8cEf'], + id: '3f01b55457674041b75e41829bcee1dca', + name: 'sre-group1', + updated_at: '2020-02-09T10:16:07.996Z' + }, + { + created_at: '2020-02-09T10:16:07.996Z', + host_ids: ['bA6deCFc19564430AB814bf8F70E8cEf'], + id: '3f01b55457674041b75e41829bcee1dca', + name: 'sre-group2', + updated_at: '2020-02-09T10:16:07.996Z' + }, + { + created_at: '2020-02-09T10:16:07.996Z', + host_ids: ['bA6deCFc19564430AB814bf8F70E8cEf'], + id: '3f01b55457674041b75e41829bcee1dca', + name: 'sre-group3', + updated_at: '2020-02-09T10:16:07.996Z' + } + ] + } +]; + +describe('render Rename Group Modal', () => { + before(() => { + cy.window().then(window => window.insights = { + chrome: { + isProd: false, + auth: { + getUser: () => { + return Promise.resolve({}); + } + } + } + }); + }); + + beforeEach(() => { + cy.intercept('GET', '**/api/inventory/v1/groups', { + statusCode: 200, body: { + ...mockResponse[0] + } + }).as('validate'); + cy.intercept('PATCH', '**/api/inventory/v1/groups/1', { + statusCode: 200, body: { + ...mockResponse[0] + } + }).as('rename'); + + mount( + + + console.log('data reloaded')} + modalState={{ id: '1', name: 'sre-group' }} + /> + + + ); + }); + + it('Input is fillable and firing a validation request that succeeds', () => { + cy.get(TEXT_INPUT).type('0'); + cy.wait('@validate').then((xhr) => { + expect(xhr.request.url).to.contain('groups');} + ); + cy.get(`button[type="submit"]`).should('have.attr', 'aria-disabled', 'true'); + }); + + it('User can rename the group', () => { + cy.get(TEXT_INPUT).type('newname'); + cy.get(`button[type="submit"]`).should('have.attr', 'aria-disabled', 'false'); + cy.get(`button[type="submit"]`).click(); + cy.wait('@rename'); + }); +}); diff --git a/src/components/InventoryGroups/Modals/RenameGroupModal.js b/src/components/InventoryGroups/Modals/RenameGroupModal.js new file mode 100644 index 000000000..d6c353736 --- /dev/null +++ b/src/components/InventoryGroups/Modals/RenameGroupModal.js @@ -0,0 +1,88 @@ +import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; +import validatorTypes from '@data-driven-forms/react-form-renderer/validator-types'; +import componentTypes from '@data-driven-forms/react-form-renderer/component-types'; +import Modal from './Modal'; +import awesomeDebouncePromise from 'awesome-debounce-promise'; +import { validateGroupName, updateGroupById } from '../utils/api'; +import { nameValidator } from '../helpers/validate'; +import apiWithToast from '../utils/apiWithToast'; +import { useDispatch } from 'react-redux'; + +const renameGroupSchema = (namePresenceValidator) => ({ + fields: [ + { + component: componentTypes.TEXT_FIELD, + name: 'name', + label: 'Name', + helperText: + 'Can only contain letters, numbers, spaces, hyphens ( - ), and underscores( _ ).', + isRequired: true, + validate: [ + namePresenceValidator, + { type: validatorTypes.REQUIRED }, + { type: validatorTypes.MAX_LENGTH, threshold: 50 }, + nameValidator + ] + } + ] +}); + +const RenameGroupModal = ({ + isModalOpen, + setIsModalOpen, + modalState, + reloadData +}) => { + const { id, name } = modalState; + const dispatch = useDispatch(); + + const handleRenameModal = (values) => { + const statusMessages = { + onSuccess: { + title: 'Success', + description: `${name} has been renamed to ${values.name} successfully` + }, + onError: { title: 'Error', description: 'Failed to rename group' } + }; + apiWithToast(dispatch, () => updateGroupById(id, values), statusMessages); + }; + + const schema = useMemo(() => { + const check = async (value) => { + const results = await validateGroupName(value); + if (results === true) { + throw 'Group name already exists'; + } + + return undefined; + }; + + // eslint-disable-next-line new-cap + const d = awesomeDebouncePromise(check, 500, { onlyResolvesLast: false }); + return renameGroupSchema(d); + }, []); + + return ( + setIsModalOpen(false)} + title="Rename group" + submitLabel="Save" + schema={schema} + initialValues={modalState} + onSubmit={handleRenameModal} + reloadData={reloadData} + /> + ); +}; + +RenameGroupModal.propTypes = { + id: PropTypes.number, + modalState: PropTypes.object, + isModalOpen: PropTypes.bool, + setIsModalOpen: PropTypes.func, + reloadData: PropTypes.func +}; + +export default RenameGroupModal; diff --git a/src/components/InventoryGroups/Modals/__tests__/RenameGroupModal.test.js b/src/components/InventoryGroups/Modals/__tests__/RenameGroupModal.test.js new file mode 100644 index 000000000..c2e19798e --- /dev/null +++ b/src/components/InventoryGroups/Modals/__tests__/RenameGroupModal.test.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { getStore } from '../../../../store'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; + +import RenameGroupModal from '../RenameGroupModal'; + +describe('CreateGroupModal', () => { + it('renders correctly', () => { + render( + + + console.log('data reloaded')} + modalState={{ id: '1', name: 'sre-group' }} + /> + + + ); + expect(screen.getByRole('heading', { name: /Rename group/ })).toBeInTheDocument(); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Save/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Cancel/ })).toBeInTheDocument(); + }); +}); diff --git a/src/components/InventoryGroups/utils/api.js b/src/components/InventoryGroups/utils/api.js index 36e1e162d..5d506a734 100644 --- a/src/components/InventoryGroups/utils/api.js +++ b/src/components/InventoryGroups/utils/api.js @@ -25,8 +25,15 @@ export const validateGroupName = (name) => { .then((resp) => resp?.results.some((group) => group.name === name)); }; +export const updateGroupById = (id, payload) => { + return instance.patch(`${INVENTORY_API_BASE}/groups/${id}`, { + name: payload.name + }); +}; + export const deleteGroupById = (id) => { return instance.delete(`${INVENTORY_API_BASE}/groups/${id}`); + }; getGroups.propTypes = {