pageFiltersStore.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import {createStore} from 'reflux';
  2. import {getDefaultSelection} from 'sentry/components/organizations/pageFilters/utils';
  3. import type {PageFilters, PinnedPageFilter} from 'sentry/types/core';
  4. import {valueIsEqual} from 'sentry/utils/object/valueIsEqual';
  5. import type {StrictStoreDefinition} from './types';
  6. function datetimeHasSameValue(
  7. a: PageFilters['datetime'],
  8. b: PageFilters['datetime']
  9. ): boolean {
  10. if (Object.keys(a).length !== Object.keys(b).length) {
  11. return false;
  12. }
  13. for (const key in a) {
  14. if (a[key] instanceof Date && b[key] instanceof Date) {
  15. // This will fail on invalid dates as NaN !== NaN,
  16. // but thats fine since we don't want invalid dates to be equal
  17. if (a[key].getTime() === b[key].getTime()) {
  18. continue;
  19. }
  20. return false;
  21. }
  22. if (a[key] === null && b[key] === null) {
  23. continue;
  24. }
  25. if (a[key] !== b[key]) {
  26. return false;
  27. }
  28. }
  29. return true;
  30. }
  31. interface PageFiltersState {
  32. /**
  33. * The set of page filters which have been pinned but do not match the current
  34. * URL state.
  35. */
  36. desyncedFilters: Set<PinnedPageFilter>;
  37. /**
  38. * Are page filters ready?
  39. */
  40. isReady: boolean;
  41. /**
  42. * The set of page filters which are currently pinned
  43. */
  44. pinnedFilters: Set<PinnedPageFilter>;
  45. /**
  46. * The current page filter selection
  47. */
  48. selection: PageFilters;
  49. /**
  50. * Whether to save changes to local storage. This setting should be page-specific:
  51. * most pages should have it on (default) and some, like Dashboard Details, need it
  52. * off.
  53. */
  54. shouldPersist: boolean;
  55. }
  56. interface PageFiltersStoreDefinition extends StrictStoreDefinition<PageFiltersState> {
  57. onInitializeUrlState(
  58. newSelection: PageFilters,
  59. pinned: Set<PinnedPageFilter>,
  60. persist?: boolean
  61. ): void;
  62. onReset(): void;
  63. pin(filter: PinnedPageFilter, pin: boolean): void;
  64. reset(selection?: PageFilters): void;
  65. updateDateTime(datetime: PageFilters['datetime']): void;
  66. updateDesyncedFilters(filters: Set<PinnedPageFilter>): void;
  67. updateEnvironments(environments: string[] | null): void;
  68. updatePersistence(shouldPersist: boolean): void;
  69. updateProjects(projects: PageFilters['projects'], environments: null | string[]): void;
  70. }
  71. const storeConfig: PageFiltersStoreDefinition = {
  72. state: {
  73. isReady: false,
  74. selection: getDefaultSelection(),
  75. pinnedFilters: new Set(),
  76. desyncedFilters: new Set(),
  77. shouldPersist: true,
  78. },
  79. init() {
  80. // XXX: Do not use `this.listenTo` in this store. We avoid usage of reflux
  81. // listeners due to their leaky nature in tests.
  82. this.reset(this.state.selection);
  83. },
  84. reset(selection) {
  85. this.state = {
  86. ...this.state,
  87. isReady: false,
  88. selection: selection || getDefaultSelection(),
  89. pinnedFilters: new Set(),
  90. };
  91. },
  92. /**
  93. * Initializes the page filters store data
  94. */
  95. onInitializeUrlState(newSelection, pinned, persist = true) {
  96. this.state = {
  97. ...this.state,
  98. isReady: true,
  99. selection: newSelection,
  100. pinnedFilters: pinned,
  101. shouldPersist: persist,
  102. };
  103. this.trigger(this.getState());
  104. },
  105. getState() {
  106. return this.state;
  107. },
  108. onReset() {
  109. this.reset();
  110. this.trigger(this.getState());
  111. },
  112. updatePersistence(shouldPersist: boolean) {
  113. this.state = {...this.state, shouldPersist};
  114. this.trigger(this.getState());
  115. },
  116. updateDesyncedFilters(filters: Set<PinnedPageFilter>) {
  117. this.state = {...this.state, desyncedFilters: filters};
  118. this.trigger(this.getState());
  119. },
  120. updateProjects(projects = [], environments = null) {
  121. if (valueIsEqual(this.state.selection.projects, projects)) {
  122. return;
  123. }
  124. const newDesyncedFilters = new Set(this.state.desyncedFilters);
  125. if (this.state.desyncedFilters.has('projects')) {
  126. newDesyncedFilters.delete('projects');
  127. }
  128. const selection = {
  129. ...this.state.selection,
  130. projects,
  131. environments:
  132. environments === null ? this.state.selection.environments : environments,
  133. };
  134. this.state = {...this.state, selection, desyncedFilters: newDesyncedFilters};
  135. this.trigger(this.getState());
  136. },
  137. updateDateTime(newDateTime) {
  138. if (datetimeHasSameValue(this.state.selection.datetime, newDateTime)) {
  139. return;
  140. }
  141. const newDesyncedFilters = new Set(this.state.desyncedFilters);
  142. if (this.state.desyncedFilters.has('datetime')) {
  143. newDesyncedFilters.delete('datetime');
  144. }
  145. this.state = {
  146. ...this.state,
  147. selection: {
  148. ...this.state.selection,
  149. datetime: newDateTime,
  150. },
  151. desyncedFilters: newDesyncedFilters,
  152. };
  153. this.trigger(this.getState());
  154. },
  155. updateEnvironments(environments) {
  156. if (valueIsEqual(this.state.selection.environments, environments)) {
  157. return;
  158. }
  159. const newDesyncedFilters = new Set(this.state.desyncedFilters);
  160. if (this.state.desyncedFilters.has('environments')) {
  161. newDesyncedFilters.delete('environments');
  162. }
  163. this.state = {
  164. ...this.state,
  165. desyncedFilters: newDesyncedFilters,
  166. selection: {
  167. ...this.state.selection,
  168. environments: environments ?? [],
  169. },
  170. };
  171. this.trigger(this.getState());
  172. },
  173. pin(filter, pin) {
  174. const newPinnedFilters = new Set(this.state.pinnedFilters);
  175. if (pin) {
  176. newPinnedFilters.add(filter);
  177. } else {
  178. newPinnedFilters.delete(filter);
  179. }
  180. this.state = {...this.state, pinnedFilters: newPinnedFilters};
  181. this.trigger(this.getState());
  182. },
  183. };
  184. const PageFiltersStore = createStore(storeConfig);
  185. export default PageFiltersStore;