organization.tsx 5.9 KB

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