mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
Merge pull request #4896 from mabashian/ui_next-notifs
Refactor notifications list and add it to JT details
Reviewed-by: Michael Abashian
https://github.com/mabashian
This commit is contained in:
@@ -8,6 +8,7 @@ import JobTemplates from './models/JobTemplates';
|
|||||||
import Jobs from './models/Jobs';
|
import Jobs from './models/Jobs';
|
||||||
import Labels from './models/Labels';
|
import Labels from './models/Labels';
|
||||||
import Me from './models/Me';
|
import Me from './models/Me';
|
||||||
|
import NotificationTemplates from './models/NotificationTemplates';
|
||||||
import Organizations from './models/Organizations';
|
import Organizations from './models/Organizations';
|
||||||
import Projects from './models/Projects';
|
import Projects from './models/Projects';
|
||||||
import ProjectUpdates from './models/ProjectUpdates';
|
import ProjectUpdates from './models/ProjectUpdates';
|
||||||
@@ -30,6 +31,7 @@ const JobTemplatesAPI = new JobTemplates();
|
|||||||
const JobsAPI = new Jobs();
|
const JobsAPI = new Jobs();
|
||||||
const LabelsAPI = new Labels();
|
const LabelsAPI = new Labels();
|
||||||
const MeAPI = new Me();
|
const MeAPI = new Me();
|
||||||
|
const NotificationTemplatesAPI = new NotificationTemplates();
|
||||||
const OrganizationsAPI = new Organizations();
|
const OrganizationsAPI = new Organizations();
|
||||||
const ProjectsAPI = new Projects();
|
const ProjectsAPI = new Projects();
|
||||||
const ProjectUpdatesAPI = new ProjectUpdates();
|
const ProjectUpdatesAPI = new ProjectUpdates();
|
||||||
@@ -53,6 +55,7 @@ export {
|
|||||||
JobsAPI,
|
JobsAPI,
|
||||||
LabelsAPI,
|
LabelsAPI,
|
||||||
MeAPI,
|
MeAPI,
|
||||||
|
NotificationTemplatesAPI,
|
||||||
OrganizationsAPI,
|
OrganizationsAPI,
|
||||||
ProjectsAPI,
|
ProjectsAPI,
|
||||||
ProjectUpdatesAPI,
|
ProjectUpdatesAPI,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import Base from '../Base';
|
import Base from '../Base';
|
||||||
|
import NotificationsMixin from '../mixins/Notifications.mixin';
|
||||||
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
||||||
|
|
||||||
class JobTemplates extends InstanceGroupsMixin(Base) {
|
class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
||||||
constructor(http) {
|
constructor(http) {
|
||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/job_templates/';
|
this.baseUrl = '/api/v2/job_templates/';
|
||||||
|
|||||||
10
awx/ui_next/src/api/models/NotificationTemplates.js
Normal file
10
awx/ui_next/src/api/models/NotificationTemplates.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Base from '../Base';
|
||||||
|
|
||||||
|
class NotificationTemplates extends Base {
|
||||||
|
constructor(http) {
|
||||||
|
super(http);
|
||||||
|
this.baseUrl = '/api/v2/notification_templates/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotificationTemplates;
|
||||||
@@ -4,13 +4,14 @@ import { withRouter } from 'react-router-dom';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import { OrganizationsAPI } from '@api';
|
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import NotificationListItem from '@components/NotificationsList/NotificationListItem';
|
import NotificationListItem from '@components/NotificationList/NotificationListItem';
|
||||||
import PaginatedDataList from '@components/PaginatedDataList';
|
import PaginatedDataList from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
|
import { NotificationTemplatesAPI } from '@api';
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('notification', {
|
const QS_CONFIG = getQSConfig('notification', {
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 5,
|
page_size: 5,
|
||||||
@@ -23,7 +24,7 @@ const COLUMNS = [
|
|||||||
{ key: 'created', name: 'Created', isSortable: true, isNumeric: true },
|
{ key: 'created', name: 'Created', isSortable: true, isNumeric: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
class OrganizationNotifications extends Component {
|
class NotificationList extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -57,26 +58,23 @@ class OrganizationNotifications extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadNotifications() {
|
async loadNotifications() {
|
||||||
const { id, location } = this.props;
|
const { id, location, apiModel } = this.props;
|
||||||
const { typeLabels } = this.state;
|
const { typeLabels } = this.state;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
|
||||||
const promises = [OrganizationsAPI.readNotificationTemplates(id, params)];
|
const promises = [NotificationTemplatesAPI.read(params)];
|
||||||
|
|
||||||
if (!typeLabels) {
|
if (!typeLabels) {
|
||||||
promises.push(OrganizationsAPI.readOptionsNotificationTemplates(id));
|
promises.push(NotificationTemplatesAPI.readOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ contentError: null, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { count: itemCount = 0, results: notifications = [] },
|
data: { count: itemCount = 0, results: notifications = [] },
|
||||||
} = await OrganizationsAPI.readNotificationTemplates(id, params);
|
} = await NotificationTemplatesAPI.read(params);
|
||||||
|
|
||||||
const optionsResponse = await OrganizationsAPI.readOptionsNotificationTemplates(
|
const optionsResponse = await NotificationTemplatesAPI.readOptions();
|
||||||
id,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
let idMatchParams;
|
let idMatchParams;
|
||||||
if (notifications.length > 0) {
|
if (notifications.length > 0) {
|
||||||
@@ -90,9 +88,9 @@ class OrganizationNotifications extends Component {
|
|||||||
{ data: successTemplates },
|
{ data: successTemplates },
|
||||||
{ data: errorTemplates },
|
{ data: errorTemplates },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
OrganizationsAPI.readNotificationTemplatesStarted(id, idMatchParams),
|
apiModel.readNotificationTemplatesStarted(id, idMatchParams),
|
||||||
OrganizationsAPI.readNotificationTemplatesSuccess(id, idMatchParams),
|
apiModel.readNotificationTemplatesSuccess(id, idMatchParams),
|
||||||
OrganizationsAPI.readNotificationTemplatesError(id, idMatchParams),
|
apiModel.readNotificationTemplatesError(id, idMatchParams),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const stateToUpdate = {
|
const stateToUpdate = {
|
||||||
@@ -129,7 +127,7 @@ class OrganizationNotifications extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleNotificationToggle(notificationId, isCurrentlyOn, status) {
|
async handleNotificationToggle(notificationId, isCurrentlyOn, status) {
|
||||||
const { id } = this.props;
|
const { id, apiModel } = this.props;
|
||||||
|
|
||||||
let stateArrayName;
|
let stateArrayName;
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
@@ -158,13 +156,13 @@ class OrganizationNotifications extends Component {
|
|||||||
this.setState({ toggleLoading: true });
|
this.setState({ toggleLoading: true });
|
||||||
try {
|
try {
|
||||||
if (isCurrentlyOn) {
|
if (isCurrentlyOn) {
|
||||||
await OrganizationsAPI.disassociateNotificationTemplate(
|
await apiModel.disassociateNotificationTemplate(
|
||||||
id,
|
id,
|
||||||
notificationId,
|
notificationId,
|
||||||
status
|
status
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await OrganizationsAPI.associateNotificationTemplate(
|
await apiModel.associateNotificationTemplate(
|
||||||
id,
|
id,
|
||||||
notificationId,
|
notificationId,
|
||||||
status
|
status
|
||||||
@@ -204,7 +202,7 @@ class OrganizationNotifications extends Component {
|
|||||||
hasContentLoading={hasContentLoading}
|
hasContentLoading={hasContentLoading}
|
||||||
items={notifications}
|
items={notifications}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
pluralizedItemName="Notifications"
|
pluralizedItemName={i18n._(t`Notifications`)}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={COLUMNS}
|
toolbarColumns={COLUMNS}
|
||||||
renderItem={notification => (
|
renderItem={notification => (
|
||||||
@@ -235,7 +233,7 @@ class OrganizationNotifications extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OrganizationNotifications.propTypes = {
|
NotificationList.propTypes = {
|
||||||
id: number.isRequired,
|
id: number.isRequired,
|
||||||
canToggleNotifications: bool.isRequired,
|
canToggleNotifications: bool.isRequired,
|
||||||
location: shape({
|
location: shape({
|
||||||
@@ -243,5 +241,5 @@ OrganizationNotifications.propTypes = {
|
|||||||
}).isRequired,
|
}).isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OrganizationNotifications as _OrganizationNotifications };
|
export { NotificationList as _NotificationList };
|
||||||
export default withI18n()(withRouter(OrganizationNotifications));
|
export default withI18n()(withRouter(NotificationList));
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { OrganizationsAPI } from '@api';
|
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
import { sleep } from '@testUtils/testUtils';
|
import { sleep } from '@testUtils/testUtils';
|
||||||
|
|
||||||
import OrganizationNotifications from './OrganizationNotifications';
|
import { NotificationTemplatesAPI } from '@api';
|
||||||
|
|
||||||
|
import NotificationList from './NotificationList';
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
describe('<OrganizationNotifications />', () => {
|
describe('<NotificationList />', () => {
|
||||||
const data = {
|
const data = {
|
||||||
count: 2,
|
count: 2,
|
||||||
results: [
|
results: [
|
||||||
@@ -32,7 +34,19 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
OrganizationsAPI.readOptionsNotificationTemplates.mockReturnValue({
|
const MockModel = jest.fn().mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
readNotificationTemplatesSuccess: jest.fn(),
|
||||||
|
readNotificationTemplatesError: jest.fn(),
|
||||||
|
readNotificationTemplatesStarted: jest.fn(),
|
||||||
|
associateNotificationTemplate: jest.fn(),
|
||||||
|
disassociateNotificationTemplate: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const MockModelAPI = new MockModel();
|
||||||
|
|
||||||
|
NotificationTemplatesAPI.readOptions.mockReturnValue({
|
||||||
data: {
|
data: {
|
||||||
actions: {
|
actions: {
|
||||||
GET: {
|
GET: {
|
||||||
@@ -45,14 +59,14 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
OrganizationsAPI.readNotificationTemplates.mockReturnValue({ data });
|
NotificationTemplatesAPI.read.mockReturnValue({ data });
|
||||||
OrganizationsAPI.readNotificationTemplatesSuccess.mockReturnValue({
|
MockModelAPI.readNotificationTemplatesSuccess.mockReturnValue({
|
||||||
data: { results: [{ id: 1 }] },
|
data: { results: [{ id: 1 }] },
|
||||||
});
|
});
|
||||||
OrganizationsAPI.readNotificationTemplatesError.mockReturnValue({
|
MockModelAPI.readNotificationTemplatesError.mockReturnValue({
|
||||||
data: { results: [{ id: 2 }] },
|
data: { results: [{ id: 2 }] },
|
||||||
});
|
});
|
||||||
OrganizationsAPI.readNotificationTemplatesStarted.mockReturnValue({
|
MockModelAPI.readNotificationTemplatesStarted.mockReturnValue({
|
||||||
data: { results: [{ id: 3 }] },
|
data: { results: [{ id: 3 }] },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -63,7 +77,7 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
|
|
||||||
test('initially renders succesfully', async () => {
|
test('initially renders succesfully', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
@@ -72,15 +86,15 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
|
|
||||||
test('should render list fetched of items', async () => {
|
test('should render list fetched of items', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(OrganizationsAPI.readNotificationTemplates).toHaveBeenCalled();
|
expect(NotificationTemplatesAPI.read).toHaveBeenCalled();
|
||||||
expect(
|
expect(wrapper.find('NotificationList').state('notifications')).toEqual(
|
||||||
wrapper.find('OrganizationNotifications').state('notifications')
|
data.results
|
||||||
).toEqual(data.results);
|
);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
expect(items).toHaveLength(3);
|
expect(items).toHaveLength(3);
|
||||||
expect(items.at(0).prop('successTurnedOn')).toEqual(true);
|
expect(items.at(0).prop('successTurnedOn')).toEqual(true);
|
||||||
@@ -96,13 +110,13 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
|
|
||||||
test('should enable success notification', async () => {
|
test('should enable success notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('successTemplateIds')
|
wrapper.find('NotificationList').state('successTemplateIds')
|
||||||
).toEqual([1]);
|
).toEqual([1]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
@@ -110,7 +124,7 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(1)
|
.at(1)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
'success'
|
'success'
|
||||||
@@ -118,47 +132,48 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('successTemplateIds')
|
wrapper.find('NotificationList').state('successTemplateIds')
|
||||||
).toEqual([1, 2]);
|
).toEqual([1, 2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should enable error notification', async () => {
|
test('should enable error notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([
|
||||||
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
|
2,
|
||||||
).toEqual([2]);
|
]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
.at(0)
|
.at(0)
|
||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(2)
|
.at(2)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
'error'
|
'error'
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([
|
||||||
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
|
2,
|
||||||
).toEqual([2, 1]);
|
1,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should enable start notification', async () => {
|
test('should enable start notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('startedTemplateIds')
|
wrapper.find('NotificationList').state('startedTemplateIds')
|
||||||
).toEqual([3]);
|
).toEqual([3]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
@@ -166,7 +181,7 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(0)
|
.at(0)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
'started'
|
'started'
|
||||||
@@ -174,19 +189,19 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('startedTemplateIds')
|
wrapper.find('NotificationList').state('startedTemplateIds')
|
||||||
).toEqual([3, 1]);
|
).toEqual([3, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should disable success notification', async () => {
|
test('should disable success notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('successTemplateIds')
|
wrapper.find('NotificationList').state('successTemplateIds')
|
||||||
).toEqual([1]);
|
).toEqual([1]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
@@ -194,51 +209,55 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(1)
|
.at(1)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(
|
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
OrganizationsAPI.disassociateNotificationTemplate
|
1,
|
||||||
).toHaveBeenCalledWith(1, 1, 'success');
|
1,
|
||||||
|
'success'
|
||||||
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('successTemplateIds')
|
wrapper.find('NotificationList').state('successTemplateIds')
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should disable error notification', async () => {
|
test('should disable error notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([
|
||||||
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
|
2,
|
||||||
).toEqual([2]);
|
]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
.at(1)
|
.at(1)
|
||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(2)
|
.at(2)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(
|
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
OrganizationsAPI.disassociateNotificationTemplate
|
1,
|
||||||
).toHaveBeenCalledWith(1, 2, 'error');
|
2,
|
||||||
|
'error'
|
||||||
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual(
|
||||||
wrapper.find('OrganizationNotifications').state('errorTemplateIds')
|
[]
|
||||||
).toEqual([]);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should disable start notification', async () => {
|
test('should disable start notification', async () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<OrganizationNotifications id={1} canToggleNotifications />
|
<NotificationList id={1} canToggleNotifications apiModel={MockModelAPI} />
|
||||||
);
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('startedTemplateIds')
|
wrapper.find('NotificationList').state('startedTemplateIds')
|
||||||
).toEqual([3]);
|
).toEqual([3]);
|
||||||
const items = wrapper.find('NotificationListItem');
|
const items = wrapper.find('NotificationListItem');
|
||||||
items
|
items
|
||||||
@@ -246,13 +265,15 @@ describe('<OrganizationNotifications />', () => {
|
|||||||
.find('Switch')
|
.find('Switch')
|
||||||
.at(0)
|
.at(0)
|
||||||
.prop('onChange')();
|
.prop('onChange')();
|
||||||
expect(
|
expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith(
|
||||||
OrganizationsAPI.disassociateNotificationTemplate
|
1,
|
||||||
).toHaveBeenCalledWith(1, 3, 'started');
|
3,
|
||||||
|
'started'
|
||||||
|
);
|
||||||
await sleep(0);
|
await sleep(0);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('OrganizationNotifications').state('startedTemplateIds')
|
wrapper.find('NotificationList').state('startedTemplateIds')
|
||||||
).toEqual([]);
|
).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -106,9 +106,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__DataListCell-j7c411-0",
|
"componentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"lastClassName": "hoXOpW",
|
"lastClassName": "dXsFLF",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;justify-content:",
|
"display:flex;justify-content:",
|
||||||
[Function],
|
[Function],
|
||||||
@@ -122,7 +122,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__DataListCell",
|
"displayName": "NotificationListItem__DataListCell",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
|
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -132,10 +132,10 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
forwardedRef={null}
|
forwardedRef={null}
|
||||||
>
|
>
|
||||||
<DataListCell
|
<DataListCell
|
||||||
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
|
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
|
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF"
|
||||||
>
|
>
|
||||||
<Styled(Link)
|
<Styled(Link)
|
||||||
to={
|
to={
|
||||||
@@ -209,9 +209,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__DataListCell-j7c411-0",
|
"componentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"lastClassName": "hoXOpW",
|
"lastClassName": "dXsFLF",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;justify-content:",
|
"display:flex;justify-content:",
|
||||||
[Function],
|
[Function],
|
||||||
@@ -225,7 +225,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__DataListCell",
|
"displayName": "NotificationListItem__DataListCell",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
|
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -235,10 +235,10 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
forwardedRef={null}
|
forwardedRef={null}
|
||||||
>
|
>
|
||||||
<DataListCell
|
<DataListCell
|
||||||
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
|
className="NotificationListItem__DataListCell-w674ng-0 faYgxF"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
|
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 faYgxF"
|
||||||
>
|
>
|
||||||
Slack
|
Slack
|
||||||
</div>
|
</div>
|
||||||
@@ -255,9 +255,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__DataListCell-j7c411-0",
|
"componentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"isStatic": false,
|
"isStatic": false,
|
||||||
"lastClassName": "hoXOpW",
|
"lastClassName": "dXsFLF",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;justify-content:",
|
"display:flex;justify-content:",
|
||||||
[Function],
|
[Function],
|
||||||
@@ -271,7 +271,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__DataListCell",
|
"displayName": "NotificationListItem__DataListCell",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
|
"styledComponentId": "NotificationListItem__DataListCell-w674ng-0",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -282,11 +282,11 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
righthalf="true"
|
righthalf="true"
|
||||||
>
|
>
|
||||||
<DataListCell
|
<DataListCell
|
||||||
className="NotificationListItem__DataListCell-j7c411-0 hoXOpW"
|
className="NotificationListItem__DataListCell-w674ng-0 dXsFLF"
|
||||||
righthalf="true"
|
righthalf="true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 hoXOpW"
|
className="pf-c-data-list__cell NotificationListItem__DataListCell-w674ng-0 dXsFLF"
|
||||||
righthalf="true"
|
righthalf="true"
|
||||||
>
|
>
|
||||||
<NotificationListItem__Switch
|
<NotificationListItem__Switch
|
||||||
@@ -305,9 +305,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__Switch-j7c411-1",
|
"componentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"lastClassName": "ceuHGn",
|
"lastClassName": "hbNxaH",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;flex-wrap:no-wrap;",
|
"display:flex;flex-wrap:no-wrap;",
|
||||||
],
|
],
|
||||||
@@ -315,7 +315,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__Switch",
|
"displayName": "NotificationListItem__Switch",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__Switch-j7c411-1",
|
"styledComponentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -332,7 +332,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
aria-label="Toggle notification start"
|
aria-label="Toggle notification start"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-started-toggle"
|
id="notification-9000-started-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -345,7 +345,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
componentProps={
|
componentProps={
|
||||||
Object {
|
Object {
|
||||||
"aria-label": "Toggle notification start",
|
"aria-label": "Toggle notification start",
|
||||||
"className": "NotificationListItem__Switch-j7c411-1 ceuHGn",
|
"className": "NotificationListItem__Switch-w674ng-1 hbNxaH",
|
||||||
"id": "notification-9000-started-toggle",
|
"id": "notification-9000-started-toggle",
|
||||||
"isChecked": false,
|
"isChecked": false,
|
||||||
"isDisabled": false,
|
"isDisabled": false,
|
||||||
@@ -358,7 +358,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle notification start"
|
aria-label="Toggle notification start"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-started-toggle"
|
id="notification-9000-started-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -373,7 +373,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="pf-c-switch NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="pf-c-switch NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
htmlFor="notification-9000-started-toggle"
|
htmlFor="notification-9000-started-toggle"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -425,9 +425,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__Switch-j7c411-1",
|
"componentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"lastClassName": "ceuHGn",
|
"lastClassName": "hbNxaH",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;flex-wrap:no-wrap;",
|
"display:flex;flex-wrap:no-wrap;",
|
||||||
],
|
],
|
||||||
@@ -435,7 +435,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__Switch",
|
"displayName": "NotificationListItem__Switch",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__Switch-j7c411-1",
|
"styledComponentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -452,7 +452,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
aria-label="Toggle notification success"
|
aria-label="Toggle notification success"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-success-toggle"
|
id="notification-9000-success-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -465,7 +465,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
componentProps={
|
componentProps={
|
||||||
Object {
|
Object {
|
||||||
"aria-label": "Toggle notification success",
|
"aria-label": "Toggle notification success",
|
||||||
"className": "NotificationListItem__Switch-j7c411-1 ceuHGn",
|
"className": "NotificationListItem__Switch-w674ng-1 hbNxaH",
|
||||||
"id": "notification-9000-success-toggle",
|
"id": "notification-9000-success-toggle",
|
||||||
"isChecked": false,
|
"isChecked": false,
|
||||||
"isDisabled": false,
|
"isDisabled": false,
|
||||||
@@ -478,7 +478,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle notification success"
|
aria-label="Toggle notification success"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-success-toggle"
|
id="notification-9000-success-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -493,7 +493,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="pf-c-switch NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="pf-c-switch NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
htmlFor="notification-9000-success-toggle"
|
htmlFor="notification-9000-success-toggle"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -545,9 +545,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"$$typeof": Symbol(react.forward_ref),
|
"$$typeof": Symbol(react.forward_ref),
|
||||||
"attrs": Array [],
|
"attrs": Array [],
|
||||||
"componentStyle": ComponentStyle {
|
"componentStyle": ComponentStyle {
|
||||||
"componentId": "NotificationListItem__Switch-j7c411-1",
|
"componentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"isStatic": true,
|
"isStatic": true,
|
||||||
"lastClassName": "ceuHGn",
|
"lastClassName": "hbNxaH",
|
||||||
"rules": Array [
|
"rules": Array [
|
||||||
"display:flex;flex-wrap:no-wrap;",
|
"display:flex;flex-wrap:no-wrap;",
|
||||||
],
|
],
|
||||||
@@ -555,7 +555,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
"displayName": "NotificationListItem__Switch",
|
"displayName": "NotificationListItem__Switch",
|
||||||
"foldedComponentIds": Array [],
|
"foldedComponentIds": Array [],
|
||||||
"render": [Function],
|
"render": [Function],
|
||||||
"styledComponentId": "NotificationListItem__Switch-j7c411-1",
|
"styledComponentId": "NotificationListItem__Switch-w674ng-1",
|
||||||
"target": [Function],
|
"target": [Function],
|
||||||
"toString": [Function],
|
"toString": [Function],
|
||||||
"warnTooManyClasses": [Function],
|
"warnTooManyClasses": [Function],
|
||||||
@@ -572,7 +572,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Component
|
<Component
|
||||||
aria-label="Toggle notification failure"
|
aria-label="Toggle notification failure"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-error-toggle"
|
id="notification-9000-error-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -585,7 +585,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
componentProps={
|
componentProps={
|
||||||
Object {
|
Object {
|
||||||
"aria-label": "Toggle notification failure",
|
"aria-label": "Toggle notification failure",
|
||||||
"className": "NotificationListItem__Switch-j7c411-1 ceuHGn",
|
"className": "NotificationListItem__Switch-w674ng-1 hbNxaH",
|
||||||
"id": "notification-9000-error-toggle",
|
"id": "notification-9000-error-toggle",
|
||||||
"isChecked": false,
|
"isChecked": false,
|
||||||
"isDisabled": false,
|
"isDisabled": false,
|
||||||
@@ -598,7 +598,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
aria-label="Toggle notification failure"
|
aria-label="Toggle notification failure"
|
||||||
className="NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
id="notification-9000-error-toggle"
|
id="notification-9000-error-toggle"
|
||||||
isChecked={false}
|
isChecked={false}
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
@@ -613,7 +613,7 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="pf-c-switch NotificationListItem__Switch-j7c411-1 ceuHGn"
|
className="pf-c-switch NotificationListItem__Switch-w674ng-1 hbNxaH"
|
||||||
htmlFor="notification-9000-error-toggle"
|
htmlFor="notification-9000-error-toggle"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
2
awx/ui_next/src/components/NotificationList/index.js
Normal file
2
awx/ui_next/src/components/NotificationList/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default } from './NotificationList';
|
||||||
|
export { default as NotificationListItem } from './NotificationListItem';
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './NotificationListItem';
|
|
||||||
@@ -11,10 +11,10 @@ import styled from 'styled-components';
|
|||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
|
import NotificationList from '@components/NotificationList/NotificationList';
|
||||||
import { OrganizationAccess } from './OrganizationAccess';
|
import { OrganizationAccess } from './OrganizationAccess';
|
||||||
import OrganizationDetail from './OrganizationDetail';
|
import OrganizationDetail from './OrganizationDetail';
|
||||||
import OrganizationEdit from './OrganizationEdit';
|
import OrganizationEdit from './OrganizationEdit';
|
||||||
import OrganizationNotifications from './OrganizationNotifications';
|
|
||||||
import OrganizationTeams from './OrganizationTeams';
|
import OrganizationTeams from './OrganizationTeams';
|
||||||
import { OrganizationsAPI } from '@api';
|
import { OrganizationsAPI } from '@api';
|
||||||
|
|
||||||
@@ -228,9 +228,10 @@ class Organization extends Component {
|
|||||||
<Route
|
<Route
|
||||||
path="/organizations/:id/notifications"
|
path="/organizations/:id/notifications"
|
||||||
render={() => (
|
render={() => (
|
||||||
<OrganizationNotifications
|
<NotificationList
|
||||||
id={Number(match.params.id)}
|
id={Number(match.params.id)}
|
||||||
canToggleNotifications={canToggleNotifications}
|
canToggleNotifications={canToggleNotifications}
|
||||||
|
apiModel={OrganizationsAPI}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './OrganizationNotifications';
|
|
||||||
@@ -5,9 +5,10 @@ import { Card, CardHeader, PageSection } from '@patternfly/react-core';
|
|||||||
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
|
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
|
||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
|
import NotificationList from '@components/NotificationList';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
import JobTemplateDetail from './JobTemplateDetail';
|
import JobTemplateDetail from './JobTemplateDetail';
|
||||||
import { JobTemplatesAPI } from '@api';
|
import { JobTemplatesAPI, OrganizationsAPI } from '@api';
|
||||||
import JobTemplateEdit from './JobTemplateEdit';
|
import JobTemplateEdit from './JobTemplateEdit';
|
||||||
|
|
||||||
class Template extends Component {
|
class Template extends Component {
|
||||||
@@ -18,12 +19,14 @@ class Template extends Component {
|
|||||||
contentError: null,
|
contentError: null,
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
template: null,
|
template: null,
|
||||||
|
isNotifAdmin: false,
|
||||||
};
|
};
|
||||||
this.loadTemplate = this.loadTemplate.bind(this);
|
this.loadTemplate = this.loadTemplate.bind(this);
|
||||||
|
this.loadTemplateAndRoles = this.loadTemplateAndRoles.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await this.loadTemplate();
|
await this.loadTemplateAndRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUpdate(prevProps) {
|
async componentDidUpdate(prevProps) {
|
||||||
@@ -33,6 +36,31 @@ class Template extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadTemplateAndRoles() {
|
||||||
|
const { match, setBreadcrumb } = this.props;
|
||||||
|
const id = parseInt(match.params.id, 10);
|
||||||
|
|
||||||
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
|
try {
|
||||||
|
const [{ data }, notifAdminRes] = await Promise.all([
|
||||||
|
JobTemplatesAPI.readDetail(id),
|
||||||
|
OrganizationsAPI.read({
|
||||||
|
page_size: 1,
|
||||||
|
role_level: 'notification_admin_role',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
setBreadcrumb(data);
|
||||||
|
this.setState({
|
||||||
|
template: data,
|
||||||
|
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ contentError: err });
|
||||||
|
} finally {
|
||||||
|
this.setState({ hasContentLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async loadTemplate() {
|
async loadTemplate() {
|
||||||
const { setBreadcrumb, match } = this.props;
|
const { setBreadcrumb, match } = this.props;
|
||||||
const { id } = match.params;
|
const { id } = match.params;
|
||||||
@@ -50,18 +78,47 @@ class Template extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { history, i18n, location, match } = this.props;
|
const { history, i18n, location, match, me } = this.props;
|
||||||
const { contentError, hasContentLoading, template } = this.state;
|
const {
|
||||||
|
contentError,
|
||||||
|
hasContentLoading,
|
||||||
|
isNotifAdmin,
|
||||||
|
template,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
|
||||||
|
|
||||||
const tabsArray = [
|
const tabsArray = [
|
||||||
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
|
{ name: i18n._(t`Details`), link: `${match.url}/details`, id: 0 },
|
||||||
{ name: i18n._(t`Access`), link: '/home', id: 1 },
|
{ name: i18n._(t`Access`), link: '/home', id: 1 },
|
||||||
{ name: i18n._(t`Notifications`), link: '/home', id: 2 },
|
|
||||||
{ name: i18n._(t`Schedules`), link: '/home', id: 3 },
|
|
||||||
{ name: i18n._(t`Completed Jobs`), link: '/home', id: 4 },
|
|
||||||
{ name: i18n._(t`Survey`), link: '/home', id: 5 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (canSeeNotificationsTab) {
|
||||||
|
tabsArray.push({
|
||||||
|
name: i18n._(t`Notifications`),
|
||||||
|
link: `${match.url}/notifications`,
|
||||||
|
id: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabsArray.push(
|
||||||
|
{
|
||||||
|
name: i18n._(t`Schedules`),
|
||||||
|
link: '/home',
|
||||||
|
id: canSeeNotificationsTab ? 3 : 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Completed Jobs`),
|
||||||
|
link: '/home',
|
||||||
|
id: canSeeNotificationsTab ? 4 : 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Survey`),
|
||||||
|
link: '/home',
|
||||||
|
id: canSeeNotificationsTab ? 5 : 4,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let cardHeader = hasContentLoading ? null : (
|
let cardHeader = hasContentLoading ? null : (
|
||||||
<CardHeader style={{ padding: 0 }}>
|
<CardHeader style={{ padding: 0 }}>
|
||||||
<RoutedTabs history={history} tabsArray={tabsArray} />
|
<RoutedTabs history={history} tabsArray={tabsArray} />
|
||||||
@@ -89,6 +146,7 @@ class Template extends Component {
|
|||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card className="awx-c-card">
|
<Card className="awx-c-card">
|
||||||
@@ -99,7 +157,7 @@ class Template extends Component {
|
|||||||
to="/templates/:templateType/:id/details"
|
to="/templates/:templateType/:id/details"
|
||||||
exact
|
exact
|
||||||
/>
|
/>
|
||||||
{template && [
|
{template && (
|
||||||
<Route
|
<Route
|
||||||
key="details"
|
key="details"
|
||||||
path="/templates/:templateType/:id/details"
|
path="/templates/:templateType/:id/details"
|
||||||
@@ -110,30 +168,44 @@ class Template extends Component {
|
|||||||
template={template}
|
template={template}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>,
|
/>
|
||||||
|
)}
|
||||||
|
{template && (
|
||||||
<Route
|
<Route
|
||||||
key="edit"
|
key="edit"
|
||||||
path="/templates/:templateType/:id/edit"
|
path="/templates/:templateType/:id/edit"
|
||||||
render={() => <JobTemplateEdit template={template} />}
|
render={() => <JobTemplateEdit template={template} />}
|
||||||
/>,
|
/>
|
||||||
|
)}
|
||||||
|
{canSeeNotificationsTab && (
|
||||||
<Route
|
<Route
|
||||||
key="not-found"
|
path="/templates/:templateType/:id/notifications"
|
||||||
path="*"
|
render={() => (
|
||||||
render={() =>
|
<NotificationList
|
||||||
!hasContentLoading && (
|
id={Number(match.params.id)}
|
||||||
<ContentError isNotFound>
|
canToggleNotifications={isNotifAdmin}
|
||||||
{match.params.id && (
|
apiModel={JobTemplatesAPI}
|
||||||
<Link
|
/>
|
||||||
to={`/templates/${match.params.templateType}/${match.params.id}/details`}
|
)}
|
||||||
>
|
/>
|
||||||
{i18n._(`View Template Details`)}
|
)}
|
||||||
</Link>
|
<Route
|
||||||
)}
|
key="not-found"
|
||||||
</ContentError>
|
path="*"
|
||||||
)
|
render={() =>
|
||||||
}
|
!hasContentLoading && (
|
||||||
/>,
|
<ContentError isNotFound>
|
||||||
]}
|
{match.params.id && (
|
||||||
|
<Link
|
||||||
|
to={`/templates/${match.params.templateType}/${match.params.id}/details`}
|
||||||
|
>
|
||||||
|
{i18n._(`View Template Details`)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</ContentError>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|||||||
@@ -1,20 +1,52 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { JobTemplatesAPI, OrganizationsAPI } from '@api';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
import Template, { _Template } from './Template';
|
import Template, { _Template } from './Template';
|
||||||
|
|
||||||
|
import mockJobTemplateData from './shared/data.job_template.json';
|
||||||
|
|
||||||
|
jest.mock('@api');
|
||||||
|
|
||||||
|
JobTemplatesAPI.readDetail.mockResolvedValue({
|
||||||
|
data: mockJobTemplateData,
|
||||||
|
});
|
||||||
|
|
||||||
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 1,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockMe = {
|
||||||
|
is_super_user: true,
|
||||||
|
is_system_auditor: false,
|
||||||
|
};
|
||||||
|
|
||||||
describe('<Template />', () => {
|
describe('<Template />', () => {
|
||||||
test('initially renders succesfully', () => {
|
test('initially renders succesfully', () => {
|
||||||
mountWithContexts(<Template />);
|
mountWithContexts(<Template setBreadcrumb={() => {}} me={mockMe} />);
|
||||||
});
|
});
|
||||||
test('When component mounts API is called and the response is put in state', async done => {
|
test('When component mounts API is called and the response is put in state', async done => {
|
||||||
const loadTemplate = jest.spyOn(_Template.prototype, 'loadTemplate');
|
const loadTemplateAndRoles = jest.spyOn(
|
||||||
const wrapper = mountWithContexts(<Template />);
|
_Template.prototype,
|
||||||
|
'loadTemplateAndRoles'
|
||||||
|
);
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<Template setBreadcrumb={() => {}} me={mockMe} />
|
||||||
|
);
|
||||||
await waitForElement(
|
await waitForElement(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Template',
|
'Template',
|
||||||
el => el.state('hasContentLoading') === true
|
el => el.state('hasContentLoading') === true
|
||||||
);
|
);
|
||||||
expect(loadTemplate).toHaveBeenCalled();
|
expect(loadTemplateAndRoles).toHaveBeenCalled();
|
||||||
await waitForElement(
|
await waitForElement(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Template',
|
'Template',
|
||||||
@@ -22,4 +54,37 @@ describe('<Template />', () => {
|
|||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
test('notifications tab shown for admins', async done => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<Template setBreadcrumb={() => {}} me={mockMe} />
|
||||||
|
);
|
||||||
|
const tabs = await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'.pf-c-tabs__item',
|
||||||
|
el => el.length === 6
|
||||||
|
);
|
||||||
|
expect(tabs.at(2).text()).toEqual('Notifications');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
test('notifications tab hidden with reduced permissions', async done => {
|
||||||
|
OrganizationsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 0,
|
||||||
|
next: null,
|
||||||
|
previous: null,
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<Template setBreadcrumb={() => {}} me={mockMe} />
|
||||||
|
);
|
||||||
|
const tabs = await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'.pf-c-tabs__item',
|
||||||
|
el => el.length === 5
|
||||||
|
);
|
||||||
|
tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class Templates extends Component {
|
|||||||
[`/templates/${template.type}/${template.id}/edit`]: i18n._(
|
[`/templates/${template.type}/${template.id}/edit`]: i18n._(
|
||||||
t`Edit Details`
|
t`Edit Details`
|
||||||
),
|
),
|
||||||
|
[`/templates/${template.type}/${template.id}/notifications`]: i18n._(
|
||||||
|
t`Notifications`
|
||||||
|
),
|
||||||
};
|
};
|
||||||
this.setState({ breadcrumbConfig });
|
this.setState({ breadcrumbConfig });
|
||||||
};
|
};
|
||||||
|
|||||||
162
awx/ui_next/src/screens/Template/shared/data.job_template.json
Normal file
162
awx/ui_next/src/screens/Template/shared/data.job_template.json
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"type": "job_template",
|
||||||
|
"url": "/api/v2/job_templates/7/",
|
||||||
|
"related": {
|
||||||
|
"named_url": "/api/v2/job_templates/Mike's JT/",
|
||||||
|
"created_by": "/api/v2/users/1/",
|
||||||
|
"modified_by": "/api/v2/users/1/",
|
||||||
|
"labels": "/api/v2/job_templates/7/labels/",
|
||||||
|
"inventory": "/api/v2/inventories/1/",
|
||||||
|
"project": "/api/v2/projects/6/",
|
||||||
|
"extra_credentials": "/api/v2/job_templates/7/extra_credentials/",
|
||||||
|
"credentials": "/api/v2/job_templates/7/credentials/",
|
||||||
|
"last_job": "/api/v2/jobs/12/",
|
||||||
|
"jobs": "/api/v2/job_templates/7/jobs/",
|
||||||
|
"schedules": "/api/v2/job_templates/7/schedules/",
|
||||||
|
"activity_stream": "/api/v2/job_templates/7/activity_stream/",
|
||||||
|
"launch": "/api/v2/job_templates/7/launch/",
|
||||||
|
"notification_templates_started": "/api/v2/job_templates/7/notification_templates_started/",
|
||||||
|
"notification_templates_success": "/api/v2/job_templates/7/notification_templates_success/",
|
||||||
|
"notification_templates_error": "/api/v2/job_templates/7/notification_templates_error/",
|
||||||
|
"access_list": "/api/v2/job_templates/7/access_list/",
|
||||||
|
"survey_spec": "/api/v2/job_templates/7/survey_spec/",
|
||||||
|
"object_roles": "/api/v2/job_templates/7/object_roles/",
|
||||||
|
"instance_groups": "/api/v2/job_templates/7/instance_groups/",
|
||||||
|
"slice_workflow_jobs": "/api/v2/job_templates/7/slice_workflow_jobs/",
|
||||||
|
"copy": "/api/v2/job_templates/7/copy/"
|
||||||
|
},
|
||||||
|
"summary_fields": {
|
||||||
|
"inventory": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Mike's Inventory",
|
||||||
|
"description": "",
|
||||||
|
"has_active_failures": false,
|
||||||
|
"total_hosts": 1,
|
||||||
|
"hosts_with_active_failures": 0,
|
||||||
|
"total_groups": 0,
|
||||||
|
"groups_with_active_failures": 0,
|
||||||
|
"has_inventory_sources": false,
|
||||||
|
"total_inventory_sources": 0,
|
||||||
|
"inventory_sources_with_failures": 0,
|
||||||
|
"organization_id": 1,
|
||||||
|
"kind": ""
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"id": 6,
|
||||||
|
"name": "Mike's Project",
|
||||||
|
"description": "",
|
||||||
|
"status": "successful",
|
||||||
|
"scm_type": "git"
|
||||||
|
},
|
||||||
|
"last_job": {
|
||||||
|
"id": 12,
|
||||||
|
"name": "Mike's JT",
|
||||||
|
"description": "",
|
||||||
|
"finished": "2019-10-01T14:34:35.142483Z",
|
||||||
|
"status": "successful",
|
||||||
|
"failed": false
|
||||||
|
},
|
||||||
|
"last_update": {
|
||||||
|
"id": 12,
|
||||||
|
"name": "Mike's JT",
|
||||||
|
"description": "",
|
||||||
|
"status": "successful",
|
||||||
|
"failed": false
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "admin",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": ""
|
||||||
|
},
|
||||||
|
"modified_by": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "admin",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": ""
|
||||||
|
},
|
||||||
|
"object_roles": {
|
||||||
|
"admin_role": {
|
||||||
|
"description": "Can manage all aspects of the job template",
|
||||||
|
"name": "Admin",
|
||||||
|
"id": 24
|
||||||
|
},
|
||||||
|
"execute_role": {
|
||||||
|
"description": "May run the job template",
|
||||||
|
"name": "Execute",
|
||||||
|
"id": 25
|
||||||
|
},
|
||||||
|
"read_role": {
|
||||||
|
"description": "May view settings for the job template",
|
||||||
|
"name": "Read",
|
||||||
|
"id": 26
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user_capabilities": {
|
||||||
|
"edit": true,
|
||||||
|
"delete": true,
|
||||||
|
"start": true,
|
||||||
|
"schedule": true,
|
||||||
|
"copy": true
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"count": 0,
|
||||||
|
"results": []
|
||||||
|
},
|
||||||
|
"survey": {
|
||||||
|
"title": "",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"recent_jobs": [
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"status": "successful",
|
||||||
|
"finished": "2019-10-01T14:34:35.142483Z",
|
||||||
|
"type": "job"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra_credentials": [],
|
||||||
|
"credentials": []
|
||||||
|
},
|
||||||
|
"created": "2019-09-30T16:18:34.564820Z",
|
||||||
|
"modified": "2019-10-01T14:47:31.818431Z",
|
||||||
|
"name": "Mike's JT",
|
||||||
|
"description": "",
|
||||||
|
"job_type": "run",
|
||||||
|
"inventory": 1,
|
||||||
|
"project": 6,
|
||||||
|
"playbook": "ping.yml",
|
||||||
|
"scm_branch": "",
|
||||||
|
"forks": 0,
|
||||||
|
"limit": "",
|
||||||
|
"verbosity": 0,
|
||||||
|
"extra_vars": "",
|
||||||
|
"job_tags": "",
|
||||||
|
"force_handlers": false,
|
||||||
|
"skip_tags": "",
|
||||||
|
"start_at_task": "",
|
||||||
|
"timeout": 0,
|
||||||
|
"use_fact_cache": false,
|
||||||
|
"last_job_run": "2019-10-01T14:34:35.142483Z",
|
||||||
|
"last_job_failed": false,
|
||||||
|
"next_job_run": null,
|
||||||
|
"status": "successful",
|
||||||
|
"host_config_key": "",
|
||||||
|
"ask_scm_branch_on_launch": false,
|
||||||
|
"ask_diff_mode_on_launch": false,
|
||||||
|
"ask_variables_on_launch": false,
|
||||||
|
"ask_limit_on_launch": false,
|
||||||
|
"ask_tags_on_launch": false,
|
||||||
|
"ask_skip_tags_on_launch": false,
|
||||||
|
"ask_job_type_on_launch": false,
|
||||||
|
"ask_verbosity_on_launch": false,
|
||||||
|
"ask_inventory_on_launch": false,
|
||||||
|
"ask_credential_on_launch": false,
|
||||||
|
"survey_enabled": true,
|
||||||
|
"become_enabled": false,
|
||||||
|
"diff_mode": false,
|
||||||
|
"allow_simultaneous": false,
|
||||||
|
"custom_virtualenv": null,
|
||||||
|
"job_slice_count": 1
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user