organization.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. // XXX(epurkhiser): Ensure the LatestContextStore is initialized before we set
  2. // the active org. Otherwise we will trigger an action that does nothing
  3. import 'sentry/stores/latestContextStore';
  4. import * as Sentry from '@sentry/react';
  5. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  6. import {setActiveOrganization} from 'sentry/actionCreators/organizations';
  7. import ProjectActions from 'sentry/actions/projectActions';
  8. import {Client, ResponseMeta} from 'sentry/api';
  9. import OrganizationStore from 'sentry/stores/organizationStore';
  10. import PageFiltersStore from 'sentry/stores/pageFiltersStore';
  11. import TeamStore from 'sentry/stores/teamStore';
  12. import {Organization, Project, Team} from 'sentry/types';
  13. import {getPreloadedDataPromise} from 'sentry/utils/getPreloadedData';
  14. import parseLinkHeader from 'sentry/utils/parseLinkHeader';
  15. async function fetchOrg(
  16. api: Client,
  17. slug: string,
  18. isInitialFetch?: boolean
  19. ): Promise<Organization> {
  20. const [org] = await getPreloadedDataPromise(
  21. 'organization',
  22. slug,
  23. () =>
  24. // This data should get preloaded in static/sentry/index.ejs
  25. // If this url changes make sure to update the preload
  26. api.requestPromise(`/organizations/${slug}/`, {
  27. includeAllArgs: true,
  28. query: {detailed: 0},
  29. }),
  30. isInitialFetch
  31. );
  32. if (!org) {
  33. throw new Error('retrieved organization is falsey');
  34. }
  35. OrganizationStore.onUpdate(org, {replace: true});
  36. setActiveOrganization(org);
  37. return org;
  38. }
  39. async function fetchProjectsAndTeams(
  40. slug: string,
  41. isInitialFetch?: boolean
  42. ): Promise<
  43. [
  44. [Project[], string | undefined, XMLHttpRequest | ResponseMeta | undefined],
  45. [Team[], string | undefined, XMLHttpRequest | ResponseMeta | undefined]
  46. ]
  47. > {
  48. // Create a new client so the request is not cancelled
  49. const uncancelableApi = new Client();
  50. const projectsPromise = getPreloadedDataPromise(
  51. 'projects',
  52. slug,
  53. () =>
  54. // This data should get preloaded in static/sentry/index.ejs
  55. // If this url changes make sure to update the preload
  56. uncancelableApi.requestPromise(`/organizations/${slug}/projects/`, {
  57. includeAllArgs: true,
  58. query: {
  59. all_projects: 1,
  60. collapse: 'latestDeploys',
  61. },
  62. }),
  63. isInitialFetch
  64. );
  65. const teamsPromise = getPreloadedDataPromise(
  66. 'teams',
  67. slug,
  68. // This data should get preloaded in static/sentry/index.ejs
  69. // If this url changes make sure to update the preload
  70. () =>
  71. uncancelableApi.requestPromise(`/organizations/${slug}/teams/`, {
  72. includeAllArgs: true,
  73. }),
  74. isInitialFetch
  75. );
  76. try {
  77. return await Promise.all([projectsPromise, teamsPromise]);
  78. } catch (err) {
  79. // It's possible these requests fail with a 403 if the user has a role with
  80. // insufficient access to projects and teams, but *can* access org details
  81. // (e.g. billing). An example of this is in org settings.
  82. //
  83. // Ignore 403s and bubble up other API errors
  84. if (err.status !== 403) {
  85. throw err;
  86. }
  87. }
  88. return [
  89. [[], undefined, undefined],
  90. [[], undefined, undefined],
  91. ];
  92. }
  93. /**
  94. * Fetches an organization's details
  95. *
  96. * @param api A reference to the api client
  97. * @param slug The organization slug
  98. * @param silent Should we silently update the organization (do not clear the
  99. * current organization in the store)
  100. */
  101. export function fetchOrganizationDetails(
  102. api: Client,
  103. slug: string,
  104. silent: boolean,
  105. isInitialFetch?: boolean
  106. ) {
  107. if (!silent) {
  108. OrganizationStore.reset();
  109. ProjectActions.reset();
  110. TeamStore.reset();
  111. PageFiltersStore.onReset();
  112. }
  113. const loadOrganization = async () => {
  114. try {
  115. await fetchOrg(api, slug, isInitialFetch);
  116. } catch (err) {
  117. if (!err) {
  118. return;
  119. }
  120. OrganizationStore.onFetchOrgError(err);
  121. if (err.status === 403 || err.status === 401) {
  122. const errMessage =
  123. typeof err.responseJSON?.detail === 'string'
  124. ? err.responseJSON?.detail
  125. : typeof err.responseJSON?.detail?.message === 'string'
  126. ? err.responseJSON?.detail.message
  127. : null;
  128. if (errMessage) {
  129. addErrorMessage(errMessage);
  130. }
  131. return;
  132. }
  133. Sentry.captureException(err);
  134. }
  135. };
  136. const loadTeamsAndProjects = async () => {
  137. const [[projects], [teams, , resp]] = await fetchProjectsAndTeams(
  138. slug,
  139. isInitialFetch
  140. );
  141. ProjectActions.loadProjects(projects);
  142. const teamPageLinks = resp?.getResponseHeader('Link');
  143. if (teamPageLinks) {
  144. const paginationObject = parseLinkHeader(teamPageLinks);
  145. const hasMore = paginationObject?.next?.results ?? false;
  146. const cursor = paginationObject.next?.cursor;
  147. TeamStore.loadInitialData(teams, hasMore, cursor);
  148. } else {
  149. TeamStore.loadInitialData(teams);
  150. }
  151. };
  152. return Promise.all([loadOrganization(), loadTeamsAndProjects()]);
  153. }