Skip to content

Commit 8a1f16f

Browse files
authored
cloud setup flow QA (#1367)
1 parent 6944d00 commit 8a1f16f

13 files changed

+515
-168
lines changed

www/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@nivo/geo": "0.83.0",
4545
"@nivo/line": "0.83.0",
4646
"@octokit/core": "4.2.1",
47-
"@pluralsh/design-system": "3.67.1",
47+
"@pluralsh/design-system": "3.69.2",
4848
"@react-spring/web": "9.7.3",
4949
"@stripe/react-stripe-js": "2.1.0",
5050
"@stripe/stripe-js": "1.54.0",

www/src/components/create-cluster/ConsoleCreationStatus.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import styled, { useTheme } from 'styled-components'
33

44
import { ConsoleInstanceFragment } from 'generated/graphql'
55

6+
import { statusToLabel } from 'components/overview/clusters/plural-cloud/CloudInstanceTableCols'
7+
68
import { useCreateClusterContext } from './CreateClusterWizard'
79

810
export function ConsoleCreationStatus({
@@ -50,7 +52,9 @@ export function ConsoleCreationStatus({
5052
color={theme.colors['text-primary-accent']}
5153
{...theme.partials.text.badgeLabel}
5254
>
53-
<span>Status: {consoleInstance?.status}</span>
55+
<span css={{ width: 'max-content' }}>
56+
Status: {statusToLabel[consoleInstance?.status]}
57+
</span>
5458
<Spinner />
5559
</Flex>
5660
)}

www/src/components/create-cluster/CreateCluster.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export function CreateCluster() {
105105
css={{ width: '100%' }}
106106
secondary
107107
startIcon={<ReturnIcon />}
108-
onClick={() => navigate('/overview')}
108+
onClick={() => navigate('/overview/clusters/plural-cloud')}
109109
>
110110
Back home
111111
</Button>
@@ -151,10 +151,9 @@ export function clearCreateClusterState() {
151151
localStorage.removeItem(`plural-${HOSTING_OPTION_KEY}`)
152152
localStorage.removeItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
153153
}
154+
154155
export function hasUnfinishedCreation() {
155-
const curConsoleInstanceId = localStorage.getItem(
156-
`plural-${CUR_CONSOLE_INSTANCE_KEY}`
157-
)
156+
const curConsoleInstanceId = getUnfinishedConsoleInstanceId()
158157

159158
return (
160159
!!curConsoleInstanceId &&
@@ -163,6 +162,12 @@ export function hasUnfinishedCreation() {
163162
)
164163
}
165164

165+
export function getUnfinishedConsoleInstanceId() {
166+
return localStorage
167+
.getItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
168+
?.replace(/"/g, '')
169+
}
170+
166171
const MainWrapperSC = styled.div(({ theme }) => ({
167172
display: 'flex',
168173
justifyContent: 'space-between',

www/src/components/create-cluster/steps/ConfigureCloudInstanceStep.tsx

+29-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
useCreateClusterContext,
2626
} from '../CreateClusterWizard'
2727

28+
const nameRegex = /^[a-z][a-z0-9-][a-z0-9]{4,9}$/
29+
2830
export function ConfigureCloudInstanceStep() {
2931
const theme = useTheme()
3032
const { setCurStep, setContinueBtn, setConsoleInstanceId } =
@@ -34,9 +36,10 @@ export function ConfigureCloudInstanceStep() {
3436
const [size, setSize] = useState<ConsoleSize>(ConsoleSize.Small)
3537
const [cloud, setCloud] = useState<CloudProvider>(CloudProvider.Aws)
3638
const [region, setRegion] = useState<string>(regions[0])
39+
const isNameValid = nameRegex.test(name)
3740

3841
const canSubmit = !!(
39-
name &&
42+
isNameValid &&
4043
size &&
4144
cloud &&
4245
(cloud === CloudProvider.Aws ? region : true)
@@ -89,9 +92,22 @@ export function ConfigureCloudInstanceStep() {
8992
After completing this step it may take a few minutes for your Console to
9093
deploy. It will run in the background as you proceed.
9194
</Callout>
92-
<FormFieldSC label="Cluster name">
95+
<FormFieldSC
96+
label="Cluster name"
97+
hint={
98+
<FormFieldCaptionSC $name={name}>
99+
Name must be between 6 and 11 characters, lowercase, alphanumeric,
100+
and begin with a letter.
101+
</FormFieldCaptionSC>
102+
}
103+
>
93104
<Input
94105
placeholder="Enter cluster name"
106+
borderColor={
107+
name === '' || isNameValid
108+
? undefined
109+
: theme.colors['border-danger']
110+
}
95111
value={name}
96112
onChange={(e) => setName(e.target.value)}
97113
/>
@@ -147,4 +163,15 @@ export const FormFieldSC = styled(FormField)(({ theme }) => ({
147163
color: theme.colors.text,
148164
}))
149165

166+
const FormFieldCaptionSC = styled.span<{
167+
$name: string
168+
}>(({ theme, $name }) => ({
169+
...theme.partials.text.caption,
170+
color: nameRegex.test($name)
171+
? theme.colors['text-success-light']
172+
: $name !== ''
173+
? theme.colors['text-danger-light']
174+
: theme.colors['text-light'],
175+
}))
176+
150177
const regions = ['us-east-1']

www/src/components/create-cluster/steps/HostingOptionsStep.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import { CloudOption } from 'components/shell/onboarding/sections/cloud/CloudOpt
33

44
import { useBillingSubscription } from 'components/account/billing/BillingSubscriptionProvider'
55

6+
import { useTheme } from 'styled-components'
7+
68
import { useCreateClusterContext } from '../CreateClusterWizard'
79

810
export function HostingOptionsStep() {
11+
const theme = useTheme()
912
const { hostingOption, setHostingOption } = useCreateClusterContext()
1013
const { isPaidPlan, isTrialPlan, daysUntilTrialExpires, isTrialExpired } =
1114
useBillingSubscription()
@@ -20,14 +23,24 @@ export function HostingOptionsStep() {
2023
<CloudOption
2124
selected={hostingOption === 'local'}
2225
onClick={() => setHostingOption('local')}
23-
icon={<CloudIcon size={40} />}
26+
icon={
27+
<CloudIcon
28+
size={40}
29+
color={theme.colors['icon-light']}
30+
/>
31+
}
2432
header="Deploy Yourself"
2533
description="Host your control plane in your own cloud."
2634
/>
2735
<CloudOption
2836
selected={hostingOption === 'cloud'}
2937
onClick={() => setHostingOption('cloud')}
30-
icon={<ConsoleIcon size={40} />}
38+
icon={
39+
<ConsoleIcon
40+
size={40}
41+
color={theme.colors['icon-light']}
42+
/>
43+
}
3144
header="Use Plural Cloud"
3245
description="Host your control plane in a Plural Cloud instance."
3346
/>

www/src/components/layout/Sidebar.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ function Sidebar(props: Omit<ComponentProps<typeof DSSidebar>, 'variant'>) {
177177
const previousUserData = getPreviousUserData()
178178
const theme = useTheme()
179179
const me = useContext(CurrentUserContext)
180-
const menuItems = MENU_ITEMS
180+
const menuItems = MENU_ITEMS.filter(
181+
(item) => item.path !== '/shell' || me.hasShell
182+
)
181183
const { pathname } = useLocation()
182184
const active = useCallback(
183185
(menuItem: Parameters<typeof isActiveMenuItem>[0]) =>

www/src/components/overview/OverviewHeader.tsx

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Button, SubTab, TabList } from '@pluralsh/design-system'
22
import { Flex } from 'honorable'
3-
import { ReactElement, useRef } from 'react'
3+
import { ReactElement, useRef, useState } from 'react'
44
import { useLocation, useNavigate } from 'react-router-dom'
55

66
import { hasUnfinishedCreation } from 'components/create-cluster/CreateCluster'
77

88
import { LinkTabWrap } from '../utils/Tabs'
99

10+
import { useDeleteUnfinishedInstance } from './clusters/plural-cloud/DeleteInstance'
11+
1012
const DIRECTORY = [
1113
{ path: '/overview/clusters/self-hosted', label: 'Self-hosted clusters' },
1214
{ path: '/overview/clusters/plural-cloud', label: 'Plural cloud instances' },
@@ -18,6 +20,11 @@ export default function OverviewHeader(): ReactElement {
1820
const { pathname } = useLocation()
1921
const currentTab = DIRECTORY.find((tab) => pathname?.startsWith(tab.path))
2022

23+
const [showUnfinished, setShowUnfinished] = useState(hasUnfinishedCreation())
24+
const { triggerDelete, loading } = useDeleteUnfinishedInstance({
25+
onClear: () => setShowUnfinished(false),
26+
})
27+
2128
return (
2229
<Flex justifyContent="space-between">
2330
<TabList
@@ -37,9 +44,20 @@ export default function OverviewHeader(): ReactElement {
3744
</LinkTabWrap>
3845
))}
3946
</TabList>
40-
<Button onClick={() => navigate('/create-cluster')}>
41-
{hasUnfinishedCreation() ? 'Resume cluster creation' : 'Create cluster'}
42-
</Button>
47+
<Flex gap="medium">
48+
{showUnfinished && (
49+
<Button
50+
destructive
51+
loading={loading}
52+
onClick={triggerDelete}
53+
>
54+
Cancel cluster creation
55+
</Button>
56+
)}
57+
<Button onClick={() => navigate('/create-cluster')}>
58+
{showUnfinished ? 'Resume cluster creation' : 'Create cluster'}
59+
</Button>
60+
</Flex>
4361
</Flex>
4462
)
4563
}

www/src/components/overview/clusters/ClusterListEmptyState.tsx

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { Button, Card, ClusterIcon, Flex } from '@pluralsh/design-system'
22
import { hasUnfinishedCreation } from 'components/create-cluster/CreateCluster'
3+
import { useState } from 'react'
34
import { useNavigate } from 'react-router-dom'
45
import styled, { useTheme } from 'styled-components'
56

7+
import { useDeleteUnfinishedInstance } from './plural-cloud/DeleteInstance'
8+
69
export default function ClusterListEmptyState() {
710
const theme = useTheme()
811
const navigate = useNavigate()
912

13+
const [showUnfinished, setShowUnfinished] = useState(hasUnfinishedCreation())
14+
const { triggerDelete, loading } = useDeleteUnfinishedInstance({
15+
onClear: () => setShowUnfinished(false),
16+
})
17+
1018
return (
1119
<Card css={{ minWidth: 'fit-content' }}>
1220
<Wrapper>
@@ -34,10 +42,18 @@ export default function ClusterListEmptyState() {
3442
css={{ maxWidth: 300, width: '100%' }}
3543
onClick={() => navigate('/create-cluster')}
3644
>
37-
{hasUnfinishedCreation()
38-
? 'Resume cluster creation'
39-
: 'Create cluster'}
45+
{showUnfinished ? 'Resume cluster creation' : 'Create cluster'}
4046
</Button>
47+
{showUnfinished && (
48+
<Button
49+
loading={loading}
50+
destructive
51+
css={{ maxWidth: 300, width: '100%' }}
52+
onClick={triggerDelete}
53+
>
54+
Cancel cluster creation
55+
</Button>
56+
)}
4157
</Wrapper>
4258
</Card>
4359
)

www/src/components/overview/clusters/plural-cloud/CloudInstanceTableCols.tsx

+16-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ function getStatusSeverity(
4646
}
4747
}
4848

49+
export const statusToLabel = {
50+
[ConsoleInstanceStatus.DatabaseCreated]: 'Database Created',
51+
[ConsoleInstanceStatus.DatabaseDeleted]: 'Database Deleted',
52+
[ConsoleInstanceStatus.DeploymentCreated]: 'Deployment Created',
53+
[ConsoleInstanceStatus.DeploymentDeleted]: 'Deployment Deleted',
54+
[ConsoleInstanceStatus.Pending]: 'Pending',
55+
[ConsoleInstanceStatus.Provisioned]: 'Provisioned',
56+
[ConsoleInstanceStatus.StackCreated]: 'Stack Created',
57+
[ConsoleInstanceStatus.StackDeleted]: 'Stack Deleted',
58+
}
59+
4960
const ColInstance = columnHelper.accessor((instance) => instance.name, {
5061
id: 'instance',
5162
header: 'Instance',
@@ -70,8 +81,11 @@ const ColStatus = columnHelper.accessor((instance) => instance.status, {
7081
header: 'Status',
7182
enableSorting: true,
7283
cell: ({ getValue }) => (
73-
<Chip severity={getStatusSeverity(getValue())}>
74-
{firstLetterUppercase(getValue())}
84+
<Chip
85+
css={{ width: 'max-content' }}
86+
severity={getStatusSeverity(getValue())}
87+
>
88+
{statusToLabel[getValue()]}
7589
</Chip>
7690
),
7791
})

www/src/components/overview/clusters/plural-cloud/ConsoleInstanceOIDC.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export function ConsoleInstanceOIDC({
3636
}) {
3737
const [open, setOpen] = useState(false)
3838

39+
if (!instance.console?.owner?.id) return null
40+
3941
return (
4042
<Suspense
4143
fallback={

www/src/components/overview/clusters/plural-cloud/DeleteInstance.tsx

+28-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { useTheme } from 'styled-components'
22

33
import { Button, Flex, Input, Modal } from '@pluralsh/design-system'
4+
import {
5+
clearCreateClusterState,
6+
getUnfinishedConsoleInstanceId,
7+
} from 'components/create-cluster/CreateCluster'
48
import { GqlError } from 'components/utils/Alert'
59
import {
610
ConsoleInstanceFragment,
711
useDeleteConsoleInstanceMutation,
812
} from 'generated/graphql'
9-
import { useState } from 'react'
10-
import {
11-
CUR_CONSOLE_INSTANCE_KEY,
12-
clearCreateClusterState,
13-
} from 'components/create-cluster/CreateCluster'
13+
import { useCallback, useState } from 'react'
1414

1515
export function DeleteInstanceModal({
1616
open,
@@ -51,10 +51,7 @@ function DeleteInstance({
5151
const [mutation, { loading, error }] = useDeleteConsoleInstanceMutation({
5252
variables: { id: instance.id },
5353
onCompleted: () => {
54-
if (
55-
`"${instance.id}"` ===
56-
localStorage.getItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
57-
) {
54+
if (instance.id === getUnfinishedConsoleInstanceId()) {
5855
clearCreateClusterState()
5956
}
6057
onClose()
@@ -109,3 +106,25 @@ function DeleteInstance({
109106
</Flex>
110107
)
111108
}
109+
110+
export function useDeleteUnfinishedInstance({
111+
onClear,
112+
}: {
113+
onClear?: () => void
114+
}) {
115+
const id = getUnfinishedConsoleInstanceId()
116+
const [mutation, { loading, error }] = useDeleteConsoleInstanceMutation()
117+
const triggerDelete = useCallback(() => {
118+
clearCreateClusterState()
119+
onClear?.()
120+
if (id && id !== 'null' && id !== 'undefined') {
121+
mutation({ variables: { id } })
122+
}
123+
}, [id, mutation, onClear])
124+
125+
return {
126+
triggerDelete,
127+
loading,
128+
error,
129+
}
130+
}

0 commit comments

Comments
 (0)