Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 13 additions & 19 deletions js/console/src/components/workbenches/WorkbenchesIntegrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
Button,
Card,
Chip,
EmptyState,
Flex,
} from '@pluralsh/design-system'
import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState'
import { PageSidecarPortal } from 'components/layout/PageLayout'
import {
DisplayButton,
Expand All @@ -23,7 +23,7 @@ import Fuse, { type IFuseOptions } from 'fuse.js'
import { useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { WORKBENCHES_TOOLS_CREATE_ABS_PATH } from 'routes/workbenchesRoutesConsts'
import styled, { useTheme } from 'styled-components'
import styled from 'styled-components'
import {
WORKBENCHES_TOOLS_PROVIDER_PARAM,
WORKBENCHES_TOOLS_TYPE_PARAM,
Expand All @@ -41,7 +41,6 @@ const SEARCH_OPTIONS: IFuseOptions<WorkbenchToolCard> = {
threshold: 0.25,
}
export function WorkbenchesIntegrations() {
const theme = useTheme()
const [query, setQuery] = useState('')
const [filtersVisible, setFiltersVisible] = useState(true)
const [selectedCategories, setSelectedCategories] = useState<string[]>([])
Expand Down Expand Up @@ -135,23 +134,18 @@ export function WorkbenchesIntegrations() {
minWidth={0}
>
{showEmptyState ? (
<Card
css={{
padding: theme.spacing.large,
...(theme.mode === 'light' && {
backgroundColor: theme.colors['fill-zero'],
}),
}}
<WorkbenchEmptyState
inset={false}
message="No tools match your filters"
description="Try a different search, or reset filters to see all tools."
>
<EmptyState message="There are no results with these filters.">
<Button
secondary
onClick={resetSearchAndFilters}
>
Reset filters
</Button>
</EmptyState>
</Card>
<Button
small
onClick={resetSearchAndFilters}
>
Reset filters
</Button>
</WorkbenchEmptyState>
) : (
<CardGrid styles={workbenchToolCardGridStyles(280)}>
{filteredCards.map(
Expand Down
18 changes: 7 additions & 11 deletions js/console/src/components/workbenches/WorkbenchesList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {
AddIcon,
ArrowRightIcon,
Card,
EmptyState,
Flex,
} from '@pluralsh/design-system'
import { AddIcon, ArrowRightIcon, Card, Flex } from '@pluralsh/design-system'
import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState'
import * as DesignSystem from '@pluralsh/design-system'
import { useSetPageHeaderContent } from 'components/cd/ContinuousDeployment'
import { ToolbarSearchInput } from 'components/layout/PageToolbar'
Expand Down Expand Up @@ -57,7 +52,6 @@ const WorkbenchIcon = (DesignSystem as { WorkbenchIcon?: ComponentType })
.WorkbenchIcon

export function WorkbenchesList() {
const theme = useTheme()
const [searchString, setSearchString] = useState('')
const debouncedSearchString = useThrottle(searchString, 200)

Expand Down Expand Up @@ -98,9 +92,11 @@ export function WorkbenchesList() {
styles={workbenchToolCardGridStyles(WORKBENCH_CARD_MIN_WIDTH)}
/>
) : showSearchEmptyState ? (
<Card css={{ padding: theme.spacing.large }}>
<EmptyState message="No workbenches found" />
</Card>
<WorkbenchEmptyState
inset={false}
message="No workbenches match your search"
description="Try a different name, or clear the search to see all workbenches."
/>
) : (
<CardGrid
onBottomReached={() =>
Expand Down
88 changes: 66 additions & 22 deletions js/console/src/components/workbenches/common/WorkbenchBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, EmptyState, Flex, Spinner } from '@pluralsh/design-system'
import { Button, Flex, Spinner } from '@pluralsh/design-system'
import { isNil } from 'lodash'
import {
ReactNode,
Expand All @@ -12,6 +12,8 @@ import {
import { Link } from 'react-router-dom'
import styled, { DefaultTheme } from 'styled-components'

import { WorkbenchEmptyState, WorkbenchEmptyText } from './WorkbenchEmptyState'

// Shared building blocks for the workbench Board views (issues, jobs, alerts).

type LoadMoreProps = {
Expand Down Expand Up @@ -138,43 +140,85 @@ export type EmptyListState = {
onResetFilters: () => void
}

function emptyListMessage(
function emptyListCopy(
noun: string,
{ searching, filtered }: { searching: boolean; filtered: boolean }
) {
if (searching && filtered) return `No ${noun} match your search and filters.`
if (searching) return `No matching ${noun} found.`
if (filtered) return `No ${noun} match the current filters.`
return `No ${noun} found.`
if (searching && filtered)
return {
message: `No ${noun} match your search and filters`,
description: 'Try a different search, or reset filters.',
}
if (searching)
return {
message: `No ${noun} match your search`,
description: 'Try a different search term.',
}
if (filtered)
return {
message: `No ${noun} match your filters`,
description: `Reset filters to see all ${noun}.`,
}
return {
message: `No ${noun} yet`,
description:
UNFILTERED_EMPTY_DESCRIPTIONS[noun] ??
`${noun[0].toUpperCase()}${noun.slice(1)} will show up here.`,
}
}

const UNFILTERED_EMPTY_DESCRIPTIONS: Record<string, string> = {
jobs: 'Jobs show up here when this workbench runs from a prompt, schedule, or trigger.',
alerts: 'Alerts routed to this workbench will show up here.',
issues: 'Issues routed to this workbench will show up here.',
}

// The empty (or first loading) state of a list, naming what emptied it.
export function BoardEmptyList({
noun,
loading,
emptyState: { searching, filtered, onResetFilters },
sidebar = false,
}: {
noun: string
loading: boolean
emptyState: EmptyListState
// a narrow list column rather than a full board
sidebar?: boolean
}) {
return loading ? (
<BoardCenteredSC>
<Spinner />
</BoardCenteredSC>
if (loading)
return (
<BoardCenteredSC>
<Spinner />
</BoardCenteredSC>
)

const { message, description } = emptyListCopy(noun, { searching, filtered })
// offers to reset the display filters, when they narrowed the list
const reset = filtered && (
<Button
small
onClick={onResetFilters}
>
Reset filters
</Button>
)

return sidebar ? (
<Flex
direction="column"
align="center"
>
<WorkbenchEmptyText>{message}</WorkbenchEmptyText>
{reset}
</Flex>
) : (
<EmptyState message={emptyListMessage(noun, { searching, filtered })}>
{/* offers to reset the display filters, when they narrowed the list */}
{filtered && (
<Button
small
secondary
onClick={onResetFilters}
>
Reset filters
</Button>
)}
</EmptyState>
<WorkbenchEmptyState
message={message}
description={description}
>
{reset}
</WorkbenchEmptyState>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ReactNode } from 'react'
import styled from 'styled-components'

export function WorkbenchEmptyState({
message,
description,
children,
variant = 'card',
inset = true,
className,
}: {
message: string
description?: ReactNode
children?: ReactNode
// 'plain' drops the card chrome, for use inside an existing card or panel
variant?: 'card' | 'plain'
// false when the parent already pads its content
inset?: boolean
className?: string
}) {
return (
<OuterSC
$inset={inset}
className={className}
>
<CardSC $plain={variant === 'plain'}>
<MessageSC>{message}</MessageSC>
{description && <DescriptionSC>{description}</DescriptionSC>}
{children && <ActionsSC>{children}</ActionsSC>}
</CardSC>
</OuterSC>
)
}

// for narrow sidebars and columns, where a card would be too heavy
export function WorkbenchEmptyText({ children }: { children: ReactNode }) {
return <EmptyTextSC>{children}</EmptyTextSC>
}

const OuterSC = styled.div<{ $inset: boolean }>(({ theme, $inset }) => ({
display: 'flex',
flex: 1,
minHeight: 0,
...($inset && { padding: theme.spacing.large }),
}))

const CardSC = styled.div<{ $plain: boolean }>(({ theme, $plain }) => ({
display: 'flex',
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: 0,
padding: theme.spacing.xlarge,
textAlign: 'center',
...(!$plain && {
border: theme.borders.default,
borderRadius: theme.borderRadiuses.large,
backgroundColor: theme.colors['fill-one'],
}),
}))

const MessageSC = styled.p(({ theme }) => ({
...theme.partials.text.body1Bold,
color: theme.colors.text,
margin: 0,
}))

const DescriptionSC = styled.p(({ theme }) => ({
...theme.partials.text.body2,
color: theme.colors['text-light'],
margin: `${theme.spacing.xxsmall}px 0 0`,
maxWidth: 480,
overflowWrap: 'anywhere',
textWrap: 'pretty',
}))

const ActionsSC = styled.div(({ theme }) => ({
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: theme.spacing.small,
marginTop: theme.spacing.medium,
}))

const EmptyTextSC = styled.p(({ theme }) => ({
...theme.partials.text.body2,
color: theme.colors['text-xlight'],
margin: `${theme.spacing.large}px auto`,
padding: `0 ${theme.spacing.medium}px`,
textAlign: 'center',
}))
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,7 @@ function IssueCard({
function EmptyColumnCard() {
return (
<EmptyCardSC>
<EmptyCardTextSC>
No tickets available in this status yet.
</EmptyCardTextSC>
<EmptyCardTextSC>No issues in this status yet.</EmptyCardTextSC>
</EmptyCardSC>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EmptyState, Flex } from '@pluralsh/design-system'
import { Flex } from '@pluralsh/design-system'
import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState'
import { TimeRangeControl } from 'components/utils/timerange/TimeRangeControl'
import {
DEFAULT_TIME_RANGE,
Expand Down Expand Up @@ -38,7 +39,10 @@ export function PublicWorkbenchDashboard() {
if (error || !dashboard)
return (
<PageSC>
<EmptyState message="This dashboard isn't available" />
<WorkbenchEmptyState
message="Dashboard unavailable"
description="This link may have expired, or the dashboard is no longer shared publicly."
/>
</PageSC>
)

Expand Down
10 changes: 7 additions & 3 deletions js/console/src/components/workbenches/workbench/Workbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
BookmarkIcon,
BotIcon,
Button,
EmptyState,
EventScheduleIcon,
Flex,
GearTrainIcon,
Expand All @@ -16,6 +15,7 @@ import {
WebhooksIcon,
useSetBreadcrumbs,
} from '@pluralsh/design-system'
import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState'
import {
useWorkbenchJobCountsQuery,
WorkbenchJobStatus,
Expand Down Expand Up @@ -477,15 +477,19 @@ export function Workbench() {

if (notFound)
return (
<EmptyState message="Workbench not found.">
<WorkbenchEmptyState
message="Workbench not found"
description="It may have been deleted, or you may not have access to it."
>
<Button
small
as={Link}
to={WORKBENCHES_ABS_PATH}
startIcon={<ReturnIcon />}
>
View all workbenches
</Button>
</EmptyState>
</WorkbenchEmptyState>
)
if (error)
return (
Expand Down
Loading
Loading