organization.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import * as Sentry from '@sentry/react';
  2. import {addErrorMessage} from 'app/actionCreators/indicator';
  3. import {setActiveOrganization} from 'app/actionCreators/organizations';
  4. import GlobalSelectionActions from 'app/actions/globalSelectionActions';
  5. import OrganizationActions from 'app/actions/organizationActions';
  6. import ProjectActions from 'app/actions/projectActions';
  7. import TeamActions from 'app/actions/teamActions';
  8. import {Client} from 'app/api';
  9. import ProjectsStore from 'app/stores/projectsStore';
  10. import TeamStore from 'app/stores/teamStore';
  11. import {Organization, Project, Team} from 'app/types';
  12. import {getPreloadedDataPromise} from 'app/utils/getPreloadedData';
  13. async function fetchOrg(
  14. api: Client,
  15. slug: string,
  16. detailed: boolean,
  17. isInitialFetch?: boolean
  18. ): Promise<Organization> {
  19. const detailedQueryParam = detailed ? 1 : 0;
  20. const org = await getPreloadedDataPromise(
  21. `organization?detailed=${detailedQueryParam}`,
  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. query: {detailed: detailedQueryParam},
  28. }),
  29. isInitialFetch
  30. );
  31. if (!org) {
  32. throw new Error('retrieved organization is falsey');
  33. }
  34. OrganizationActions.update(org, {replace: true});
  35. setActiveOrganization(org);
  36. return org;
  37. }
  38. async function fetchProjectsAndTeams(
  39. slug: string,
  40. isInitialFetch?: boolean
  41. ): Promise<[Project[], Team[]]> {
  42. // Create a new client so the request is not cancelled
  43. const uncancelableApi = new Client();
  44. try {
  45. const [projects, teams] = await Promise.all([
  46. getPreloadedDataPromise(
  47. 'projects',
  48. slug,
  49. () =>
  50. // This data should get preloaded in static/sentry/index.ejs
  51. // If this url changes make sure to update the preload
  52. uncancelableApi.requestPromise(`/organizations/${slug}/projects/`, {
  53. query: {
  54. all_projects: 1,
  55. collapse: 'latestDeploys',
  56. },
  57. }),
  58. isInitialFetch
  59. ),
  60. getPreloadedDataPromise(
  61. 'teams',
  62. slug,
  63. // This data should get preloaded in static/sentry/index.ejs
  64. // If this url changes make sure to update the preload
  65. () => uncancelableApi.requestPromise(`/organizations/${slug}/teams/`),
  66. isInitialFetch
  67. ),
  68. ]);
  69. return [projects, teams];
  70. } catch (err) {
  71. // It's possible these requests fail with a 403 if the user has a role with insufficient access
  72. // to projects and teams, but *can* access org details (e.g. billing).
  73. // An example of this is in org settings.
  74. //
  75. // Ignore 403s and bubble up other API errors
  76. if (err.status !== 403) {
  77. throw err;
  78. }
  79. }
  80. return [[], []];
  81. }
  82. /**
  83. * Fetches an organization's details with an option for the detailed representation
  84. * with teams and projects
  85. *
  86. * @param api A reference to the api client
  87. * @param slug The organization slug
  88. * @param detailed Whether or not the detailed org details should be retrieved
  89. * @param silent Should we silently update the organization (do not clear the
  90. * current organization in the store)
  91. */
  92. export async function fetchOrganizationDetails(
  93. api: Client,
  94. slug: string,
  95. detailed: boolean,
  96. silent: boolean,
  97. isInitialFetch?: boolean
  98. ) {
  99. if (!silent) {
  100. OrganizationActions.fetchOrg();
  101. ProjectActions.reset();
  102. GlobalSelectionActions.reset();
  103. }
  104. try {
  105. const promises: Array<Promise<any>> = [fetchOrg(api, slug, detailed, isInitialFetch)];
  106. if (!detailed) {
  107. promises.push(fetchProjectsAndTeams(slug, isInitialFetch));
  108. }
  109. const [org, projectsAndTeams] = await Promise.all(promises);
  110. if (!detailed) {
  111. const [projects, teams] = projectsAndTeams as [Project[], Team[]];
  112. ProjectActions.loadProjects(projects);
  113. TeamActions.loadTeams(teams);
  114. }
  115. if (org && detailed) {
  116. // TODO(davidenwang): Change these to actions after organization.projects
  117. // and organization.teams no longer exists. Currently if they were changed
  118. // to actions it would cause OrganizationContext to rerender many times
  119. TeamStore.loadInitialData(org.teams);
  120. ProjectsStore.loadInitialData(org.projects);
  121. }
  122. } catch (err) {
  123. if (!err) {
  124. return;
  125. }
  126. OrganizationActions.fetchOrgError(err);
  127. if (err.status === 403 || err.status === 401) {
  128. const errMessage =
  129. typeof err.responseJSON?.detail === 'string'
  130. ? err.responseJSON?.detail
  131. : typeof err.responseJSON?.detail?.message === 'string'
  132. ? err.responseJSON?.detail.message
  133. : null;
  134. if (errMessage) {
  135. addErrorMessage(errMessage);
  136. }
  137. return;
  138. }
  139. Sentry.captureException(err);
  140. }
  141. }