releasesProvider.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import {createContext, useContext, useEffect, useState} from 'react';
  2. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  3. import {Client} from 'sentry/api';
  4. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  5. import {t} from 'sentry/locale';
  6. import {Organization, PageFilters, Release} from 'sentry/types';
  7. import handleXhrErrorResponse from 'sentry/utils/handleXhrErrorResponse';
  8. import useApi from '../useApi';
  9. function fetchReleases(
  10. api: Client,
  11. orgSlug: string,
  12. selection: PageFilters,
  13. search: string
  14. ) {
  15. const {environments, projects, datetime} = selection;
  16. return api.requestPromise(`/organizations/${orgSlug}/releases/`, {
  17. method: 'GET',
  18. data: {
  19. sort: 'date',
  20. project: projects,
  21. per_page: 50,
  22. environment: environments,
  23. query: search,
  24. ...normalizeDateTimeParams(datetime),
  25. },
  26. });
  27. }
  28. type ReleasesProviderProps = {
  29. children: React.ReactNode;
  30. organization: Organization;
  31. selection: PageFilters;
  32. skipLoad?: boolean;
  33. };
  34. function ReleasesProvider({
  35. children,
  36. organization,
  37. selection,
  38. skipLoad = false,
  39. }: ReleasesProviderProps) {
  40. const api = useApi();
  41. const [releases, setReleases] = useState<Release[]>([]);
  42. const [searchTerm, setSearchTerm] = useState<string>('');
  43. const [loading, setLoading] = useState(true);
  44. function handleSearch(search: string) {
  45. setSearchTerm(search);
  46. }
  47. useEffect(() => {
  48. if (skipLoad) {
  49. setLoading(false);
  50. return undefined;
  51. }
  52. let shouldCancelRequest = false;
  53. setLoading(true);
  54. fetchReleases(api, organization.slug, selection, searchTerm)
  55. .then(response => {
  56. if (shouldCancelRequest) {
  57. setLoading(false);
  58. return;
  59. }
  60. setLoading(false);
  61. setReleases(response);
  62. })
  63. .catch(e => {
  64. if (shouldCancelRequest) {
  65. setLoading(false);
  66. return;
  67. }
  68. const errorResponse = e?.responseJSON ?? t('Unable to fetch releases');
  69. addErrorMessage(errorResponse);
  70. setLoading(false);
  71. handleXhrErrorResponse(errorResponse)(e);
  72. });
  73. return () => {
  74. shouldCancelRequest = true;
  75. };
  76. // eslint-disable-next-line react-hooks/exhaustive-deps
  77. }, [skipLoad, api, organization.slug, JSON.stringify(selection), searchTerm]);
  78. return (
  79. <ReleasesContext.Provider value={{releases, loading, onSearch: handleSearch}}>
  80. {children}
  81. </ReleasesContext.Provider>
  82. );
  83. }
  84. interface ReleasesContextValue {
  85. loading: boolean;
  86. /**
  87. * This is an action provided to consumers for them to update the current
  88. * releases result set using a simple search query.
  89. *
  90. * Will always add new options into the store.
  91. */
  92. onSearch: (searchTerm: string) => void;
  93. releases: Release[];
  94. }
  95. const ReleasesContext = createContext<ReleasesContextValue | undefined>(undefined);
  96. function useReleases() {
  97. const releasesContext = useContext(ReleasesContext);
  98. if (!releasesContext) {
  99. throw new Error('releasesContext was called outside of ReleasesProvider');
  100. }
  101. return releasesContext;
  102. }
  103. const ReleasesConsumer = ReleasesContext.Consumer;
  104. export {ReleasesContext, ReleasesConsumer, ReleasesProvider, useReleases};