mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
Add wf node list item info popover (#11587)
This commit is contained in:
parent
ab3de5898d
commit
30d1d63813
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
? `
|
||||
|
||||
@ -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 />}
|
||||
|
||||
@ -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({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user