Compare commits

...

1 Commits

Author SHA1 Message Date
Alex Corey
bea8b1a754 Adds an Instance Group component that renders IGs as a PF Label 2022-10-26 13:36:14 -04:00
11 changed files with 72 additions and 155 deletions

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { arrayOf, bool, number, shape, string } from 'prop-types';
import { Label, LabelGroup } from '@patternfly/react-core';
import { Link } from 'react-router-dom';
function InstanceGroupLabels({ labels, isLinkable }) {
const buildLinkURL = (isContainerGroup) =>
isContainerGroup
? '/instance_groups/container_group/'
: '/instance_groups/';
return (
<LabelGroup numLabels={5}>
{labels.map(({ id, name, is_container_group }) =>
isLinkable ? (
<Label
color="blue"
key={id}
render={({ className, content, componentRef }) => (
<Link
className={className}
innerRef={componentRef}
to={`${buildLinkURL(is_container_group)}${id}/details`}
>
{content}
</Link>
)}
>
{name}
</Label>
) : (
<Label color="blue" key={id}>
{name}
</Label>
)
)}
</LabelGroup>
);
}
InstanceGroupLabels.propTypes = {
labels: arrayOf(shape({ id: number.isRequired, name: string.isRequired }))
.isRequired,
isLinkable: bool,
};
InstanceGroupLabels.defaultProps = { isLinkable: false };
export default InstanceGroupLabels;

View File

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

View File

@@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { Chip, Divider, Title } from '@patternfly/react-core'; import { Chip, Divider, Title } from '@patternfly/react-core';
import { toTitleCase } from 'util/strings'; import { toTitleCase } from 'util/strings';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import CredentialChip from '../CredentialChip'; import CredentialChip from '../CredentialChip';
import ChipGroup from '../ChipGroup'; import ChipGroup from '../ChipGroup';
import { DetailList, Detail, UserDateDetail } from '../DetailList'; import { DetailList, Detail, UserDateDetail } from '../DetailList';
@@ -227,21 +228,7 @@ function PromptDetail({
label={t`Instance Groups`} label={t`Instance Groups`}
rows={4} rows={4}
value={ value={
<ChipGroup <InstanceGroupLabels labels={overrides.instance_groups} />
numChips={5}
totalChips={overrides.instance_groups.length}
ouiaId="prompt-instance-groups-chips"
>
{overrides.instance_groups.map((instance_group) => (
<Chip
key={instance_group.id}
ouiaId={`instance-group-${instance_group.id}-chip`}
isReadOnly
>
{instance_group.name}
</Chip>
))}
</ChipGroup>
} }
/> />
)} )}

View File

@@ -10,6 +10,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import { JobTemplatesAPI, SchedulesAPI, WorkflowJobTemplatesAPI } from 'api'; import { JobTemplatesAPI, SchedulesAPI, WorkflowJobTemplatesAPI } from 'api';
import { parseVariableField, jsonToYaml } from 'util/yaml'; import { parseVariableField, jsonToYaml } from 'util/yaml';
import { useConfig } from 'contexts/Config'; import { useConfig } from 'contexts/Config';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import parseRuleObj from '../shared/parseRuleObj'; import parseRuleObj from '../shared/parseRuleObj';
import FrequencyDetails from './FrequencyDetails'; import FrequencyDetails from './FrequencyDetails';
import AlertModal from '../../AlertModal'; import AlertModal from '../../AlertModal';
@@ -27,11 +28,6 @@ import { VariablesDetail } from '../../CodeEditor';
import { VERBOSITY } from '../../VerbositySelectField'; import { VERBOSITY } from '../../VerbositySelectField';
import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext'; import getHelpText from '../../../screens/Template/shared/JobTemplate.helptext';
const buildLinkURL = (instance) =>
instance.is_container_group
? '/instance_groups/container_group/'
: '/instance_groups/';
const PromptDivider = styled(Divider)` const PromptDivider = styled(Divider)`
margin-top: var(--pf-global--spacer--lg); margin-top: var(--pf-global--spacer--lg);
margin-bottom: var(--pf-global--spacer--lg); margin-bottom: var(--pf-global--spacer--lg);
@@ -498,26 +494,7 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
fullWidth fullWidth
label={t`Instance Groups`} label={t`Instance Groups`}
value={ value={
<ChipGroup <InstanceGroupLabels labels={instanceGroups} isLinkable />
numChips={5}
totalChips={instanceGroups.length}
ouiaId="instance-group-chips"
>
{instanceGroups.map((ig) => (
<Link
to={`${buildLinkURL(ig)}${ig.id}/details`}
key={ig.id}
>
<Chip
key={ig.id}
ouiaId={`instance-group-${ig.id}-chip`}
isReadOnly
>
{ig.name}
</Chip>
</Link>
))}
</ChipGroup>
} }
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Link, useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { t, Plural } from '@lingui/macro'; import { t, Plural } from '@lingui/macro';
import { import {
Button, Button,
@@ -11,7 +11,6 @@ import {
CodeBlockCode, CodeBlockCode,
Tooltip, Tooltip,
Slider, Slider,
Label,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { DownloadIcon, OutlinedClockIcon } from '@patternfly/react-icons'; import { DownloadIcon, OutlinedClockIcon } from '@patternfly/react-icons';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -34,6 +33,7 @@ import useRequest, {
useDismissableError, useDismissableError,
} from 'hooks/useRequest'; } from 'hooks/useRequest';
import HealthCheckAlert from 'components/HealthCheckAlert'; import HealthCheckAlert from 'components/HealthCheckAlert';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import RemoveInstanceButton from '../Shared/RemoveInstanceButton'; import RemoveInstanceButton from '../Shared/RemoveInstanceButton';
const Unavailable = styled.span` const Unavailable = styled.span`
@@ -156,11 +156,6 @@ function InstanceDetail({ setBreadcrumb, isK8s }) {
</> </>
); );
const buildLinkURL = (inst) =>
inst.is_container_group
? '/instance_groups/container_group/'
: '/instance_groups/';
const { error, dismissError } = useDismissableError( const { error, dismissError } = useDismissableError(
updateInstanceError || healthCheckError updateInstanceError || healthCheckError
); );
@@ -225,25 +220,9 @@ function InstanceDetail({ setBreadcrumb, isK8s }) {
label={t`Instance Groups`} label={t`Instance Groups`}
dataCy="instance-groups" dataCy="instance-groups"
helpText={t`The Instance Groups to which this instance belongs.`} helpText={t`The Instance Groups to which this instance belongs.`}
value={instanceGroups.map((ig) => ( value={
<React.Fragment key={ig.id}> <InstanceGroupLabels labels={instanceGroups} isLinkable />
<Label }
color="blue"
isTruncated
render={({ className, content, componentRef }) => (
<Link
to={`${buildLinkURL(ig)}${ig.id}/details`}
className={className}
innerRef={componentRef}
>
{content}
</Link>
)}
>
{ig.name}
</Label>{' '}
</React.Fragment>
))}
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />
)} )}

View File

@@ -23,6 +23,7 @@ import { InventoriesAPI } from 'api';
import useRequest, { useDismissableError } from 'hooks/useRequest'; import useRequest, { useDismissableError } from 'hooks/useRequest';
import { Inventory } from 'types'; import { Inventory } from 'types';
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import getHelpText from '../shared/Inventory.helptext'; import getHelpText from '../shared/Inventory.helptext';
function InventoryDetail({ inventory }) { function InventoryDetail({ inventory }) {
@@ -105,23 +106,7 @@ function InventoryDetail({ inventory }) {
<Detail <Detail
fullWidth fullWidth
label={t`Instance Groups`} label={t`Instance Groups`}
value={ value={<InstanceGroupLabels labels={instanceGroups} isLinkable />}
<ChipGroup
numChips={5}
totalChips={instanceGroups?.length}
ouiaId="instance-group-chips"
>
{instanceGroups?.map((ig) => (
<Chip
key={ig.id}
isReadOnly
ouiaId={`instance-group-${ig.id}-chip`}
>
{ig.name}
</Chip>
))}
</ChipGroup>
}
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />
)} )}

View File

@@ -131,9 +131,8 @@ describe('<InventoryDetail />', () => {
expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledWith( expect(InventoriesAPI.readInstanceGroups).toHaveBeenCalledWith(
mockInventory.id mockInventory.id
); );
const chip = wrapper.find('Chip').at(0); const label = wrapper.find('Label').at(0);
expect(chip.prop('isReadOnly')).toEqual(true); expect(label.prop('children')).toEqual('Foo');
expect(chip.prop('children')).toEqual('Foo');
}); });
test('should not load instance groups', async () => { test('should not load instance groups', async () => {

View File

@@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, Chip, Label } from '@patternfly/react-core'; import { Button, Label } from '@patternfly/react-core';
import { Inventory } from 'types'; import { Inventory } from 'types';
import { InventoriesAPI, UnifiedJobsAPI } from 'api'; import { InventoriesAPI, UnifiedJobsAPI } from 'api';
@@ -10,7 +10,6 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import AlertModal from 'components/AlertModal'; import AlertModal from 'components/AlertModal';
import { CardBody, CardActionsRow } from 'components/Card'; import { CardBody, CardActionsRow } from 'components/Card';
import ChipGroup from 'components/ChipGroup';
import { VariablesDetail } from 'components/CodeEditor'; import { VariablesDetail } from 'components/CodeEditor';
import ContentError from 'components/ContentError'; import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading'; import ContentLoading from 'components/ContentLoading';
@@ -18,6 +17,7 @@ import DeleteButton from 'components/DeleteButton';
import { DetailList, Detail, UserDateDetail } from 'components/DetailList'; import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import Sparkline from 'components/Sparkline'; import Sparkline from 'components/Sparkline';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
function SmartInventoryDetail({ inventory }) { function SmartInventoryDetail({ inventory }) {
const history = useHistory(); const history = useHistory();
@@ -120,23 +120,7 @@ function SmartInventoryDetail({ inventory }) {
<Detail <Detail
fullWidth fullWidth
label={t`Instance groups`} label={t`Instance groups`}
value={ value={<InstanceGroupLabels labels={instanceGroups} />}
<ChipGroup
numChips={5}
totalChips={instanceGroups.length}
ouiaId="instance-group-chips"
>
{instanceGroups.map((ig) => (
<Chip
key={ig.id}
isReadOnly
ouiaId={`instance-group-${ig.id}-chip`}
>
{ig.name}
</Chip>
))}
</ChipGroup>
}
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />
<VariablesDetail <VariablesDetail

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState, useCallback } from 'react';
import { Link, useHistory, useRouteMatch } from 'react-router-dom'; import { Link, useHistory, useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, Chip } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';
import { OrganizationsAPI } from 'api'; import { OrganizationsAPI } from 'api';
import { DetailList, Detail, UserDateDetail } from 'components/DetailList'; import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
import { CardBody, CardActionsRow } from 'components/Card'; import { CardBody, CardActionsRow } from 'components/Card';
@@ -16,6 +16,7 @@ import ErrorDetail from 'components/ErrorDetail';
import useRequest, { useDismissableError } from 'hooks/useRequest'; import useRequest, { useDismissableError } from 'hooks/useRequest';
import { useConfig } from 'contexts/Config'; import { useConfig } from 'contexts/Config';
import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail'; import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
function OrganizationDetail({ organization }) { function OrganizationDetail({ organization }) {
@@ -79,11 +80,6 @@ function OrganizationDetail({ organization }) {
return <ContentError error={contentError} />; return <ContentError error={contentError} />;
} }
const buildLinkURL = (instance) =>
instance.is_container_group
? '/instance_groups/container_group/'
: '/instance_groups/';
return ( return (
<CardBody> <CardBody>
<DetailList> <DetailList>
@@ -126,25 +122,7 @@ function OrganizationDetail({ organization }) {
fullWidth fullWidth
label={t`Instance Groups`} label={t`Instance Groups`}
helpText={t`The Instance Groups for this Organization to run on.`} helpText={t`The Instance Groups for this Organization to run on.`}
value={ value={<InstanceGroupLabels labels={instanceGroups} isLinkable />}
<ChipGroup
numChips={5}
totalChips={instanceGroups.length}
ouiaId="instance-group-chips"
>
{instanceGroups.map((ig) => (
<Link to={`${buildLinkURL(ig)}${ig.id}/details`} key={ig.id}>
<Chip
key={ig.id}
isReadOnly
ouiaId={`instance-group-${ig.id}-chip`}
>
{ig.name}
</Chip>
</Link>
))}
</ChipGroup>
}
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />
)} )}

View File

@@ -90,7 +90,7 @@ describe('<OrganizationDetail />', () => {
await waitForElement(component, 'ContentLoading', (el) => el.length === 0); await waitForElement(component, 'ContentLoading', (el) => el.length === 0);
expect( expect(
component component
.find('Chip') .find('Label')
.findWhere((el) => el.text() === 'One') .findWhere((el) => el.text() === 'One')
.exists() .exists()
).toBe(true); ).toBe(true);

View File

@@ -34,6 +34,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import useBrandName from 'hooks/useBrandName'; import useBrandName from 'hooks/useBrandName';
import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail'; import ExecutionEnvironmentDetail from 'components/ExecutionEnvironmentDetail';
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
import InstanceGroupLabels from 'components/InstanceGroupLabels';
import getHelpText from '../shared/JobTemplate.helptext'; import getHelpText from '../shared/JobTemplate.helptext';
function JobTemplateDetail({ template }) { function JobTemplateDetail({ template }) {
@@ -167,11 +168,6 @@ function JobTemplateDetail({ template }) {
); );
}; };
const buildLinkURL = (instance) =>
instance.is_container_group
? '/instance_groups/container_group/'
: '/instance_groups/';
if (instanceGroupsError) { if (instanceGroupsError) {
return <ContentError error={instanceGroupsError} />; return <ContentError error={instanceGroupsError} />;
} }
@@ -422,25 +418,7 @@ function JobTemplateDetail({ template }) {
label={t`Instance Groups`} label={t`Instance Groups`}
dataCy="jt-detail-instance-groups" dataCy="jt-detail-instance-groups"
helpText={helpText.instanceGroups} helpText={helpText.instanceGroups}
value={ value={<InstanceGroupLabels labels={instanceGroups} isLinkable />}
<ChipGroup
numChips={5}
totalChips={instanceGroups.length}
ouiaId="instance-group-chips"
>
{instanceGroups.map((ig) => (
<Link to={`${buildLinkURL(ig)}${ig.id}/details`} key={ig.id}>
<Chip
key={ig.id}
ouiaId={`instance-group-${ig.id}-chip`}
isReadOnly
>
{ig.name}
</Chip>
</Link>
))}
</ChipGroup>
}
isEmpty={instanceGroups.length === 0} isEmpty={instanceGroups.length === 0}
/> />
{job_tags && ( {job_tags && (