index.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  2. import SelectField from 'sentry/components/forms/fields/selectField';
  3. import Form from 'sentry/components/forms/form';
  4. import type {Data} from 'sentry/components/forms/types';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import NarrowLayout from 'sentry/components/narrowLayout';
  7. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  8. import {t, tct} from 'sentry/locale';
  9. import ConfigStore from 'sentry/stores/configStore';
  10. import type {Organization} from 'sentry/types/organization';
  11. import type {Project} from 'sentry/types/project';
  12. import {useApiQuery, useMutation} from 'sentry/utils/queryClient';
  13. import useApi from 'sentry/utils/useApi';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  16. type TransferDetails = {
  17. organizations: Organization[];
  18. project: Project;
  19. };
  20. function AcceptProjectTransfer() {
  21. const api = useApi({persistInFlight: true});
  22. const location = useLocation();
  23. const regionHost = (): string | undefined => {
  24. // Because this route happens outside of OrganizationContext we
  25. // need to use initial data to decide which host to send the request to
  26. // as `/accept-transfer/` cannot be resolved to a region.
  27. const initialData = window.__initialData;
  28. let host: string | undefined = undefined;
  29. if (initialData && initialData.links?.regionUrl !== initialData.links?.sentryUrl) {
  30. host = initialData.links.regionUrl;
  31. }
  32. return host;
  33. };
  34. const {
  35. data: transferDetails,
  36. isPending,
  37. isError,
  38. error,
  39. } = useApiQuery<TransferDetails>(
  40. ['/accept-transfer/', {query: location.query, host: regionHost()}],
  41. {
  42. staleTime: 0,
  43. }
  44. );
  45. const handleSubmitMutation = useMutation({
  46. mutationFn: (formData: Data) => {
  47. return api.requestPromise('/accept-transfer/', {
  48. method: 'POST',
  49. host: regionHost(),
  50. data: {
  51. data: location.query.data,
  52. organization: formData.organization,
  53. },
  54. });
  55. },
  56. onSuccess: (_, formData) => {
  57. const orgSlug = formData.organization;
  58. const projectSlug = transferDetails?.project.slug;
  59. const sentryUrl = ConfigStore.get('links').sentryUrl;
  60. if (!projectSlug) {
  61. window.location.href = `${sentryUrl}/organizations/${orgSlug}/projects/`;
  62. } else {
  63. window.location.href = `${sentryUrl}/organizations/${orgSlug}/settings/projects/${projectSlug}/teams/`;
  64. // done this way since we need to change subdomains
  65. }
  66. },
  67. onError: () => {
  68. const errorMsg =
  69. error?.responseJSON && typeof error.responseJSON.detail === 'string'
  70. ? error.responseJSON.detail
  71. : '';
  72. addErrorMessage(
  73. tct('Unable to transfer project. [errorMsg]', {errorMsg: errorMsg ?? ''})
  74. );
  75. },
  76. });
  77. if (isPending) {
  78. return <LoadingIndicator />;
  79. }
  80. if (isError) {
  81. // Check if there is an error message with `transferDetails` endpoint
  82. // If so, show as toast and ignore, otherwise log to sentry
  83. if (error?.responseJSON && typeof error.responseJSON.detail === 'string') {
  84. addErrorMessage(error.responseJSON.detail);
  85. }
  86. }
  87. const options = transferDetails?.organizations.map(org => ({
  88. label: org.slug,
  89. value: org.slug,
  90. }));
  91. const organization = options?.[0]?.value;
  92. return (
  93. <NarrowLayout>
  94. <SentryDocumentTitle title={t('Accept Project Transfer')} />
  95. <SettingsPageHeader title={t('Approve Transfer Project Request')} />
  96. <p>
  97. {tct(
  98. 'Projects must be transferred to a specific [organization]. You can grant specific teams access to the project later under the [projectSettings]. (Note that granting access to at least one team is necessary for the project to appear in all parts of the UI.)',
  99. {
  100. organization: <strong>{t('Organization')}</strong>,
  101. projectSettings: <strong>{t('Project Settings')}</strong>,
  102. }
  103. )}
  104. </p>
  105. {transferDetails && (
  106. <p>
  107. {tct('Please select which [organization] you want for the project [project].', {
  108. organization: <strong>{t('Organization')}</strong>,
  109. project: transferDetails.project.slug,
  110. })}
  111. </p>
  112. )}
  113. <Form
  114. onSubmit={data => handleSubmitMutation.mutate(data)}
  115. submitLabel={t('Transfer Project')}
  116. submitPriority="danger"
  117. initialData={organization ? {organization} : undefined}
  118. >
  119. <SelectField
  120. options={options}
  121. label={t('Organization')}
  122. name="organization"
  123. style={{borderBottom: 'none'}}
  124. />
  125. </Form>
  126. </NarrowLayout>
  127. );
  128. }
  129. export default AcceptProjectTransfer;