diff --git a/awx/ui_next/src/api/index.js b/awx/ui_next/src/api/index.js index 41bef37154..7d05309b3a 100644 --- a/awx/ui_next/src/api/index.js +++ b/awx/ui_next/src/api/index.js @@ -8,6 +8,7 @@ import JobTemplates from './models/JobTemplates'; import Jobs from './models/Jobs'; import Labels from './models/Labels'; import Me from './models/Me'; +import NotificationTemplates from './models/NotificationTemplates'; import Organizations from './models/Organizations'; import Projects from './models/Projects'; import ProjectUpdates from './models/ProjectUpdates'; @@ -30,6 +31,7 @@ const JobTemplatesAPI = new JobTemplates(); const JobsAPI = new Jobs(); const LabelsAPI = new Labels(); const MeAPI = new Me(); +const NotificationTemplatesAPI = new NotificationTemplates(); const OrganizationsAPI = new Organizations(); const ProjectsAPI = new Projects(); const ProjectUpdatesAPI = new ProjectUpdates(); @@ -53,6 +55,7 @@ export { JobsAPI, LabelsAPI, MeAPI, + NotificationTemplatesAPI, OrganizationsAPI, ProjectsAPI, ProjectUpdatesAPI, diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index a6af575b44..cff6858db7 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -1,7 +1,8 @@ import Base from '../Base'; +import NotificationsMixin from '../mixins/Notifications.mixin'; import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin'; -class JobTemplates extends InstanceGroupsMixin(Base) { +class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) { constructor(http) { super(http); this.baseUrl = '/api/v2/job_templates/'; diff --git a/awx/ui_next/src/api/models/NotificationTemplates.js b/awx/ui_next/src/api/models/NotificationTemplates.js new file mode 100644 index 0000000000..7736921ad2 --- /dev/null +++ b/awx/ui_next/src/api/models/NotificationTemplates.js @@ -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; diff --git a/awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.jsx b/awx/ui_next/src/components/NotificationList/NotificationList.jsx similarity index 84% rename from awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.jsx rename to awx/ui_next/src/components/NotificationList/NotificationList.jsx index 56bf6cd5e9..8ae2c2c3f3 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.jsx +++ b/awx/ui_next/src/components/NotificationList/NotificationList.jsx @@ -4,13 +4,14 @@ import { withRouter } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { OrganizationsAPI } from '@api'; import AlertModal from '@components/AlertModal'; import ErrorDetail from '@components/ErrorDetail'; -import NotificationListItem from '@components/NotificationsList/NotificationListItem'; +import NotificationListItem from '@components/NotificationList/NotificationListItem'; import PaginatedDataList from '@components/PaginatedDataList'; import { getQSConfig, parseQueryString } from '@util/qs'; +import { NotificationTemplatesAPI } from '@api'; + const QS_CONFIG = getQSConfig('notification', { page: 1, page_size: 5, @@ -23,7 +24,7 @@ const COLUMNS = [ { key: 'created', name: 'Created', isSortable: true, isNumeric: true }, ]; -class OrganizationNotifications extends Component { +class NotificationList extends Component { constructor(props) { super(props); this.state = { @@ -57,26 +58,23 @@ class OrganizationNotifications extends Component { } async loadNotifications() { - const { id, location } = this.props; + const { id, location, apiModel } = this.props; const { typeLabels } = this.state; const params = parseQueryString(QS_CONFIG, location.search); - const promises = [OrganizationsAPI.readNotificationTemplates(id, params)]; + const promises = [NotificationTemplatesAPI.read(params)]; if (!typeLabels) { - promises.push(OrganizationsAPI.readOptionsNotificationTemplates(id)); + promises.push(NotificationTemplatesAPI.readOptions()); } this.setState({ contentError: null, hasContentLoading: true }); try { const { data: { count: itemCount = 0, results: notifications = [] }, - } = await OrganizationsAPI.readNotificationTemplates(id, params); + } = await NotificationTemplatesAPI.read(params); - const optionsResponse = await OrganizationsAPI.readOptionsNotificationTemplates( - id, - params - ); + const optionsResponse = await NotificationTemplatesAPI.readOptions(); let idMatchParams; if (notifications.length > 0) { @@ -90,9 +88,9 @@ class OrganizationNotifications extends Component { { data: successTemplates }, { data: errorTemplates }, ] = await Promise.all([ - OrganizationsAPI.readNotificationTemplatesStarted(id, idMatchParams), - OrganizationsAPI.readNotificationTemplatesSuccess(id, idMatchParams), - OrganizationsAPI.readNotificationTemplatesError(id, idMatchParams), + apiModel.readNotificationTemplatesStarted(id, idMatchParams), + apiModel.readNotificationTemplatesSuccess(id, idMatchParams), + apiModel.readNotificationTemplatesError(id, idMatchParams), ]); const stateToUpdate = { @@ -129,7 +127,7 @@ class OrganizationNotifications extends Component { } async handleNotificationToggle(notificationId, isCurrentlyOn, status) { - const { id } = this.props; + const { id, apiModel } = this.props; let stateArrayName; if (status === 'success') { @@ -158,13 +156,13 @@ class OrganizationNotifications extends Component { this.setState({ toggleLoading: true }); try { if (isCurrentlyOn) { - await OrganizationsAPI.disassociateNotificationTemplate( + await apiModel.disassociateNotificationTemplate( id, notificationId, status ); } else { - await OrganizationsAPI.associateNotificationTemplate( + await apiModel.associateNotificationTemplate( id, notificationId, status @@ -204,7 +202,7 @@ class OrganizationNotifications extends Component { hasContentLoading={hasContentLoading} items={notifications} itemCount={itemCount} - pluralizedItemName="Notifications" + pluralizedItemName={i18n._(t`Notifications`)} qsConfig={QS_CONFIG} toolbarColumns={COLUMNS} renderItem={notification => ( @@ -235,7 +233,7 @@ class OrganizationNotifications extends Component { } } -OrganizationNotifications.propTypes = { +NotificationList.propTypes = { id: number.isRequired, canToggleNotifications: bool.isRequired, location: shape({ @@ -243,5 +241,5 @@ OrganizationNotifications.propTypes = { }).isRequired, }; -export { OrganizationNotifications as _OrganizationNotifications }; -export default withI18n()(withRouter(OrganizationNotifications)); +export { NotificationList as _NotificationList }; +export default withI18n()(withRouter(NotificationList)); diff --git a/awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.test.jsx b/awx/ui_next/src/components/NotificationList/NotificationList.test.jsx similarity index 58% rename from awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.test.jsx rename to awx/ui_next/src/components/NotificationList/NotificationList.test.jsx index 7318d32795..08fd6f161e 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationNotifications/OrganizationNotifications.test.jsx +++ b/awx/ui_next/src/components/NotificationList/NotificationList.test.jsx @@ -1,13 +1,15 @@ import React from 'react'; -import { OrganizationsAPI } from '@api'; + import { mountWithContexts } from '@testUtils/enzymeHelpers'; import { sleep } from '@testUtils/testUtils'; -import OrganizationNotifications from './OrganizationNotifications'; +import { NotificationTemplatesAPI } from '@api'; + +import NotificationList from './NotificationList'; jest.mock('@api'); -describe('', () => { +describe('', () => { const data = { count: 2, results: [ @@ -32,7 +34,19 @@ describe('', () => { ], }; - 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: { actions: { GET: { @@ -45,14 +59,14 @@ describe('', () => { }); beforeEach(() => { - OrganizationsAPI.readNotificationTemplates.mockReturnValue({ data }); - OrganizationsAPI.readNotificationTemplatesSuccess.mockReturnValue({ + NotificationTemplatesAPI.read.mockReturnValue({ data }); + MockModelAPI.readNotificationTemplatesSuccess.mockReturnValue({ data: { results: [{ id: 1 }] }, }); - OrganizationsAPI.readNotificationTemplatesError.mockReturnValue({ + MockModelAPI.readNotificationTemplatesError.mockReturnValue({ data: { results: [{ id: 2 }] }, }); - OrganizationsAPI.readNotificationTemplatesStarted.mockReturnValue({ + MockModelAPI.readNotificationTemplatesStarted.mockReturnValue({ data: { results: [{ id: 3 }] }, }); }); @@ -63,7 +77,7 @@ describe('', () => { test('initially renders succesfully', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); @@ -72,15 +86,15 @@ describe('', () => { test('should render list fetched of items', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); - expect(OrganizationsAPI.readNotificationTemplates).toHaveBeenCalled(); - expect( - wrapper.find('OrganizationNotifications').state('notifications') - ).toEqual(data.results); + expect(NotificationTemplatesAPI.read).toHaveBeenCalled(); + expect(wrapper.find('NotificationList').state('notifications')).toEqual( + data.results + ); const items = wrapper.find('NotificationListItem'); expect(items).toHaveLength(3); expect(items.at(0).prop('successTurnedOn')).toEqual(true); @@ -96,13 +110,13 @@ describe('', () => { test('should enable success notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('successTemplateIds') + wrapper.find('NotificationList').state('successTemplateIds') ).toEqual([1]); const items = wrapper.find('NotificationListItem'); items @@ -110,7 +124,7 @@ describe('', () => { .find('Switch') .at(1) .prop('onChange')(); - expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith( + expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( 1, 2, 'success' @@ -118,47 +132,48 @@ describe('', () => { await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('successTemplateIds') + wrapper.find('NotificationList').state('successTemplateIds') ).toEqual([1, 2]); }); test('should enable error notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); - expect( - wrapper.find('OrganizationNotifications').state('errorTemplateIds') - ).toEqual([2]); + expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([ + 2, + ]); const items = wrapper.find('NotificationListItem'); items .at(0) .find('Switch') .at(2) .prop('onChange')(); - expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith( + expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( 1, 1, 'error' ); await sleep(0); wrapper.update(); - expect( - wrapper.find('OrganizationNotifications').state('errorTemplateIds') - ).toEqual([2, 1]); + expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([ + 2, + 1, + ]); }); test('should enable start notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('startedTemplateIds') + wrapper.find('NotificationList').state('startedTemplateIds') ).toEqual([3]); const items = wrapper.find('NotificationListItem'); items @@ -166,7 +181,7 @@ describe('', () => { .find('Switch') .at(0) .prop('onChange')(); - expect(OrganizationsAPI.associateNotificationTemplate).toHaveBeenCalledWith( + expect(MockModelAPI.associateNotificationTemplate).toHaveBeenCalledWith( 1, 1, 'started' @@ -174,19 +189,19 @@ describe('', () => { await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('startedTemplateIds') + wrapper.find('NotificationList').state('startedTemplateIds') ).toEqual([3, 1]); }); test('should disable success notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('successTemplateIds') + wrapper.find('NotificationList').state('successTemplateIds') ).toEqual([1]); const items = wrapper.find('NotificationListItem'); items @@ -194,51 +209,55 @@ describe('', () => { .find('Switch') .at(1) .prop('onChange')(); - expect( - OrganizationsAPI.disassociateNotificationTemplate - ).toHaveBeenCalledWith(1, 1, 'success'); + expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( + 1, + 1, + 'success' + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('successTemplateIds') + wrapper.find('NotificationList').state('successTemplateIds') ).toEqual([]); }); test('should disable error notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); - expect( - wrapper.find('OrganizationNotifications').state('errorTemplateIds') - ).toEqual([2]); + expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual([ + 2, + ]); const items = wrapper.find('NotificationListItem'); items .at(1) .find('Switch') .at(2) .prop('onChange')(); - expect( - OrganizationsAPI.disassociateNotificationTemplate - ).toHaveBeenCalledWith(1, 2, 'error'); + expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( + 1, + 2, + 'error' + ); await sleep(0); wrapper.update(); - expect( - wrapper.find('OrganizationNotifications').state('errorTemplateIds') - ).toEqual([]); + expect(wrapper.find('NotificationList').state('errorTemplateIds')).toEqual( + [] + ); }); test('should disable start notification', async () => { const wrapper = mountWithContexts( - + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('startedTemplateIds') + wrapper.find('NotificationList').state('startedTemplateIds') ).toEqual([3]); const items = wrapper.find('NotificationListItem'); items @@ -246,13 +265,15 @@ describe('', () => { .find('Switch') .at(0) .prop('onChange')(); - expect( - OrganizationsAPI.disassociateNotificationTemplate - ).toHaveBeenCalledWith(1, 3, 'started'); + expect(MockModelAPI.disassociateNotificationTemplate).toHaveBeenCalledWith( + 1, + 3, + 'started' + ); await sleep(0); wrapper.update(); expect( - wrapper.find('OrganizationNotifications').state('startedTemplateIds') + wrapper.find('NotificationList').state('startedTemplateIds') ).toEqual([]); }); }); diff --git a/awx/ui_next/src/components/NotificationsList/NotificationListItem.jsx b/awx/ui_next/src/components/NotificationList/NotificationListItem.jsx similarity index 100% rename from awx/ui_next/src/components/NotificationsList/NotificationListItem.jsx rename to awx/ui_next/src/components/NotificationList/NotificationListItem.jsx diff --git a/awx/ui_next/src/components/NotificationsList/NotificationListItem.test.jsx b/awx/ui_next/src/components/NotificationList/NotificationListItem.test.jsx similarity index 100% rename from awx/ui_next/src/components/NotificationsList/NotificationListItem.test.jsx rename to awx/ui_next/src/components/NotificationList/NotificationListItem.test.jsx diff --git a/awx/ui_next/src/screens/Organization/OrganizationNotifications/__snapshots__/OrganizationNotifications.test.jsx.snap b/awx/ui_next/src/components/NotificationList/__snapshots__/NotificationList.test.jsx.snap similarity index 96% rename from awx/ui_next/src/screens/Organization/OrganizationNotifications/__snapshots__/OrganizationNotifications.test.jsx.snap rename to awx/ui_next/src/components/NotificationList/__snapshots__/NotificationList.test.jsx.snap index 971793a30b..6e1ad5ede3 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationNotifications/__snapshots__/OrganizationNotifications.test.jsx.snap +++ b/awx/ui_next/src/components/NotificationList/__snapshots__/NotificationList.test.jsx.snap @@ -1,8 +1,86 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` initially renders succesfully 1`] = ` +exports[` initially renders succesfully 1`] = ` @@ -10,13 +88,169 @@ exports[` initially renders succesfully 1`] = ` update={true} withHash={true} > - - initially renders succesfully 1`] = ` "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "componentId": "NotificationListItem__DataListCell-j7c411-0", + "componentId": "NotificationListItem__DataListCell-w674ng-0", "isStatic": false, - "lastClassName": "hoXOpW", + "lastClassName": "dXsFLF", "rules": Array [ "display:flex;justify-content:", [Function], @@ -2053,7 +2287,7 @@ exports[` initially renders succesfully 1`] = ` "displayName": "NotificationListItem__DataListCell", "foldedComponentIds": Array [], "render": [Function], - "styledComponentId": "NotificationListItem__DataListCell-j7c411-0", + "styledComponentId": "NotificationListItem__DataListCell-w674ng-0", "target": [Function], "toString": [Function], "warnTooManyClasses": [Function], @@ -2063,10 +2297,10 @@ exports[` initially renders succesfully 1`] = ` forwardedRef={null} >
initially renders succesfully 1`] = ` "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "componentId": "NotificationListItem__DataListCell-j7c411-0", + "componentId": "NotificationListItem__DataListCell-w674ng-0", "isStatic": false, - "lastClassName": "hoXOpW", + "lastClassName": "dXsFLF", "rules": Array [ "display:flex;justify-content:", [Function], @@ -2156,7 +2390,7 @@ exports[` initially renders succesfully 1`] = ` "displayName": "NotificationListItem__DataListCell", "foldedComponentIds": Array [], "render": [Function], - "styledComponentId": "NotificationListItem__DataListCell-j7c411-0", + "styledComponentId": "NotificationListItem__DataListCell-w674ng-0", "target": [Function], "toString": [Function], "warnTooManyClasses": [Function], @@ -2166,10 +2400,10 @@ exports[` initially renders succesfully 1`] = ` forwardedRef={null} >
Email
@@ -2186,9 +2420,9 @@ exports[` initially renders succesfully 1`] = ` "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "componentId": "NotificationListItem__DataListCell-j7c411-0", + "componentId": "NotificationListItem__DataListCell-w674ng-0", "isStatic": false, - "lastClassName": "hoXOpW", + "lastClassName": "dXsFLF", "rules": Array [ "display:flex;justify-content:", [Function], @@ -2202,7 +2436,7 @@ exports[` initially renders succesfully 1`] = ` "displayName": "NotificationListItem__DataListCell", "foldedComponentIds": Array [], "render": [Function], - "styledComponentId": "NotificationListItem__DataListCell-j7c411-0", + "styledComponentId": "NotificationListItem__DataListCell-w674ng-0", "target": [Function], "toString": [Function], "warnTooManyClasses": [Function], @@ -2213,11 +2447,11 @@ exports[` initially renders succesfully 1`] = ` righthalf="true" >
initially renders succesfully 1`] = ` "$$typeof": Symbol(react.forward_ref), "attrs": Array [], "componentStyle": ComponentStyle { - "componentId": "NotificationListItem__Switch-j7c411-1", + "componentId": "NotificationListItem__Switch-w674ng-1", "isStatic": true, - "lastClassName": "ceuHGn", + "lastClassName": "hbNxaH", "rules": Array [ "display:flex;flex-wrap:no-wrap;", ], @@ -2246,7 +2480,7 @@ exports[` initially renders succesfully 1`] = ` "displayName": "NotificationListItem__Switch", "foldedComponentIds": Array [], "render": [Function], - "styledComponentId": "NotificationListItem__Switch-j7c411-1", + "styledComponentId": "NotificationListItem__Switch-w674ng-1", "target": [Function], "toString": [Function], "warnTooManyClasses": [Function], @@ -2263,7 +2497,7 @@ exports[` initially renders succesfully 1`] = ` > initially renders succesfully 1`] = ` componentProps={ Object { "aria-label": "Toggle notification start", - "className": "NotificationListItem__Switch-j7c411-1 ceuHGn", + "className": "NotificationListItem__Switch-w674ng-1 hbNxaH", "id": "notification-1-started-toggle", "isChecked": false, "isDisabled": false, @@ -2289,7 +2523,7 @@ exports[` initially renders succesfully 1`] = ` > initially renders succesfully 1`] = ` } >