index.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {Component, Fragment} from 'react';
  2. import {browserHistory, RouteComponentProps} from 'react-router';
  3. import {switchOrganization} from 'app/actionCreators/organizations';
  4. import AlertActions from 'app/actions/alertActions';
  5. import {Client} from 'app/api';
  6. import Alert from 'app/components/alert';
  7. import Button from 'app/components/button';
  8. import ErrorBoundary from 'app/components/errorBoundary';
  9. import Footer from 'app/components/footer';
  10. import {Body, Main} from 'app/components/layouts/thirds';
  11. import {IconWarning} from 'app/icons';
  12. import {t, tct} from 'app/locale';
  13. import {Organization} from 'app/types';
  14. import getRouteStringFromRoutes from 'app/utils/getRouteStringFromRoutes';
  15. import withOrganization from 'app/utils/withOrganization';
  16. import OrganizationContext from 'app/views/organizationContext';
  17. type InProgressProps = {
  18. organization: Organization;
  19. };
  20. function DeletionInProgress({organization}: InProgressProps) {
  21. return (
  22. <Body>
  23. <Main>
  24. <Alert type="warning" icon={<IconWarning />}>
  25. {tct(
  26. 'The [organization] organization is currently in the process of being deleted from Sentry.',
  27. {
  28. organization: <strong>{organization.slug}</strong>,
  29. }
  30. )}
  31. </Alert>
  32. </Main>
  33. </Body>
  34. );
  35. }
  36. type PendingProps = {
  37. organization: Organization;
  38. };
  39. type PendingState = {
  40. submitInProgress: boolean;
  41. };
  42. class DeletionPending extends Component<PendingProps, PendingState> {
  43. state: PendingState = {submitInProgress: false};
  44. componentWillUnmount() {
  45. this.api.clear();
  46. }
  47. api = new Client();
  48. onRestore = () => {
  49. if (this.state.submitInProgress) {
  50. return;
  51. }
  52. this.setState({submitInProgress: true});
  53. this.api.request(`/organizations/${this.props.organization.slug}/`, {
  54. method: 'PUT',
  55. data: {cancelDeletion: true},
  56. success: () => {
  57. window.location.reload();
  58. },
  59. error: () => {
  60. AlertActions.addAlert({
  61. message:
  62. 'We were unable to restore this organization. Please try again or contact support.',
  63. type: 'error',
  64. });
  65. this.setState({submitInProgress: false});
  66. },
  67. });
  68. };
  69. render() {
  70. const {organization} = this.props;
  71. const access = new Set(organization.access);
  72. return (
  73. <Body>
  74. <Main>
  75. <h3>{t('Deletion Scheduled')}</h3>
  76. <p>
  77. {tct('The [organization] organization is currently scheduled for deletion.', {
  78. organization: <strong>{organization.slug}</strong>,
  79. })}
  80. </p>
  81. {access.has('org:admin') ? (
  82. <div>
  83. <p>
  84. {t(
  85. 'Would you like to cancel this process and restore the organization back to the original state?'
  86. )}
  87. </p>
  88. <p>
  89. <Button
  90. priority="primary"
  91. onClick={this.onRestore}
  92. disabled={this.state.submitInProgress}
  93. >
  94. {t('Restore Organization')}
  95. </Button>
  96. </p>
  97. </div>
  98. ) : (
  99. <p>
  100. {t(
  101. 'If this is a mistake, contact an organization owner and ask them to restore this organization.'
  102. )}
  103. </p>
  104. )}
  105. <p>
  106. <small>
  107. {t(
  108. "Note: Restoration is available until the process begins. Once it does, there's no recovering the data that has been removed."
  109. )}
  110. </small>
  111. </p>
  112. </Main>
  113. </Body>
  114. );
  115. }
  116. }
  117. type OrganizationDetailsProps = {
  118. organization?: Organization;
  119. children?: React.ReactNode;
  120. };
  121. const OrganizationDetailsBody = withOrganization(function OrganizationDetailsBody({
  122. children,
  123. organization,
  124. }: OrganizationDetailsProps) {
  125. const status = organization?.status?.id;
  126. if (organization && status === 'pending_deletion') {
  127. return <DeletionPending organization={organization} />;
  128. }
  129. if (organization && status === 'deletion_in_progress') {
  130. return <DeletionInProgress organization={organization} />;
  131. }
  132. return (
  133. <Fragment>
  134. <ErrorBoundary>{children}</ErrorBoundary>
  135. <Footer />
  136. </Fragment>
  137. );
  138. });
  139. type Props = {
  140. detailed: boolean;
  141. } & RouteComponentProps<{orgId: string}, {}>;
  142. export default class OrganizationDetails extends Component<Props> {
  143. componentDidMount() {
  144. const {routes} = this.props;
  145. const isOldRoute = getRouteStringFromRoutes(routes) === '/:orgId/';
  146. if (isOldRoute) {
  147. browserHistory.replace(`/organizations/${this.props.params.orgId}/`);
  148. }
  149. }
  150. componentDidUpdate(prevProps: Props) {
  151. if (
  152. prevProps.params &&
  153. this.props.params &&
  154. prevProps.params.orgId !== this.props.params.orgId
  155. ) {
  156. switchOrganization();
  157. }
  158. }
  159. render() {
  160. return (
  161. <OrganizationContext includeSidebar useLastOrganization {...this.props}>
  162. <OrganizationDetailsBody {...this.props}>
  163. {this.props.children}
  164. </OrganizationDetailsBody>
  165. </OrganizationContext>
  166. );
  167. }
  168. }
  169. export function LightWeightOrganizationDetails(props: Omit<Props, 'detailed'>) {
  170. return <OrganizationDetails detailed={false} {...props} />;
  171. }