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

Cleanup abilities/permissions #2734

Merged
merged 10 commits into from
Jul 3, 2024
47 changes: 26 additions & 21 deletions assets/js/common/CleanUpButton/CleanUpButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import classNames from 'classnames';

import Button from '@common/Button';
import Spinner from '@common/Spinner';
import DisabledGuard from '@common/DisabledGuard';

function CleanUpButton({
className,
size = 'small',
type = 'primary-white',
cleaning,
userAbilities,
permittedFor,
onClick,
}) {
const buttonClasses = classNames(
Expand All @@ -19,27 +22,29 @@ function CleanUpButton({
);

return (
<Button
type={type}
className={buttonClasses}
size={size}
disabled={cleaning}
onClick={() => onClick()}
>
{cleaning === true ? (
<Spinner className="justify-center flex" />
) : (
<>
<EOS_CLEANING_SERVICES
size="base"
className="fill-jungle-green-500 inline"
/>
<span className="text-jungle-green-500 text-sm font-bold pl-1.5">
Clean up
</span>
</>
)}
</Button>
<DisabledGuard userAbilities={userAbilities} permitted={permittedFor}>
<Button
type={type}
className={buttonClasses}
size={size}
disabled={cleaning}
onClick={() => onClick()}
>
{cleaning === true ? (
<Spinner className="justify-center flex" />
) : (
<>
<EOS_CLEANING_SERVICES
size="base"
className="fill-jungle-green-500 inline"
/>
<span className="text-jungle-green-500 text-sm font-bold pl-1.5">
Clean up
</span>
</>
)}
</Button>
</DisabledGuard>
);
}

Expand Down
19 changes: 18 additions & 1 deletion assets/js/common/CleanUpButton/CleanUpButton.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export default {
defaultValue: { summary: false },
},
},
userAbilities: {
control: 'array',
description: 'Current user abilities',
},
permittedFor: {
control: 'array',
description: 'Abilities that allow check selection',
},
onClick: { action: 'Click button' },
className: {
control: 'text',
Expand Down Expand Up @@ -42,7 +50,9 @@ export default {
};

export const Default = {
args: {},
args: {
userAbilities: [{ name: 'all', resource: 'all' }],
},
};

export const Cleaning = {
Expand All @@ -67,3 +77,10 @@ export const AbsentInstanceRow = {
size: 'fit',
},
};

export const Unauthorized = {
args: {
...Default.args,
userAbilities: [],
},
};
24 changes: 22 additions & 2 deletions assets/js/common/CleanUpButton/CleanUpButton.test.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import userEvent from '@testing-library/user-event';

import CleanUpButton from '.';

describe('Button', () => {
it('should display the clean up button', () => {
render(<CleanUpButton />);
render(
<CleanUpButton userAbilities={[{ name: 'all', resource: 'all' }]} />
);
expect(screen.getByRole('button')).toHaveTextContent('Clean up');
});

it('should display the clean up in cleaning state', () => {
render(<CleanUpButton cleaning />);
render(
<CleanUpButton
cleaning
userAbilities={[{ name: 'all', resource: 'all' }]}
/>
);
const spinnerElement = screen.getByRole('alert');
expect(spinnerElement).toBeInTheDocument();
});

it('should forbid the cleanup', async () => {
const user = userEvent.setup();

render(<CleanUpButton userAbilities={[]} />);

await user.hover(screen.getByText('Clean up'));

expect(
screen.queryByText('You are not authorized for this action')
).toBeVisible();
});
});
1 change: 1 addition & 0 deletions assets/js/lib/test-utils/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const defaultInitialState = {
},
checksSelection: { host: {}, cluster: {} },
catalog: { loading: false, data: [], error: null },
user: { abilities: [{ name: 'all', resource: 'all' }] },
};

export const withState = (component, initialState = {}) => {
Expand Down
4 changes: 4 additions & 0 deletions assets/js/pages/DatabaseDetails/DatabaseDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import BackButton from '@common/BackButton';
import { GenericSystemDetails } from '@pages/SapSystemDetails';

import { getEnrichedDatabaseDetails } from '@state/selectors/sapSystem';
import { getUserProfile } from '@state/selectors/user';
import { deregisterDatabaseInstance } from '@state/databases';

function DatabaseDetails() {
const { id } = useParams();
const database = useSelector((state) =>
getEnrichedDatabaseDetails(state, id)
);
const { abilities } = useSelector(getUserProfile);
const dispatch = useDispatch();

if (!database) {
Expand All @@ -29,6 +31,8 @@ function DatabaseDetails() {
title="HANA Database Details"
type={DATABASE_TYPE}
system={database}
userAbilities={abilities}
cleanUpPermittedFor={['cleanup:database_instance']}
onInstanceCleanUp={(instance) => {
dispatch(deregisterDatabaseInstance(instance));
}}
Expand Down
19 changes: 18 additions & 1 deletion assets/js/pages/DatabaseDetails/DatabaseDetails.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export default {
control: 'object',
description: 'The represented HANA database',
},
userAbilities: {
control: 'array',
description: 'Current user abilities',
},
cleanUpPermittedFor: {
control: 'array',
description: 'Abilities that allow instance clean up',
},
onInstanceCleanUp: {
action: 'Clean up instance',
description: 'Deregister and clean up an absent instance',
Expand All @@ -63,12 +71,21 @@ export const Database = {
title: 'Database Details',
type: DATABASE_TYPE,
system: database,
userAbilities: [{ name: 'all', resource: 'all' }],
cleanUpPermittedFor: ['cleanup:database_instance'],
},
};

export const DatabaseWithAbsentInstance = {
args: {
...Database,
...Database.args,
system: databaseWithAbsentInstance,
},
};

export const CleanUpUnauthorized = {
args: {
...DatabaseWithAbsentInstance.args,
userAbilities: [],
},
};
15 changes: 12 additions & 3 deletions assets/js/pages/DatabasesOverview/DatabaseItemOverview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<InstanceOverview
instanceType={DATABASE_TYPE}
instance={instance}
userAbilities={userAbilities}
cleanUpPermittedFor={['cleanup:database_instance']}
onCleanUpClick={onCleanUpClick}
/>
);
Expand All @@ -27,6 +29,7 @@ const databaseInstanceColumns = [
function PlainDatabaseItemOverview({
instances,
asDatabaseLayer = false,
userAbilities,
onCleanUpClick,
}) {
return (
Expand Down Expand Up @@ -59,6 +62,7 @@ function PlainDatabaseItemOverview({
<DatabaseInstance
key={`${instance.host_id}_${instance.instance_number}`}
instance={instance}
userAbilities={userAbilities}
onCleanUpClick={onCleanUpClick}
/>
))}
Expand All @@ -69,21 +73,23 @@ function PlainDatabaseItemOverview({
);
}

function DatabaseLayer({ instances, onCleanUpClick }) {
function DatabaseLayer({ instances, userAbilities, onCleanUpClick }) {
return (
<PlainDatabaseItemOverview
instances={instances}
asDatabaseLayer
userAbilities={userAbilities}
onCleanUpClick={onCleanUpClick}
/>
);
}

function DatabaseInstances({ instances, onCleanUpClick }) {
function DatabaseInstances({ instances, userAbilities, onCleanUpClick }) {
return (
<div className="p-2">
<PlainDatabaseItemOverview
instances={instances}
userAbilities={userAbilities}
onCleanUpClick={onCleanUpClick}
/>
</div>
Expand All @@ -93,18 +99,21 @@ function DatabaseInstances({ instances, onCleanUpClick }) {
function DatabaseItemOverview({
database,
asDatabaseLayer = false,
userAbilities,
onCleanUpClick,
}) {
const { databaseInstances } = database;

return asDatabaseLayer ? (
<DatabaseLayer
instances={databaseInstances}
userAbilities={userAbilities}
onCleanUpClick={onCleanUpClick}
/>
) : (
<DatabaseInstances
instances={databaseInstances}
userAbilities={userAbilities}
onCleanUpClick={onCleanUpClick}
/>
);
Expand Down
2 changes: 2 additions & 0 deletions assets/js/pages/DatabasesOverview/DatabasesOverview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function DatabasesOverview({
databases,
databaseInstances,
loading,
userAbilities,
onTagAdd,
onTagRemove,
onInstanceCleanUp,
Expand Down Expand Up @@ -116,6 +117,7 @@ function DatabasesOverview({
collapsibleDetailRenderer: (database) => (
<DatabaseItemOverview
database={database}
userAbilities={userAbilities}
onCleanUpClick={(instance, _type) => {
setCleanUpModalOpen(true);
setInstanceToDeregister(instance);
Expand Down
14 changes: 14 additions & 0 deletions assets/js/pages/DatabasesOverview/DatabasesOverview.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -110,6 +114,7 @@ export const Databases = {
databases,
databaseInstances: enrichedInstances,
loading: false,
userAbilities: [{ name: 'all', resource: 'all' }],
},
};

Expand All @@ -118,6 +123,7 @@ export const WithSystemReplication = {
databases: [databaseWithSR],
databaseInstances: systemReplicationInstances,
loading: false,
userAbilities: [{ name: 'all', resource: 'all' }],
},
};

Expand 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: [],
},
};
31 changes: 31 additions & 0 deletions assets/js/pages/DatabasesOverview/DatabasesOverview.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('DatabasesOverview component', () => {
<DatabasesOverview
databases={[database]}
databaseInstances={database.database_instances}
userAbilities={[{ name: 'all', resource: 'all' }]}
onInstanceCleanUp={mockedCleanUp}
/>
);
Expand All @@ -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(
<DatabasesOverview
databases={[database]}
databaseInstances={database.database_instances}
userAbilities={[]}
/>
);

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', () => {
Expand Down
Loading
Loading