Skip to content

Commit e8ab350

Browse files
authored
feat: add overlay for deleting PAYG and Contract orgs (#6379)
* feat: add overlay for deleting PAYG and Contract orgs * chore: refactor delete org overlay * chore: resolve nits from code review * fix: cant exit overlay while deleting an org * chore: add explicit flag to default org notification, refactor [skip CI] * chore: change support message, trigger notification based on new org, not redirect from orgslist * chore: move delete notification into helper function [skip CI] * chore: refactor to alternative design (notify, then redirect) * chore: add upper bound, notification for delayed responses * chore: fix formatting of error notifications * chore: replace magic numbers with constants
1 parent 5a604d7 commit e8ab350

File tree

9 files changed

+341
-7
lines changed

9 files changed

+341
-7
lines changed

src/accounts/CancellationTerms.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const CancellationTerms: FC<Props> = ({hasAgreedToTerms, onAgreedToTerms}) => (
2828
</li>
2929
<li>
3030
Before continuing, you are responsible for exporting any data or content
31-
(including dashboards, tasks and variables) you wish to keep. (edited)
31+
(including dashboards, tasks, and variables) you wish to keep. (edited)
3232
</li>
3333
</ul>
3434
<span onClick={onAgreedToTerms} data-testid="agree-terms--input">

src/identity/selectors/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
import {AppState} from 'src/types'
33
import {RemoteDataState} from '@influxdata/clockface'
44

5+
export const selectCurrentAccount = (
6+
state: AppState
7+
): AppState['identity']['currentIdentity']['account'] => {
8+
return state.identity.currentIdentity.account
9+
}
10+
511
export const selectCurrentAccountId = (
612
state: AppState
713
): AppState['identity']['currentIdentity']['account']['id'] => {
@@ -20,6 +26,12 @@ export const selectCurrentIdentity = (
2026
return state.identity.currentIdentity
2127
}
2228

29+
export const selectCurrentOrg = (
30+
state: AppState
31+
): AppState['identity']['currentIdentity']['org'] => {
32+
return state.identity.currentIdentity.org
33+
}
34+
2335
export const selectCurrentOrgId = (
2436
state: AppState
2537
): AppState['identity']['currentIdentity']['org']['id'] => {

src/organizations/components/DeleteOrgOverlay.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ const DeleteOrgOverlay: FC = () => {
120120
</li>
121121
<li>
122122
Before continuing, you are responsible for exporting any data or
123-
content - including dashboards, tasks and variable - from the user
124-
interface.
123+
content - including dashboards, tasks, and variables - from the
124+
user interface.
125125
</li>
126126
</ul>
127127
<span
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
@import '@influxdata/clockface/dist/variables.scss';
2+
3+
.org-delete-overlay {
4+
color: #c6cad3;
5+
font-size: $cf-text-base-1;
6+
font-weight: 400;
7+
ul {
8+
margin-top: 0px;
9+
padding-left: $cf-space-m;
10+
11+
li {
12+
color: #c6cad3;
13+
font-size: $cf-text-base-1;
14+
font-weight: 400;
15+
}
16+
}
17+
}
18+
19+
.org-delete-overlay--accept-terms-radio-button {
20+
margin-right: $cf-space-xs;
21+
}
22+
23+
.org-delete-overlay--conditions-instruction {
24+
color: #c6cad3;
25+
font-size: $cf-text-base-1;
26+
font-weight: 400;
27+
28+
label {
29+
align-items: center;
30+
align-self: center;
31+
}
32+
}
33+
34+
.org-delete-overlay--warning-message {
35+
color: #c6cad3;
36+
font-size: $cf-text-base-1;
37+
font-weight: 500;
38+
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Libraries
2+
import React, {FC, useContext, useState} from 'react'
3+
import {useDispatch, useSelector} from 'react-redux'
4+
import {
5+
Alert,
6+
AlignItems,
7+
Button,
8+
ComponentColor,
9+
ComponentSize,
10+
ComponentStatus,
11+
FlexBox,
12+
FlexDirection,
13+
IconFont,
14+
Input,
15+
InputLabel,
16+
InputType,
17+
Overlay,
18+
} from '@influxdata/clockface'
19+
20+
// Selectors
21+
import {
22+
selectCurrentAccount,
23+
selectCurrentOrg,
24+
selectUser,
25+
} from 'src/identity/selectors'
26+
27+
// API
28+
import {deleteOrganization} from 'src/identity/apis/org'
29+
30+
// Notifications
31+
import {
32+
deleteOrgDelayed,
33+
deleteOrgFailed,
34+
deleteOrgSuccess,
35+
} from 'src/shared/copy/notifications'
36+
import {notify} from 'src/shared/actions/notifications'
37+
38+
// Utils
39+
import {OverlayContext} from 'src/overlays/components/OverlayController'
40+
import {reportErrorThroughHoneyBadger} from 'src/shared/utils/errors'
41+
42+
// Styles
43+
import './DeletePaidOrgOverlay.scss'
44+
45+
// Constants
46+
import {CLOUD_URL} from 'src/shared/constants'
47+
const WAIT_IN_MS_BEFORE_REDIRECTING_TO_DEFAULT_ORG = 4000
48+
const WAIT_IN_MS_BEFORE_SHOWING_DELAY_NOTIFICATION = 5000
49+
50+
// Required to avoid unwarranted console errors from clockface <Input /> when type=radio.
51+
const noop = () => null
52+
53+
const linkStyle = {
54+
color: 'white',
55+
textDecoration: 'underline',
56+
}
57+
58+
const SupportLink = (): JSX.Element => {
59+
return (
60+
<a
61+
data-testid="go-to-new-org--link"
62+
href="mailto:support@influxdata.com"
63+
style={linkStyle}
64+
target="_blank"
65+
>
66+
support@influxdata.com
67+
</a>
68+
)
69+
}
70+
71+
export const DeletePaidOrgOverlay: FC = () => {
72+
const account = useSelector(selectCurrentAccount)
73+
const org = useSelector(selectCurrentOrg)
74+
const user = useSelector(selectUser)
75+
const dispatch = useDispatch()
76+
const {onClose} = useContext(OverlayContext)
77+
78+
const [deleteButtonStatus, setDeleteButtonStatus] = useState(
79+
ComponentStatus.Disabled
80+
)
81+
const [userAcceptedTerms, setUserAcceptedTerms] = useState(false)
82+
83+
const orgDeleteInProgress = deleteButtonStatus === ComponentStatus.Loading
84+
const onClickCancel = orgDeleteInProgress ? noop : onClose
85+
86+
const toggleAcceptedTerms = () => {
87+
const currentAcceptanceStatus = !userAcceptedTerms
88+
setUserAcceptedTerms(currentAcceptanceStatus)
89+
90+
setDeleteButtonStatus(
91+
currentAcceptanceStatus
92+
? ComponentStatus.Default
93+
: ComponentStatus.Disabled
94+
)
95+
}
96+
97+
const handleDeleteOrg = () => {
98+
setDeleteButtonStatus(ComponentStatus.Loading)
99+
100+
const responseTimer = setTimeout(() => {
101+
dispatch(notify(deleteOrgDelayed(SupportLink)))
102+
103+
toggleAcceptedTerms()
104+
}, WAIT_IN_MS_BEFORE_SHOWING_DELAY_NOTIFICATION)
105+
106+
deleteOrganization(org.id)
107+
.then(() => {
108+
clearTimeout(responseTimer)
109+
dispatch(notify(deleteOrgSuccess(org.name, account.name)))
110+
setTimeout(() => {
111+
onClose()
112+
window.location.href = CLOUD_URL
113+
}, WAIT_IN_MS_BEFORE_REDIRECTING_TO_DEFAULT_ORG)
114+
})
115+
.catch(err => {
116+
clearTimeout(responseTimer)
117+
dispatch(notify(deleteOrgFailed(SupportLink, org.name)))
118+
reportErrorThroughHoneyBadger(err, {
119+
name: 'Org deletion failed',
120+
context: {
121+
user,
122+
account,
123+
org,
124+
},
125+
})
126+
setDeleteButtonStatus(ComponentStatus.Disabled)
127+
setUserAcceptedTerms(false)
128+
})
129+
}
130+
131+
return (
132+
<Overlay.Container
133+
className="org-delete-overlay"
134+
maxWidth={600}
135+
testID="create-org-overlay--container"
136+
>
137+
<Overlay.Header
138+
onDismiss={onClickCancel}
139+
testID="create-org-overlay--header"
140+
title="Delete Organization"
141+
/>
142+
<Overlay.Body>
143+
<Alert
144+
className="org-delete-overlay--warning-message"
145+
color={ComponentColor.Danger}
146+
icon={IconFont.AlertTriangle}
147+
>
148+
You will be able to recover this organization's data for up to 7 days
149+
by contacting support. It will be unrecoverable afterwards.
150+
</Alert>
151+
<br />
152+
<ul>
153+
<li>
154+
All of your writes, queries, and tasks for <b>{org.name}</b> will be{' '}
155+
<b>suspended immediately</b>.
156+
</li>
157+
<li>
158+
Your final billing statement will be calculated for any usage and
159+
storage incurred prior to the deletion of your organization and your
160+
credit card will be charged for the amount.
161+
</li>
162+
<li>
163+
Before continuing, you are responsible for exporting any data or
164+
content - including dashboards, tasks, and variables - from the user
165+
interface.
166+
</li>
167+
<li>
168+
On deleting this organization, you will return to your default
169+
organization in the current account. If this is the last
170+
organization in your account, you will be prompted to create a new
171+
organization.
172+
</li>
173+
</ul>
174+
<FlexBox
175+
alignItems={AlignItems.FlexStart}
176+
className="org-delete-overlay--conditions-instruction"
177+
direction={FlexDirection.Row}
178+
>
179+
<FlexBox
180+
data-testid="org-delete-overlay--accept-terms-box"
181+
direction={FlexDirection.Row}
182+
onClick={toggleAcceptedTerms}
183+
>
184+
<Input
185+
checked={userAcceptedTerms}
186+
className="org-delete-overlay--accept-terms-radio-button"
187+
onChange={noop}
188+
size={ComponentSize.ExtraSmall}
189+
testID="org-delete-overlay--accept-terms-radio-button"
190+
titleText="I understand and agree to these conditions."
191+
type={InputType.Checkbox}
192+
></Input>
193+
<InputLabel
194+
active={true}
195+
className="org-delete-overlay--conditions-instruction"
196+
size={ComponentSize.Small}
197+
>
198+
I understand and agree to these conditions.
199+
</InputLabel>
200+
</FlexBox>
201+
</FlexBox>
202+
</Overlay.Body>
203+
<Overlay.Footer>
204+
<Button
205+
color={ComponentColor.Default}
206+
onClick={onClickCancel}
207+
testID="create-org-form-cancel"
208+
text="Cancel"
209+
/>
210+
<Button
211+
color={ComponentColor.Danger}
212+
onClick={handleDeleteOrg}
213+
status={deleteButtonStatus}
214+
testID="create-org-form-submit"
215+
text="Delete Organization"
216+
/>
217+
</Overlay.Footer>
218+
</Overlay.Container>
219+
)
220+
}

src/organizations/components/OrgProfileTab/DeletePanel.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {selectCurrentIdentity} from 'src/identity/selectors'
2323
import {NotificationButtonElement} from 'src/types'
2424

2525
import 'src/organizations/components/OrgProfileTab/style.scss'
26+
import {dismissOverlay, showOverlay} from 'src/overlays/actions/overlays'
2627

2728
const DeletePanel: FC = () => {
2829
const {user, account} = useSelector(selectCurrentIdentity)
@@ -31,6 +32,14 @@ const DeletePanel: FC = () => {
3132
const {status, users} = useContext(UsersContext)
3233
const dispatch = useDispatch()
3334

35+
const handleDeletePaidAccount = () => {
36+
dispatch(
37+
showOverlay('delete-org-in-paid-account', null, () =>
38+
dispatch(dismissOverlay())
39+
)
40+
)
41+
}
42+
3443
const handleShowDeleteOverlay = () => {
3544
const payload = {
3645
org: org.id,
@@ -52,16 +61,21 @@ const DeletePanel: FC = () => {
5261
dispatch(notify(deleteAccountWarning(buttonElement)))
5362
}
5463

55-
let handleDeleteClick = handleShowDeleteOverlay
64+
let handleDeleteFreeAccount = handleShowDeleteOverlay
5665

5766
if (users.length > 1) {
58-
handleDeleteClick = handleShowWarning
67+
handleDeleteFreeAccount = handleShowWarning
5968
}
6069

70+
const showDeleteButtonInFreeAccount = account.type === 'free'
71+
const showDeleteButtonInPaidAccount =
72+
isFlagEnabled('createDeleteOrgs') &&
73+
(account.type === 'pay_as_you_go' || account.type === 'contract')
74+
6175
return (
6276
<PageSpinner loading={status}>
6377
<>
64-
{CLOUD && account.type === 'free' && (
78+
{CLOUD && showDeleteButtonInFreeAccount && (
6579
<>
6680
<FlexBox.Child>
6781
<h4>Delete Organization</h4>
@@ -75,7 +89,26 @@ const DeletePanel: FC = () => {
7589
testID="delete-org--button"
7690
text="Delete"
7791
icon={IconFont.Trash_New}
78-
onClick={handleDeleteClick}
92+
onClick={handleDeleteFreeAccount}
93+
/>
94+
</FlexBox.Child>
95+
</>
96+
)}
97+
{CLOUD && showDeleteButtonInPaidAccount && (
98+
<>
99+
<FlexBox.Child>
100+
<h4>Delete Organization</h4>
101+
<p className="org-profile-tab--heading org-profile-tab--deleteHeading">
102+
Delete the <b>{org.name}</b> organization and remove any data
103+
that you have loaded.
104+
</p>
105+
</FlexBox.Child>
106+
<FlexBox.Child>
107+
<Button
108+
icon={IconFont.Trash_New}
109+
onClick={handleDeletePaidAccount}
110+
testID="delete-org--button"
111+
text="Delete"
79112
/>
80113
</FlexBox.Child>
81114
</>

src/overlays/components/OverlayController.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import FeedbackQuestionsOverlay from 'src/support/components/FeedbackQuestionsOv
4747
import ConfirmationOverlay from 'src/support/components/ConfirmationOverlay'
4848
import {CreateOrganizationOverlay} from 'src/identity/components/GlobalHeader/GlobalHeaderDropdown/CreateOrganization/CreateOrganizationOverlay'
4949
import {MarketoAccountUpgradeOverlay} from 'src/identity/components/MarketoAccountUpgradeOverlay'
50+
import {DeletePaidOrgOverlay} from 'src/organizations/components/OrgProfileTab/DeletePaidOrgOverlay'
5051

5152
// Actions
5253
import {dismissOverlay} from 'src/overlays/actions/overlays'
@@ -185,6 +186,9 @@ export const OverlayController: FunctionComponent = () => {
185186
case 'marketo-upgrade-account-overlay':
186187
activeOverlay.current = <MarketoAccountUpgradeOverlay />
187188
break
189+
case 'delete-org-in-paid-account':
190+
activeOverlay.current = <DeletePaidOrgOverlay />
191+
break
188192
default:
189193
activeOverlay.current = null
190194
break

0 commit comments

Comments
 (0)