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 = {