resolves advanced search button

This commit is contained in:
Alex Corey
2022-02-01 09:37:43 -05:00
parent 50e8c299c6
commit 14a99a7b9e
17 changed files with 7877 additions and 6922 deletions

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { Plural, t } from '@lingui/macro';
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { useKebabifiedMenu } from 'contexts/Kebabified';
function HealthCheckButton({ isDisabled, onClick, selectedItems }) {
const { isKebabified } = useKebabifiedMenu();
const hopNodeSelected =
selectedItems.filter((instance) => instance.node_type === 'hop').length > 0;
const hasSelectedItems = selectedItems.length > 0;
const buildTooltip = () => {
if (hopNodeSelected) {
return (
<Plural
value={hopNodeSelected}
one="Cannot run health check on a hop node. Deselect the hop node to run a health check."
other="Cannot run health check on hop nodes. Deselect the hop nodes to run health checks."
/>
);
}
return selectedItems.length ? (
<Plural
value={selectedItems.length}
one="Click to run a health check on the selected instance."
other="Click to run a health check on the selected instances."
/>
) : (
t`Select an instance to run a health check.`
);
};
if (isKebabified) {
return (
<Tooltip data-cy="healthCheckTooltip" content={buildTooltip()}>
<DropdownItem
key="approve"
isDisabled={hopNodeSelected || isDisabled || !hasSelectedItems}
component="button"
onClick={onClick}
ouiaId="health-check"
>
{t`Health Check`}
</DropdownItem>
</Tooltip>
);
}
return (
<Tooltip data-cy="healthCheckTooltip" content={buildTooltip()}>
<div>
<Button
isDisabled={hopNodeSelected || isDisabled || !hasSelectedItems}
variant="secondary"
ouiaId="health-check"
onClick={onClick}
>{t`Health Check`}</Button>
</div>
</Tooltip>
);
}
export default HealthCheckButton;

View File

@@ -0,0 +1 @@
export { default } from './HealthCheckButton';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { t } from '@lingui/macro';
import { Plural, t } from '@lingui/macro';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import 'styled-components/macro'; import 'styled-components/macro';
@@ -16,7 +15,6 @@ import DisassociateButton from 'components/DisassociateButton';
import AssociateModal from 'components/AssociateModal'; import AssociateModal from 'components/AssociateModal';
import AlertModal from 'components/AlertModal'; import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import useRequest, { import useRequest, {
useDeleteItems, useDeleteItems,
useDismissableError, useDismissableError,
@@ -24,8 +22,7 @@ import useRequest, {
import useSelected from 'hooks/useSelected'; import useSelected from 'hooks/useSelected';
import { InstanceGroupsAPI, InstancesAPI } from 'api'; import { InstanceGroupsAPI, InstancesAPI } from 'api';
import { getQSConfig, parseQueryString, mergeParams } from 'util/qs'; import { getQSConfig, parseQueryString, mergeParams } from 'util/qs';
import HealthCheckButton from 'components/HealthCheckButton/HealthCheckButton';
import { Button, Tooltip } from '@patternfly/react-core';
import InstanceListItem from './InstanceListItem'; import InstanceListItem from './InstanceListItem';
const QS_CONFIG = getQSConfig('instance', { const QS_CONFIG = getQSConfig('instance', {
@@ -83,11 +80,16 @@ function InstanceList() {
fetchInstances(); fetchInstances();
}, [fetchInstances]); }, [fetchInstances]);
const { error: healthCheckError, request: fetchHealthCheck } = useRequest( const {
error: healthCheckError,
request: fetchHealthCheck,
isLoading: isHealthCheckLoading,
} = useRequest(
useCallback(async () => { useCallback(async () => {
await Promise.all(selected.map(({ id }) => InstancesAPI.healthCheck(id))); await Promise.all(selected.map(({ id }) => InstancesAPI.healthCheck(id)));
fetchInstances(); fetchInstances();
}, [selected, fetchInstances]) clearSelected();
}, [selected, clearSelected, fetchInstances])
); );
const { const {
@@ -168,7 +170,9 @@ function InstanceList() {
<> <>
<PaginatedTable <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading || isDisassociateLoading} hasContentLoading={
isLoading || isDisassociateLoading || isHealthCheckLoading
}
items={instances} items={instances}
itemCount={count} itemCount={count}
pluralizedItemName={t`Instances`} pluralizedItemName={t`Instances`}
@@ -182,6 +186,15 @@ function InstanceList() {
key: 'hostname__icontains', key: 'hostname__icontains',
isDefault: true, isDefault: true,
}, },
{
name: t`Node Type`,
key: `or__node_type`,
options: [
[`control`, t`Control`],
[`execution`, t`Execution`],
[`hybrid`, t`Hybrid`],
],
},
]} ]}
toolbarSortColumns={[ toolbarSortColumns={[
{ {
@@ -216,28 +229,11 @@ function InstanceList() {
itemsToDisassociate={selected} itemsToDisassociate={selected}
modalTitle={t`Disassociate instance from instance group?`} modalTitle={t`Disassociate instance from instance group?`}
/>, />,
<Tooltip <HealthCheckButton
content={ isDisabled={!canAdd}
selected.length ? ( onClick={fetchHealthCheck}
<Plural selectedItems={selected}
value={selected.length} />,
one="Click to run a health check on the selected instance."
other="Click to run a health check on the selected instances."
/>
) : (
t`Select an instance to run a health check.`
)
}
>
<div>
<Button
isDisabled={!canAdd || !selected.length}
variant="secondary"
ouiaId="health-check"
onClick={fetchHealthCheck}
>{t`Health Check`}</Button>
</div>
</Tooltip>,
]} ]}
emptyStateControls={ emptyStateControls={
canAdd ? ( canAdd ? (
@@ -253,9 +249,8 @@ function InstanceList() {
<HeaderRow qsConfig={QS_CONFIG} isExpandable> <HeaderRow qsConfig={QS_CONFIG} isExpandable>
<HeaderCell sortKey="hostname">{t`Name`}</HeaderCell> <HeaderCell sortKey="hostname">{t`Name`}</HeaderCell>
<HeaderCell sortKey="errors">{t`Status`}</HeaderCell> <HeaderCell sortKey="errors">{t`Status`}</HeaderCell>
<HeaderCell>{t`Running Jobs`}</HeaderCell> <HeaderCell sortKey="node_type">{t`Node Type`}</HeaderCell>
<HeaderCell>{t`Total Jobs`}</HeaderCell> <HeaderCell sortKey="capacity_adjustment">{t`Capacity Adjustment`}</HeaderCell>
<HeaderCell>{t`Capacity Adjustment`}</HeaderCell>
<HeaderCell>{t`Used Capacity`}</HeaderCell> <HeaderCell>{t`Used Capacity`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell> <HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow> </HeaderRow>

View File

@@ -61,6 +61,7 @@ function InstanceListItem({
rowIndex, rowIndex,
}) { }) {
const { me = {} } = useConfig(); const { me = {} } = useConfig();
const { id } = useParams();
const [forks, setForks] = useState( const [forks, setForks] = useState(
computeForks( computeForks(
instance.mem_capacity, instance.mem_capacity,
@@ -68,7 +69,6 @@ function InstanceListItem({
instance.capacity_adjustment instance.capacity_adjustment
) )
); );
const { id } = useParams();
const labelId = `check-action-${instance.id}`; const labelId = `check-action-${instance.id}`;
@@ -147,8 +147,7 @@ function InstanceListItem({
<StatusLabel status={instance.errors ? 'error' : 'healthy'} /> <StatusLabel status={instance.errors ? 'error' : 'healthy'} />
</Tooltip> </Tooltip>
</Td> </Td>
<Td dataLabel={t`Running Jobs`}>{instance.jobs_running}</Td> <Td dataLabel={t`Node Type`}>{instance.node_type}</Td>
<Td dataLabel={t`Total Jobs`}>{instance.jobs_total}</Td>
<Td dataLabel={t`Capacity Adjustment`}> <Td dataLabel={t`Capacity Adjustment`}>
<SliderHolder data-cy="slider-holder"> <SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div> <div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
@@ -197,7 +196,8 @@ function InstanceListItem({
<Td colSpan={7}> <Td colSpan={7}>
<ExpandableRowContent> <ExpandableRowContent>
<DetailList> <DetailList>
<Detail label={t`Node Type`} value={instance.node_type} /> <Detail value={instance.jobs_running} label={t`Running Jobs`} />
<Detail value={instance.jobs_total} label={t`Total Jobs`} />
<Detail <Detail
label={t`Policy Type`} label={t`Policy Type`}
value={instance.managed_by_policy ? t`Auto` : t`Manual`} value={instance.managed_by_policy ? t`Auto` : t`Manual`}

View File

@@ -274,9 +274,8 @@ describe('<InstanceListItem/>', () => {
); );
}); });
expect(wrapper.find('InstanceListItem').prop('isExpanded')).toBe(true); expect(wrapper.find('InstanceListItem').prop('isExpanded')).toBe(true);
expect(wrapper.find('Detail[label="Node Type"]').prop('value')).toBe( expect(wrapper.find('Detail[label="Running Jobs"]').prop('value')).toBe(0);
'hybrid' expect(wrapper.find('Detail[label="Total Jobs"]').prop('value')).toBe(68);
);
expect(wrapper.find('Detail[label="Policy Type"]').prop('value')).toBe( expect(wrapper.find('Detail[label="Policy Type"]').prop('value')).toBe(
'Auto' 'Auto'
); );

View File

@@ -149,19 +149,18 @@ function InstanceDetail({ setBreadcrumb }) {
} }
/> />
<Detail label={t`Node Type`} value={instance.node_type} /> <Detail label={t`Node Type`} value={instance.node_type} />
<Detail
label={t`Policy Type`}
value={instance.managed_by_policy ? t`Auto` : t`Manual`}
/>
<Detail label={t`Running Jobs`} value={instance.jobs_running} />
<Detail label={t`Total Jobs`} value={instance.jobs_total} />
{!isHopNode && ( {!isHopNode && (
<> <>
<Detail
label={t`Policy Type`}
value={instance.managed_by_policy ? t`Auto` : t`Manual`}
/>
<Detail label={t`Running Jobs`} value={instance.jobs_running} />
<Detail label={t`Total Jobs`} value={instance.jobs_total} />
<Detail <Detail
label={t`Last Health Check`} label={t`Last Health Check`}
value={formatDateString(healthCheck?.last_health_check)} value={formatDateString(healthCheck?.last_health_check)}
/> />
<Detail <Detail
label={t`Capacity Adjustment`} label={t`Capacity Adjustment`}
value={ value={

View File

@@ -1,8 +1,8 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { t } from '@lingui/macro';
import { Plural, t } from '@lingui/macro';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import 'styled-components/macro'; import 'styled-components/macro';
import { PageSection, Card } from '@patternfly/react-core';
import useExpanded from 'hooks/useExpanded'; import useExpanded from 'hooks/useExpanded';
import DataListToolbar from 'components/DataListToolbar'; import DataListToolbar from 'components/DataListToolbar';
@@ -13,13 +13,11 @@ import PaginatedTable, {
} from 'components/PaginatedTable'; } from 'components/PaginatedTable';
import AlertModal from 'components/AlertModal'; import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import useRequest, { useDismissableError } from 'hooks/useRequest'; import useRequest, { useDismissableError } from 'hooks/useRequest';
import useSelected from 'hooks/useSelected'; import useSelected from 'hooks/useSelected';
import { InstancesAPI } from 'api'; import { InstancesAPI } from 'api';
import { getQSConfig, parseQueryString } from 'util/qs'; import { getQSConfig, parseQueryString } from 'util/qs';
import HealthCheckButton from 'components/HealthCheckButton';
import { Button, Tooltip, PageSection, Card } from '@patternfly/react-core';
import InstanceListItem from './InstanceListItem'; import InstanceListItem from './InstanceListItem';
const QS_CONFIG = getQSConfig('instance', { const QS_CONFIG = getQSConfig('instance', {
@@ -69,7 +67,11 @@ function InstanceList() {
fetchInstances(); fetchInstances();
}, [fetchInstances]); }, [fetchInstances]);
const { error: healthCheckError, request: fetchHealthCheck } = useRequest( const {
error: healthCheckError,
request: fetchHealthCheck,
isLoading: isHealthCheckLoading,
} = useRequest(
useCallback(async () => { useCallback(async () => {
await Promise.all( await Promise.all(
selected selected
@@ -86,38 +88,13 @@ function InstanceList() {
const { expanded, isAllExpanded, handleExpand, expandAll } = const { expanded, isAllExpanded, handleExpand, expandAll } =
useExpanded(instances); useExpanded(instances);
const hopNodeSelected = selected.filter(
(instance) => instance.node_type === 'hop'
).length;
const buildTooltip = () => {
if (hopNodeSelected) {
return (
<Plural
value={hopNodeSelected}
one="Cannot run health check on a hop node. Deselect the hop node to run a health check."
other="Cannot run health check on hop nodes. Deselect the hop nodes to run health checks."
/>
);
}
return selected.length ? (
<Plural
value={selected.length}
one="Click to run a health check on the selected instance."
other="Click to run a health check on the selected instances."
/>
) : (
t`Select an instance to run a health check.`
);
};
return ( return (
<> <>
<PageSection> <PageSection>
<Card> <Card>
<PaginatedTable <PaginatedTable
contentError={contentError} contentError={contentError}
hasContentLoading={isLoading} hasContentLoading={isLoading || isHealthCheckLoading}
items={instances} items={instances}
itemCount={count} itemCount={count}
pluralizedItemName={t`Instances`} pluralizedItemName={t`Instances`}
@@ -157,18 +134,10 @@ function InstanceList() {
onExpandAll={expandAll} onExpandAll={expandAll}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
additionalControls={[ additionalControls={[
<Tooltip ouiaId="healthCheckTooltip" content={buildTooltip()}> <HealthCheckButton
<div> onClick={fetchHealthCheck}
<Button selectedItems={selected}
isDisabled={ />,
!selected.length || Boolean(hopNodeSelected)
}
variant="secondary"
ouiaId="health-check"
onClick={fetchHealthCheck}
>{t`Health Check`}</Button>
</div>
</Tooltip>,
]} ]}
/> />
)} )}
@@ -179,7 +148,7 @@ function InstanceList() {
<HeaderCell sortKey="node_type">{t`Node Type`}</HeaderCell> <HeaderCell sortKey="node_type">{t`Node Type`}</HeaderCell>
<HeaderCell sortKey="capacity_adjustment">{t`Capacity Adjustment`}</HeaderCell> <HeaderCell sortKey="capacity_adjustment">{t`Capacity Adjustment`}</HeaderCell>
<HeaderCell>{t`Used Capacity`}</HeaderCell> <HeaderCell>{t`Used Capacity`}</HeaderCell>
<HeaderCell sortKey="enabled">{t`Actions`}</HeaderCell> <HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow> </HeaderRow>
} }
renderRow={(instance, index) => ( renderRow={(instance, index) => (

View File

@@ -143,7 +143,7 @@ describe('<InstanceList/>', () => {
expect(wrapper.find('InstanceListItem').length).toBe(4); expect(wrapper.find('InstanceListItem').length).toBe(4);
}); });
test('should run health check', async () => { test('Should run health check', async () => {
// Ensures health check button is disabled on mount // Ensures health check button is disabled on mount
expect( expect(
wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled') wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
@@ -168,7 +168,7 @@ describe('<InstanceList/>', () => {
); );
expect(InstancesAPI.healthCheck).toBeCalledTimes(3); expect(InstancesAPI.healthCheck).toBeCalledTimes(3);
}); });
test('should render health check error', async () => { test('Should render health check error', async () => {
InstancesAPI.healthCheck.mockRejectedValue( InstancesAPI.healthCheck.mockRejectedValue(
new Error({ new Error({
response: { response: {
@@ -206,6 +206,8 @@ describe('<InstanceList/>', () => {
expect( expect(
wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled') wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
).toBe(true); ).toBe(true);
expect(wrapper.find('Tooltip[ouiaId="healthCheckTooltip"]').length).toBe(1); expect(wrapper.find('Tooltip[data-cy="healthCheckTooltip"]').length).toBe(
1
);
}); });
}); });

View File

@@ -106,20 +106,25 @@ function InstanceListItem({
); );
debounceUpdateInstance({ capacity_adjustment: roundedValue }); debounceUpdateInstance({ capacity_adjustment: roundedValue });
}; };
const isHopNode = instance.node_type === 'hop';
return ( return (
<> <>
<Tr <Tr
id={`instance-row-${instance.id}`} id={`instance-row-${instance.id}`}
ouiaId={`instance-row-${instance.id}`} ouiaId={`instance-row-${instance.id}`}
> >
<Td {isHopNode ? (
expand={{ <Td />
rowIndex, ) : (
isExpanded, <Td
onToggle: onExpand, expand={{
}} rowIndex,
/> isExpanded,
disabled: isHopNode,
onToggle: onExpand,
}}
/>
)}
<Td <Td
select={{ select={{
rowIndex, rowIndex,
@@ -133,88 +138,92 @@ function InstanceListItem({
<b>{instance.hostname}</b> <b>{instance.hostname}</b>
</Link> </Link>
</Td> </Td>
<Td dataLabel={t`Status`}> {!instance.node_type !== 'hop' && (
<Tooltip <Td dataLabel={t`Status`}>
content={ <Tooltip
<div> content={
{t`Last Health Check`} <div>
&nbsp; {t`Last Health Check`}
{formatDateString(instance.last_health_check)} &nbsp;
</div> {formatDateString(instance.last_health_check)}
}
>
<StatusLabel status={instance.errors ? 'error' : 'healthy'} />
</Tooltip>
</Td>
<Td dataLabel={t`Node Type`}>{instance.node_type}</Td>
{instance.node_type !== 'hop' && (
<Td dataLabel={t`Capacity Adjustment`}>
<SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
<SliderForks data-cy="slider-forks">
<div data-cy="number-forks">
<Plural value={forks} one="# fork" other="# forks" />
</div> </div>
<Slider }
areCustomStepsContinuous >
max={1} <StatusLabel status={instance.errors ? 'error' : 'healthy'} />
min={0} </Tooltip>
step={0.1} </Td>
value={instance.capacity_adjustment} )}
onChange={handleChangeValue} <Td dataLabel={t`Node Type`}>{instance.node_type}</Td>
isDisabled={!me?.is_superuser || !instance.enabled} {!isHopNode && (
data-cy="slider" <>
<Td dataLabel={t`Capacity Adjustment`}>
<SliderHolder data-cy="slider-holder">
<div data-cy="cpu-capacity">{t`CPU ${instance.cpu_capacity}`}</div>
<SliderForks data-cy="slider-forks">
<div data-cy="number-forks">
<Plural value={forks} one="# fork" other="# forks" />
</div>
<Slider
areCustomStepsContinuous
max={1}
min={0}
step={0.1}
value={instance.capacity_adjustment}
onChange={handleChangeValue}
isDisabled={!me?.is_superuser || !instance.enabled}
data-cy="slider"
/>
</SliderForks>
<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div>
</SliderHolder>
</Td>
<Td
dataLabel={t`Instance group used capacity`}
css="--pf-c-table--cell--MinWidth: 175px;"
>
{usedCapacity(instance)}
</Td>
<ActionsTd
dataLabel={t`Actions`}
css="--pf-c-table--cell--Width: 125px"
>
<ActionItem visible>
<InstanceToggle
css="display: inline-flex;"
fetchInstances={fetchInstances}
instance={instance}
/> />
</SliderForks> </ActionItem>
<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div> </ActionsTd>
</SliderHolder> </>
</Td>
)}
{instance.node_type !== 'hop' && (
<Td
dataLabel={t`Instance group used capacity`}
css="--pf-c-table--cell--MinWidth: 175px;"
>
{usedCapacity(instance)}
</Td>
)}
{instance.node_type !== 'hop' && (
<ActionsTd
dataLabel={t`Actions`}
css="--pf-c-table--cell--Width: 125px"
>
<ActionItem visible>
<InstanceToggle
css="display: inline-flex;"
fetchInstances={fetchInstances}
instance={instance}
/>
</ActionItem>
</ActionsTd>
)} )}
</Tr> </Tr>
<Tr {!isHopNode && (
ouiaId={`instance-row-${instance.id}-expanded`} <Tr
isExpanded={isExpanded} ouiaId={`instance-row-${instance.id}-expanded`}
> isExpanded={isExpanded}
<Td colSpan={2} /> >
<Td colSpan={7}> <Td colSpan={2} />
<ExpandableRowContent> <Td colSpan={7}>
<DetailList> <ExpandableRowContent>
<Detail value={instance.jobs_running} label={t`Running Jobs`} /> <DetailList>
<Detail value={instance.jobs_total} label={t`Total Jobs`} /> <Detail value={instance.jobs_running} label={t`Running Jobs`} />
<Detail <Detail value={instance.jobs_total} label={t`Total Jobs`} />
label={t`Policy Type`} <Detail
value={instance.managed_by_policy ? t`Auto` : t`Manual`} label={t`Policy Type`}
/> value={instance.managed_by_policy ? t`Auto` : t`Manual`}
<Detail />
label={t`Last Health Check`} <Detail
value={formatDateString(instance.last_health_check)} label={t`Last Health Check`}
/> value={formatDateString(instance.last_health_check)}
</DetailList> />
</ExpandableRowContent> </DetailList>
</Td> </ExpandableRowContent>
</Tr> </Td>
</Tr>
)}
{updateError && ( {updateError && (
<AlertModal <AlertModal
variant="error" variant="error"

View File

@@ -17,17 +17,14 @@ function Instances() {
} }
setBreadcrumbConfig({ setBreadcrumbConfig({
'/instances': t`Instances`, '/instances': t`Instances`,
[`/instances/${instance.id}`]: t`${instance.hostname}`, [`/instances/${instance.id}`]: `${instance.hostname}`,
[`/instances/${instance.id}/details`]: t`Details`, [`/instances/${instance.id}/details`]: t`Details`,
}); });
}, []); }, []);
return ( return (
<> <>
<ScreenHeader <ScreenHeader streamType="instance" breadcrumbConfig={breadcrumbConfig} />
streamType="instances"
breadcrumbConfig={breadcrumbConfig}
/>
<Switch> <Switch>
<Route path="/instances/:id"> <Route path="/instances/:id">
<Instance setBreadcrumb={buildBreadcrumbConfig} /> <Instance setBreadcrumb={buildBreadcrumbConfig} />