mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 23:51:09 +03:00
Add Edit/Delete buttons to JT Schedule Details
Also, add unit-tests to the related changes. Closes: https://github.com/ansible/awx/issues/6171
This commit is contained in:
parent
72de660ea1
commit
271b19bf09
@ -1,12 +1,13 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useHistory, useLocation } from 'react-router-dom';
|
||||||
import { RRule, rrulestr } from 'rrule';
|
import { RRule, rrulestr } from 'rrule';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Schedule } from '@types';
|
import { Schedule } from '@types';
|
||||||
import { Chip, ChipGroup, Title } from '@patternfly/react-core';
|
import { Chip, ChipGroup, Title, Button } from '@patternfly/react-core';
|
||||||
import { CardBody } from '@components/Card';
|
import AlertModal from '@components/AlertModal';
|
||||||
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import CredentialChip from '@components/CredentialChip';
|
import CredentialChip from '@components/CredentialChip';
|
||||||
@ -15,6 +16,8 @@ import { ScheduleOccurrences, ScheduleToggle } from '@components/Schedule';
|
|||||||
import { formatDateString } from '@util/dates';
|
import { formatDateString } from '@util/dates';
|
||||||
import useRequest from '@util/useRequest';
|
import useRequest from '@util/useRequest';
|
||||||
import { SchedulesAPI } from '@api';
|
import { SchedulesAPI } from '@api';
|
||||||
|
import DeleteButton from '@components/DeleteButton';
|
||||||
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
|
|
||||||
const PromptTitle = styled(Title)`
|
const PromptTitle = styled(Title)`
|
||||||
--pf-c-title--m-md--FontWeight: 700;
|
--pf-c-title--m-md--FontWeight: 700;
|
||||||
@ -42,6 +45,23 @@ function ScheduleDetail({ schedule, i18n }) {
|
|||||||
timezone,
|
timezone,
|
||||||
} = schedule;
|
} = schedule;
|
||||||
|
|
||||||
|
const [deletionError, setDeletionError] = useState(null);
|
||||||
|
const [hasContentLoading, setHasContentLoading] = useState(false);
|
||||||
|
const history = useHistory();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const pathRoot = pathname.substr(0, pathname.indexOf('schedules'));
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
setHasContentLoading(true);
|
||||||
|
try {
|
||||||
|
await SchedulesAPI.destroy(id);
|
||||||
|
history.push(`${pathRoot}schedules`);
|
||||||
|
} catch (error) {
|
||||||
|
setDeletionError(error);
|
||||||
|
}
|
||||||
|
setHasContentLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
result: [credentials, preview],
|
result: [credentials, preview],
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -79,7 +99,7 @@ function ScheduleDetail({ schedule, i18n }) {
|
|||||||
(job_tags && job_tags.length > 0) ||
|
(job_tags && job_tags.length > 0) ||
|
||||||
(skip_tags && skip_tags.length > 0);
|
(skip_tags && skip_tags.length > 0);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || hasContentLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +214,37 @@ function ScheduleDetail({ schedule, i18n }) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DetailList>
|
</DetailList>
|
||||||
|
<CardActionsRow>
|
||||||
|
{summary_fields?.user_capabilities?.edit && (
|
||||||
|
<Button
|
||||||
|
aria-label={i18n._(t`Edit`)}
|
||||||
|
component={Link}
|
||||||
|
to={pathname.replace('details', 'edit')}
|
||||||
|
>
|
||||||
|
{i18n._(t`Edit`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{summary_fields?.user_capabilities?.delete && (
|
||||||
|
<DeleteButton
|
||||||
|
name={name}
|
||||||
|
modalTitle={i18n._(t`Delete Schedule`)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
>
|
||||||
|
{i18n._(t`Delete`)}
|
||||||
|
</DeleteButton>
|
||||||
|
)}
|
||||||
|
</CardActionsRow>
|
||||||
|
{deletionError && (
|
||||||
|
<AlertModal
|
||||||
|
isOpen={deletionError}
|
||||||
|
variant="danger"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={() => setDeletionError(null)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to delete schedule.`)}
|
||||||
|
<ErrorDetail error={deletionError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,9 @@ describe('<ScheduleDetail />', () => {
|
|||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('details should render with the proper values without prompts', async () => {
|
test('details should render with the proper values without prompts', async () => {
|
||||||
SchedulesAPI.readCredentials.mockResolvedValueOnce({
|
SchedulesAPI.readCredentials.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
@ -260,4 +262,89 @@ describe('<ScheduleDetail />', () => {
|
|||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show edit button for users with edit permission', async () => {
|
||||||
|
SchedulesAPI.readCredentials.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
count: 0,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route
|
||||||
|
path="/templates/job_template/:id/schedules/:scheduleId"
|
||||||
|
component={() => <ScheduleDetail schedule={schedule} />}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: { params: { id: 1 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const editButton = await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'ScheduleDetail Button[aria-label="Edit"]'
|
||||||
|
);
|
||||||
|
expect(editButton.text()).toEqual('Edit');
|
||||||
|
expect(editButton.prop('to')).toBe(
|
||||||
|
'/templates/job_template/1/schedules/1/edit'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Error dialog shown for failed deletion', async () => {
|
||||||
|
SchedulesAPI.destroy.mockImplementationOnce(() =>
|
||||||
|
Promise.reject(new Error())
|
||||||
|
);
|
||||||
|
SchedulesAPI.readCredentials.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
count: 0,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Route
|
||||||
|
path="/templates/job_template/:id/schedules/:scheduleId"
|
||||||
|
component={() => <ScheduleDetail schedule={schedule} />}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: {
|
||||||
|
router: {
|
||||||
|
history,
|
||||||
|
route: {
|
||||||
|
location: history.location,
|
||||||
|
match: { params: { id: 1 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||||
|
});
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'Modal[title="Error!"]',
|
||||||
|
el => el.length === 1
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Modal[title="Error!"]').invoke('onClose')();
|
||||||
|
});
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'Modal[title="Error!"]',
|
||||||
|
el => el.length === 0
|
||||||
|
);
|
||||||
|
expect(SchedulesAPI.destroy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user