organization.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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 {ApiResult} from 'sentry/api';
  8. import {Client} from 'sentry/api';
  9. import OrganizationStore from 'sentry/stores/organizationStore';
  10. import ProjectsStore from 'sentry/stores/projectsStore';
  11. import TeamStore from 'sentry/stores/teamStore';
  12. import type {Organization, Team} from 'sentry/types/organization';
  13. import type {Project} from 'sentry/types/project';
  14. import FeatureFlagOverrides from 'sentry/utils/featureFlagOverrides';
  15. import {
  16. addOrganizationFeaturesHandler,
  17. buildSentryFeaturesHandler,
  18. } from 'sentry/utils/featureFlags';
  19. import parseLinkHeader from 'sentry/utils/parseLinkHeader';
  20. import type RequestError from 'sentry/utils/requestError/requestError';
  21. async function fetchOrg(api: Client, slug: string): Promise<Organization> {
  22. const [org] = await api.requestPromise(`/organizations/${slug}/`, {
  23. includeAllArgs: true,
  24. query: {detailed: 0, include_feature_flags: 1},
  25. });
  26. if (!org) {
  27. throw new Error('retrieved organization is falsey');
  28. }
  29. FeatureFlagOverrides.singleton().loadOrg(org);
  30. addOrganizationFeaturesHandler({
  31. organization: org,
  32. handler: buildSentryFeaturesHandler('feature.organizations:'),
  33. });
  34. OrganizationStore.onUpdate(org, {replace: true});
  35. setActiveOrganization(org);
  36. const scope = Sentry.getCurrentScope();
  37. // XXX(dcramer): this is duplicated in sdk.py on the backend
  38. scope.setTag('organization', org.id);
  39. scope.setTag('organization.slug', org.slug);
  40. scope.setContext('organization', {id: org.id, slug: org.slug});
  41. return org;
  42. }
  43. async function fetchProjectsAndTeams(
  44. slug: string
  45. ): Promise<[ApiResult<Project[]>, ApiResult<Team[]>]> {
  46. // Create a new client so the request is not cancelled
  47. const uncancelableApi = new Client();
  48. const projectsPromise = uncancelableApi.requestPromise(
  49. `/organizations/${slug}/projects/`,
  50. {
  51. includeAllArgs: true,
  52. query: {
  53. all_projects: 1,
  54. collapse: ['latestDeploys', 'unusedFeatures'],
  55. },
  56. }
  57. );
  58. const teamsPromise = uncancelableApi.requestPromise(`/organizations/${slug}/teams/`, {
  59. includeAllArgs: true,
  60. });
  61. try {
  62. return await Promise.all([projectsPromise, teamsPromise]);
  63. } catch (err) {
  64. // It's possible these requests fail with a 403 if the user has a role with
  65. // insufficient access to projects and teams, but *can* access org details
  66. // (e.g. billing). An example of this is in org settings.
  67. //
  68. // Ignore 403s and bubble up other API errors
  69. if (err.status !== 403) {
  70. throw err;
  71. }
  72. }
  73. return [
  74. [[], undefined, undefined],
  75. [[], undefined, undefined],
  76. ];
  77. }
  78. /**
  79. * Fetches an organization's details
  80. *
  81. * @param api A reference to the api client
  82. * @param slug The organization slug
  83. */
  84. export async function fetchOrganizationDetails(api: Client, slug: string): Promise<void> {
  85. const getErrorMessage = (err: RequestError) => {
  86. if (typeof err.responseJSON?.detail === 'string') {
  87. return err.responseJSON?.detail;
  88. }
  89. if (typeof err.responseJSON?.detail?.message === 'string') {
  90. return err.responseJSON?.detail.message;
  91. }
  92. return null;
  93. };
  94. const loadOrganization = async () => {
  95. let org: Organization | undefined = undefined;
  96. try {
  97. org = await fetchOrg(api, slug);
  98. } catch (err) {
  99. if (!err) {
  100. throw err;
  101. }
  102. OrganizationStore.onFetchOrgError(err);
  103. if (err.status === 403 || err.status === 401) {
  104. const errMessage = getErrorMessage(err);
  105. if (errMessage) {
  106. addErrorMessage(errMessage);
  107. throw errMessage;
  108. }
  109. return undefined;
  110. }
  111. Sentry.captureException(err);
  112. }
  113. return org;
  114. };
  115. const loadTeamsAndProjects = async () => {
  116. const [[projects], [teams, , resp]] = await fetchProjectsAndTeams(slug);
  117. ProjectsStore.loadInitialData(projects ?? []);
  118. const teamPageLinks = resp?.getResponseHeader('Link');
  119. if (teamPageLinks) {
  120. const paginationObject = parseLinkHeader(teamPageLinks);
  121. const hasMore = paginationObject?.next?.results ?? false;
  122. const cursor = paginationObject.next?.cursor;
  123. TeamStore.loadInitialData(teams, hasMore, cursor);
  124. } else {
  125. TeamStore.loadInitialData(teams);
  126. }
  127. return [projects, teams];
  128. };
  129. await Promise.all([loadOrganization(), loadTeamsAndProjects()]);
  130. }