Skip to content

Commit e7f6cdc

Browse files
committed
Merge branch '13739/alert-to-react'
2 parents 4112591 + f1660aa commit e7f6cdc

20 files changed

+313
-131
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { AppNotification } from 'app/types/';
2+
3+
export enum ActionTypes {
4+
AddAppNotification = 'ADD_APP_NOTIFICATION',
5+
ClearAppNotification = 'CLEAR_APP_NOTIFICATION',
6+
}
7+
8+
interface AddAppNotificationAction {
9+
type: ActionTypes.AddAppNotification;
10+
payload: AppNotification;
11+
}
12+
13+
interface ClearAppNotificationAction {
14+
type: ActionTypes.ClearAppNotification;
15+
payload: number;
16+
}
17+
18+
export type Action = AddAppNotificationAction | ClearAppNotificationAction;
19+
20+
export const clearAppNotification = (appNotificationId: number) => ({
21+
type: ActionTypes.ClearAppNotification,
22+
payload: appNotificationId,
23+
});
24+
25+
export const notifyApp = (appNotification: AppNotification) => ({
26+
type: ActionTypes.AddAppNotification,
27+
payload: appNotification,
28+
});

public/app/core/actions/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { updateLocation } from './location';
22
import { updateNavIndex, UpdateNavIndexAction } from './navModel';
3+
import { notifyApp, clearAppNotification } from './appNotification';
34

4-
export { updateLocation, updateNavIndex, UpdateNavIndexAction };
5+
export { updateLocation, updateNavIndex, UpdateNavIndexAction, notifyApp, clearAppNotification };

public/app/core/angular_wrappers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
55
import { SearchResult } from './components/search/SearchResult';
66
import { TagFilter } from './components/TagFilter/TagFilter';
77
import { SideMenu } from './components/sidemenu/SideMenu';
8+
import AppNotificationList from './components/AppNotifications/AppNotificationList';
89

910
export function registerAngularDirectives() {
1011
react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
1112
react2AngularDirective('sidemenu', SideMenu, []);
13+
react2AngularDirective('appNotificationsList', AppNotificationList, []);
1214
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
1315
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
1416
react2AngularDirective('searchResult', SearchResult, []);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { Component } from 'react';
2+
import { AppNotification } from 'app/types';
3+
4+
interface Props {
5+
appNotification: AppNotification;
6+
onClearNotification: (id) => void;
7+
}
8+
9+
export default class AppNotificationItem extends Component<Props> {
10+
shouldComponentUpdate(nextProps) {
11+
return this.props.appNotification.id !== nextProps.appNotification.id;
12+
}
13+
14+
componentDidMount() {
15+
const { appNotification, onClearNotification } = this.props;
16+
setTimeout(() => {
17+
onClearNotification(appNotification.id);
18+
}, appNotification.timeout);
19+
}
20+
21+
render() {
22+
const { appNotification, onClearNotification } = this.props;
23+
return (
24+
<div className={`alert-${appNotification.severity} alert`}>
25+
<div className="alert-icon">
26+
<i className={appNotification.icon} />
27+
</div>
28+
<div className="alert-body">
29+
<div className="alert-title">{appNotification.title}</div>
30+
<div className="alert-text">{appNotification.text}</div>
31+
</div>
32+
<button type="button" className="alert-close" onClick={() => onClearNotification(appNotification.id)}>
33+
<i className="fa fa fa-remove" />
34+
</button>
35+
</div>
36+
);
37+
}
38+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { PureComponent } from 'react';
2+
import appEvents from 'app/core/app_events';
3+
import AppNotificationItem from './AppNotificationItem';
4+
import { notifyApp, clearAppNotification } from 'app/core/actions';
5+
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
6+
import { AppNotification, StoreState } from 'app/types';
7+
import {
8+
createErrorNotification,
9+
createSuccessNotification,
10+
createWarningNotification,
11+
} from '../../copy/appNotification';
12+
13+
export interface Props {
14+
appNotifications: AppNotification[];
15+
notifyApp: typeof notifyApp;
16+
clearAppNotification: typeof clearAppNotification;
17+
}
18+
19+
export class AppNotificationList extends PureComponent<Props> {
20+
componentDidMount() {
21+
const { notifyApp } = this.props;
22+
23+
appEvents.on('alert-warning', options => notifyApp(createWarningNotification(options[0], options[1])));
24+
appEvents.on('alert-success', options => notifyApp(createSuccessNotification(options[0], options[1])));
25+
appEvents.on('alert-error', options => notifyApp(createErrorNotification(options[0], options[1])));
26+
}
27+
28+
onClearAppNotification = id => {
29+
this.props.clearAppNotification(id);
30+
};
31+
32+
render() {
33+
const { appNotifications } = this.props;
34+
35+
return (
36+
<div>
37+
{appNotifications.map((appNotification, index) => {
38+
return (
39+
<AppNotificationItem
40+
key={`${appNotification.id}-${index}`}
41+
appNotification={appNotification}
42+
onClearNotification={id => this.onClearAppNotification(id)}
43+
/>
44+
);
45+
})}
46+
</div>
47+
);
48+
}
49+
}
50+
51+
const mapStateToProps = (state: StoreState) => ({
52+
appNotifications: state.appNotifications.appNotifications,
53+
});
54+
55+
const mapDispatchToProps = {
56+
notifyApp,
57+
clearAppNotification,
58+
};
59+
60+
export default connectWithStore(AppNotificationList, mapStateToProps, mapDispatchToProps);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { AppNotification, AppNotificationSeverity, AppNotificationTimeout } from 'app/types';
2+
3+
const defaultSuccessNotification: AppNotification = {
4+
title: '',
5+
text: '',
6+
severity: AppNotificationSeverity.Success,
7+
icon: 'fa fa-check',
8+
timeout: AppNotificationTimeout.Success,
9+
};
10+
11+
const defaultWarningNotification: AppNotification = {
12+
title: '',
13+
text: '',
14+
severity: AppNotificationSeverity.Warning,
15+
icon: 'fa fa-exclamation',
16+
timeout: AppNotificationTimeout.Warning,
17+
};
18+
19+
const defaultErrorNotification: AppNotification = {
20+
title: '',
21+
text: '',
22+
severity: AppNotificationSeverity.Error,
23+
icon: 'fa fa-exclamation-triangle',
24+
timeout: AppNotificationTimeout.Error,
25+
};
26+
27+
export const createSuccessNotification = (title: string, text?: string): AppNotification => ({
28+
...defaultSuccessNotification,
29+
title: title,
30+
text: text,
31+
id: Date.now(),
32+
});
33+
34+
export const createErrorNotification = (title: string, text?: string): AppNotification => ({
35+
...defaultErrorNotification,
36+
title: title,
37+
text: text,
38+
id: Date.now(),
39+
});
40+
41+
export const createWarningNotification = (title: string, text?: string): AppNotification => ({
42+
...defaultWarningNotification,
43+
title: title,
44+
text: text,
45+
id: Date.now(),
46+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { appNotificationsReducer } from './appNotification';
2+
import { ActionTypes } from '../actions/appNotification';
3+
import { AppNotificationSeverity, AppNotificationTimeout } from 'app/types/';
4+
5+
describe('clear alert', () => {
6+
it('should filter alert', () => {
7+
const id1 = 1540301236048;
8+
const id2 = 1540301248293;
9+
10+
const initialState = {
11+
appNotifications: [
12+
{
13+
id: id1,
14+
severity: AppNotificationSeverity.Success,
15+
icon: 'success',
16+
title: 'test',
17+
text: 'test alert',
18+
timeout: AppNotificationTimeout.Success,
19+
},
20+
{
21+
id: id2,
22+
severity: AppNotificationSeverity.Warning,
23+
icon: 'warning',
24+
title: 'test2',
25+
text: 'test alert fail 2',
26+
timeout: AppNotificationTimeout.Warning,
27+
},
28+
],
29+
};
30+
31+
const result = appNotificationsReducer(initialState, {
32+
type: ActionTypes.ClearAppNotification,
33+
payload: id2,
34+
});
35+
36+
const expectedResult = {
37+
appNotifications: [
38+
{
39+
id: id1,
40+
severity: AppNotificationSeverity.Success,
41+
icon: 'success',
42+
title: 'test',
43+
text: 'test alert',
44+
timeout: AppNotificationTimeout.Success,
45+
},
46+
],
47+
};
48+
49+
expect(result).toEqual(expectedResult);
50+
});
51+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AppNotification, AppNotificationsState } from 'app/types/';
2+
import { Action, ActionTypes } from '../actions/appNotification';
3+
4+
export const initialState: AppNotificationsState = {
5+
appNotifications: [] as AppNotification[],
6+
};
7+
8+
export const appNotificationsReducer = (state = initialState, action: Action): AppNotificationsState => {
9+
switch (action.type) {
10+
case ActionTypes.AddAppNotification:
11+
return { ...state, appNotifications: state.appNotifications.concat([action.payload]) };
12+
case ActionTypes.ClearAppNotification:
13+
return {
14+
...state,
15+
appNotifications: state.appNotifications.filter(appNotification => appNotification.id !== action.payload),
16+
};
17+
}
18+
return state;
19+
};

public/app/core/reducers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { navIndexReducer as navIndex } from './navModel';
22
import { locationReducer as location } from './location';
3+
import { appNotificationsReducer as appNotifications } from './appNotification';
34

45
export default {
56
navIndex,
67
location,
8+
appNotifications,
79
};

public/app/core/services/alert_srv.ts

Lines changed: 4 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,12 @@
1-
import angular from 'angular';
2-
import _ from 'lodash';
31
import coreModule from 'app/core/core_module';
4-
import appEvents from 'app/core/app_events';
52

63
export class AlertSrv {
7-
list: any[];
4+
constructor() {}
85

9-
/** @ngInject */
10-
constructor(private $timeout, private $rootScope) {
11-
this.list = [];
12-
}
13-
14-
init() {
15-
this.$rootScope.onAppEvent(
16-
'alert-error',
17-
(e, alert) => {
18-
this.set(alert[0], alert[1], 'error', 12000);
19-
},
20-
this.$rootScope
21-
);
22-
23-
this.$rootScope.onAppEvent(
24-
'alert-warning',
25-
(e, alert) => {
26-
this.set(alert[0], alert[1], 'warning', 5000);
27-
},
28-
this.$rootScope
29-
);
30-
31-
this.$rootScope.onAppEvent(
32-
'alert-success',
33-
(e, alert) => {
34-
this.set(alert[0], alert[1], 'success', 3000);
35-
},
36-
this.$rootScope
37-
);
38-
39-
appEvents.on('alert-warning', options => this.set(options[0], options[1], 'warning', 5000));
40-
appEvents.on('alert-success', options => this.set(options[0], options[1], 'success', 3000));
41-
appEvents.on('alert-error', options => this.set(options[0], options[1], 'error', 7000));
42-
}
43-
44-
getIconForSeverity(severity) {
45-
switch (severity) {
46-
case 'success':
47-
return 'fa fa-check';
48-
case 'error':
49-
return 'fa fa-exclamation-triangle';
50-
default:
51-
return 'fa fa-exclamation';
52-
}
53-
}
54-
55-
set(title, text, severity, timeout) {
56-
if (_.isObject(text)) {
57-
console.log('alert error', text);
58-
if (text.statusText) {
59-
text = `HTTP Error (${text.status}) ${text.statusText}`;
60-
}
61-
}
62-
63-
const newAlert = {
64-
title: title || '',
65-
text: text || '',
66-
severity: severity || 'info',
67-
icon: this.getIconForSeverity(severity),
68-
};
69-
70-
const newAlertJson = angular.toJson(newAlert);
71-
72-
// remove same alert if it already exists
73-
_.remove(this.list, value => {
74-
return angular.toJson(value) === newAlertJson;
75-
});
76-
77-
this.list.push(newAlert);
78-
if (timeout > 0) {
79-
this.$timeout(() => {
80-
this.list = _.without(this.list, newAlert);
81-
}, timeout);
82-
}
83-
84-
if (!this.$rootScope.$$phase) {
85-
this.$rootScope.$digest();
86-
}
87-
88-
return newAlert;
89-
}
90-
91-
clear(alert) {
92-
this.list = _.without(this.list, alert);
93-
}
94-
95-
clearAll() {
96-
this.list = [];
6+
set() {
7+
console.log('old depricated alert srv being used');
978
}
989
}
9910

11+
// this is just added to not break old plugins that might be using it
10012
coreModule.service('alertSrv', AlertSrv);

0 commit comments

Comments
 (0)