Add wf node list item info popover (#11587)

This commit is contained in:
Marliana Lara 2022-02-01 11:10:24 -05:00 committed by GitHub
parent ab3de5898d
commit 30d1d63813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 3 deletions

View File

@ -3,6 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { t } from '@lingui/macro';
import { Td, Tr } from '@patternfly/react-table';
import { ActionsTd } from 'components/PaginatedTable';
const CheckboxListItem = ({
isRadio = false,
@ -15,6 +16,7 @@ const CheckboxListItem = ({
onSelect,
columns,
item,
rowActions,
}) => {
const handleRowClick = () => {
if (isSelected && !isRadio) {
@ -62,6 +64,16 @@ const CheckboxListItem = ({
<b>{label}</b>
</Td>
)}
{rowActions && (
<ActionsTd>
{rowActions.map((rowAction) => {
const {
props: { id },
} = rowAction;
return <React.Fragment key={id}>{rowAction}</React.Fragment>;
})}
</ActionsTd>
)}
</Tr>
);
};

View File

@ -21,4 +21,33 @@ describe('CheckboxListItem', () => {
);
expect(wrapper).toHaveLength(1);
});
test('should render row actions', () => {
const wrapper = mount(
<table>
<tbody>
<CheckboxListItem
itemId={1}
name="Buzz"
label="Buzz"
isSelected={false}
onSelect={() => {}}
onDeselect={() => {}}
rowActions={[
<div id="1">action_1</div>,
<div id="2">action_2</div>,
]}
/>
</tbody>
</table>
);
expect(
wrapper
.find('ActionsTd')
.containsAllMatchingElements([
<div id="1">action_1</div>,
<div id="2">action_2</div>,
])
).toEqual(true);
});
});

View File

@ -2,7 +2,7 @@ import React from 'react';
import { TextList, TextListVariants } from '@patternfly/react-core';
import styled from 'styled-components';
const DetailList = ({ children, stacked, ...props }) => (
const DetailList = ({ children, stacked, compact, ...props }) => (
<TextList component={TextListVariants.dl} {...props}>
{children}
</TextList>
@ -10,8 +10,8 @@ const DetailList = ({ children, stacked, ...props }) => (
export default styled(DetailList)`
display: grid;
grid-gap: 20px;
align-items: start;
${(props) => (props.compact ? `column-gap: 20px;` : `grid-gap: 20px;`)}
${(props) =>
props.stacked
? `

View File

@ -2,13 +2,19 @@ import React, { useEffect, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Popover } from '@patternfly/react-core';
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
import { func, shape } from 'prop-types';
import { JobTemplatesAPI } from 'api';
import { getQSConfig, parseQueryString } from 'util/qs';
import useRequest from 'hooks/useRequest';
import DataListToolbar from 'components/DataListToolbar';
import CheckboxListItem from 'components/CheckboxListItem';
import ChipGroup from 'components/ChipGroup';
import CredentialChip from 'components/CredentialChip';
import DataListToolbar from 'components/DataListToolbar';
import { Detail, DetailList } from 'components/DetailList';
import PaginatedTable, {
ActionItem,
HeaderCell,
HeaderRow,
getSearchableKeys,
@ -20,6 +26,52 @@ const QS_CONFIG = getQSConfig('job-templates', {
order_by: 'name',
});
function TemplatePopoverContent({ template }) {
return (
<DetailList compact stacked>
<Detail
label={t`Inventory`}
value={template.summary_fields?.inventory?.name}
dataCy={`template-${template.id}-inventory`}
/>
<Detail
label={t`Project`}
value={template.summary_fields?.project?.name}
dataCy={`template-${template.id}-project`}
/>
<Detail
label={t`Playbook`}
value={template?.playbook}
dataCy={`template-${template.id}-playbook`}
/>
{template.summary_fields?.credentials &&
template.summary_fields.credentials.length ? (
<Detail
fullWidth
label={t`Credentials`}
dataCy={`template-${template.id}-credentials`}
value={
<ChipGroup
numChips={5}
totalChips={template.summary_fields.credentials.length}
ouiaId={`template-${template.id}-credential-chips`}
>
{template.summary_fields.credentials.map((c) => (
<CredentialChip
key={c.id}
credential={c}
isReadOnly
ouiaId={`credential-${c.id}-chip`}
/>
))}
</ChipGroup>
}
/>
) : null}
</DetailList>
);
}
function JobTemplatesList({ nodeResource, onUpdateNodeResource }) {
const location = useLocation();
@ -81,6 +133,18 @@ function JobTemplatesList({ nodeResource, onUpdateNodeResource }) {
onSelect={() => onUpdateNodeResource(item)}
onDeselect={() => onUpdateNodeResource(null)}
isRadio
rowActions={[
<ActionItem id={item.id} visible>
<Popover
bodyContent={<TemplatePopoverContent template={item} />}
headerContent={<div>{t`Details`}</div>}
id={`item-${item.id}-info-popover`}
position="right"
>
<OutlinedQuestionCircleIcon />
</Popover>
</ActionItem>,
]}
/>
)}
renderToolbar={(props) => <DataListToolbar {...props} fillWidth />}

View File

@ -82,6 +82,45 @@ describe('JobTemplatesList', () => {
});
});
test('Row should display popover', async () => {
JobTemplatesAPI.read.mockResolvedValueOnce({
data: {
count: 1,
results: [
{
id: 1,
name: 'Test Job Template',
type: 'job_template',
url: '/api/v2/job_templates/1',
inventory: 1,
project: 2,
},
],
},
});
JobTemplatesAPI.readOptions.mockResolvedValue({
data: {
actions: {
GET: {},
POST: {},
},
related_search_fields: [],
},
});
await act(async () => {
wrapper = mountWithContexts(
<JobTemplatesList
nodeResource={nodeResource}
onUpdateNodeResource={onUpdateNodeResource}
/>
);
});
wrapper.update();
expect(
wrapper.find('CheckboxListItem[name="Test Job Template"] Popover').length
).toBe(1);
});
test('Error shown when read() request errors', async () => {
JobTemplatesAPI.read.mockRejectedValue(new Error());
JobTemplatesAPI.readOptions.mockResolvedValue({