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

View File

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

View File

@ -149,19 +149,18 @@ function InstanceDetail({ setBreadcrumb }) {
}
/>
<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 && (
<>
<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
label={t`Last Health Check`}
value={formatDateString(healthCheck?.last_health_check)}
/>
<Detail
label={t`Capacity Adjustment`}
value={

View File

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

View File

@ -143,7 +143,7 @@ describe('<InstanceList/>', () => {
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
expect(
wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
@ -168,7 +168,7 @@ describe('<InstanceList/>', () => {
);
expect(InstancesAPI.healthCheck).toBeCalledTimes(3);
});
test('should render health check error', async () => {
test('Should render health check error', async () => {
InstancesAPI.healthCheck.mockRejectedValue(
new Error({
response: {
@ -206,6 +206,8 @@ describe('<InstanceList/>', () => {
expect(
wrapper.find('Button[ouiaId="health-check"]').prop('isDisabled')
).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 });
};
const isHopNode = instance.node_type === 'hop';
return (
<>
<Tr
id={`instance-row-${instance.id}`}
ouiaId={`instance-row-${instance.id}`}
>
<Td
expand={{
rowIndex,
isExpanded,
onToggle: onExpand,
}}
/>
{isHopNode ? (
<Td />
) : (
<Td
expand={{
rowIndex,
isExpanded,
disabled: isHopNode,
onToggle: onExpand,
}}
/>
)}
<Td
select={{
rowIndex,
@ -133,88 +138,92 @@ function InstanceListItem({
<b>{instance.hostname}</b>
</Link>
</Td>
<Td dataLabel={t`Status`}>
<Tooltip
content={
<div>
{t`Last Health Check`}
&nbsp;
{formatDateString(instance.last_health_check)}
</div>
}
>
<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" />
{!instance.node_type !== 'hop' && (
<Td dataLabel={t`Status`}>
<Tooltip
content={
<div>
{t`Last Health Check`}
&nbsp;
{formatDateString(instance.last_health_check)}
</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"
}
>
<StatusLabel status={instance.errors ? 'error' : 'healthy'} />
</Tooltip>
</Td>
)}
<Td dataLabel={t`Node Type`}>{instance.node_type}</Td>
{!isHopNode && (
<>
<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>
<div data-cy="mem-capacity">{t`RAM ${instance.mem_capacity}`}</div>
</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>
</ActionItem>
</ActionsTd>
</>
)}
</Tr>
<Tr
ouiaId={`instance-row-${instance.id}-expanded`}
isExpanded={isExpanded}
>
<Td colSpan={2} />
<Td colSpan={7}>
<ExpandableRowContent>
<DetailList>
<Detail value={instance.jobs_running} label={t`Running Jobs`} />
<Detail value={instance.jobs_total} label={t`Total Jobs`} />
<Detail
label={t`Policy Type`}
value={instance.managed_by_policy ? t`Auto` : t`Manual`}
/>
<Detail
label={t`Last Health Check`}
value={formatDateString(instance.last_health_check)}
/>
</DetailList>
</ExpandableRowContent>
</Td>
</Tr>
{!isHopNode && (
<Tr
ouiaId={`instance-row-${instance.id}-expanded`}
isExpanded={isExpanded}
>
<Td colSpan={2} />
<Td colSpan={7}>
<ExpandableRowContent>
<DetailList>
<Detail value={instance.jobs_running} label={t`Running Jobs`} />
<Detail value={instance.jobs_total} label={t`Total Jobs`} />
<Detail
label={t`Policy Type`}
value={instance.managed_by_policy ? t`Auto` : t`Manual`}
/>
<Detail
label={t`Last Health Check`}
value={formatDateString(instance.last_health_check)}
/>
</DetailList>
</ExpandableRowContent>
</Td>
</Tr>
)}
{updateError && (
<AlertModal
variant="error"

View File

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