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

Add permissions to the Settings View #2760

Merged
merged 20 commits into from
Jul 11, 2024
Merged
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
19 changes: 15 additions & 4 deletions assets/js/common/ActivityLogsConfig/ActivityLogsConfig.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { noop } from 'lodash';

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

const activityLogsSettingsPermittedFor = ['all:activity_logs_settings'];
/**
* Simplistic utility to transform a time interval in a readable string
*/
@@ -29,15 +31,24 @@ function TimeLabel({ time }) {
* object that defines a retention time interval. `unit` is one of `day`, `week`, `month`, `year`.'
* @param {function} props.onEditClick Handles the edit button click
*/
function ActivityLogsConfig({ retentionTime, onEditClick = noop }) {
function ActivityLogsConfig({
retentionTime,
onEditClick = noop,
userAbilities,
}) {
return (
<div className="container max-w-7xl mx-auto p-4 sm:p-6 lg:p-8 bg-white dark:bg-gray-800 rounded-lg">
<div>
<h2 className="text-2xl font-bold inline-block">Activity Logs</h2>
<span className="float-right">
<Button type="primary-white-fit" size="small" onClick={onEditClick}>
Edit Settings
</Button>
<DisabledGuard
userAbilities={userAbilities}
permitted={activityLogsSettingsPermittedFor}
>
<Button type="primary-white-fit" size="small" onClick={onEditClick}>
Edit Settings
</Button>
</DisabledGuard>
</span>
</div>
<p className="mt-3 mb-3 text-gray-500">
15 changes: 13 additions & 2 deletions assets/js/common/ActivityLogsConfig/ActivityLogsConfig.stories.jsx
Original file line number Diff line number Diff line change
@@ -16,26 +16,37 @@ export default {
description: 'Callback when the edit button is clicked',
control: { type: 'function' },
},
userAbilities: {
description: 'Users abilities that allow editing activity logs settings',
control: 'array',
},
},
};

export const Default = {
args: {
retentionTime: { value: 1, unit: 'month' },
onEditClick: action('edit clicked'),
userAbilities: [{ name: 'all', resource: 'all' }],
},
};

export const WithPlurals = {
args: {
...Default.args,
retentionTime: { value: 2, unit: 'month' },
onEditClick: action('edit clicked'),
},
};

export const WithInvalidValue = {
args: {
...Default.args,
retentionTime: { is: 'invalid' },
onEditClick: action('edit clicked'),
},
};
export const EditUnauthorized = {
args: {
...Default.args,
userAbilities: [],
},
};
34 changes: 32 additions & 2 deletions assets/js/common/ActivityLogsConfig/ActivityLogsConfig.test.jsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,8 @@ import '@testing-library/jest-dom';

import ActivityLogsConfig from '.';

const adminAbilities = [{ name: 'all', resource: 'all' }];

describe('ActivityLogsConfig', () => {
it.each`
time | expected
@@ -18,7 +20,12 @@ describe('ActivityLogsConfig', () => {
`(
'should render `$expected` with retention time `$time`',
({ time, expected }) => {
render(<ActivityLogsConfig retentionTime={time} />);
render(
<ActivityLogsConfig
retentionTime={time}
userAbilities={adminAbilities}
/>
);

expect(screen.getByText(expected)).toBeInTheDocument();
}
@@ -28,10 +35,33 @@ describe('ActivityLogsConfig', () => {
const spy = jest.fn();
const user = userEvent.setup();

render(<ActivityLogsConfig onEditClick={spy} />);
render(
<ActivityLogsConfig onEditClick={spy} userAbilities={adminAbilities} />
);

await user.click(screen.getByRole('button', { name: 'Edit Settings' }));

expect(spy).toHaveBeenCalledTimes(1);
});

it('should render with a disabled Edit Settings button as the user does not the right permissions', async () => {
const user = userEvent.setup();
const spy = jest.fn();
const userWithoutPermission = [];

render(
<ActivityLogsConfig
onEditClick={spy}
userAbilities={userWithoutPermission}
/>
);

expect(screen.getByText('Edit Settings')).toBeDisabled();
await user.click(screen.getByText('Edit Settings'));
expect(spy).not.toHaveBeenCalled();
await user.hover(screen.getByText('Edit Settings'));
expect(
screen.queryByText('You are not authorized for this action')
).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -19,17 +19,21 @@ export default {
type: 'boolean',
},
},
onGenerate: {
action: 'Generate key',
description: 'New key is generated',
},
onClose: {
action: 'Cancel key',
description: 'Closes the modal',
},
generatedApiKeyExpiration: {
description:
'The new generated api key expiration expressed in ISO8601 Timestamp',
},
generatedApiKey: {
description: 'The new generated api key',
},
onGenerate: {
action: 'Generate key',
description: 'New key is generated',
},
},
args: {
loading: false,
Original file line number Diff line number Diff line change
@@ -21,7 +21,6 @@ describe('ApiKeySettingsModal', () => {
describe('Generation form', () => {
it('render the generation form', async () => {
const user = userEvent.setup();

await act(async () => {
render(<ApiKeySettingsModal open />);
});
42 changes: 28 additions & 14 deletions assets/js/common/SuseManagerConfig/SuseManagerConfig.jsx
Original file line number Diff line number Diff line change
@@ -3,9 +3,12 @@ import { defaultTo, noop } from 'lodash';

import Button from '@common/Button';

import DisabledGuard from '@common/DisabledGuard';
import SuseManagerClearSettingsModal from '@common/SuseManagerClearSettingsDialog';
import CertificateUploadDate from './CertificateUploadDate';

const sumaSettingsPermittedFor = ['all:suma_settings'];

function SuseManagerConfig({
url = 'https://',
username,
@@ -17,6 +20,7 @@ function SuseManagerConfig({
onClearSettings = noop,
onTestConnection = noop,
onCancel = noop,
userAbilities,
}) {
return (
<>
@@ -41,22 +45,32 @@ function SuseManagerConfig({
>
Test Connection
</Button>
<Button
className="mr-2"
type="primary-white-fit"
size="small"
onClick={onEditClick}
<DisabledGuard
userAbilities={userAbilities}
permitted={sumaSettingsPermittedFor}
>
Edit Settings
</Button>
<Button
aria-label="clear-suma-settings"
type="danger"
size="small"
onClick={onClearClick}
<Button
className="mr-2"
type="primary-white-fit"
size="small"
onClick={onEditClick}
>
Edit Settings
</Button>
</DisabledGuard>
<DisabledGuard
userAbilities={userAbilities}
permitted={sumaSettingsPermittedFor}
>
Clear Settings
</Button>
<Button
aria-label="clear-suma-settings"
type="danger"
size="small"
onClick={onClearClick}
>
Clear Settings
</Button>
</DisabledGuard>
</span>
</div>
<p className="mt-3 mb-3 text-gray-500">
18 changes: 17 additions & 1 deletion assets/js/common/SuseManagerConfig/SuseManagerConfig.stories.jsx
Original file line number Diff line number Diff line change
@@ -10,6 +10,10 @@ export default {
type: 'text',
},
},
userAbilities: {
description: 'Users abilities that allow changing SUSE Manager settings',
control: 'array',
},
url: {
description: 'SUSE Manager URL',
control: {
@@ -67,6 +71,7 @@ export const Default = {
url: 'https://trento-project.io/suse-manager',
username: 'trentoAdm',
certUploadDate: '2024-01-29T08:41:47.291734Z',
userAbilities: [{ name: 'all', resource: 'all' }],
},
};

@@ -77,4 +82,15 @@ export const WithVeryLongSUMAUrl = {
},
};

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

export const EditUnauthorized = {
args: {
...Default.args,
userAbilities: [],
},
};
36 changes: 35 additions & 1 deletion assets/js/common/SuseManagerConfig/SuseManagerConfig.test.jsx
Original file line number Diff line number Diff line change
@@ -6,9 +6,11 @@ import '@testing-library/jest-dom';

import SuseManagerConfig from '.';

const adminUser = [{ name: 'all', resource: 'all' }];

describe('SuseManagerConfig', () => {
it('renders a default state', () => {
render(<SuseManagerConfig />);
render(<SuseManagerConfig userAbilities={adminUser} />);

expect(screen.getByText('https://')).toBeInTheDocument();
expect(screen.getAllByText('-')).toHaveLength(1);
@@ -30,6 +32,7 @@ describe('SuseManagerConfig', () => {
username={username}
certUploadDate={certUploadDate}
onEditClick={onEditClick}
userAbilities={adminUser}
/>
);

@@ -55,6 +58,7 @@ describe('SuseManagerConfig', () => {
certUploadDate={faker.date.anytime()}
testConnectionEnabled
onTestConnection={onTestConnection}
userAbilities={adminUser}
/>
);
expect(screen.getByLabelText('test-suma-connection')).toBeEnabled();
@@ -63,4 +67,34 @@ describe('SuseManagerConfig', () => {
await user.click(testConnectionButton);
expect(onTestConnection).toHaveBeenCalled();
});

it('renders default state without ability to edit or clear settings', async () => {
const userWithoutPermission = [];
const user = userEvent.setup();
const onEditClick = jest.fn();
const onClearClick = jest.fn();
render(
<SuseManagerConfig
userAbilities={userWithoutPermission}
onEditClick={onEditClick}
onClearClick={onClearClick}
/>
);

expect(screen.getByText('Edit Settings')).toBeDisabled();
await user.click(screen.getByText('Edit Settings'));
expect(onEditClick).not.toHaveBeenCalled();
await user.hover(screen.getByText('Edit Settings'));
expect(
screen.queryAllByText('You are not authorized for this action')[0]
).toBeVisible();

expect(screen.getByText('Clear Settings')).toBeDisabled();
await user.click(screen.getByText('Clear Settings'));
expect(onClearClick).not.toHaveBeenCalled();
await user.hover(screen.getByText('Clear Settings'));
expect(
screen.queryAllByText('You are not authorized for this action')[1]
).toBeVisible();
});
});
Loading
Loading