From fb1073b0ae318562962f301d0160739718de9283 Mon Sep 17 00:00:00 2001 From: Alessio Biancalana <alessio.biancalana@suse.com> Date: Wed, 15 Nov 2023 17:06:23 +0100 Subject: [PATCH 1/3] Pass props down to ASCS/ERS component --- .../components/ClusterDetails/ClusterDetailsPage.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assets/js/components/ClusterDetails/ClusterDetailsPage.jsx b/assets/js/components/ClusterDetails/ClusterDetailsPage.jsx index f043c04a6a..de05aad30b 100644 --- a/assets/js/components/ClusterDetails/ClusterDetailsPage.jsx +++ b/assets/js/components/ClusterDetails/ClusterDetailsPage.jsx @@ -91,13 +91,23 @@ export function ClusterDetailsPage() { case 'ascs_ers': return ( <AscsErsClusterDetails + clusterID={clusterID} clusterName={getClusterName(cluster)} + selectedChecks={cluster.selected_checks} + hasSelectedChecks={hasSelectedChecks} cibLastWritten={cluster.cib_last_written} provider={cluster.provider} hosts={clusterHosts} sapSystems={clusterSapSystems} details={cluster.details} catalog={catalog} + lastExecution={lastExecution} + onStartExecution={(_, hostList, checks, navigateFunction) => + dispatch( + executionRequested(clusterID, hostList, checks, navigateFunction) + ) + } + navigate={navigate} /> ); default: From 652eabf20e4759ec7d814b87f29f30be62e826bd Mon Sep 17 00:00:00 2001 From: Alessio Biancalana <alessio.biancalana@suse.com> Date: Wed, 15 Nov 2023 17:07:37 +0100 Subject: [PATCH 2/3] Add buttons for checks selection and navigation --- .../ClusterDetails/AscsErsClusterDetails.jsx | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/assets/js/components/ClusterDetails/AscsErsClusterDetails.jsx b/assets/js/components/ClusterDetails/AscsErsClusterDetails.jsx index 7ee426b07a..b1bf87c231 100644 --- a/assets/js/components/ClusterDetails/AscsErsClusterDetails.jsx +++ b/assets/js/components/ClusterDetails/AscsErsClusterDetails.jsx @@ -1,6 +1,10 @@ import React, { useState, useEffect } from 'react'; import { get } from 'lodash'; +import { EOS_SETTINGS, EOS_CLEAR_ALL, EOS_PLAY_CIRCLE } from 'eos-icons-react'; +import { RUNNING_STATES } from '@state/lastExecutions'; + +import Button from '@components/Button'; import PageHeader from '@components/PageHeader'; import BackButton from '@components/BackButton'; import Table from '@components/Table'; @@ -10,6 +14,7 @@ import DottedPagination from '@components/DottedPagination'; import ClusterNodeLink from '@components/ClusterDetails/ClusterNodeLink'; import SapSystemLink from '@components/SapSystemLink'; import { renderEnsaVersion } from '@components/SapSystemDetails'; +import Tooltip from '@components/Tooltip'; import CheckResultsOverview from '@components/CheckResultsOverview'; @@ -65,13 +70,19 @@ const nodeDetailsConfig = { }; function AscsErsClusterDetails({ + clusterID, clusterName, + selectedChecks, + hasSelectedChecks, cibLastWritten, provider, hosts, sapSystems, details, catalog, + lastExecution, + onStartExecution, + navigate, }) { const [enrichedSapSystems, setEnrichedSapSystems] = useState([]); const [currentSapSystem, setCurrentSapSystem] = useState(null); @@ -91,6 +102,15 @@ function AscsErsClusterDetails({ const catalogLoading = get(catalog, 'loading'); const catalogError = get(catalog, 'error'); + const executionData = get(lastExecution, 'data'); + const executionLoading = get(lastExecution, 'loading', true); + const executionError = get(lastExecution, 'error'); + + const startExecutionDisabled = + executionLoading || + !hasSelectedChecks || + RUNNING_STATES.includes(executionData?.status); + return ( <div> <BackButton url="/clusters">Back to Clusters</BackButton> @@ -101,6 +121,51 @@ function AscsErsClusterDetails({ <span className="font-bold">{clusterName}</span> </PageHeader> </div> + <div className="flex w-1/2 justify-end"> + <div className="flex w-fit whitespace-nowrap"> + <Button + type="primary-white" + className="inline-block mx-0.5 border-green-500 border" + size="small" + onClick={() => navigate(`/clusters/${clusterID}/settings`)} + > + <EOS_SETTINGS className="inline-block fill-jungle-green-500" />{' '} + Check Selection + </Button> + + <Button + type="primary-white" + className="mx-0.5 border-green-500 border" + size="small" + onClick={() => navigate(`/clusters/${clusterID}/executions/last`)} + > + <EOS_CLEAR_ALL className="inline-block fill-jungle-green-500" />{' '} + Show Results + </Button> + + <Tooltip + isEnabled={!hasSelectedChecks} + content="Select some Checks first!" + place="bottom" + > + <Button + type="primary" + className="mx-1" + onClick={() => { + onStartExecution(clusterID, hosts, selectedChecks, navigate); + }} + disabled={startExecutionDisabled} + > + <EOS_PLAY_CIRCLE + className={`${ + !startExecutionDisabled ? 'fill-white' : 'fill-gray-200' + } inline-block align-sub`} + />{' '} + Start Execution + </Button> + </Tooltip> + </div> + </div> </div> <div className="flex xl:flex-row flex-col"> <div className="mt-4 bg-white shadow rounded-lg py-8 px-8 xl:w-2/5 mr-4"> @@ -174,10 +239,15 @@ function AscsErsClusterDetails({ </div> <div className="mt-4 bg-white shadow rounded-lg py-4 xl:w-1/4"> <CheckResultsOverview + data={executionData} catalogDataEmpty={catalogData?.length === 0} - loading={catalogLoading} - error={catalogError} - onCheckClick={() => {}} + loading={catalogLoading || executionLoading} + error={catalogError || executionError} + onCheckClick={(health) => + navigate( + `/clusters/${clusterID}/executions/last?health=${health}` + ) + } /> </div> </div> From 999bc48e71b7edf0dfe9914cfd0e173b97fbfeec Mon Sep 17 00:00:00 2001 From: Alessio Biancalana <alessio.biancalana@suse.com> Date: Wed, 15 Nov 2023 17:08:01 +0100 Subject: [PATCH 3/3] Add tests --- .../AscsErsClusterDetails.test.jsx | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/assets/js/components/ClusterDetails/AscsErsClusterDetails.test.jsx b/assets/js/components/ClusterDetails/AscsErsClusterDetails.test.jsx index d64a881cdb..7481dee862 100644 --- a/assets/js/components/ClusterDetails/AscsErsClusterDetails.test.jsx +++ b/assets/js/components/ClusterDetails/AscsErsClusterDetails.test.jsx @@ -1,5 +1,6 @@ import React from 'react'; +import { faker } from '@faker-js/faker'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; @@ -7,10 +8,13 @@ import '@testing-library/jest-dom'; import { renderWithRouter } from '@lib/test-utils'; import { + hostFactory, buildHostsFromAscsErsClusterDetails, buildSapSystemsFromAscsErsClusterDetails, ascsErsClusterDetailsFactory, clusterFactory, + checksExecutionCompletedFactory, + checksExecutionRunningFactory, } from '@lib/test-utils/factories'; import { providerData } from '@components/ProviderLabel/ProviderLabel'; @@ -275,4 +279,129 @@ describe('ClusterDetails AscsErsClusterDetails component', () => { }); }); }); + + it('should suggest to the user to select some checks if the selection is empty', async () => { + const user = userEvent.setup(); + + const { + clusterID, + clusterName, + cib_last_written: cibLastWritten, + type: clusterType, + sid, + provider, + details, + } = clusterFactory.build({ type: 'ascs_ers' }); + + const hosts = hostFactory.buildList(2, { cluster_id: clusterID }); + + renderWithRouter( + <AscsErsClusterDetails + clusterID={clusterID} + clusterName={clusterName} + selectedChecks={[]} + hasSelectedChecks={false} + hosts={hosts} + clusterType={clusterType} + cibLastWritten={cibLastWritten} + sid={sid} + provider={provider} + sapSystems={[]} + details={details} + lastExecution={null} + /> + ); + + const startExecutionButton = screen.getByText('Start Execution'); + await user.hover(startExecutionButton); + expect(screen.queryByText('Select some Checks first!')).toBeVisible(); + }); + + const executionId = faker.string.uuid(); + + const executionScenarios = [ + { + name: 'Execution is being loaded from wanda', + selectedChecks: ['some'], + hasSelectedChecks: true, + lastExecution: { data: null, loading: true, error: null }, + }, + { + name: 'No checks were selected', + selectedChecks: [], + hasSelectedChecks: false, + lastExecution: { + data: checksExecutionCompletedFactory.build({ + execution_id: executionId, + }), + loading: false, + error: null, + }, + }, + { + name: 'Execution is still running', + selectedChecks: ['A123'], + hasSelectedChecks: true, + lastExecution: { + data: checksExecutionRunningFactory.build({ + execution_id: executionId, + }), + loading: false, + error: null, + }, + }, + { + name: 'Execution has been requested', + selectedChecks: ['A123'], + hasSelectedChecks: true, + lastExecution: { + data: { + execution_id: executionId, + status: 'requested', + }, + loading: false, + error: null, + }, + }, + ]; + + it.each(executionScenarios)( + 'should disable starting a new execution when $name', + ({ selectedChecks, hasSelectedChecks, lastExecution }) => { + const hanaCluster = clusterFactory.build({ + type: 'ascs_ers', + }); + + const { + clusterID, + clusterName, + cib_last_written: cibLastWritten, + type: clusterType, + sid, + provider, + details, + } = hanaCluster; + + const hosts = hostFactory.buildList(2, { cluster_id: clusterID }); + + renderWithRouter( + <AscsErsClusterDetails + clusterID={clusterID} + clusterName={clusterName} + selectedChecks={selectedChecks} + hasSelectedChecks={hasSelectedChecks} + hosts={hosts} + clusterType={clusterType} + cibLastWritten={cibLastWritten} + sid={sid} + provider={provider} + sapSystems={[]} + details={details} + lastExecution={lastExecution} + /> + ); + + expect(screen.getByText('Start Execution')).toBeDisabled(); + } + ); });