diff --git a/awx/ui/src/components/CheckboxListItem/CheckboxListItem.js b/awx/ui/src/components/CheckboxListItem/CheckboxListItem.js
index fee1892cbc..bce6a322c2 100644
--- a/awx/ui/src/components/CheckboxListItem/CheckboxListItem.js
+++ b/awx/ui/src/components/CheckboxListItem/CheckboxListItem.js
@@ -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 = ({
{label}
)}
+ {rowActions && (
+
+ {rowActions.map((rowAction) => {
+ const {
+ props: { id },
+ } = rowAction;
+ return {rowAction};
+ })}
+
+ )}
);
};
diff --git a/awx/ui/src/components/CheckboxListItem/CheckboxListItem.test.js b/awx/ui/src/components/CheckboxListItem/CheckboxListItem.test.js
index b45cb6c3a8..fe54582de2 100644
--- a/awx/ui/src/components/CheckboxListItem/CheckboxListItem.test.js
+++ b/awx/ui/src/components/CheckboxListItem/CheckboxListItem.test.js
@@ -21,4 +21,33 @@ describe('CheckboxListItem', () => {
);
expect(wrapper).toHaveLength(1);
});
+
+ test('should render row actions', () => {
+ const wrapper = mount(
+
+
+ {}}
+ onDeselect={() => {}}
+ rowActions={[
+ action_1
,
+ action_2
,
+ ]}
+ />
+
+
+ );
+ expect(
+ wrapper
+ .find('ActionsTd')
+ .containsAllMatchingElements([
+ action_1
,
+ action_2
,
+ ])
+ ).toEqual(true);
+ });
});
diff --git a/awx/ui/src/components/DetailList/DetailList.js b/awx/ui/src/components/DetailList/DetailList.js
index e6999d8246..b03a9146fe 100644
--- a/awx/ui/src/components/DetailList/DetailList.js
+++ b/awx/ui/src/components/DetailList/DetailList.js
@@ -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 }) => (
{children}
@@ -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
? `
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js
index 4f94bdb398..eccd397cd9 100644
--- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js
+++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.js
@@ -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 (
+
+
+
+
+ {template.summary_fields?.credentials &&
+ template.summary_fields.credentials.length ? (
+
+ {template.summary_fields.credentials.map((c) => (
+
+ ))}
+
+ }
+ />
+ ) : null}
+
+ );
+}
+
function JobTemplatesList({ nodeResource, onUpdateNodeResource }) {
const location = useLocation();
@@ -81,6 +133,18 @@ function JobTemplatesList({ nodeResource, onUpdateNodeResource }) {
onSelect={() => onUpdateNodeResource(item)}
onDeselect={() => onUpdateNodeResource(null)}
isRadio
+ rowActions={[
+
+ }
+ headerContent={{t`Details`}
}
+ id={`item-${item.id}-info-popover`}
+ position="right"
+ >
+
+
+ ,
+ ]}
/>
)}
renderToolbar={(props) => }
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.js b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.js
index 0456680b7b..069dd8cc3f 100644
--- a/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.js
+++ b/awx/ui/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/JobTemplatesList.test.js
@@ -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(
+
+ );
+ });
+ 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({