savedSearchesStore.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import findIndex from 'lodash/findIndex';
  2. import {createStore, StoreDefinition} from 'reflux';
  3. import SavedSearchesActions from 'sentry/actions/savedSearchesActions';
  4. import {SavedSearch, SavedSearchType} from 'sentry/types';
  5. import {makeSafeRefluxStore} from 'sentry/utils/makeSafeRefluxStore';
  6. type State = {
  7. hasError: boolean;
  8. isLoading: boolean;
  9. savedSearches: SavedSearch[];
  10. };
  11. interface SavedSearchesStoreDefinition extends StoreDefinition {
  12. findByQuery(query: string, sort: string): SavedSearch | undefined;
  13. get(): State;
  14. getFilteredSearches(type: SavedSearchType, id?: string): SavedSearch[];
  15. onPinSearch(type: SavedSearchType, query: string, sort: string): void;
  16. reset(): void;
  17. updateExistingSearch(id: string, changes: Partial<SavedSearch>): SavedSearch;
  18. }
  19. const storeConfig: SavedSearchesStoreDefinition = {
  20. unsubscribeListeners: [],
  21. state: {
  22. savedSearches: [],
  23. hasError: false,
  24. isLoading: true,
  25. },
  26. init() {
  27. const {
  28. startFetchSavedSearches,
  29. fetchSavedSearchesSuccess,
  30. fetchSavedSearchesError,
  31. createSavedSearchSuccess,
  32. deleteSavedSearchSuccess,
  33. pinSearch,
  34. pinSearchSuccess,
  35. resetSavedSearches,
  36. unpinSearch,
  37. } = SavedSearchesActions;
  38. this.unsubscribeListeners.push(
  39. this.listenTo(startFetchSavedSearches, this.onStartFetchSavedSearches)
  40. );
  41. this.unsubscribeListeners.push(
  42. this.listenTo(fetchSavedSearchesSuccess, this.onFetchSavedSearchesSuccess)
  43. );
  44. this.unsubscribeListeners.push(
  45. this.listenTo(fetchSavedSearchesError, this.onFetchSavedSearchesError)
  46. );
  47. this.unsubscribeListeners.push(this.listenTo(resetSavedSearches, this.onReset));
  48. this.unsubscribeListeners.push(
  49. this.listenTo(createSavedSearchSuccess, this.onCreateSavedSearchSuccess)
  50. );
  51. this.unsubscribeListeners.push(
  52. this.listenTo(deleteSavedSearchSuccess, this.onDeleteSavedSearchSuccess)
  53. );
  54. this.unsubscribeListeners.push(this.listenTo(pinSearch, this.onPinSearch));
  55. this.unsubscribeListeners.push(
  56. this.listenTo(pinSearchSuccess, this.onPinSearchSuccess)
  57. );
  58. this.unsubscribeListeners.push(this.listenTo(unpinSearch, this.onUnpinSearch));
  59. this.reset();
  60. },
  61. reset() {
  62. this.state = {
  63. savedSearches: [],
  64. hasError: false,
  65. isLoading: true,
  66. };
  67. },
  68. get() {
  69. return this.state;
  70. },
  71. /**
  72. * If pinned search, remove from list if user created pin (e.g. not org saved search and not global)
  73. * Otherwise change `isPinned` to false (e.g. if it's default or org saved search)
  74. */
  75. getFilteredSearches(type, existingSearchId) {
  76. return this.state.savedSearches
  77. .filter(
  78. (savedSearch: SavedSearch) =>
  79. !(
  80. savedSearch.isPinned &&
  81. savedSearch.type === type &&
  82. !savedSearch.isOrgCustom &&
  83. !savedSearch.isGlobal &&
  84. savedSearch.id !== existingSearchId
  85. )
  86. )
  87. .map((savedSearch: SavedSearch) => {
  88. if (
  89. typeof existingSearchId !== 'undefined' &&
  90. existingSearchId === savedSearch.id
  91. ) {
  92. // Do not update existing search
  93. return savedSearch;
  94. }
  95. return {...savedSearch, isPinned: false};
  96. });
  97. },
  98. updateExistingSearch(id, updateObj) {
  99. const index = findIndex(
  100. this.state.savedSearches,
  101. (savedSearch: SavedSearch) => savedSearch.id === id
  102. );
  103. if (index === -1) {
  104. return null;
  105. }
  106. const existingSavedSearch = this.state.savedSearches[index];
  107. const newSavedSearch = {
  108. ...existingSavedSearch,
  109. ...updateObj,
  110. };
  111. this.state.savedSearches[index] = newSavedSearch;
  112. return newSavedSearch;
  113. },
  114. /**
  115. * Find saved search by query string
  116. */
  117. findByQuery(query, sort) {
  118. return this.state.savedSearches.find(
  119. savedSearch => query === savedSearch.query && sort === savedSearch.sort
  120. );
  121. },
  122. /**
  123. * Reset store to initial state
  124. */
  125. onReset() {
  126. this.reset();
  127. this.trigger(this.state);
  128. },
  129. onStartFetchSavedSearches() {
  130. this.state = {
  131. ...this.state,
  132. isLoading: true,
  133. };
  134. this.trigger(this.state);
  135. },
  136. onFetchSavedSearchesSuccess(data) {
  137. if (!Array.isArray(data)) {
  138. data = [];
  139. }
  140. this.state = {
  141. ...this.state,
  142. savedSearches: data,
  143. isLoading: false,
  144. };
  145. this.trigger(this.state);
  146. },
  147. onFetchSavedSearchesError(_resp) {
  148. this.state = {
  149. ...this.state,
  150. savedSearches: [],
  151. isLoading: false,
  152. hasError: true,
  153. };
  154. this.trigger(this.state);
  155. },
  156. onCreateSavedSearchSuccess(resp) {
  157. this.state = {
  158. ...this.state,
  159. savedSearches: [...this.state.savedSearches, resp],
  160. };
  161. this.trigger(this.state);
  162. },
  163. onDeleteSavedSearchSuccess(search) {
  164. this.state = {
  165. ...this.state,
  166. savedSearches: this.state.savedSearches.filter(item => item.id !== search.id),
  167. };
  168. this.trigger(this.state);
  169. },
  170. onPinSearch(type, query, sort) {
  171. const existingSearch = this.findByQuery(query, sort);
  172. if (existingSearch) {
  173. this.updateExistingSearch(existingSearch.id, {isPinned: true});
  174. }
  175. const newPinnedSearch = !existingSearch
  176. ? [
  177. {
  178. id: null,
  179. name: 'My Pinned Search',
  180. type,
  181. query,
  182. sort,
  183. isPinned: true,
  184. },
  185. ]
  186. : [];
  187. this.state = {
  188. ...this.state,
  189. savedSearches: [
  190. ...newPinnedSearch,
  191. // There can only be 1 pinned search, so the rest must be unpinned
  192. // Also if we are pinning an existing search, then filter that out too
  193. ...this.getFilteredSearches(type, existingSearch && existingSearch.id),
  194. ],
  195. };
  196. this.trigger(this.state);
  197. },
  198. onPinSearchSuccess(resp) {
  199. const existingSearch = this.findByQuery(resp.query, resp.sort);
  200. if (existingSearch) {
  201. this.updateExistingSearch(existingSearch.id, resp);
  202. }
  203. this.trigger(this.state);
  204. },
  205. onUnpinSearch(type) {
  206. this.state = {
  207. ...this.state,
  208. // Design decision that there can only be 1 pinned search per `type`
  209. savedSearches: this.getFilteredSearches(type),
  210. };
  211. this.trigger(this.state);
  212. },
  213. };
  214. const SavedSearchesStore = createStore(makeSafeRefluxStore(storeConfig));
  215. export default SavedSearchesStore;