diff --git a/awx/ui/src/components/HealthCheckAlert/HealthCheckAlert.js b/awx/ui/src/components/HealthCheckAlert/HealthCheckAlert.js index d10f2c1362..5d3a77be3a 100644 --- a/awx/ui/src/components/HealthCheckAlert/HealthCheckAlert.js +++ b/awx/ui/src/components/HealthCheckAlert/HealthCheckAlert.js @@ -1,7 +1,15 @@ import React from 'react'; import { t } from '@lingui/macro'; -import { Alert, Button, AlertActionCloseButton } from '@patternfly/react-core'; +import { + Alert as PFAlert, + Button, + AlertActionCloseButton, +} from '@patternfly/react-core'; +import styled from 'styled-components'; +const Alert = styled(PFAlert)` + z-index: 1; +`; function HealthCheckAlert({ onSetHealthCheckAlert }) { return ( window.location.reload(false)} + onClick={() => window.location.reload()} >{t`Reload`} } diff --git a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js b/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js index 43d9fe6eae..eb24f1f9aa 100644 --- a/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js +++ b/awx/ui/src/screens/InstanceGroup/InstanceDetails/InstanceDetails.js @@ -28,6 +28,7 @@ import RoutedTabs from 'components/RoutedTabs'; import ContentError from 'components/ContentError'; import ContentLoading from 'components/ContentLoading'; import { Detail, DetailList } from 'components/DetailList'; +import HealthCheckAlert from 'components/HealthCheckAlert'; import StatusLabel from 'components/StatusLabel'; import useRequest, { useDeleteItems, @@ -66,6 +67,7 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { const history = useHistory(); const [healthCheck, setHealthCheck] = useState({}); + const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false); const [forks, setForks] = useState(); const { @@ -79,7 +81,6 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { data: { results }, } = await InstanceGroupsAPI.readInstances(instanceGroup.id); let instanceDetails; - let healthCheckDetails; const isAssociated = results.some( ({ id: instId }) => instId === parseInt(instanceId, 10) ); @@ -92,7 +93,7 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { ]); instanceDetails = details; - healthCheckDetails = healthCheckData; + setHealthCheck(healthCheckData); } else { throw new Error( `This instance is not associated with this instance group` @@ -100,7 +101,6 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { } setBreadcrumb(instanceGroup, instanceDetails); - setHealthCheck(healthCheckDetails); setForks( computeForks( instanceDetails.mem_capacity, @@ -121,8 +121,12 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { request: fetchHealthCheck, } = useRequest( useCallback(async () => { - const { data } = await InstancesAPI.healthCheck(instanceId); + const { status } = await InstancesAPI.healthCheck(instanceId); + const { data } = await InstancesAPI.readHealthCheckDetail(instanceId); setHealthCheck(data); + if (status === 200) { + setShowHealthCheckAlert(true); + } }, [instanceId]) ); @@ -188,6 +192,9 @@ function InstanceDetails({ setBreadcrumb, instanceGroup }) { return ( <> + {showHealthCheckAlert ? ( + + ) : null} ', () => { expect(InstancesAPI.readDetail).not.toBeCalled(); }); - test('Should make request for Health Check', async () => { - InstancesAPI.healthCheck.mockResolvedValue({ - data: { - uuid: '00000000-0000-0000-0000-000000000000', - hostname: 'awx_1', - version: '19.1.0', - last_health_check: '2021-09-15T18:02:07.270664Z', - errors: '', - cpu: 8, - memory: 6232231936, - cpu_capacity: 32, - mem_capacity: 38, - capacity: 38, - }, - }); - InstanceGroupsAPI.readInstances.mockResolvedValue({ - data: { - results: [ - { - id: 1, - }, - { - id: 2, - }, - ], - }, - }); - jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({ - me: { is_superuser: true }, - })); - await act(async () => { - wrapper = mountWithContexts( - {}} - /> - ); - }); - await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0); - expect( - wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled') - ).toBe(false); - await act(async () => { - wrapper.find("Button[ouiaId='health-check-button']").prop('onClick')(); - }); - expect(InstancesAPI.healthCheck).toBeCalledWith(1); - wrapper.update(); - expect( - wrapper.find("Detail[label='Last Health Check']").prop('value') - ).toBe('9/15/2021, 6:02:07 PM'); - }); - test('Should handle api error for health check', async () => { InstancesAPI.healthCheck.mockRejectedValue( new Error({ diff --git a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js b/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js index 332c4319e1..c42cbbda51 100644 --- a/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js +++ b/awx/ui/src/screens/InstanceGroup/Instances/InstanceList.js @@ -23,6 +23,7 @@ import useSelected from 'hooks/useSelected'; import { InstanceGroupsAPI, InstancesAPI } from 'api'; import { getQSConfig, parseQueryString, mergeParams } from 'util/qs'; import HealthCheckButton from 'components/HealthCheckButton/HealthCheckButton'; +import HealthCheckAlert from 'components/HealthCheckAlert'; import InstanceListItem from './InstanceListItem'; const QS_CONFIG = getQSConfig('instance', { @@ -33,6 +34,7 @@ const QS_CONFIG = getQSConfig('instance', { function InstanceList({ instanceGroup }) { const [isModalOpen, setIsModalOpen] = useState(false); + const [showHealthCheckAlert, setShowHealthCheckAlert] = useState(false); const location = useLocation(); const { id: instanceGroupId } = useParams(); @@ -86,9 +88,15 @@ function InstanceList({ instanceGroup }) { isLoading: isHealthCheckLoading, } = useRequest( useCallback(async () => { - await Promise.all(selected.map(({ id }) => InstancesAPI.healthCheck(id))); - fetchInstances(); - }, [selected, fetchInstances]) + const [...response] = await Promise.all( + selected + .filter(({ node_type }) => node_type !== 'hop') + .map(({ id }) => InstancesAPI.healthCheck(id)) + ); + if (response) { + setShowHealthCheckAlert(true); + } + }, [selected]) ); const handleHealthCheck = async () => { @@ -171,6 +179,9 @@ function InstanceList({ instanceGroup }) { return ( <> + {showHealthCheckAlert ? ( + + ) : null} { - const { data } = await InstancesAPI.healthCheck(id); + const { status } = await InstancesAPI.healthCheck(id); + const { data } = await InstancesAPI.readHealthCheckDetail(id); setHealthCheck(data); + if (status === 200) { + setShowHealthCheckAlert(true); + } }, [id]) ); @@ -175,192 +181,197 @@ function InstanceDetail({ setBreadcrumb, isK8s }) { const isHopNode = instance.node_type === 'hop'; return ( - - - - - ) : null - } - /> - - {!isHopNode && ( - <> - - - - - {instanceGroups && ( - ( - - {' '} - - ))} - isEmpty={instanceGroups.length === 0} - /> - )} - - {instance.related?.install_bundle && ( - - - - } - /> - )} - -
{t`CPU ${instance.cpu_capacity}`}
- -
- -
- -
-
{t`RAM ${instance.mem_capacity}`}
- - } - /> - - ) : ( - {t`Unavailable`} - ) - } - /> - - )} - {healthCheck?.errors && ( + <> + {showHealthCheckAlert ? ( + + ) : null} + + + - {healthCheck?.errors} - + instance.node_state ? ( + + ) : null } /> - )} - - {!isHopNode && ( - - {me.is_superuser && isK8s && instance.node_type === 'execution' && ( - + {!isHopNode && ( + <> + + + + + {instanceGroups && ( + ( + + {' '} + + ))} + isEmpty={instanceGroups.length === 0} + /> + )} + + {instance.related?.install_bundle && ( + + + + } + /> + )} + +
{t`CPU ${instance.cpu_capacity}`}
+ +
+ +
+ +
+
{t`RAM ${instance.mem_capacity}`}
+ + } + /> + + ) : ( + {t`Unavailable`} + ) + } + /> + + )} + {healthCheck?.errors && ( + + {healthCheck?.errors} + + } /> )} - - - - -
- )} +
+ {!isHopNode && ( + + {me.is_superuser && isK8s && instance.node_type === 'execution' && ( + + )} + + + + + + )} - {error && ( - - {updateInstanceError - ? t`Failed to update capacity adjustment.` - : t`Failed to disassociate one or more instances.`} - - - )} + {error && ( + + {updateInstanceError + ? t`Failed to update capacity adjustment.` + : t`Failed to disassociate one or more instances.`} + + + )} - {removeError && ( - - {t`Failed to remove one or more instances.`} - - - )} -
+ {removeError && ( + + {t`Failed to remove one or more instances.`} + + + )} +
+ ); } diff --git a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js b/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js index 019b43a3dd..cc038c6624 100644 --- a/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js +++ b/awx/ui/src/screens/Instances/InstanceDetail/InstanceDetail.test.js @@ -165,41 +165,6 @@ describe('', () => { expect(wrapper.find('InstanceToggle').length).toBe(1); }); - test('Should make request for Health Check', async () => { - InstancesAPI.healthCheck.mockResolvedValue({ - data: { - uuid: '00000000-0000-0000-0000-000000000000', - hostname: 'awx_1', - version: '19.1.0', - last_health_check: '2021-09-15T18:02:07.270664Z', - errors: '', - cpu: 8, - memory: 6232231936, - cpu_capacity: 32, - mem_capacity: 38, - capacity: 38, - }, - }); - jest.spyOn(ConfigContext, 'useConfig').mockImplementation(() => ({ - me: { is_superuser: true }, - })); - await act(async () => { - wrapper = mountWithContexts( {}} />); - }); - await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0); - expect( - wrapper.find("Button[ouiaId='health-check-button']").prop('isDisabled') - ).toBe(false); - await act(async () => { - wrapper.find("Button[ouiaId='health-check-button']").prop('onClick')(); - }); - expect(InstancesAPI.healthCheck).toBeCalledWith(1); - wrapper.update(); - expect( - wrapper.find("Detail[label='Last Health Check']").prop('value') - ).toBe('9/15/2021, 6:02:07 PM'); - }); - test('Should handle api error for health check', async () => { InstancesAPI.healthCheck.mockRejectedValue( new Error({ diff --git a/awx/ui/src/screens/Instances/InstanceList/InstanceList.js b/awx/ui/src/screens/Instances/InstanceList/InstanceList.js index 14f4202fc9..fdebb58833 100644 --- a/awx/ui/src/screens/Instances/InstanceList/InstanceList.js +++ b/awx/ui/src/screens/Instances/InstanceList/InstanceList.js @@ -93,10 +93,9 @@ function InstanceList() { if (response) { setShowHealthCheckAlert(true); } - - return response; }, [selected]) ); + const handleHealthCheck = async () => { await fetchHealthCheck(); clearSelected();