Skip to content

Commit

Permalink
cloud setup flow QA (#1367)
Browse files Browse the repository at this point in the history
  • Loading branch information
jsladerman authored Sep 26, 2024
1 parent 6944d00 commit 8a1f16f
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 168 deletions.
2 changes: 1 addition & 1 deletion www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@nivo/geo": "0.83.0",
"@nivo/line": "0.83.0",
"@octokit/core": "4.2.1",
"@pluralsh/design-system": "3.67.1",
"@pluralsh/design-system": "3.69.2",
"@react-spring/web": "9.7.3",
"@stripe/react-stripe-js": "2.1.0",
"@stripe/stripe-js": "1.54.0",
Expand Down
6 changes: 5 additions & 1 deletion www/src/components/create-cluster/ConsoleCreationStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import styled, { useTheme } from 'styled-components'

import { ConsoleInstanceFragment } from 'generated/graphql'

import { statusToLabel } from 'components/overview/clusters/plural-cloud/CloudInstanceTableCols'

import { useCreateClusterContext } from './CreateClusterWizard'

export function ConsoleCreationStatus({
Expand Down Expand Up @@ -50,7 +52,9 @@ export function ConsoleCreationStatus({
color={theme.colors['text-primary-accent']}
{...theme.partials.text.badgeLabel}
>
<span>Status: {consoleInstance?.status}</span>
<span css={{ width: 'max-content' }}>
Status: {statusToLabel[consoleInstance?.status]}
</span>
<Spinner />
</Flex>
)}
Expand Down
13 changes: 9 additions & 4 deletions www/src/components/create-cluster/CreateCluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function CreateCluster() {
css={{ width: '100%' }}
secondary
startIcon={<ReturnIcon />}
onClick={() => navigate('/overview')}
onClick={() => navigate('/overview/clusters/plural-cloud')}
>
Back home
</Button>
Expand Down Expand Up @@ -151,10 +151,9 @@ export function clearCreateClusterState() {
localStorage.removeItem(`plural-${HOSTING_OPTION_KEY}`)
localStorage.removeItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
}

export function hasUnfinishedCreation() {
const curConsoleInstanceId = localStorage.getItem(
`plural-${CUR_CONSOLE_INSTANCE_KEY}`
)
const curConsoleInstanceId = getUnfinishedConsoleInstanceId()

return (
!!curConsoleInstanceId &&
Expand All @@ -163,6 +162,12 @@ export function hasUnfinishedCreation() {
)
}

export function getUnfinishedConsoleInstanceId() {
return localStorage
.getItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
?.replace(/"/g, '')
}

const MainWrapperSC = styled.div(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
useCreateClusterContext,
} from '../CreateClusterWizard'

const nameRegex = /^[a-z][a-z0-9-][a-z0-9]{4,9}$/

export function ConfigureCloudInstanceStep() {
const theme = useTheme()
const { setCurStep, setContinueBtn, setConsoleInstanceId } =
Expand All @@ -34,9 +36,10 @@ export function ConfigureCloudInstanceStep() {
const [size, setSize] = useState<ConsoleSize>(ConsoleSize.Small)
const [cloud, setCloud] = useState<CloudProvider>(CloudProvider.Aws)
const [region, setRegion] = useState<string>(regions[0])
const isNameValid = nameRegex.test(name)

const canSubmit = !!(
name &&
isNameValid &&
size &&
cloud &&
(cloud === CloudProvider.Aws ? region : true)
Expand Down Expand Up @@ -89,9 +92,22 @@ export function ConfigureCloudInstanceStep() {
After completing this step it may take a few minutes for your Console to
deploy. It will run in the background as you proceed.
</Callout>
<FormFieldSC label="Cluster name">
<FormFieldSC
label="Cluster name"
hint={
<FormFieldCaptionSC $name={name}>
Name must be between 6 and 11 characters, lowercase, alphanumeric,
and begin with a letter.
</FormFieldCaptionSC>
}
>
<Input
placeholder="Enter cluster name"
borderColor={
name === '' || isNameValid
? undefined
: theme.colors['border-danger']
}
value={name}
onChange={(e) => setName(e.target.value)}
/>
Expand Down Expand Up @@ -147,4 +163,15 @@ export const FormFieldSC = styled(FormField)(({ theme }) => ({
color: theme.colors.text,
}))

const FormFieldCaptionSC = styled.span<{
$name: string
}>(({ theme, $name }) => ({
...theme.partials.text.caption,
color: nameRegex.test($name)
? theme.colors['text-success-light']
: $name !== ''
? theme.colors['text-danger-light']
: theme.colors['text-light'],
}))

const regions = ['us-east-1']
17 changes: 15 additions & 2 deletions www/src/components/create-cluster/steps/HostingOptionsStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { CloudOption } from 'components/shell/onboarding/sections/cloud/CloudOpt

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

import { useTheme } from 'styled-components'

import { useCreateClusterContext } from '../CreateClusterWizard'

export function HostingOptionsStep() {
const theme = useTheme()
const { hostingOption, setHostingOption } = useCreateClusterContext()
const { isPaidPlan, isTrialPlan, daysUntilTrialExpires, isTrialExpired } =
useBillingSubscription()
Expand All @@ -20,14 +23,24 @@ export function HostingOptionsStep() {
<CloudOption
selected={hostingOption === 'local'}
onClick={() => setHostingOption('local')}
icon={<CloudIcon size={40} />}
icon={
<CloudIcon
size={40}
color={theme.colors['icon-light']}
/>
}
header="Deploy Yourself"
description="Host your control plane in your own cloud."
/>
<CloudOption
selected={hostingOption === 'cloud'}
onClick={() => setHostingOption('cloud')}
icon={<ConsoleIcon size={40} />}
icon={
<ConsoleIcon
size={40}
color={theme.colors['icon-light']}
/>
}
header="Use Plural Cloud"
description="Host your control plane in a Plural Cloud instance."
/>
Expand Down
4 changes: 3 additions & 1 deletion www/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ function Sidebar(props: Omit<ComponentProps<typeof DSSidebar>, 'variant'>) {
const previousUserData = getPreviousUserData()
const theme = useTheme()
const me = useContext(CurrentUserContext)
const menuItems = MENU_ITEMS
const menuItems = MENU_ITEMS.filter(
(item) => item.path !== '/shell' || me.hasShell
)
const { pathname } = useLocation()
const active = useCallback(
(menuItem: Parameters<typeof isActiveMenuItem>[0]) =>
Expand Down
26 changes: 22 additions & 4 deletions www/src/components/overview/OverviewHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Button, SubTab, TabList } from '@pluralsh/design-system'
import { Flex } from 'honorable'
import { ReactElement, useRef } from 'react'
import { ReactElement, useRef, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'

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

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

import { useDeleteUnfinishedInstance } from './clusters/plural-cloud/DeleteInstance'

const DIRECTORY = [
{ path: '/overview/clusters/self-hosted', label: 'Self-hosted clusters' },
{ path: '/overview/clusters/plural-cloud', label: 'Plural cloud instances' },
Expand All @@ -18,6 +20,11 @@ export default function OverviewHeader(): ReactElement {
const { pathname } = useLocation()
const currentTab = DIRECTORY.find((tab) => pathname?.startsWith(tab.path))

const [showUnfinished, setShowUnfinished] = useState(hasUnfinishedCreation())
const { triggerDelete, loading } = useDeleteUnfinishedInstance({
onClear: () => setShowUnfinished(false),
})

return (
<Flex justifyContent="space-between">
<TabList
Expand All @@ -37,9 +44,20 @@ export default function OverviewHeader(): ReactElement {
</LinkTabWrap>
))}
</TabList>
<Button onClick={() => navigate('/create-cluster')}>
{hasUnfinishedCreation() ? 'Resume cluster creation' : 'Create cluster'}
</Button>
<Flex gap="medium">
{showUnfinished && (
<Button
destructive
loading={loading}
onClick={triggerDelete}
>
Cancel cluster creation
</Button>
)}
<Button onClick={() => navigate('/create-cluster')}>
{showUnfinished ? 'Resume cluster creation' : 'Create cluster'}
</Button>
</Flex>
</Flex>
)
}
22 changes: 19 additions & 3 deletions www/src/components/overview/clusters/ClusterListEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { Button, Card, ClusterIcon, Flex } from '@pluralsh/design-system'
import { hasUnfinishedCreation } from 'components/create-cluster/CreateCluster'
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'

import { useDeleteUnfinishedInstance } from './plural-cloud/DeleteInstance'

export default function ClusterListEmptyState() {
const theme = useTheme()
const navigate = useNavigate()

const [showUnfinished, setShowUnfinished] = useState(hasUnfinishedCreation())
const { triggerDelete, loading } = useDeleteUnfinishedInstance({
onClear: () => setShowUnfinished(false),
})

return (
<Card css={{ minWidth: 'fit-content' }}>
<Wrapper>
Expand Down Expand Up @@ -34,10 +42,18 @@ export default function ClusterListEmptyState() {
css={{ maxWidth: 300, width: '100%' }}
onClick={() => navigate('/create-cluster')}
>
{hasUnfinishedCreation()
? 'Resume cluster creation'
: 'Create cluster'}
{showUnfinished ? 'Resume cluster creation' : 'Create cluster'}
</Button>
{showUnfinished && (
<Button
loading={loading}
destructive
css={{ maxWidth: 300, width: '100%' }}
onClick={triggerDelete}
>
Cancel cluster creation
</Button>
)}
</Wrapper>
</Card>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ function getStatusSeverity(
}
}

export const statusToLabel = {
[ConsoleInstanceStatus.DatabaseCreated]: 'Database Created',
[ConsoleInstanceStatus.DatabaseDeleted]: 'Database Deleted',
[ConsoleInstanceStatus.DeploymentCreated]: 'Deployment Created',
[ConsoleInstanceStatus.DeploymentDeleted]: 'Deployment Deleted',
[ConsoleInstanceStatus.Pending]: 'Pending',
[ConsoleInstanceStatus.Provisioned]: 'Provisioned',
[ConsoleInstanceStatus.StackCreated]: 'Stack Created',
[ConsoleInstanceStatus.StackDeleted]: 'Stack Deleted',
}

const ColInstance = columnHelper.accessor((instance) => instance.name, {
id: 'instance',
header: 'Instance',
Expand All @@ -70,8 +81,11 @@ const ColStatus = columnHelper.accessor((instance) => instance.status, {
header: 'Status',
enableSorting: true,
cell: ({ getValue }) => (
<Chip severity={getStatusSeverity(getValue())}>
{firstLetterUppercase(getValue())}
<Chip
css={{ width: 'max-content' }}
severity={getStatusSeverity(getValue())}
>
{statusToLabel[getValue()]}
</Chip>
),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export function ConsoleInstanceOIDC({
}) {
const [open, setOpen] = useState(false)

if (!instance.console?.owner?.id) return null

return (
<Suspense
fallback={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useTheme } from 'styled-components'

import { Button, Flex, Input, Modal } from '@pluralsh/design-system'
import {
clearCreateClusterState,
getUnfinishedConsoleInstanceId,
} from 'components/create-cluster/CreateCluster'
import { GqlError } from 'components/utils/Alert'
import {
ConsoleInstanceFragment,
useDeleteConsoleInstanceMutation,
} from 'generated/graphql'
import { useState } from 'react'
import {
CUR_CONSOLE_INSTANCE_KEY,
clearCreateClusterState,
} from 'components/create-cluster/CreateCluster'
import { useCallback, useState } from 'react'

export function DeleteInstanceModal({
open,
Expand Down Expand Up @@ -51,10 +51,7 @@ function DeleteInstance({
const [mutation, { loading, error }] = useDeleteConsoleInstanceMutation({
variables: { id: instance.id },
onCompleted: () => {
if (
`"${instance.id}"` ===
localStorage.getItem(`plural-${CUR_CONSOLE_INSTANCE_KEY}`)
) {
if (instance.id === getUnfinishedConsoleInstanceId()) {
clearCreateClusterState()
}
onClose()
Expand Down Expand Up @@ -109,3 +106,25 @@ function DeleteInstance({
</Flex>
)
}

export function useDeleteUnfinishedInstance({
onClear,
}: {
onClear?: () => void
}) {
const id = getUnfinishedConsoleInstanceId()
const [mutation, { loading, error }] = useDeleteConsoleInstanceMutation()
const triggerDelete = useCallback(() => {
clearCreateClusterState()
onClear?.()
if (id && id !== 'null' && id !== 'undefined') {
mutation({ variables: { id } })
}
}, [id, mutation, onClear])

return {
triggerDelete,
loading,
error,
}
}
Loading

0 comments on commit 8a1f16f

Please sign in to comment.