alertStore.tsx 3.1 KB

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