organization.tsx 5.7 KB

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