-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Checks results coming from Wanda (#987)
* Add a new page for the new checks results visualization * Add new aliases to Storybook config * Add ExecutionResults component * Export some legacy components to be reused in ExecutionResults * Wire up exection results page * Add wanda API client * Refactor utility functions inside checks result module * Add labels to checks results * Add tests to ExecutuionResults * Address the majority of the comments * Cover checksUtils with tests * Filter away expect_same expectations in the checks count * Externalize label computing into a function * Add more tests, use getCheckDescription * Refactor getCheckDescription to use findCheck
- Loading branch information
1 parent
d4bfb3d
commit e0aae7b
Showing
13 changed files
with
666 additions
and
11 deletions.
There are no files selected for viewing
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
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
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
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,83 @@ | ||
import { faker } from '@faker-js/faker'; | ||
import { | ||
checksExecutionFactory, | ||
catalogCheckFactory, | ||
} from '@lib/test-utils/factories'; | ||
|
||
import { | ||
getCheckDescription, | ||
getCheckResults, | ||
getChecks, | ||
getHealth, | ||
getHosts, | ||
} from './checksUtils'; | ||
|
||
describe('checksUtils', () => { | ||
it('getChecksResults returns a list of checks results', () => { | ||
const agentID = faker.datatype.uuid; | ||
const checksExecution = checksExecutionFactory.build({ agentID }); | ||
const checksResult = getCheckResults(checksExecution); | ||
|
||
expect(checksResult[0].agents_check_results[0].agent_id).toBe(agentID); | ||
expect( | ||
checksResult[0].agents_check_results[0].expectation_evaluations.length | ||
).toBe(1); | ||
}); | ||
|
||
it('getChecksResults returns an empty list when there are no checks results', () => { | ||
expect(getCheckResults({})).toStrictEqual([]); | ||
}); | ||
|
||
it('getHosts returns hostnames', () => { | ||
const agentID = faker.datatype.uuid; | ||
const { check_results: checkResults } = checksExecutionFactory.build({ | ||
agentID, | ||
}); | ||
|
||
expect(getHosts(checkResults)).toStrictEqual([agentID]); | ||
}); | ||
|
||
it('getHealth should return health', () => { | ||
const agentID = faker.datatype.uuid(); | ||
const checkID = faker.datatype.uuid(); | ||
const { check_results: checkResults } = checksExecutionFactory.build({ | ||
agentID, | ||
checkID, | ||
}); | ||
const { health, expectations, failedExpectations } = getHealth( | ||
checkResults, | ||
checkID, | ||
agentID | ||
); | ||
|
||
expect(health).toBe('passing'); | ||
expect(expectations).toBe(1); | ||
expect(failedExpectations).toBe(0); | ||
}); | ||
|
||
it('getHealth should return undefined when check is not found', () => { | ||
const agentID = faker.datatype.uuid(); | ||
const { check_results: checkResults } = checksExecutionFactory.build({ | ||
agentID, | ||
}); | ||
const healthInfo = getHealth(checkResults, 'carbonara', agentID); | ||
|
||
expect(healthInfo).toBe(undefined); | ||
}); | ||
|
||
it('getChecks should return a list of the checks', () => { | ||
const checkID = faker.datatype.uuid(); | ||
const { check_results: checkResults } = checksExecutionFactory.build({ | ||
checkID, | ||
}); | ||
|
||
expect(getChecks(checkResults)).toStrictEqual([checkID]); | ||
}); | ||
|
||
it('getDescription should return a check description', () => { | ||
const catalog = catalogCheckFactory.buildList(2); | ||
const [{ id, description }] = catalog; | ||
|
||
expect(getCheckDescription(catalog, id)).toBe(description); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -1,3 +1,24 @@ | ||
import ChecksResults from './ChecksResults'; | ||
import ResultsContainer from './ResultsContainer'; | ||
import HostResultsWrapper from './HostResultsWrapper'; | ||
import CheckResult from './CheckResult'; | ||
import { | ||
getHosts, | ||
getChecks, | ||
getHealth, | ||
getCheckResults, | ||
getCheckDescription, | ||
} from './checksUtils'; | ||
|
||
export { | ||
ResultsContainer, | ||
HostResultsWrapper, | ||
CheckResult, | ||
getHosts, | ||
getChecks, | ||
getHealth, | ||
getCheckResults, | ||
getCheckDescription, | ||
}; | ||
|
||
export default ChecksResults; |
159 changes: 159 additions & 0 deletions
159
assets/js/components/ExecutionResults/ExecutionResults.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,159 @@ | ||
import React, { useEffect, useState } from 'react'; | ||
import classNames from 'classnames'; | ||
|
||
import ReactMarkdown from 'react-markdown'; | ||
import remarkGfm from 'remark-gfm'; | ||
|
||
import { logError } from '@lib/log'; | ||
import { getExecutionResult, getCatalog } from '@lib/api/wanda'; | ||
|
||
import Modal from '@components/Modal'; | ||
import BackButton from '@components/BackButton'; | ||
import WarningBanner from '@components/Banners/WarningBanner'; | ||
import LoadingBox from '@components/LoadingBox'; | ||
import { | ||
ResultsContainer, | ||
HostResultsWrapper, | ||
CheckResult, | ||
getHosts, | ||
getChecks, | ||
getHealth, | ||
getCheckResults, | ||
getCheckDescription, | ||
} from '@components/ChecksResults'; | ||
import { UNKNOWN_PROVIDER } from '@components/ClusterDetails/ClusterSettings'; | ||
|
||
const truncatedClusterNameClasses = | ||
'font-bold truncate w-60 inline-block align-top'; | ||
|
||
const getLabel = (health, expectations, failedExpectations) => | ||
health === 'passing' | ||
? `${expectations}/${expectations} expectations passed` | ||
: `${failedExpectations}/${expectations} failed`; | ||
|
||
const ExecutionResults = ({ | ||
clusterID, | ||
executionID, | ||
clusterName, | ||
cloudProvider, | ||
hostnames = [], | ||
onExecutionFetch = getExecutionResult, | ||
onCatalogFetch = getCatalog, | ||
onCatalogRefresh = () => {}, | ||
}) => { | ||
const [loading, setLoading] = useState(false); | ||
const [executionData, setExecutionData] = useState(null); | ||
const [catalog, setCatalog] = useState(null); | ||
const [selectedCheck, setSelectedCheck] = useState(null); | ||
const [modalOpen, setModalOpen] = useState(false); | ||
|
||
useEffect(() => { | ||
setLoading(true); | ||
Promise.all([onExecutionFetch(executionID), onCatalogFetch()]) | ||
.then( | ||
([{ data: fetchedExecutionData }, { data: fetchedCatalogData }]) => { | ||
setLoading(false); | ||
setExecutionData(fetchedExecutionData); | ||
setCatalog(fetchedCatalogData.items); | ||
} | ||
) | ||
.catch((error) => { | ||
setLoading(false); | ||
logError(error); | ||
}); | ||
}, [onExecutionFetch, onCatalogFetch, setExecutionData, setCatalog]); | ||
|
||
if (loading) { | ||
return <LoadingBox text="Loading checks execution..." />; | ||
} | ||
|
||
if (executionData?.status === 'running') { | ||
return <LoadingBox text="Check execution currently running..." />; | ||
} | ||
|
||
const checkResults = getCheckResults(executionData); | ||
const hosts = getHosts(checkResults); | ||
const checks = getChecks(checkResults); | ||
|
||
return ( | ||
<div> | ||
<Modal | ||
title={getCheckDescription(catalog, selectedCheck)} | ||
open={modalOpen} | ||
onClose={() => setModalOpen(false)} | ||
> | ||
<ReactMarkdown className="markdown" remarkPlugins={[remarkGfm]}> | ||
{getCheckDescription(catalog, selectedCheck)} | ||
</ReactMarkdown> | ||
</Modal> | ||
<BackButton url={`/clusters/${clusterID}`}> | ||
Back to Cluster Details | ||
</BackButton> | ||
<div className="flex mb-4 justify-between"> | ||
<h1 className="text-3xl w-3/5"> | ||
<span className="font-medium">Checks Results for cluster</span>{' '} | ||
<span | ||
className={classNames('font-bold', truncatedClusterNameClasses)} | ||
> | ||
{clusterName} | ||
</span> | ||
</h1> | ||
</div> | ||
{cloudProvider == UNKNOWN_PROVIDER && ( | ||
<WarningBanner> | ||
The following results are valid for on-premise bare metal platforms. | ||
<br /> | ||
If you are running your HANA cluster on a different platform, please | ||
use results with caution | ||
</WarningBanner> | ||
)} | ||
<ResultsContainer | ||
catalogError={false} | ||
clusterID={clusterID} | ||
hasAlreadyChecksResults | ||
selectedChecks={checks} | ||
onCatalogRefresh={onCatalogRefresh} | ||
> | ||
{hosts && | ||
hosts.map((hostID, idx) => ( | ||
<HostResultsWrapper | ||
key={idx} | ||
hostname={hostnames.find(({ id }) => hostID === id)?.hostname} | ||
reachable | ||
unreachableMessage="" | ||
> | ||
{checks.map((checkID) => { | ||
const { health, expectations, failedExpectations } = getHealth( | ||
checkResults, | ||
checkID, | ||
hostID | ||
); | ||
const label = getLabel( | ||
health, | ||
expectations, | ||
failedExpectations | ||
); | ||
|
||
return ( | ||
<CheckResult | ||
key={checkID} | ||
checkId={checkID} | ||
description={getCheckDescription(catalog, checkID)} | ||
executionState={executionData?.status} | ||
health={health} | ||
label={label} | ||
onClick={() => { | ||
setModalOpen(true); | ||
setSelectedCheck(checkID); | ||
}} | ||
/> | ||
); | ||
})} | ||
</HostResultsWrapper> | ||
))} | ||
</ResultsContainer> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ExecutionResults; |
Oops, something went wrong.