Merge pull request #8780 from mabashian/7879-notif-copy-search

Add support for notification template copy and advanced search

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-12-16 20:39:52 +00:00 committed by GitHub
commit a5e54c3858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 12 deletions

View File

@ -34,7 +34,7 @@ function CopyButton({
<>
<Tooltip content={helperText.tooltip} position="top">
<Button
isDisabled={isDisabled}
isDisabled={isLoading || isDisabled}
aria-label={i18n._(t`Copy`)}
variant="plain"
onClick={copyItemToAPI}

View File

@ -187,7 +187,7 @@ function NotificationList({
key: 'description__icontains',
},
{
name: i18n._(t`Type`),
name: i18n._(t`Notification type`),
key: 'or__notification_type',
options: [
['email', i18n._(t`Email`)],

View File

@ -29,27 +29,41 @@ function NotificationTemplatesList({ i18n }) {
const addUrl = `${match.url}/add`;
const {
result: { templates, count, actions },
result: {
templates,
count,
actions,
relatedSearchableKeys,
searchableKeys,
},
error: contentError,
isLoading: isTemplatesLoading,
request: fetchTemplates,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, location.search);
const responses = await Promise.all([
const [response, actionsResponse] = await Promise.all([
NotificationTemplatesAPI.read(params),
NotificationTemplatesAPI.readOptions(),
]);
return {
templates: responses[0].data.results,
count: responses[0].data.count,
actions: responses[1].data.actions,
templates: response.data.results,
count: response.data.count,
actions: actionsResponse.data.actions,
relatedSearchableKeys: (
actionsResponse.data?.related_search_fields || []
).map(val => val.slice(0, -8)),
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
};
}, [location]),
{
templates: [],
count: 0,
actions: {},
relatedSearchableKeys: [],
searchableKeys: [],
}
);
@ -109,7 +123,7 @@ function NotificationTemplatesList({ i18n }) {
key: 'description__icontains',
},
{
name: i18n._(t`Type`),
name: i18n._(t`Notification type`),
key: 'or__notification_type',
options: [
['email', i18n._(t`Email`)],
@ -125,14 +139,16 @@ function NotificationTemplatesList({ i18n }) {
],
},
{
name: i18n._(t`Created By (Username)`),
name: i18n._(t`Created by (username)`),
key: 'created_by__username__icontains',
},
{
name: i18n._(t`Modified By (Username)`),
name: i18n._(t`Modified by (username)`),
key: 'modified_by__username__icontains',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSortColumns={[
{
name: i18n._(t`Name`),
@ -166,6 +182,7 @@ function NotificationTemplatesList({ i18n }) {
renderItem={template => (
<NotificationTemplateListItem
key={template.id}
fetchTemplates={fetchTemplates}
template={template}
detailUrl={`${match.url}/${template.id}`}
isSelected={selected.some(row => row.id === template.id)}

View File

@ -14,9 +14,11 @@ import {
Tooltip,
} from '@patternfly/react-core';
import { PencilAltIcon, BellIcon } from '@patternfly/react-icons';
import { timeOfDay } from '../../../util/dates';
import { NotificationTemplatesAPI, NotificationsAPI } from '../../../api';
import DataListCell from '../../../components/DataListCell';
import StatusLabel from '../../../components/StatusLabel';
import CopyButton from '../../../components/CopyButton';
import useRequest from '../../../util/useRequest';
import { NOTIFICATION_TYPES } from '../constants';
@ -24,7 +26,7 @@ const DataListAction = styled(_DataListAction)`
align-items: center;
display: grid;
grid-gap: 16px;
grid-template-columns: 40px 40px;
grid-template-columns: repeat(3, 40px);
`;
const NUM_RETRIES = 25;
@ -33,6 +35,7 @@ const RETRY_TIMEOUT = 5000;
function NotificationTemplateListItem({
template,
detailUrl,
fetchTemplates,
isSelected,
onSelect,
i18n,
@ -42,6 +45,22 @@ function NotificationTemplateListItem({
? recentNotifications[0]?.status
: null;
const [status, setStatus] = useState(latestStatus);
const [isCopyDisabled, setIsCopyDisabled] = useState(false);
const copyTemplate = useCallback(async () => {
await NotificationTemplatesAPI.copy(template.id, {
name: `${template.name} @ ${timeOfDay()}`,
});
await fetchTemplates();
}, [template.id, template.name, fetchTemplates]);
const handleCopyStart = useCallback(() => {
setIsCopyDisabled(true);
}, []);
const handleCopyFinish = useCallback(() => {
setIsCopyDisabled(false);
}, []);
useEffect(() => {
setStatus(latestStatus);
@ -114,7 +133,7 @@ function NotificationTemplateListItem({
aria-label={i18n._(t`Test Notification`)}
variant="plain"
onClick={sendTestNotification}
disabled={isLoading}
isDisabled={isLoading || status === 'running'}
>
<BellIcon />
</Button>
@ -136,6 +155,18 @@ function NotificationTemplateListItem({
) : (
<div />
)}
{template.summary_fields.user_capabilities.copy && (
<CopyButton
copyItem={copyTemplate}
isCopyDisabled={isCopyDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
helperText={{
tooltip: i18n._(t`Copy Notification Template`),
errorMessage: i18n._(t`Failed to copy template.`),
}}
/>
)}
</DataListAction>
</DataListItemRow>
</DataListItem>

View File

@ -13,6 +13,7 @@ const template = {
summary_fields: {
user_capabilities: {
edit: true,
copy: true,
},
recent_notifications: [
{
@ -63,4 +64,56 @@ describe('<NotificationTemplateListItem />', () => {
.text()
).toEqual('Running');
});
test('should call api to copy inventory', async () => {
NotificationTemplatesAPI.copy.mockResolvedValue();
const wrapper = mountWithContexts(
<NotificationTemplateListItem
template={template}
detailUrl="/notification_templates/3/detail"
/>
);
await act(async () =>
wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
);
expect(NotificationTemplatesAPI.copy).toHaveBeenCalled();
jest.clearAllMocks();
});
test('should render proper alert modal on copy error', async () => {
NotificationTemplatesAPI.copy.mockRejectedValue(new Error());
const wrapper = mountWithContexts(
<NotificationTemplateListItem
template={template}
detailUrl="/notification_templates/3/detail"
/>
);
await act(async () =>
wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
);
wrapper.update();
expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
jest.clearAllMocks();
});
test('should not render copy button', async () => {
const wrapper = mountWithContexts(
<NotificationTemplateListItem
template={{
...template,
summary_fields: {
user_capabilities: {
copy: false,
edit: false,
},
},
}}
detailUrl="/notification_templates/3/detail"
/>
);
expect(wrapper.find('CopyButton').length).toBe(0);
});
});