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

Uniform Target Checks Selection #1842

Merged
merged 6 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
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
69 changes: 69 additions & 0 deletions assets/js/components/ChecksSelection/ChecksSelectionHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import { EOS_PLAY_CIRCLE } from 'eos-icons-react';
import classNames from 'classnames';

import Button from '@components/Button';
import Tooltip from '@components/Tooltip';

import { canStartExecution } from '@components/ChecksSelection';

function ChecksSelectionHeader({
targetID,
targetName,
backTo,
pageHeader,
isSavingSelection,
savedSelection,
selection,
onSaveSelection = () => {},
onStartExecution = () => {},
}) {
const isAbleToStartExecution = canStartExecution(
savedSelection,
isSavingSelection
);
return (
<div className="w-full px-2 sm:px-0">
{backTo}
<div className="flex flex-wrap">
<div className="flex w-1/2 h-auto overflow-hidden overflow-ellipsis break-words">
{pageHeader}
</div>
<div className="flex w-1/2 justify-end">
<div className="flex w-fit whitespace-nowrap">
<Button
type="primary"
className="mx-1"
onClick={() => onSaveSelection(selection, targetID, targetName)}
disabled={isSavingSelection}
>
Save Checks Selection
</Button>
<Tooltip
className="w-56"
content="Click Start Execution or wait for Trento to periodically run checks."
visible={isAbleToStartExecution}
>
<Button
type="primary"
className="mx-1"
onClick={onStartExecution}
disabled={!isAbleToStartExecution}
>
<EOS_PLAY_CIRCLE
className={classNames('inline-block align-sub', {
'fill-white': isAbleToStartExecution,
'fill-gray-200': !isAbleToStartExecution,
})}
/>{' '}
Start Execution
</Button>
</Tooltip>
</div>
</div>
</div>
</div>
);
}

export default ChecksSelectionHeader;
170 changes: 170 additions & 0 deletions assets/js/components/ChecksSelection/ChecksSelectionHeader.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import React from 'react';
import { faker } from '@faker-js/faker';
import { MemoryRouter } from 'react-router-dom';

import PageHeader from '@components/PageHeader';
import BackButton from '@components/BackButton';
import ChecksSelectionHeader from './ChecksSelectionHeader';

export default {
title: 'ChecksSelectionHeader',
component: ChecksSelectionHeader,
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
],
argTypes: {
targetID: {
control: 'string',
description: 'The target identifier',
table: {
type: { summary: 'string' },
},
},
targetName: {
control: 'string',
description: 'The target name',
table: {
type: { summary: 'string' },
},
},
backTo: {
description:
'A Component that renders the back button to the target details',
},
pageHeader: {
description:
'A Component that renders the page header for the specific target',
},
selection: {
control: 'array',
description: 'The check selection currently displayed',
},
savedSelection: {
control: 'array',
description: 'The last saved check selection for the target',
},
isSavingSelection: {
control: 'boolean',
description:
'Whether Save Checks Selection button is enabled or disabled',
table: {
type: { summary: 'boolean' },
},
},
onSaveSelection: {
description: 'Updates the selected checks on save',
table: {
type: { summary: 'function' },
},
},
onStartExecution: {
description: 'Starts the host checks execution',
table: {
type: { summary: 'function' },
},
},
},
};

const targetID = faker.datatype.uuid();
const targetName = faker.lorem.word(7);
const selection = [faker.datatype.uuid()];
const savedSelection = [faker.datatype.uuid()];

export const Default = {
args: {
targetID,
targetName,
backTo: (
<BackButton url={`/target/${targetID}`}>
Back to Target Details
</BackButton>
),
pageHeader: (
<PageHeader>
Target Settings for <span className="font-bold">{targetName}</span>
</PageHeader>
),
selection,
savedSelection,
isSavingSelection: false,
},
};

export const ClusterChecksSelection = {
args: {
targetID,
targetName,
backTo: (
<BackButton url={`/clusters/${targetID}`}>
Back to Cluster Details
</BackButton>
),
pageHeader: (
<PageHeader>
Cluster Settings for <span className="font-bold">{targetName}</span>
</PageHeader>
),
selection,
savedSelection,
isSavingSelection: false,
},
};

export const HostChecksSelection = {
args: {
targetID,
targetName,
backTo: (
<BackButton url={`/hosts/${targetID}`}>Back to Host Details</BackButton>
),
pageHeader: (
<PageHeader>
Check Settings for <span className="font-bold">{targetName}</span>
</PageHeader>
),
selection,
savedSelection,
isSavingSelection: false,
},
};

export const SavedSelectionDisabled = {
args: {
targetID,
targetName,
backTo: (
<BackButton url={`/hosts/${targetID}`}>Back to Host Details</BackButton>
),
pageHeader: (
<PageHeader>
Check Settings for <span className="font-bold">{targetName}</span>
</PageHeader>
),
selection,
savedSelection,
isSavingSelection: true,
},
};

export const CannotStartExecution = {
args: {
targetID,
targetName,
backTo: (
<BackButton url={`/hosts/${targetID}`}>Back to Host Details</BackButton>
),
pageHeader: (
<PageHeader>
Check Settings for <span className="font-bold">{targetName}</span>
</PageHeader>
),
selection,
savedSelection: [],
isSavingSelection: false,
},
};
142 changes: 142 additions & 0 deletions assets/js/components/ChecksSelection/ChecksSelectionHeader.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React from 'react';

import { screen } from '@testing-library/react';
import '@testing-library/jest-dom';

import { faker } from '@faker-js/faker';

import userEvent from '@testing-library/user-event';
import ChecksSelectionHeader from './ChecksSelectionHeader';
import { renderWithRouter } from '../../lib/test-utils';

describe('ChecksSelectionHeader component', () => {
it('should render a target checks selection header', async () => {
const user = userEvent.setup();

const targetID = faker.datatype.uuid();
const targetName = faker.lorem.word();
const selection = [faker.datatype.uuid(), faker.datatype.uuid()];
const savedSelection = [faker.datatype.uuid()];
const onSaveSelection = jest.fn();
const onStartExecution = jest.fn();

renderWithRouter(
<ChecksSelectionHeader
targetID={targetID}
targetName={targetName}
backTo={<button type="button">Back to Target Details</button>}
pageHeader={<div>Target Check Settings</div>}
isSavingSelection={false}
selection={selection}
savedSelection={savedSelection}
onSaveSelection={onSaveSelection}
onStartExecution={onStartExecution}
/>
);

expect(screen.getByText('Target Check Settings')).toBeVisible();
expect(
screen.getByRole('button', { name: 'Back to Target Details' })
).toBeVisible();
expect(
screen.getByRole('button', { name: 'Save Checks Selection' })
).toBeVisible();
expect(
screen.getByRole('button', { name: 'Start Execution' })
).toBeVisible();
expect(
screen.queryByText(
'Click Start Execution or wait for Trento to periodically run checks.'
)
).toBeVisible();

// Saving a selection
await user.click(screen.getByText('Save Checks Selection'));

expect(onSaveSelection).toHaveBeenCalledWith(
selection,
targetID,
targetName
);

// Starting an execution
await user.click(screen.getByText('Start Execution'));

expect(onStartExecution).toHaveBeenCalled();
});

it('should not allow saving a selection', async () => {
const user = userEvent.setup();

const targetID = faker.datatype.uuid();
const targetName = faker.lorem.word();
const selection = [faker.datatype.uuid(), faker.datatype.uuid()];
const onSaveSelection = jest.fn();

renderWithRouter(
<ChecksSelectionHeader
targetID={targetID}
targetName={targetName}
backTo={<button type="button">Back to Target Details</button>}
pageHeader={<div>Target Check Settings</div>}
isSavingSelection
selection={selection}
savedSelection={selection}
onSaveSelection={onSaveSelection}
onStartExecution={() => {}}
/>
);

expect(screen.getByText('Save Checks Selection')).toBeDisabled();

await user.click(screen.getByText('Save Checks Selection'));

expect(onSaveSelection).not.toHaveBeenCalled();
});

const executionDisallowedScenarios = [
nelsonkopliku marked this conversation as resolved.
Show resolved Hide resolved
{
savedSelection: [],
isSavingSelection: false,
},
{
savedSelection: [faker.datatype.uuid()],
isSavingSelection: true,
},
{
savedSelection: [],
isSavingSelection: true,
},
];

it.each(executionDisallowedScenarios)(
'should not allow starting an execution',
async ({ savedSelection, isSavingSelection }) => {
const user = userEvent.setup();

const targetID = faker.datatype.uuid();
const targetName = faker.lorem.word();
const onStartExecution = jest.fn();

renderWithRouter(
<ChecksSelectionHeader
targetID={targetID}
targetName={targetName}
backTo={<button type="button">Back to Target Details</button>}
pageHeader={<div>Target Check Settings</div>}
isSavingSelection={isSavingSelection}
selection={savedSelection}
savedSelection={savedSelection}
onSaveSelection={() => {}}
onStartExecution={onStartExecution}
/>
);

expect(screen.getByText('Start Execution')).toBeDisabled();

await user.click(screen.getByText('Start Execution'));

expect(onStartExecution).not.toHaveBeenCalled();
}
);
});
Loading