alertStore.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import Reflux from 'reflux';
  2. import AlertActions from 'sentry/actions/alertActions';
  3. import {defined} from 'sentry/utils';
  4. import localStorage from 'sentry/utils/localStorage';
  5. import {Theme} from 'sentry/utils/theme';
  6. import {CommonStoreInterface} from './types';
  7. type Alert = {
  8. message: React.ReactNode;
  9. type: keyof Theme['alert'];
  10. opaque?: boolean;
  11. expireAfter?: number;
  12. key?: number;
  13. id?: string;
  14. url?: string;
  15. neverExpire?: boolean;
  16. noDuplicates?: boolean;
  17. onClose?: () => void;
  18. };
  19. type AlertStoreInterface = CommonStoreInterface<Alert[]> & {
  20. init(): void;
  21. onAddAlert(alert: Alert): void;
  22. onCloseAlert(alert: Alert, duration?: number): void;
  23. };
  24. type Internals = {
  25. alerts: Alert[];
  26. count: number;
  27. };
  28. const storeConfig: Reflux.StoreDefinition & Internals & AlertStoreInterface = {
  29. listenables: AlertActions,
  30. alerts: [],
  31. count: 0,
  32. init() {
  33. this.alerts = [];
  34. this.count = 0;
  35. },
  36. onAddAlert(alert) {
  37. const alertAlreadyExists = this.alerts.some(a => a.id === alert.id);
  38. if (alertAlreadyExists && alert.noDuplicates) {
  39. return;
  40. }
  41. if (defined(alert.id)) {
  42. const mutedData = localStorage.getItem('alerts:muted');
  43. if (typeof mutedData === 'string' && mutedData.length) {
  44. const expirations: Record<string, number> = JSON.parse(mutedData);
  45. // Remove any objects that have passed their mute duration.
  46. const now = Math.floor(new Date().valueOf() / 1000);
  47. for (const key in expirations) {
  48. if (expirations.hasOwnProperty(key) && expirations[key] < now) {
  49. delete expirations[key];
  50. }
  51. }
  52. localStorage.setItem('alerts:muted', JSON.stringify(expirations));
  53. if (expirations.hasOwnProperty(alert.id)) {
  54. return;
  55. }
  56. }
  57. } else {
  58. if (!defined(alert.expireAfter)) {
  59. alert.expireAfter = 5000;
  60. }
  61. }
  62. if (alert.expireAfter && !alert.neverExpire) {
  63. window.setTimeout(() => {
  64. this.onCloseAlert(alert);
  65. }, alert.expireAfter);
  66. }
  67. alert.key = this.count++;
  68. // intentionally recreate array via concat because of Reflux
  69. // "bug" where React components are given same reference to tracked
  70. // data objects, and don't *see* that values have changed
  71. this.alerts = this.alerts.concat([alert]);
  72. this.trigger(this.alerts);
  73. },
  74. onCloseAlert(alert, duration = 60 * 60 * 7 * 24) {
  75. if (defined(alert.id) && defined(duration)) {
  76. const expiry = Math.floor(new Date().valueOf() / 1000) + duration;
  77. const mutedData = localStorage.getItem('alerts:muted');
  78. let expirations: Record<string, number> = {};
  79. if (typeof mutedData === 'string' && expirations.length) {
  80. expirations = JSON.parse(mutedData);
  81. }
  82. expirations[alert.id] = expiry;
  83. localStorage.setItem('alerts:muted', JSON.stringify(expirations));
  84. }
  85. // TODO(dcramer): we need some animations here for closing alerts
  86. this.alerts = this.alerts.filter(item => alert !== item);
  87. this.trigger(this.alerts);
  88. },
  89. getState() {
  90. return this.alerts;
  91. },
  92. };
  93. const AlertStore = Reflux.createStore(storeConfig) as Reflux.Store & AlertStoreInterface;
  94. export default AlertStore;