Skip to content

Commit

Permalink
[fix] Add product-tier limitation to the panel (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
sijav authored Jul 9, 2024
1 parent 0db50cc commit 0a7dfc4
Show file tree
Hide file tree
Showing 23 changed files with 802 additions and 364 deletions.
314 changes: 178 additions & 136 deletions src/locales/de-DE/messages.po

Large diffs are not rendered by default.

314 changes: 178 additions & 136 deletions src/locales/en-US/messages.po

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/pages/panel/shared/queries/getWorkspaceProductTiers.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { QueryFunctionContext } from '@tanstack/react-query'
import { endPoints } from 'src/shared/constants'
import { GetWorkspaceProductTiersResponse } from 'src/shared/types/server'
import { axiosWithAuth } from 'src/shared/utils/axios'

export const getWorkspaceProductTiersQuery = ({
signal,
queryKey: [, workspaceId],
}: QueryFunctionContext<['workspace-product-tiers', string | undefined]>) => {
return workspaceId
? axiosWithAuth
.get<GetWorkspaceProductTiersResponse>(endPoints.workspaces.workspace(workspaceId).productTiers, { signal })
.then((res) => res.data)
: ({} as GetWorkspaceProductTiersResponse)
}
2 changes: 2 additions & 0 deletions src/pages/panel/shared/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export { getWorkspaceInventoryReportChecksQuery } from './getWorkspaceInventoryR
export { getWorkspaceInventoryReportSummaryQuery } from './getWorkspaceInventoryReportSummary.query'
export { getWorkspaceInventorySearchStartQuery } from './getWorkspaceInventorySearchStart.query'
export { getWorkspaceNotificationsQuery } from './getWorkspaceNotifications.query'
export { getWorkspaceProductTiersQuery } from './getWorkspaceProductTiers.query'
export { getWorkspaceUsersQuery } from './getWorkspaceUsers.query'
export { patchWorkspaceInventoryNodeSecurityIgnoreQuery } from './patchWorkspaceInventoryNodeSecurityIgnore.query'
export { postWorkspaceInventoryPropertyAttributesQuery } from './postWorkspaceInventoryPropertyAttributes.query'
export { postWorkspaceInventoryPropertyPathCompleteQuery } from './postWorkspaceInventoryPropertyPathComplete.query'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, Stack, Typography } from '@mui/material'
import { Link } from 'react-router-dom'
import { Stack, Typography } from '@mui/material'
import { AwsLogo, AzureLogo, GcpLogo } from 'src/assets/icons'
import { InternalLinkButton } from 'src/shared/link-button'
import { useNonce } from 'src/shared/providers'

export default function WorkspaceSettingsAccountsSetupCloudPage() {
Expand All @@ -16,8 +16,7 @@ export default function WorkspaceSettingsAccountsSetupCloudPage() {
>
<Stack direction="row" width="100%" height="100%" flex={1} flexWrap="wrap" alignItems="center" justifyContent="center">
<Stack direction="row" width="50%" height="50%" p={1} alignItems="center" justifyContent="center">
<Button
component={Link}
<InternalLinkButton
to="/workspace-settings/accounts/setup-cloud/aws"
color="primary"
sx={{
Expand All @@ -34,11 +33,10 @@ export default function WorkspaceSettingsAccountsSetupCloudPage() {
<Typography variant="h3" textAlign="center">
Amazon Web Services
</Typography>
</Button>
</InternalLinkButton>
</Stack>
<Stack direction="row" width="50%" height="50%" p={1} alignItems="stretch" justifyContent="center">
<Button
component={Link}
<InternalLinkButton
to="/workspace-settings/accounts/setup-cloud/gcp"
color="primary"
sx={{
Expand All @@ -55,11 +53,10 @@ export default function WorkspaceSettingsAccountsSetupCloudPage() {
<Typography variant="h3" textAlign="center">
Google Cloud Platform
</Typography>
</Button>
</InternalLinkButton>
</Stack>
<Stack direction="row" width="50%" height="50%" p={1} alignItems="stretch" justifyContent="center">
<Button
component={Link}
<InternalLinkButton
to="/workspace-settings/accounts/setup-cloud/azure"
color="primary"
sx={{
Expand All @@ -76,7 +73,7 @@ export default function WorkspaceSettingsAccountsSetupCloudPage() {
<Typography variant="h3" textAlign="center">
Azure
</Typography>
</Button>
</InternalLinkButton>
</Stack>
</Stack>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
Typography,
} from '@mui/material'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react'
import { AxiosError } from 'axios'
import { FormEvent, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { useUserProfile } from 'src/core/auth'
import { CloudAvatar } from 'src/shared/cloud-avatar'
import { InternalLinkButton } from 'src/shared/link-button'
Expand All @@ -38,13 +39,21 @@ import { replaceRowByAccount } from './replaceRowByAccount'

interface WorkspaceSettingsAccountRowProps {
account: Account
enableErrorModalContent?: ReactNode
isNotConfigured?: boolean
canEnable?: boolean
}
export const WorkspaceSettingsAccountRow = ({ account, isNotConfigured }: WorkspaceSettingsAccountRowProps) => {
export const WorkspaceSettingsAccountRow = ({
account,
enableErrorModalContent,
isNotConfigured,
canEnable,
}: WorkspaceSettingsAccountRowProps) => {
const inputRef = useRef<HTMLInputElement>()
const {
i18n: { locale },
} = useLingui()
const showCannotEnableModalRef = useRef<(show?: boolean) => void>()
const showDeleteModalRef = useRef<(show?: boolean) => void>()
const showDegradedModalRef = useRef<(show?: boolean) => void>()
const { selectedWorkspace, checkPermission } = useUserProfile()
Expand Down Expand Up @@ -120,18 +129,27 @@ export const WorkspaceSettingsAccountRow = ({ account, isNotConfigured }: Worksp
}

const handleEnableChange = (_: unknown, checked: boolean) => {
if (selectedWorkspace?.id) {
return (checked ? enableAccount : disableAccount)(
{ workspaceId: selectedWorkspace.id, id: account.id },
{
onSuccess: (data) => {
void queryClient.invalidateQueries({
predicate: (query) => typeof query.queryKey[0] === 'string' && query.queryKey[0].startsWith('workspace-cloud-account'),
})
replaceRowByAccount(queryClient, data, selectedWorkspace?.id)
if (canEnable || !checked) {
if (selectedWorkspace?.id) {
return (checked ? enableAccount : disableAccount)(
{ workspaceId: selectedWorkspace.id, id: account.id },
{
onSuccess: (data) => {
void queryClient.invalidateQueries({
predicate: (query) => typeof query.queryKey[0] === 'string' && query.queryKey[0].startsWith('workspace-cloud-account'),
})
replaceRowByAccount(queryClient, data, selectedWorkspace?.id)
},
onError: (err) => {
if ((err as AxiosError)?.status === 403) {
showCannotEnableModalRef.current?.(true)
}
},
},
},
)
)
}
} else {
showCannotEnableModalRef.current?.(true)
}
}

Expand Down Expand Up @@ -478,6 +496,17 @@ export const WorkspaceSettingsAccountRow = ({ account, isNotConfigured }: Worksp
<Trans>Name</Trans>: {accountName}
</Typography>
</Modal>
<Modal
title={<Trans>Cannot enable this account</Trans>}
openRef={showCannotEnableModalRef}
actions={
<Button color="primary" variant="contained" onClick={() => showCannotEnableModalRef.current?.(false)}>
<Trans>Ok</Trans>
</Button>
}
>
{enableErrorModalContent}
</Modal>
</TableRow>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { AddAccountButton } from 'src/shared/add-account-button'

interface WorkspaceSettingsAccountTableTitleProps extends PropsWithChildren {
isTop: boolean
withAddButton?: boolean
}

export const WorkspaceSettingsAccountTableTitle = ({ isTop, children }: WorkspaceSettingsAccountTableTitleProps) =>
export const WorkspaceSettingsAccountTableTitle = ({ isTop, withAddButton, children }: WorkspaceSettingsAccountTableTitleProps) =>
isTop ? (
<Stack
mb={{ xs: 0, sm: 1 }}
Expand All @@ -16,7 +17,7 @@ export const WorkspaceSettingsAccountTableTitle = ({ isTop, children }: Workspac
justifyContent={isTop ? 'space-between' : undefined}
>
<Typography variant="h4">{children}</Typography>
<AddAccountButton />
{withAddButton ? <AddAccountButton /> : null}
</Stack>
) : (
<Box mb={{ xs: 0, sm: 1 }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,61 @@
import { t } from '@lingui/macro'
import { useSuspenseQuery } from '@tanstack/react-query'
import { plural, t, Trans } from '@lingui/macro'
import { Alert, Typography } from '@mui/material'
import { useSuspenseQueries } from '@tanstack/react-query'
import { useUserProfile } from 'src/core/auth'
import { getWorkspaceCloudAccountsQuery } from 'src/pages/panel/shared/queries'
import { getWorkspaceCloudAccountsQuery, getWorkspaceProductTiersQuery } from 'src/pages/panel/shared/queries'
import { InternalLink } from 'src/shared/link-button'
import { GetWorkspaceProductTiersResponse } from 'src/shared/types/server'
import { WorkspaceSettingsAccountsTableItem } from './WorkspaceSettingsAccountsTableItem'

export const WorkspaceSettingsAccountsTable = () => {
const { selectedWorkspace } = useUserProfile()
const { data } = useSuspenseQuery({
queryKey: ['workspace-cloud-accounts', selectedWorkspace?.id, false],
queryFn: getWorkspaceCloudAccountsQuery,
const [{ data }, { data: currentTier }] = useSuspenseQueries({
queries: [
{
queryKey: ['workspace-cloud-accounts', selectedWorkspace?.id, false],
queryFn: getWorkspaceCloudAccountsQuery,
},
{
queryFn: getWorkspaceProductTiersQuery,
queryKey: ['workspace-product-tiers', selectedWorkspace?.id],
select: (data: GetWorkspaceProductTiersResponse) => (selectedWorkspace?.tier ? data[selectedWorkspace.tier] : undefined),
},
],
})

const accountLength =
typeof data === 'object'
? [
...new Set(
[...data.added, ...data.discovered, ...data.recent].filter((acc) => acc.enabled && acc.is_configured).map((acc) => acc.id),
),
].length
: 0
const accountLimit = currentTier?.account_limit ?? Number.POSITIVE_INFINITY

const canInviteBasedOnTier = accountLimit > accountLength

const enableErrorModalContent = canInviteBasedOnTier ? null : (
<Alert color="warning">
<Typography>
<Trans>
You currently have{' '}
{plural(accountLength, {
one: '# enabled cloud account',
other: '# enabled cloud accounts',
})}
. There must only be{' '}
{plural(accountLimit ?? 1, {
one: '# cloud account',
other: '# cloud accounts',
})}{' '}
enabled in {selectedWorkspace?.tier} tier. To increase your cloud account limit, you can upgrade your product tier{' '}
<InternalLink to="/workspace-settings/billing-receipts">here</InternalLink>.
</Trans>
</Typography>
</Alert>
)

return (
typeof data !== 'string' && (
<>
Expand All @@ -20,6 +65,8 @@ export const WorkspaceSettingsAccountsTable = () => {
isTop
isBottom={!data.discovered.length && !data.added.length}
title={t`Recently added accounts`}
canInviteBasedOnTier={canInviteBasedOnTier}
enableErrorModalContent={enableErrorModalContent}
/>
) : null}
{data?.added.length ? (
Expand All @@ -28,6 +75,8 @@ export const WorkspaceSettingsAccountsTable = () => {
isTop={!data.recent.length}
isBottom={!data.discovered.length}
title={t`Added accounts`}
canInviteBasedOnTier={canInviteBasedOnTier}
enableErrorModalContent={enableErrorModalContent}
/>
) : null}
{data?.discovered.length ? (
Expand All @@ -36,6 +85,8 @@ export const WorkspaceSettingsAccountsTable = () => {
isTop={!data.recent.length && !data.added.length}
isBottom
title={t`Discovered but unconfigured accounts`}
canInviteBasedOnTier={canInviteBasedOnTier}
enableErrorModalContent={enableErrorModalContent}
isNotConfigured
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { Box, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'
import { useState } from 'react'
import { ReactNode, useState } from 'react'
import { useUserProfile } from 'src/core/auth'
import { panelUI } from 'src/shared/constants'
import { TableView } from 'src/shared/layouts/panel-layout'
Expand All @@ -15,6 +15,8 @@ interface WorkspaceSettingsAccountsTableItemProps {
isTop: boolean
isBottom: boolean
isNotConfigured?: boolean
canInviteBasedOnTier?: boolean
enableErrorModalContent: ReactNode
}

export const WorkspaceSettingsAccountsTableItem = ({
Expand All @@ -23,6 +25,8 @@ export const WorkspaceSettingsAccountsTableItem = ({
isTop,
isBottom,
isNotConfigured,
canInviteBasedOnTier,
enableErrorModalContent,
}: WorkspaceSettingsAccountsTableItemProps) => {
const { checkPermission } = useUserProfile()
const hasPermission = checkPermission('updateCloudAccounts')
Expand All @@ -34,7 +38,9 @@ export const WorkspaceSettingsAccountsTableItem = ({
)
return (
<Box mb={isBottom ? undefined : { xs: 8, sm: 5 }} mt={isTop ? undefined : { sm: 3 }}>
<WorkspaceSettingsAccountTableTitle isTop={isTop}>{title}</WorkspaceSettingsAccountTableTitle>
<WorkspaceSettingsAccountTableTitle isTop={isTop} withAddButton={canInviteBasedOnTier}>
{title}
</WorkspaceSettingsAccountTableTitle>
<TableView
stickyPagination
paginationProps={{
Expand Down Expand Up @@ -89,7 +95,13 @@ export const WorkspaceSettingsAccountsTableItem = ({
{data
?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((account, i) => (
<WorkspaceSettingsAccountRow account={account} key={`${account.id}_${i}`} isNotConfigured={isNotConfigured} />
<WorkspaceSettingsAccountRow
account={account}
key={`${account.id}_${i}`}
isNotConfigured={isNotConfigured}
canEnable={canInviteBasedOnTier}
enableErrorModalContent={enableErrorModalContent}
/>
))}
</TableBody>
</Table>
Expand Down
Loading

0 comments on commit 0a7dfc4

Please sign in to comment.