-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Uniform Target Checks Selection (#1842)
* Add a generic ChecksSelectionHeader component * Refactor Host and Cluster settings page to use the ChecksSelectionHeader component * Remove unused FailAlert component * Adjust ChecksSelectionHeader stories * Fix start execution enablement detection * Improve boolean naming in ChecksSelectionHeader
- Loading branch information
1 parent
481bef8
commit 9993ec2
Showing
9 changed files
with
467 additions
and
401 deletions.
There are no files selected for viewing
69 changes: 69 additions & 0 deletions
69
assets/js/components/ChecksSelection/ChecksSelectionHeader.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
170
assets/js/components/ChecksSelection/ChecksSelectionHeader.stories.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
142
assets/js/components/ChecksSelection/ChecksSelectionHeader.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = [ | ||
{ | ||
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(); | ||
} | ||
); | ||
}); |
Oops, something went wrong.