Skip to content

Commit 50973c9

Browse files
authored
feat: update org profile to use new 'isSuspendable' logic (#6427)
* feat: org settings prevent deleting the last org * chore: allow for possibility of account upgrade
1 parent 94c4da6 commit 50973c9

File tree

12 files changed

+255
-129
lines changed

12 files changed

+255
-129
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"cy": "CYPRESS_dexUrl=https://$INGRESS_HOST:$PORT_HTTPS CYPRESS_baseUrl=http://localhost:9999 cypress open",
5353
"cy:dev": "source ../monitor-ci/.env && CYPRESS_dexUrl=CLOUD CYPRESS_baseUrl=https://$INGRESS_HOST:$PORT_HTTPS cypress open --config testFiles='{cloud,shared}/**/*.*'",
5454
"cy:dev-oss": "source ../monitor-ci/.env && CYPRESS_dexUrl=OSS CYPRESS_baseUrl=https://$INGRESS_HOST:$PORT_HTTPS cypress open --config testFiles='{oss,shared}/**/*.*'",
55-
"generate": "export SHA=b18f46ff9dbc89193e2eaecc91248597a8ae4c9d && export REMOTE=https://raw.githubusercontent.com/influxdata/openapi/${SHA}/ && yarn generate-meta",
55+
"generate": "export SHA=00a445641ae596ea69e9cb74fd774bf92b9a2175 && export REMOTE=https://raw.githubusercontent.com/influxdata/openapi/${SHA}/ && yarn generate-meta",
5656
"generate-local": "export REMOTE=../openapi/ && yarn generate-meta",
5757
"generate-local-cloud": "export REMOTE=../openapi/ && yarn generate-meta-cloud",
5858
"generate-meta": "if [ -z \"${CLOUD_URL}\" ]; then yarn generate-meta-oss; else yarn generate-meta-cloud; fi",

src/identity/actions/thunks/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {
88
setCurrentOrgDetailsStatus,
99
} from 'src/identity/actions/creators'
1010

11+
// Selectors
12+
import {selectCurrentIdentity} from 'src/identity/selectors'
13+
1114
// Types
1215
import {RemoteDataState, GetState, NotificationAction} from 'src/types'
1316
import {Actions as IdentityActions} from 'src/identity/actions/creators'
@@ -81,12 +84,18 @@ export const getCurrentOrgDetailsThunk =
8184

8285
dispatch(setCurrentOrgDetails(orgDetails))
8386
dispatch(setCurrentOrgDetailsStatus(RemoteDataState.Done))
87+
88+
return orgDetails
8489
} catch (err) {
8590
dispatch(setCurrentOrgDetailsStatus(RemoteDataState.Error))
8691

92+
const state = getState()
93+
const identity = selectCurrentIdentity(state)
94+
8795
reportErrorThroughHoneyBadger(err, {
8896
name: 'Failed to fetch /quartz/orgs/:orgId',
89-
context: {state: getState()},
97+
context: {identity},
9098
})
99+
throw new Error(err)
91100
}
92101
}

src/identity/apis/org.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface CurrentOrg {
4242
provider?: string
4343
regionCode?: string
4444
regionName?: string
45+
isSuspendable?: boolean
4546
}
4647

4748
export interface OrgCreationAllowance {

src/identity/selectors/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ export const selectOrgCreationAvailableUpgrade = (
6666
return state.identity.allowances.orgCreation.availableUpgrade
6767
}
6868

69+
export const selectOrgSuspendable = (
70+
state: AppState
71+
): AppState['identity']['currentIdentity']['org']['isSuspendable'] => {
72+
return state.identity.currentIdentity.org.isSuspendable
73+
}
74+
6975
export const selectQuartzActiveOrgs = (
7076
state: AppState
7177
): AppState['identity']['quartzOrganizations']['orgs'] => {
@@ -84,11 +90,6 @@ export const selectQuartzIdentityStatus = (state: AppState): RemoteDataState =>
8490
export const selectQuartzBillingStatus = (state: AppState): RemoteDataState =>
8591
state.identity.currentIdentity.loadingStatus.billingStatus
8692

87-
export const selectQuartzOrgDetailsStatus = (
88-
state: AppState
89-
): RemoteDataState =>
90-
state.identity.currentIdentity.loadingStatus.orgDetailsStatus
91-
9293
export const selectQuartzOrgs = (
9394
state: AppState
9495
): AppState['identity']['quartzOrganizations']['orgs'] => {

src/organizations/components/OrgProfileTab/DeletePanel.tsx

Lines changed: 171 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,84 @@
11
// Libraries
2-
import React, {FC, useContext} from 'react'
3-
import {useSelector, useDispatch} from 'react-redux'
4-
import {useHistory} from 'react-router-dom'
2+
import React, {FC, useContext, useRef} from 'react'
53
import {track} from 'rudder-sdk-js'
4+
import {useDispatch, useSelector} from 'react-redux'
5+
import {useHistory} from 'react-router-dom'
66

77
// Components
8-
import {Button, IconFont, FlexBox} from '@influxdata/clockface'
8+
import {
9+
Button,
10+
IconFont,
11+
FlexBox,
12+
Popover,
13+
PopoverInteraction,
14+
PopoverPosition,
15+
} from '@influxdata/clockface'
16+
import {getDeleteAccountWarningButton} from 'src/shared/components/notifications/NotificationButtons'
917
import PageSpinner from 'src/perf/components/PageSpinner'
18+
19+
// Contexts
1020
import {UsersContext} from 'src/users/context/users'
11-
import {getDeleteAccountWarningButton} from 'src/shared/components/notifications/NotificationButtons'
1221

13-
// Utils
22+
// Selectors
1423
import {getOrg} from 'src/organizations/selectors'
15-
import {notify} from 'src/shared/actions/notifications'
24+
import {
25+
selectCurrentAccount,
26+
selectCurrentIdentity,
27+
selectOrgCreationAllowance,
28+
selectOrgSuspendable,
29+
selectUser,
30+
} from 'src/identity/selectors'
31+
32+
// Notifications
1633
import {deleteAccountWarning} from 'src/shared/copy/notifications'
34+
import {notify} from 'src/shared/actions/notifications'
35+
36+
// Utils
37+
import {event} from 'src/cloud/utils/reporting'
38+
39+
// Constants
1740
import {CLOUD} from 'src/shared/constants'
1841
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
19-
import {event} from 'src/cloud/utils/reporting'
42+
import {dismissOverlay, showOverlay} from 'src/overlays/actions/overlays'
2043

2144
// Types
22-
import {selectCurrentIdentity} from 'src/identity/selectors'
2345
import {NotificationButtonElement} from 'src/types'
2446

47+
// Styles
2548
import 'src/organizations/components/OrgProfileTab/style.scss'
26-
import {dismissOverlay, showOverlay} from 'src/overlays/actions/overlays'
2749

28-
const DeletePanel: FC = () => {
50+
const linkStyle = {textDecoration: 'underline'}
51+
52+
export const DeletePanel: FC = () => {
2953
const {user, account} = useSelector(selectCurrentIdentity)
30-
const org = useSelector(getOrg)
54+
const {status} = useContext(UsersContext)
55+
56+
const shouldShowDeleteFreeAccountButton =
57+
CLOUD && account.type === 'free' && user.orgCount === 1
58+
59+
const shouldShowDeleteOrgButton =
60+
CLOUD &&
61+
isFlagEnabled('createDeleteOrgs') &&
62+
!shouldShowDeleteFreeAccountButton
63+
64+
return (
65+
<PageSpinner loading={status}>
66+
<>
67+
{shouldShowDeleteFreeAccountButton && <DeleteFreeAccountButton />}
68+
{shouldShowDeleteOrgButton && <DeleteOrgButton />}
69+
</>
70+
</PageSpinner>
71+
)
72+
}
73+
74+
const DeleteFreeAccountButton: FC = () => {
75+
const account = useSelector(selectCurrentAccount)
3176
const history = useHistory()
32-
const {status, users} = useContext(UsersContext)
33-
const dispatch = useDispatch()
77+
const org = useSelector(getOrg)
78+
const user = useSelector(selectUser)
79+
const {users} = useContext(UsersContext)
3480

35-
const handleDeletePaidAccount = () => {
36-
dispatch(
37-
showOverlay('delete-org-in-paid-account', null, () =>
38-
dispatch(dismissOverlay())
39-
)
40-
)
41-
}
81+
const dispatch = useDispatch()
4282

4383
const handleShowDeleteOverlay = () => {
4484
const payload = {
@@ -61,64 +101,122 @@ const DeletePanel: FC = () => {
61101
dispatch(notify(deleteAccountWarning(buttonElement)))
62102
}
63103

64-
let handleDeleteFreeAccount = handleShowDeleteOverlay
104+
let handleDeleteAccountFree = handleShowDeleteOverlay
65105

66106
if (users.length > 1) {
67-
handleDeleteFreeAccount = handleShowWarning
107+
handleDeleteAccountFree = handleShowWarning
68108
}
69109

70-
const shouldShowDeleteFreeAccountButton =
71-
CLOUD && account.type === 'free' && user.orgCount === 1
110+
return (
111+
<>
112+
<FlexBox.Child>
113+
<h4>Delete Organization</h4>
114+
<p className="org-profile-tab--heading org-profile-tab--deleteHeading">
115+
Delete your Free InfluxDB Cloud account and remove any data that you
116+
have loaded.
117+
</p>
118+
</FlexBox.Child>
119+
<FlexBox.Child>
120+
<Button
121+
testID="delete-org--button"
122+
text="Delete"
123+
icon={IconFont.Trash_New}
124+
onClick={handleDeleteAccountFree}
125+
/>
126+
</FlexBox.Child>
127+
</>
128+
)
129+
}
72130

73-
const shouldShowDeleteOrgButton =
74-
CLOUD &&
75-
isFlagEnabled('createDeleteOrgs') &&
76-
!shouldShowDeleteFreeAccountButton
131+
const popoverStyle = {padding: '10px'}
132+
133+
const DeleteOrgButton: FC = () => {
134+
const dispatch = useDispatch()
135+
const org = useSelector(getOrg)
136+
const orgCanBeSuspended = useSelector(selectOrgSuspendable)
137+
const canCreateOrgs = useSelector(selectOrgCreationAllowance)
138+
139+
const popoverRef = useRef()
140+
141+
const handleClickCreateOrg = (hidePopup: Function) => {
142+
dispatch(
143+
showOverlay('create-organization', null, () => dispatch(dismissOverlay()))
144+
)
145+
hidePopup()
146+
}
147+
148+
const handleClickUpgradeAccount = (hidePopup: Function) => {
149+
dispatch(
150+
showOverlay('marketo-upgrade-account-overlay', null, () =>
151+
dispatch(dismissOverlay())
152+
)
153+
)
154+
hidePopup()
155+
}
156+
157+
const handleSuspendOrg = () => {
158+
if (orgCanBeSuspended) {
159+
dispatch(
160+
showOverlay('suspend-org-in-paid-account', null, () =>
161+
dispatch(dismissOverlay())
162+
)
163+
)
164+
}
165+
}
77166

78167
return (
79-
<PageSpinner loading={status}>
80-
<>
81-
{shouldShowDeleteFreeAccountButton && (
82-
<>
83-
<FlexBox.Child>
84-
<h4>Delete Organization</h4>
85-
<p className="org-profile-tab--heading org-profile-tab--deleteHeading">
86-
Delete your Free InfluxDB Cloud account and remove any data that
87-
you have loaded.
88-
</p>
89-
</FlexBox.Child>
90-
<FlexBox.Child>
91-
<Button
92-
testID="delete-org--button"
93-
text="Delete"
94-
icon={IconFont.Trash_New}
95-
onClick={handleDeleteFreeAccount}
96-
/>
97-
</FlexBox.Child>
98-
</>
99-
)}
100-
{shouldShowDeleteOrgButton && (
101-
<>
102-
<FlexBox.Child>
103-
<h4>Delete Organization</h4>
104-
<p className="org-profile-tab--heading org-profile-tab--deleteHeading">
105-
Delete the <b>{org.name}</b> organization and remove any data
106-
that you have loaded.
107-
</p>
108-
</FlexBox.Child>
109-
<FlexBox.Child>
110-
<Button
111-
icon={IconFont.Trash_New}
112-
onClick={handleDeletePaidAccount}
113-
testID="delete-org--button"
114-
text="Delete"
115-
/>
116-
</FlexBox.Child>
117-
</>
118-
)}
119-
</>
120-
</PageSpinner>
168+
<>
169+
<FlexBox.Child>
170+
<h4>Delete Organization</h4>
171+
<p className="org-profile-tab--heading org-profile-tab--deleteHeading">
172+
Delete the <b>{org.name}</b> organization and remove any data that you
173+
have loaded.
174+
</p>
175+
</FlexBox.Child>
176+
<FlexBox.Child>
177+
<>
178+
{!orgCanBeSuspended && (
179+
<Popover
180+
contents={onHide => (
181+
<div>
182+
<>
183+
<Popover.DismissButton onClick={onHide} />
184+
<b>"{org.name}"</b> cannot be deleted because it is your
185+
last accessible organization in this account. <br />
186+
To continue, please{' '}
187+
<a
188+
className="delete-org-panel--create-org-link"
189+
onClick={
190+
canCreateOrgs
191+
? () => handleClickCreateOrg(onHide)
192+
: () => handleClickUpgradeAccount(onHide)
193+
}
194+
style={linkStyle}
195+
>
196+
{canCreateOrgs
197+
? 'create another organization'
198+
: 'upgrade this account'}
199+
</a>{' '}
200+
first.
201+
</>
202+
</div>
203+
)}
204+
hideEvent={PopoverInteraction.Click}
205+
position={PopoverPosition.ToTheRightTop}
206+
showEvent={PopoverInteraction.Click}
207+
style={popoverStyle}
208+
triggerRef={popoverRef}
209+
/>
210+
)}
211+
<Button
212+
icon={IconFont.Trash_New}
213+
onClick={handleSuspendOrg}
214+
ref={popoverRef}
215+
testID="delete-org--button"
216+
text="Delete"
217+
/>
218+
</>
219+
</FlexBox.Child>
220+
</>
121221
)
122222
}
123-
124-
export default DeletePanel

src/organizations/components/OrgProfileTab/DeletePaidOrgOverlay.tsx renamed to src/organizations/components/OrgProfileTab/SuspendPaidOrgOverlay.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {DeleteOrgOverlay, multiOrgTag} from 'src/identity/events/multiOrgEvents'
4444
import {event} from 'src/cloud/utils/reporting'
4545

4646
// Styles
47-
import './DeletePaidOrgOverlay.scss'
47+
import './SuspendPaidOrgOverlay.scss'
4848

4949
// Constants
5050
import {CLOUD_URL} from 'src/shared/constants'
@@ -72,7 +72,7 @@ const SupportLink = (): JSX.Element => {
7272
)
7373
}
7474

75-
export const DeletePaidOrgOverlay: FC = () => {
75+
export const SuspendPaidOrgOverlay: FC = () => {
7676
const account = useSelector(selectCurrentAccount)
7777
const org = useSelector(selectCurrentOrg)
7878
const user = useSelector(selectUser)

0 commit comments

Comments
 (0)