Show notification type in its own column

This commit is contained in:
mabashian 2019-06-27 10:15:11 -04:00
parent 3371a6f386
commit 1d2c21249b
7 changed files with 331 additions and 243 deletions

View File

@ -1,5 +1,9 @@
const NotificationsMixin = parent =>
class extends parent {
readOptionsNotificationTemplates(id) {
return this.http.options(`${this.baseUrl}${id}/notification_templates/`);
}
readNotificationTemplates(id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/notification_templates/`, {
params,

View File

@ -4,7 +4,6 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import {
Badge,
Switch as PFSwitch,
DataListItem,
DataListItemRow,
@ -39,6 +38,7 @@ function NotificationListItem(props) {
errorTurnedOn,
toggleNotification,
i18n,
typeLabels,
} = props;
return (
@ -47,50 +47,47 @@ function NotificationListItem(props) {
key={notification.id}
>
<DataListItemRow>
<DataListItemCells
dataListCells={[
<DataListCell key="name">
<Link
to={{
pathname: detailUrl,
}}
css="margin-right: 1.5em;"
>
<b id={`items-list-item-${notification.id}`}>
{notification.name}
</b>
</Link>
<Badge css="text-transform: capitalize;" isRead>
{notification.notification_type}
</Badge>
</DataListCell>,
<DataListCell righthalf="true" key="toggles">
<Switch
id={`notification-${notification.id}-success-toggle`}
label={i18n._(t`Successful`)}
isChecked={successTurnedOn}
isDisabled={!canToggleNotifications}
onChange={() =>
toggleNotification(
notification.id,
successTurnedOn,
'success'
)
}
aria-label={i18n._(t`Toggle notification success`)}
/>
<Switch
id={`notification-${notification.id}-error-toggle`}
label={i18n._(t`Failure`)}
isChecked={errorTurnedOn}
isDisabled={!canToggleNotifications}
onChange={() =>
toggleNotification(notification.id, errorTurnedOn, 'error')
}
aria-label={i18n._(t`Toggle notification failure`)}
/>
</DataListCell>,
]}
<DataListItemCells dataListCells={[
<DataListCell key="name">
<Link
to={{
pathname: detailUrl
}}
css="margin-right: 1.5em;"
>
<b id={`items-list-item-${notification.id}`}>{notification.name}</b>
</Link>
</DataListCell>,
<DataListCell key="type">
{typeLabels[notification.notification_type]}
</DataListCell>,
<DataListCell righthalf="true" key="toggles">
<Switch
id={`notification-${notification.id}-success-toggle`}
label={i18n._(t`Successful`)}
isChecked={successTurnedOn}
isDisabled={!canToggleNotifications}
onChange={() => toggleNotification(
notification.id,
successTurnedOn,
'success'
)}
aria-label={i18n._(t`Toggle notification success`)}
/>
<Switch
id={`notification-${notification.id}-error-toggle`}
label={i18n._(t`Failure`)}
isChecked={errorTurnedOn}
isDisabled={!canToggleNotifications}
onChange={() => toggleNotification(
notification.id,
errorTurnedOn,
'error'
)}
aria-label={i18n._(t`Toggle notification failure`)}
/>
</DataListCell>
]}
/>
</DataListItemRow>
</DataListItem>
@ -108,6 +105,7 @@ NotificationListItem.propTypes = {
errorTurnedOn: bool,
successTurnedOn: bool,
toggleNotification: func.isRequired,
typeLabels: shape().isRequired
};
NotificationListItem.defaultProps = {

View File

@ -6,6 +6,16 @@ describe('<NotificationListItem canToggleNotifications />', () => {
let wrapper;
let toggleNotification;
const mockNotif = {
id: 9000,
name: 'Foo',
notification_type: 'slack'
};
const typeLabels = {
slack: 'Slack'
};
beforeEach(() => {
toggleNotification = jest.fn();
});
@ -18,34 +28,42 @@ describe('<NotificationListItem canToggleNotifications />', () => {
jest.clearAllMocks();
});
test('initially renders succesfully', () => {
test('initially renders succesfully and displays correct label', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
notification={mockNotif}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
expect(wrapper.find('NotificationListItem')).toMatchSnapshot();
});
test('displays correct label in correct column', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={mockNotif}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
const typeCell = wrapper.find('DataListCell').at(1).find('div');
expect(typeCell.text()).toBe('Slack');
});
test('handles success click when toggle is on', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
notification={mockNotif}
successTurnedOn
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
wrapper
@ -59,15 +77,12 @@ describe('<NotificationListItem canToggleNotifications />', () => {
test('handles success click when toggle is off', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
notification={mockNotif}
successTurnedOn={false}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
wrapper
@ -81,15 +96,12 @@ describe('<NotificationListItem canToggleNotifications />', () => {
test('handles error click when toggle is on', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
notification={mockNotif}
errorTurnedOn
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
wrapper
@ -103,15 +115,12 @@ describe('<NotificationListItem canToggleNotifications />', () => {
test('handles error click when toggle is off', () => {
wrapper = mountWithContexts(
<NotificationListItem
notification={{
id: 9000,
name: 'Foo',
notification_type: 'slack',
}}
notification={mockNotif}
errorTurnedOn={false}
toggleNotification={toggleNotification}
detailUrl="/foo"
canToggleNotifications
typeLabels={typeLabels}
/>
);
wrapper

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<NotificationListItem canToggleNotifications /> initially renders succesfully 1`] = `
exports[`<NotificationListItem canToggleNotifications /> initially renders succesfully and displays correct label 1`] = `
<NotificationListItem
canToggleNotifications={true}
detailUrl="/foo"
@ -15,6 +15,11 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
}
successTurnedOn={false}
toggleNotification={[MockFunction]}
typeLabels={
Object {
"slack": "Slack",
}
}
>
<DataListItem
aria-labelledby="items-list-item-9000"
@ -52,11 +57,9 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
Foo
</b>
</ForwardRef>
<ForwardRef
isRead={true}
>
slack
</ForwardRef>
</ForwardRef>,
<ForwardRef>
Slack
</ForwardRef>,
<ForwardRef
righthalf="true"
@ -189,47 +192,55 @@ exports[`<NotificationListItem canToggleNotifications /> initially renders succe
</Link>
</StyledComponent>
</Styled(Link)>
<Styled(Badge)
isRead={true}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bwzfXH",
"isStatic": true,
"lastClassName": "chTbOZ",
"rules": Array [
"text-transform: capitalize;",
],
},
"displayName": "Styled(Badge)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bwzfXH",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isRead={true}
>
<Badge
className="sc-bwzfXH chTbOZ"
isRead={true}
>
<span
className="pf-c-badge pf-m-read sc-bwzfXH chTbOZ"
>
slack
</span>
</Badge>
</StyledComponent>
</Styled(Badge)>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="type"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-j7c411-0",
"isStatic": false,
"lastClassName": "hoXOpW",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
alignRight={false}
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
isFilled={true}
isIcon={false}
width={1}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
>
Slack
</div>
</DataListCell>
</StyledComponent>

View File

@ -35,6 +35,7 @@ class OrganizationNotifications extends Component {
notifications: [],
successTemplateIds: [],
errorTemplateIds: [],
typeLabels: null
};
this.handleNotificationToggle = this.handleNotificationToggle.bind(this);
this.handleNotificationErrorClose = this.handleNotificationErrorClose.bind(
@ -56,13 +57,25 @@ class OrganizationNotifications extends Component {
async loadNotifications() {
const { id, location } = this.props;
const { typeLabels } = this.state;
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
const promises = [
OrganizationsAPI.readNotificationTemplates(id, params)
];
if (!typeLabels) {
promises.push(OrganizationsAPI.readOptionsNotificationTemplates(id));
}
this.setState({ contentError: null, hasContentLoading: true });
try {
const {
data: { count: itemCount = 0, results: notifications = [] },
} = await OrganizationsAPI.readNotificationTemplates(id, params);
const [{
data: {
count: itemCount = 0,
results: notifications = [],
}
}, optionsResponse] = await Promise.all(promises);
let idMatchParams;
if (notifications.length > 0) {
@ -79,12 +92,21 @@ class OrganizationNotifications extends Component {
OrganizationsAPI.readNotificationTemplatesError(id, idMatchParams),
]);
this.setState({
const stateToUpdate = {
itemCount,
notifications,
successTemplateIds: successTemplates.results.map(s => s.id),
errorTemplateIds: errorTemplates.results.map(e => e.id),
});
};
if (!typeLabels) {
const { data: { actions: { GET: { notification_type: {choices} } } } } = optionsResponse;
// The structure of choices looks like [['slack', 'Slack'], ['email', 'Email'], ...]
stateToUpdate.typeLabels =
choices.reduce((map, notifType) => ({ ...map, [notifType[0]]: notifType[1]}), {});
}
this.setState(stateToUpdate);
} catch (err) {
this.setState({ contentError: err });
} finally {
@ -148,6 +170,7 @@ class OrganizationNotifications extends Component {
notifications,
successTemplateIds,
errorTemplateIds,
typeLabels,
} = this.state;
return (
@ -169,6 +192,7 @@ class OrganizationNotifications extends Component {
toggleNotification={this.handleNotificationToggle}
errorTurnedOn={errorTemplateIds.includes(notification.id)}
successTurnedOn={successTemplateIds.includes(notification.id)}
typeLabels={typeLabels}
/>
)}
/>

View File

@ -8,26 +8,36 @@ import OrganizationNotifications from './OrganizationNotifications';
jest.mock('@api');
describe('<OrganizationNotifications />', () => {
let data;
const data = {
count: 2,
results: [{
id: 1,
name: 'Notification one',
url: '/api/v2/notification_templates/1/',
notification_type: 'email',
}, {
id: 2,
name: 'Notification two',
url: '/api/v2/notification_templates/2/',
notification_type: 'email',
}]
};
OrganizationsAPI.readOptionsNotificationTemplates.mockReturnValue({
data: {
actions: {
GET: {
notification_type: {
choices: [
['email', 'Email']
]
}
}
}
}
});
beforeEach(() => {
data = {
count: 2,
results: [
{
id: 1,
name: 'Notification one',
url: '/api/v2/notification_templates/1/',
notification_type: 'email',
},
{
id: 2,
name: 'Notification two',
url: '/api/v2/notification_templates/2/',
notification_type: 'email',
},
],
};
OrganizationsAPI.readNotificationTemplates.mockReturnValue({ data });
OrganizationsAPI.readNotificationTemplatesSuccess.mockReturnValue({
data: { results: [{ id: 1 }] },

View File

@ -410,9 +410,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bxivhb",
"componentId": "sc-htpNat",
"isStatic": true,
"lastClassName": "gYEJOJ",
"lastClassName": "dqEVhr",
"rules": Array [
"flex-grow: 1;",
],
@ -420,7 +420,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(ToolbarItem)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bxivhb",
"styledComponentId": "sc-htpNat",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
@ -430,10 +430,10 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
forwardedRef={null}
>
<ToolbarItem
className="sc-bxivhb gYEJOJ"
className="sc-htpNat dqEVhr"
>
<div
className="pf-l-toolbar__item sc-bxivhb gYEJOJ"
className="pf-l-toolbar__item sc-htpNat dqEVhr"
>
<WithI18n
columns={
@ -1441,9 +1441,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-htpNat",
"componentId": "sc-bwzfXH",
"isStatic": true,
"lastClassName": "jWbbwS",
"lastClassName": "iNPUwu",
"rules": Array [
"padding: 0;",
],
@ -1451,7 +1451,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(Button)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-htpNat",
"styledComponentId": "sc-bwzfXH",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
@ -1464,7 +1464,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
>
<Button
aria-label="Sort"
className="sc-htpNat jWbbwS"
className="sc-bwzfXH iNPUwu"
component="button"
isActive={false}
isBlock={false}
@ -1479,7 +1479,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
<button
aria-disabled={null}
aria-label="Sort"
className="pf-c-button pf-m-plain sc-htpNat jWbbwS"
className="pf-c-button pf-m-plain sc-bwzfXH iNPUwu"
disabled={false}
onClick={[Function]}
tabIndex={null}
@ -1619,6 +1619,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}
successTurnedOn={true}
toggleNotification={[Function]}
typeLabels={
Object {
"email": "Email",
}
}
>
<I18n
update={true}
@ -1639,6 +1644,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}
successTurnedOn={true}
toggleNotification={[Function]}
typeLabels={
Object {
"email": "Email",
}
}
>
<DataListItem
aria-labelledby="items-list-item-1"
@ -1676,11 +1686,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
Notification one
</b>
</ForwardRef>
<ForwardRef
isRead={true}
>
email
</ForwardRef>
</ForwardRef>,
<ForwardRef>
Email
</ForwardRef>,
<ForwardRef
righthalf="true"
@ -1813,47 +1821,55 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
</Link>
</StyledComponent>
</Styled(Link)>
<Styled(Badge)
isRead={true}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bwzfXH",
"isStatic": true,
"lastClassName": "chTbOZ",
"rules": Array [
"text-transform: capitalize;",
],
},
"displayName": "Styled(Badge)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bwzfXH",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isRead={true}
>
<Badge
className="sc-bwzfXH chTbOZ"
isRead={true}
>
<span
className="pf-c-badge pf-m-read sc-bwzfXH chTbOZ"
>
email
</span>
</Badge>
</StyledComponent>
</Styled(Badge)>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="type"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-j7c411-0",
"isStatic": false,
"lastClassName": "hoXOpW",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
alignRight={false}
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
isFilled={true}
isIcon={false}
width={1}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
>
Email
</div>
</DataListCell>
</StyledComponent>
@ -2094,6 +2110,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}
successTurnedOn={false}
toggleNotification={[Function]}
typeLabels={
Object {
"email": "Email",
}
}
>
<I18n
update={true}
@ -2114,6 +2135,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
}
successTurnedOn={false}
toggleNotification={[Function]}
typeLabels={
Object {
"email": "Email",
}
}
>
<DataListItem
aria-labelledby="items-list-item-2"
@ -2151,11 +2177,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
Notification two
</b>
</ForwardRef>
<ForwardRef
isRead={true}
>
email
</ForwardRef>
</ForwardRef>,
<ForwardRef>
Email
</ForwardRef>,
<ForwardRef
righthalf="true"
@ -2288,47 +2312,55 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
</Link>
</StyledComponent>
</Styled(Link)>
<Styled(Badge)
isRead={true}
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "sc-bwzfXH",
"isStatic": true,
"lastClassName": "chTbOZ",
"rules": Array [
"text-transform: capitalize;",
],
},
"displayName": "Styled(Badge)",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "sc-bwzfXH",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isRead={true}
>
<Badge
className="sc-bwzfXH chTbOZ"
isRead={true}
>
<span
className="pf-c-badge pf-m-read sc-bwzfXH chTbOZ"
>
email
</span>
</Badge>
</StyledComponent>
</Styled(Badge)>
</div>
</DataListCell>
</StyledComponent>
</NotificationListItem__DataListCell>
<NotificationListItem__DataListCell
key="type"
>
<StyledComponent
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "NotificationListItem__DataListCell-j7c411-0",
"isStatic": false,
"lastClassName": "hoXOpW",
"rules": Array [
"display:flex;justify-content:",
[Function],
";padding-bottom:",
[Function],
";@media screen and (min-width:768px){justify-content:",
[Function],
";padding-bottom:0;}",
],
},
"displayName": "NotificationListItem__DataListCell",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "NotificationListItem__DataListCell-j7c411-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
>
<DataListCell
alignRight={false}
className="NotificationListItem__DataListCell-j7c411-0 kIdLtz"
isFilled={true}
isIcon={false}
width={1}
>
<div
className="pf-c-data-list__cell NotificationListItem__DataListCell-j7c411-0 kIdLtz"
>
Email
</div>
</DataListCell>
</StyledComponent>