From f53662a3d226fb9f4de9722f169924419e021d7c Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Tue, 2 Jul 2024 15:24:51 +0200 Subject: [PATCH] Forbid cleanup of instances in overview pages --- .../DatabaseItemOverview.jsx | 15 +++++-- .../DatabasesOverview/DatabasesOverview.jsx | 2 + .../DatabasesOverview.stories.jsx | 14 +++++++ .../DatabasesOverview.test.jsx | 31 ++++++++++++++ .../DatabasesOverviewPage.jsx | 3 ++ .../InstanceOverview/InstanceOverview.jsx | 4 ++ .../InstanceOverview.test.jsx | 30 +++++++++++++ .../SapSystemItemOverview.jsx | 8 +++- .../SapSystemsOverview.jsx | 2 + .../SapSystemsOverview.stories.jsx | 13 ++++++ .../SapSystemsOverview.test.jsx | 42 +++++++++++++++++++ .../SapSystemsOverviewPage.jsx | 3 ++ 12 files changed, 162 insertions(+), 5 deletions(-) diff --git a/assets/js/pages/DatabasesOverview/DatabaseItemOverview.jsx b/assets/js/pages/DatabasesOverview/DatabaseItemOverview.jsx index ea26d0884d..5e14cff184 100644 --- a/assets/js/pages/DatabasesOverview/DatabaseItemOverview.jsx +++ b/assets/js/pages/DatabasesOverview/DatabaseItemOverview.jsx @@ -4,11 +4,13 @@ import React from 'react'; import { DATABASE_TYPE } from '@lib/model/sapSystems'; import InstanceOverview from '@pages/InstanceOverview'; -export function DatabaseInstance({ instance, onCleanUpClick }) { +export function DatabaseInstance({ instance, userAbilities, onCleanUpClick }) { return ( ); @@ -27,6 +29,7 @@ const databaseInstanceColumns = [ function PlainDatabaseItemOverview({ instances, asDatabaseLayer = false, + userAbilities, onCleanUpClick, }) { return ( @@ -59,6 +62,7 @@ function PlainDatabaseItemOverview({ ))} @@ -69,21 +73,23 @@ function PlainDatabaseItemOverview({ ); } -function DatabaseLayer({ instances, onCleanUpClick }) { +function DatabaseLayer({ instances, userAbilities, onCleanUpClick }) { return ( ); } -function DatabaseInstances({ instances, onCleanUpClick }) { +function DatabaseInstances({ instances, userAbilities, onCleanUpClick }) { return (
@@ -93,6 +99,7 @@ function DatabaseInstances({ instances, onCleanUpClick }) { function DatabaseItemOverview({ database, asDatabaseLayer = false, + userAbilities, onCleanUpClick, }) { const { databaseInstances } = database; @@ -100,11 +107,13 @@ function DatabaseItemOverview({ return asDatabaseLayer ? ( ) : ( ); diff --git a/assets/js/pages/DatabasesOverview/DatabasesOverview.jsx b/assets/js/pages/DatabasesOverview/DatabasesOverview.jsx index 279014c755..efc810e01b 100644 --- a/assets/js/pages/DatabasesOverview/DatabasesOverview.jsx +++ b/assets/js/pages/DatabasesOverview/DatabasesOverview.jsx @@ -20,6 +20,7 @@ function DatabasesOverview({ databases, databaseInstances, loading, + userAbilities, onTagAdd, onTagRemove, onInstanceCleanUp, @@ -116,6 +117,7 @@ function DatabasesOverview({ collapsibleDetailRenderer: (database) => ( { setCleanUpModalOpen(true); setInstanceToDeregister(instance); diff --git a/assets/js/pages/DatabasesOverview/DatabasesOverview.stories.jsx b/assets/js/pages/DatabasesOverview/DatabasesOverview.stories.jsx index 957dbb2409..d321f38ebb 100644 --- a/assets/js/pages/DatabasesOverview/DatabasesOverview.stories.jsx +++ b/assets/js/pages/DatabasesOverview/DatabasesOverview.stories.jsx @@ -78,6 +78,10 @@ export default { defaultValue: { summary: false }, }, }, + userAbilities: { + control: 'array', + description: 'Current user abilities', + }, onTagAdd: { action: 'Add tag', description: 'Called when a new tag is added', @@ -110,6 +114,7 @@ export const Databases = { databases, databaseInstances: enrichedInstances, loading: false, + userAbilities: [{ name: 'all', resource: 'all' }], }, }; @@ -118,6 +123,7 @@ export const WithSystemReplication = { databases: [databaseWithSR], databaseInstances: systemReplicationInstances, loading: false, + userAbilities: [{ name: 'all', resource: 'all' }], }, }; @@ -126,5 +132,13 @@ export const WithAbsentInstances = { databases: [databaseWithAbsentInstances], databaseInstances: absentInstance, loading: false, + userAbilities: [{ name: 'all', resource: 'all' }], + }, +}; + +export const UnauthorizedCleanUp = { + args: { + ...WithAbsentInstances.args, + userAbilities: [], }, }; diff --git a/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx b/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx index e4203a69c5..529105e815 100644 --- a/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx +++ b/assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx @@ -27,6 +27,7 @@ describe('DatabasesOverview component', () => { ); @@ -47,6 +48,36 @@ describe('DatabasesOverview component', () => { database.database_instances[0] ); }); + + it('should forbid instance cleanup', async () => { + const user = userEvent.setup(); + + const database = databaseFactory.build(); + + database.database_instances[0].absent_at = faker.date + .past() + .toISOString(); + + renderWithRouter( + + ); + + const cleanUpButton = screen.getByText('Clean up').closest('button'); + + expect(cleanUpButton).toBeDisabled(); + + await user.click(cleanUpButton); + + await user.hover(cleanUpButton); + + expect( + screen.queryByText('You are not authorized for this action') + ).toBeVisible(); + }); }); describe('filtering', () => { diff --git a/assets/js/pages/DatabasesOverview/DatabasesOverviewPage.jsx b/assets/js/pages/DatabasesOverview/DatabasesOverviewPage.jsx index 0084e6c460..7332b58850 100644 --- a/assets/js/pages/DatabasesOverview/DatabasesOverviewPage.jsx +++ b/assets/js/pages/DatabasesOverview/DatabasesOverviewPage.jsx @@ -3,6 +3,7 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { getEnrichedDatabaseInstances } from '@state/selectors/sapSystem'; +import { getUserProfile } from '@state/selectors/user'; import { addTagToDatabase, removeTagFromDatabase, @@ -27,6 +28,7 @@ function DatabasesOverviewPage() { const enrichedDatabaseInstances = useSelector((state) => getEnrichedDatabaseInstances(state) ); + const { abilities } = useSelector(getUserProfile); const dispatch = useDispatch(); return ( @@ -34,6 +36,7 @@ function DatabasesOverviewPage() { databases={databases} databaseInstances={enrichedDatabaseInstances} loading={loading} + userAbilities={abilities} onTagAdd={(tag, databaseID) => { addTag(tag, databaseID); dispatch(addTagToDatabase({ tags: [{ value: tag }], id: databaseID })); diff --git a/assets/js/pages/InstanceOverview/InstanceOverview.jsx b/assets/js/pages/InstanceOverview/InstanceOverview.jsx index b2a838146c..6fab4aeb6d 100644 --- a/assets/js/pages/InstanceOverview/InstanceOverview.jsx +++ b/assets/js/pages/InstanceOverview/InstanceOverview.jsx @@ -26,6 +26,8 @@ function InstanceOverview({ absent_at: absentAt, deregistering, }, + userAbilities, + cleanUpPermittedFor, onCleanUpClick, }) { const isDatabase = DATABASE_TYPE === instanceType; @@ -77,6 +79,8 @@ function InstanceOverview({ type="transparent" className="jungle-green-500 border-none shadow-none" cleaning={deregistering} + userAbilities={userAbilities} + permittedFor={cleanUpPermittedFor} onClick={() => onCleanUpClick(instance, instanceType)} /> diff --git a/assets/js/pages/InstanceOverview/InstanceOverview.test.jsx b/assets/js/pages/InstanceOverview/InstanceOverview.test.jsx index f82520fe02..36784c905d 100644 --- a/assets/js/pages/InstanceOverview/InstanceOverview.test.jsx +++ b/assets/js/pages/InstanceOverview/InstanceOverview.test.jsx @@ -57,6 +57,7 @@ describe('InstanceOverview', () => { ); @@ -95,9 +96,38 @@ describe('InstanceOverview', () => { ); expect(screen.getByLabelText('Loading')).toBeInTheDocument(); }); + + it('should forbid instance cleanup', async () => { + const user = userEvent.setup(); + + const absentInstance = databaseInstanceFactory.build({ + absent_at: faker.date.past().toISOString(), + }); + + renderWithRouter( + + ); + + const cleanUpButton = screen.getByText('Clean up').closest('button'); + + expect(cleanUpButton).toBeDisabled(); + + await user.click(cleanUpButton); + + await user.hover(cleanUpButton); + + expect( + screen.queryByText('You are not authorized for this action') + ).toBeVisible(); + }); }); diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemItemOverview.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemItemOverview.jsx index 4a2e1a3185..223117195c 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemItemOverview.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemItemOverview.jsx @@ -5,11 +5,13 @@ import { APPLICATION_TYPE } from '@lib/model/sapSystems'; import DatabaseItemOverview from '@pages/DatabasesOverview/DatabaseItemOverview'; import InstanceOverview from '@pages/InstanceOverview'; -function ApplicationInstance({ instance, onCleanUpClick }) { +function ApplicationInstance({ instance, userAbilities, onCleanUpClick }) { return ( ); @@ -24,7 +26,7 @@ const applicationInstanceColumns = [ { key: 'cleanupButton', cssClass: 'w-48' }, ]; -function SapSystemItemOverview({ sapSystem, onCleanUpClick }) { +function SapSystemItemOverview({ sapSystem, userAbilities, onCleanUpClick }) { const { applicationInstances, databaseInstances } = sapSystem; return ( @@ -56,6 +58,7 @@ function SapSystemItemOverview({ sapSystem, onCleanUpClick }) { ))} @@ -66,6 +69,7 @@ function SapSystemItemOverview({ sapSystem, onCleanUpClick }) { diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.jsx index 4d902e1dbc..cecd2f17ff 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.jsx @@ -20,6 +20,7 @@ function SapSystemsOverview({ applicationInstances, databaseInstances, loading, + userAbilities, onTagAdd, onTagRemove, onInstanceCleanUp, @@ -106,6 +107,7 @@ function SapSystemsOverview({ collapsibleDetailRenderer: (sapSystem) => ( { setCleanUpModalOpen(true); setInstanceToDeregister(instance); diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.stories.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.stories.jsx index 0bb7bc1aef..afe21cc45b 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.stories.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.stories.jsx @@ -82,6 +82,10 @@ export default { defaultValue: { summary: false }, }, }, + userAbilities: { + control: 'array', + description: 'Current user abilities', + }, onTagAdd: { action: 'Add tag', description: 'Called when a new tag is added', @@ -115,6 +119,7 @@ export const SapSystems = { applicationInstances: enrichedApplicationInstances, databaseInstances: enrichedDatabaseInstances, loading: false, + userAbilities: [{ name: 'all', resource: 'all' }], }, }; @@ -124,5 +129,13 @@ export const WithAbsentInstances = { applicationInstances: enrichedAbsentApplicationInstances, databaseInstances: enrichedAbsentDatabaseInstances, loading: false, + userAbilities: [{ name: 'all', resource: 'all' }], + }, +}; + +export const UnauthorizedCleanUp = { + args: { + ...WithAbsentInstances.args, + userAbilities: [], }, }; diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx index 09042ca3b9..e6464ba4e6 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverview.test.jsx @@ -219,6 +219,7 @@ describe('SapSystemsOverviews component', () => { sapSystems={[sapSystem]} applicationInstances={sapSystem.application_instances} databaseInstances={sapSystem.database_instances} + userAbilities={[{ name: 'all', resource: 'all' }]} onInstanceCleanUp={mockedCleanUp} /> ); @@ -240,6 +241,47 @@ describe('SapSystemsOverviews component', () => { expect(mockedCleanUp).toHaveBeenCalledWith(sapSystem[field][0], type); } ); + + it('should forbid instance cleanup', async () => { + const user = userEvent.setup(); + + const sapSystem = sapSystemFactory.build(); + + sapSystem.database_instances[0].absent_at = faker.date + .past() + .toISOString(); + + sapSystem.application_instances[0].absent_at = faker.date + .past() + .toISOString(); + + renderWithRouter( + + ); + + const cleanUpButtons = screen.getAllByRole('button', { + name: 'Clean up', + }); + + const applicationCleanUpButton = cleanUpButtons[0].closest('button'); + const databaseCleanUpButton = cleanUpButtons[1].closest('button'); + + expect(applicationCleanUpButton).toBeDisabled(); + expect(databaseCleanUpButton).toBeDisabled(); + + await user.click(applicationCleanUpButton); + + await user.hover(applicationCleanUpButton); + + expect( + screen.queryByText('You are not authorized for this action') + ).toBeVisible(); + }); }); describe('filtering', () => { diff --git a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverviewPage.jsx b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverviewPage.jsx index 586596a40a..1953753179 100644 --- a/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverviewPage.jsx +++ b/assets/js/pages/SapSystemsOverviewPage/SapSystemsOverviewPage.jsx @@ -8,6 +8,7 @@ import { getEnrichedApplicationInstances, getEnrichedDatabaseInstances, } from '@state/selectors/sapSystem'; +import { getUserProfile } from '@state/selectors/user'; import { addTagToSAPSystem, removeTagFromSAPSystem, @@ -35,6 +36,7 @@ function SapSystemOverviewPage() { const enrichedDatabaseInstances = useSelector((state) => getEnrichedDatabaseInstances(state) ); + const { abilities } = useSelector(getUserProfile); const dispatch = useDispatch(); return ( @@ -43,6 +45,7 @@ function SapSystemOverviewPage() { applicationInstances={enrichedApplicationInstances} databaseInstances={enrichedDatabaseInstances} loading={loading} + userAbilities={abilities} onTagAdd={(tag, sapSystemID) => { addTag(tag, sapSystemID); dispatch(