releasesProvider.tsx 3.2 KB

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