organization.tsx 5.2 KB

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