diff --git a/js/console/src/components/workbenches/WorkbenchesIntegrations.tsx b/js/console/src/components/workbenches/WorkbenchesIntegrations.tsx index c837246117..99cad95750 100644 --- a/js/console/src/components/workbenches/WorkbenchesIntegrations.tsx +++ b/js/console/src/components/workbenches/WorkbenchesIntegrations.tsx @@ -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, @@ -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, @@ -41,7 +41,6 @@ const SEARCH_OPTIONS: IFuseOptions = { threshold: 0.25, } export function WorkbenchesIntegrations() { - const theme = useTheme() const [query, setQuery] = useState('') const [filtersVisible, setFiltersVisible] = useState(true) const [selectedCategories, setSelectedCategories] = useState([]) @@ -135,23 +134,18 @@ export function WorkbenchesIntegrations() { minWidth={0} > {showEmptyState ? ( - - - - - + + ) : ( {filteredCards.map( diff --git a/js/console/src/components/workbenches/WorkbenchesList.tsx b/js/console/src/components/workbenches/WorkbenchesList.tsx index daffcda2de..da1d180d09 100644 --- a/js/console/src/components/workbenches/WorkbenchesList.tsx +++ b/js/console/src/components/workbenches/WorkbenchesList.tsx @@ -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' @@ -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) @@ -98,9 +92,11 @@ export function WorkbenchesList() { styles={workbenchToolCardGridStyles(WORKBENCH_CARD_MIN_WIDTH)} /> ) : showSearchEmptyState ? ( - - - + ) : ( diff --git a/js/console/src/components/workbenches/common/WorkbenchBoard.tsx b/js/console/src/components/workbenches/common/WorkbenchBoard.tsx index 3e25736156..22733a0a56 100644 --- a/js/console/src/components/workbenches/common/WorkbenchBoard.tsx +++ b/js/console/src/components/workbenches/common/WorkbenchBoard.tsx @@ -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, @@ -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 = { @@ -138,14 +140,37 @@ 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 = { + 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. @@ -153,28 +178,47 @@ 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 ? ( - - - + if (loading) + return ( + + + + ) + + const { message, description } = emptyListCopy(noun, { searching, filtered }) + // offers to reset the display filters, when they narrowed the list + const reset = filtered && ( + + ) + + return sidebar ? ( + + {message} + {reset} + ) : ( - - {/* offers to reset the display filters, when they narrowed the list */} - {filtered && ( - - )} - + + {reset} + ) } diff --git a/js/console/src/components/workbenches/common/WorkbenchEmptyState.tsx b/js/console/src/components/workbenches/common/WorkbenchEmptyState.tsx new file mode 100644 index 0000000000..da08e09a0c --- /dev/null +++ b/js/console/src/components/workbenches/common/WorkbenchEmptyState.tsx @@ -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 ( + + + {message} + {description && {description}} + {children && {children}} + + + ) +} + +// for narrow sidebars and columns, where a card would be too heavy +export function WorkbenchEmptyText({ children }: { children: ReactNode }) { + return {children} +} + +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', +})) diff --git a/js/console/src/components/workbenches/common/WorkbenchIssuesBoard.tsx b/js/console/src/components/workbenches/common/WorkbenchIssuesBoard.tsx index f70c5c3ad0..f32668a0a8 100644 --- a/js/console/src/components/workbenches/common/WorkbenchIssuesBoard.tsx +++ b/js/console/src/components/workbenches/common/WorkbenchIssuesBoard.tsx @@ -144,9 +144,7 @@ function IssueCard({ function EmptyColumnCard() { return ( - - No tickets available in this status yet. - + No issues in this status yet. ) } diff --git a/js/console/src/components/workbenches/public/PublicWorkbenchDashboard.tsx b/js/console/src/components/workbenches/public/PublicWorkbenchDashboard.tsx index 5562350c41..58361800fd 100644 --- a/js/console/src/components/workbenches/public/PublicWorkbenchDashboard.tsx +++ b/js/console/src/components/workbenches/public/PublicWorkbenchDashboard.tsx @@ -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, @@ -38,7 +39,10 @@ export function PublicWorkbenchDashboard() { if (error || !dashboard) return ( - + ) diff --git a/js/console/src/components/workbenches/workbench/Workbench.tsx b/js/console/src/components/workbenches/workbench/Workbench.tsx index 2bb45759aa..e8702f69fc 100644 --- a/js/console/src/components/workbenches/workbench/Workbench.tsx +++ b/js/console/src/components/workbenches/workbench/Workbench.tsx @@ -3,7 +3,6 @@ import { BookmarkIcon, BotIcon, Button, - EmptyState, EventScheduleIcon, Flex, GearTrainIcon, @@ -16,6 +15,7 @@ import { WebhooksIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { useWorkbenchJobCountsQuery, WorkbenchJobStatus, @@ -477,15 +477,19 @@ export function Workbench() { if (notFound) return ( - + - + ) if (error) return ( diff --git a/js/console/src/components/workbenches/workbench/WorkbenchAlertsDetails.tsx b/js/console/src/components/workbenches/workbench/WorkbenchAlertsDetails.tsx index 9b923920d9..649f985773 100644 --- a/js/console/src/components/workbenches/workbench/WorkbenchAlertsDetails.tsx +++ b/js/console/src/components/workbenches/workbench/WorkbenchAlertsDetails.tsx @@ -1,4 +1,5 @@ -import { Chip, EmptyState } from '@pluralsh/design-system' +import { Chip } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { getAlertAnnotations, getAlertSummary, @@ -167,6 +168,7 @@ export function useWorkbenchAlertsDetails({ noun="alerts" loading={loading} emptyState={emptyState} + sidebar /> )} {alerts.map((alert) => ( @@ -207,7 +209,15 @@ export function useWorkbenchAlertsDetails({ ) - const content = ( + const content = !selected ? ( + !loading && ( + + ) + ) : ( {selected && ( )} {tab === 'Tags' && ( )} {tab === 'All information' && ( @@ -513,11 +525,20 @@ function KeyValueCard({ function KeyValueChips({ entries, emptyMessage, + emptyDescription, }: { entries: [string, string][] emptyMessage: string + emptyDescription: string }) { - if (isEmpty(entries)) return + if (isEmpty(entries)) + return ( + + ) return ( diff --git a/js/console/src/components/workbenches/workbench/WorkbenchIssuesDetails.tsx b/js/console/src/components/workbenches/workbench/WorkbenchIssuesDetails.tsx index 19ed61a73f..6014dabca6 100644 --- a/js/console/src/components/workbenches/workbench/WorkbenchIssuesDetails.tsx +++ b/js/console/src/components/workbenches/workbench/WorkbenchIssuesDetails.tsx @@ -1,4 +1,4 @@ -import { EmptyState } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { getIssueWebhookProviderIcon } from 'components/settings/webhooks/webhookIcons' import { IssueStatusIcon } from 'components/workbenches/common/IssueStatusChip' import { @@ -89,6 +89,7 @@ export function useWorkbenchIssuesDetails({ noun="issues" loading={loading} emptyState={emptyState} + sidebar /> ) : ( issues.map((issue) => ( @@ -133,7 +134,15 @@ export function useWorkbenchIssuesDetails({ ) - const content = ( + const content = !selected ? ( + !loading && ( + + ) + ) : ( {selected && (selectedJob ? ( @@ -149,7 +158,10 @@ export function useWorkbenchIssuesDetails({ {expandButton} - + ))} {selected && detailsOpen && ( diff --git a/js/console/src/components/workbenches/workbench/WorkbenchJobsDetails.tsx b/js/console/src/components/workbenches/workbench/WorkbenchJobsDetails.tsx index b7c763e7ff..f50e2902be 100644 --- a/js/console/src/components/workbenches/workbench/WorkbenchJobsDetails.tsx +++ b/js/console/src/components/workbenches/workbench/WorkbenchJobsDetails.tsx @@ -1,10 +1,10 @@ import { ArrowTopRightIcon, Chip, - EmptyState, Flex, prettifyRepoUrl, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { prettifyPrompt } from 'components/utils/contentEditableChips' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' import { StackedText } from 'components/utils/table/StackedText' @@ -114,6 +114,7 @@ export function useWorkbenchJobsDetails({ noun="jobs" loading={loading} emptyState={emptyState} + sidebar /> ) : ( jobs.map((job) => ( @@ -148,7 +149,15 @@ export function useWorkbenchJobsDetails({ ) - const content = ( + const content = !selected ? ( + !loading && ( + + ) + ) : ( {selected && ( ) : isEmpty(tabs) ? ( - + ) : ( <> diff --git a/js/console/src/components/workbenches/workbench/chatbots/ChatbotForm.tsx b/js/console/src/components/workbenches/workbench/chatbots/ChatbotForm.tsx index 49bd789cd0..ec1df5d684 100644 --- a/js/console/src/components/workbenches/workbench/chatbots/ChatbotForm.tsx +++ b/js/console/src/components/workbenches/workbench/chatbots/ChatbotForm.tsx @@ -1,7 +1,6 @@ import { AddIcon, Button, - EmptyState, Flex, FormField, ListBoxFooter, @@ -10,6 +9,7 @@ import { Select, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { useLogin } from 'components/contexts' import { GqlError } from 'components/utils/Alert' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' @@ -240,14 +240,18 @@ export function ChatbotForm({ mode }: { mode: 'create' | 'edit' }) { if (mode === 'edit' && !chatbotLoading && !chatbot) return ( - + - + ) const isLoading = diff --git a/js/console/src/components/workbenches/workbench/chatbots/Chatbots.tsx b/js/console/src/components/workbenches/workbench/chatbots/Chatbots.tsx index d57cd7c367..cf49af7d5d 100644 --- a/js/console/src/components/workbenches/workbench/chatbots/Chatbots.tsx +++ b/js/console/src/components/workbenches/workbench/chatbots/Chatbots.tsx @@ -1,8 +1,6 @@ import { ArrowTopRightIcon, Button, - Card, - EmptyState, Flex, IconFrame, PencilIcon, @@ -10,6 +8,7 @@ import { TrashCanIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { createColumnHelper } from '@tanstack/react-table' import { GqlError } from 'components/utils/Alert' import { StretchedFlex } from 'components/utils/StretchedFlex' @@ -141,42 +140,40 @@ export function Chatbots() { gap="xxsmall" /> {!!data && isEmpty(chatbots) ? ( - - - + + + + {hasExistingConnections && ( - {hasExistingConnections && ( - - )} - - - + )} + + ) : ( {entries.length === 0 ? ( - - - + ) : ( {skills.length === 0 ? ( - - + - - + Create new skill + + ) : ( - + ) : ( + - + ) const isLoading = diff --git a/js/console/src/components/workbenches/workbench/crons/CronSchedules.tsx b/js/console/src/components/workbenches/workbench/crons/CronSchedules.tsx index 046fbbeadc..68988742ab 100644 --- a/js/console/src/components/workbenches/workbench/crons/CronSchedules.tsx +++ b/js/console/src/components/workbenches/workbench/crons/CronSchedules.tsx @@ -1,7 +1,5 @@ import { Button, - Card, - EmptyState, Flex, IconFrame, PencilIcon, @@ -9,6 +7,7 @@ import { TrashCanIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { createColumnHelper } from '@tanstack/react-table' import { GqlError } from 'components/utils/Alert' import { StretchedFlex } from 'components/utils/StretchedFlex' @@ -116,22 +115,20 @@ export function CronSchedules() { gap="xxsmall" /> {!!data && isEmpty(crons) ? ( - - + - - + Create new schedule + + ) : ( + return ( + + ) } if (evalError) return diff --git a/js/console/src/components/workbenches/workbench/evals/WorkbenchEvals.tsx b/js/console/src/components/workbenches/workbench/evals/WorkbenchEvals.tsx index 18446af01c..499647fc33 100644 --- a/js/console/src/components/workbenches/workbench/evals/WorkbenchEvals.tsx +++ b/js/console/src/components/workbenches/workbench/evals/WorkbenchEvals.tsx @@ -1,6 +1,5 @@ import { Button, - EmptyState, Flex, HamburgerMenuCollapsedIcon, HamburgerMenuCollapseIcon, @@ -9,6 +8,7 @@ import { Tab, TabList, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { useEffect, useMemo, useRef, useState } from 'react' import { Link, @@ -149,14 +149,10 @@ export function WorkbenchEvals() { ) : ( {!selectedEvalRow ? ( - - - + ) : ( diff --git a/js/console/src/components/workbenches/workbench/evals/WorkbenchEvalsSidePanel.tsx b/js/console/src/components/workbenches/workbench/evals/WorkbenchEvalsSidePanel.tsx index 67cd550834..53d7d8cd67 100644 --- a/js/console/src/components/workbenches/workbench/evals/WorkbenchEvalsSidePanel.tsx +++ b/js/console/src/components/workbenches/workbench/evals/WorkbenchEvalsSidePanel.tsx @@ -1,8 +1,8 @@ import { Chip, Flex } from '@pluralsh/design-system' +import { WorkbenchEmptyText } from 'components/workbenches/common/WorkbenchEmptyState' import { type ComponentProps, useMemo, useState } from 'react' import styled, { useTheme } from 'styled-components' import { WorkbenchStoredPromptMarkdown } from '../WorkbenchStoredPromptMarkdown' -import { Body2P } from 'components/utils/typography/Text' import { evalGradeToCategory, EvalGradeCategory, @@ -153,14 +153,11 @@ export function WorkbenchEvalsSidePanel({ )} /> ) : ( - + {activeFilter === 'all' - ? 'No evals available yet.' - : 'No evals available for this filter.'} - + ? 'No evals yet. Graded jobs will show up here.' + : 'No evals match this filter.'} + )} diff --git a/js/console/src/components/workbenches/workbench/files/WorkbenchFileTree.tsx b/js/console/src/components/workbenches/workbench/files/WorkbenchFileTree.tsx index 807f39adf3..fc1c491ad7 100644 --- a/js/console/src/components/workbenches/workbench/files/WorkbenchFileTree.tsx +++ b/js/console/src/components/workbenches/workbench/files/WorkbenchFileTree.tsx @@ -9,6 +9,7 @@ import { Tooltip, TrashCanIcon, } from '@pluralsh/design-system' +import { WorkbenchEmptyText } from 'components/workbenches/common/WorkbenchEmptyState' import { FileTypeIcon, fileTreeMuiTheme, @@ -153,10 +154,14 @@ function DirectoryEntries({ /> ) if (!files?.length) - return ( + return path === '/' ? ( + + No files yet. Attachments and agent outputs will show up here. + + ) : ( ) diff --git a/js/console/src/components/workbenches/workbench/files/WorkbenchFileViewer.tsx b/js/console/src/components/workbenches/workbench/files/WorkbenchFileViewer.tsx index 28d926905c..4064f31f21 100644 --- a/js/console/src/components/workbenches/workbench/files/WorkbenchFileViewer.tsx +++ b/js/console/src/components/workbenches/workbench/files/WorkbenchFileViewer.tsx @@ -2,7 +2,6 @@ import { Button, Code, DownloadIcon, - EmptyState, Flex, IconFrame, CopyIcon, @@ -30,6 +29,7 @@ import { } from 'utils/file' import { WORKBENCH_FILES_HEADER_HEIGHT } from './WorkbenchFileTree' import { basename, workbenchFileDownloadUrl } from './workbenchFilePaths' +import { WorkbenchEmptyState } from '../../common/WorkbenchEmptyState' // text previews come back through graphql, so anything larger is download-only const PREVIEW_LIMIT = 1024 * 1024 @@ -79,22 +79,28 @@ export function WorkbenchFileViewer({ ) if (!file) return ( - ) if (file.kind === WorkbenchFileKind.Directory) - return + return ( + + ) const downloadUrl = workbenchFileDownloadUrl(file.downloadUrl) const unavailable = (description: string) => ( - {downloadUrl && } - + ) let body: ReactNode diff --git a/js/console/src/components/workbenches/workbench/files/WorkbenchFiles.tsx b/js/console/src/components/workbenches/workbench/files/WorkbenchFiles.tsx index b82ad47c9d..2e49554ef8 100644 --- a/js/console/src/components/workbenches/workbench/files/WorkbenchFiles.tsx +++ b/js/console/src/components/workbenches/workbench/files/WorkbenchFiles.tsx @@ -1,4 +1,3 @@ -import { EmptyState } from '@pluralsh/design-system' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' import { WorkbenchFileFragment } from 'generated/graphql' import { useState } from 'react' @@ -7,6 +6,7 @@ import { WORKBENCH_FILES_PATH_QUERY_PARAM } from 'routes/workbenchesRoutesConsts import { WorkbenchOutletContext, WorkbenchPageLayout } from '../Workbench' import { WorkbenchFileTree } from './WorkbenchFileTree' import { WorkbenchFileViewer } from './WorkbenchFileViewer' +import { WorkbenchEmptyState } from '../../common/WorkbenchEmptyState' import { DeleteWorkbenchFileModal } from './DeleteWorkbenchFileModal' import { toWorkbenchPath } from './workbenchFilePaths' @@ -37,9 +37,9 @@ export function WorkbenchFiles() { if (!isLoading && workbench && !workbench.filesystemEnabled) return ( - ) @@ -72,9 +72,9 @@ export function WorkbenchFiles() { onDelete={setToDelete} /> ) : ( - )} + ) : ( + return ( + + ) } return ( diff --git a/js/console/src/components/workbenches/workbench/job/WorkbenchJobUsage.tsx b/js/console/src/components/workbenches/workbench/job/WorkbenchJobUsage.tsx index bd2e82d425..949ce9cbc5 100644 --- a/js/console/src/components/workbenches/workbench/job/WorkbenchJobUsage.tsx +++ b/js/console/src/components/workbenches/workbench/job/WorkbenchJobUsage.tsx @@ -1,10 +1,5 @@ -import { - Card, - Divider, - EmptyState, - Flex, - ProgressBar, -} from '@pluralsh/design-system' +import { Card, Divider, Flex, ProgressBar } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { Body1P, Body2BoldP, @@ -47,7 +42,13 @@ export function WorkbenchJobUsage({ ) if (!hasUsage) { - return + return ( + + ) } return ( @@ -112,9 +113,11 @@ function TokenBreakdown({ if (!hasAnyTokens) { return ( - - - + ) } @@ -217,9 +220,11 @@ function CostBreakdown({ if (!hasAnyCost) { return ( - - - + ) } diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardDetail.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardDetail.tsx index 59dc500bc2..690bf637f8 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardDetail.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardDetail.tsx @@ -1,10 +1,10 @@ import { - EmptyState, ExpandIcon, Flex, HamburgerMenuCollapsedIcon, IconFrame, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { GqlError } from 'components/utils/Alert' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' import { CaptionP } from 'components/utils/typography/Text' @@ -73,7 +73,10 @@ export function DashboardDetail({ if (!dashboard) return ( - + ) diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardPanels.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardPanels.tsx index 60fa70278b..3b03ad2815 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardPanels.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchDashboardPanels.tsx @@ -2,7 +2,6 @@ import { Card, CloseIcon, DocsIcon, - EmptyState, ExpandIcon, Flex, IconFrame, @@ -10,6 +9,7 @@ import { ModalWrapper, Tooltip, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { ChatMarkdown } from 'components/ai/chatbot/ChatMarkdown' import { GqlError } from 'components/utils/Alert' import { MetricsSection } from 'components/utils/metrics/MetricsSection' @@ -100,9 +100,11 @@ export function WorkbenchDashboardPanels(props: DashboardPanelsProps) { if (graphs.length === 0) { return ( - - - + ) } @@ -143,7 +145,11 @@ function DashboardSection({ {props.graphs.length > 0 ? ( ) : ( - + )} ) @@ -393,7 +399,12 @@ function DataDashboardPanel({ {!graph.hasDatasource ? ( - + ) : dashboardPanelIsWaitingForData({ queriesEnabled, loading, @@ -403,7 +414,12 @@ function DataDashboardPanel({ ) : error && !data ? ( readOnly && /not available on public dashboards/i.test(error.message) ? ( - + ) : ( + return ( + + ) } function hasUnit(unit: Nullable) { diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorDetail.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorDetail.tsx index 35fe1c856a..ec9fa4ccc9 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorDetail.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorDetail.tsx @@ -3,7 +3,6 @@ import { Chip, ChipSeverity, DocsIcon, - EmptyState, ErrorIcon, ExpandIcon, Flex, @@ -15,6 +14,7 @@ import { Tooltip, useResizeObserver, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { Line } from '@nivo/line' import { RunStatusIcon } from 'components/ai/agent-runs/AgentRunInfoDisplays' import { POLL_INTERVAL } from 'components/cd/ContinuousDeployment' @@ -160,7 +160,10 @@ export function MonitorDetail({ if (!monitor) return ( - + ) @@ -447,7 +450,12 @@ function MonitorThresholdChart({ if (!isLog) { return ( - + ) } @@ -531,9 +539,13 @@ function MetricsThresholdPreview({ if (error && !data) return if (isEmpty(graphData[0].data)) return ( - ) @@ -664,7 +676,14 @@ function LogThresholdPreview({ ) if (error && !data) return if (isEmpty(graphData[0].data)) - return + return ( + + ) return ( diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorSettings.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorSettings.tsx index 59b2b04541..cfa7ecba27 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorSettings.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitorSettings.tsx @@ -1,11 +1,11 @@ import { Button, Card, - EmptyState, Flex, ReturnIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { getMonitorFormStepIcon, isMonitorFormValid, @@ -91,7 +91,11 @@ export function WorkbenchMonitorSettings() { if (!monitor) return ( - + ) @@ -99,8 +103,13 @@ export function WorkbenchMonitorSettings() { return ( {monitor.name} - + - + ) @@ -118,8 +127,13 @@ export function WorkbenchMonitorSettings() { return ( {monitor.name} - + - + ) diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoring.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoring.tsx index 2f47c75cb2..f9401141b5 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoring.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoring.tsx @@ -1,11 +1,17 @@ -import { Card, EmptyState } from '@pluralsh/design-system' +import { AddIcon, Button } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { + useWorkbenchDashboardsQuery, useWorkbenchMonitoringDashboardQuery, useWorkbenchMonitorQuery, + useWorkbenchMonitorsQuery, } from 'generated/graphql' +import { isEmpty } from 'lodash' import { useMemo, useState } from 'react' -import { useOutletContext, useParams } from 'react-router-dom' +import { useNavigate, useOutletContext, useParams } from 'react-router-dom' import { + getWorkbenchMonitoringDashboardCreateAbsPath, + getWorkbenchMonitoringMonitorCreateAbsPath, WORKBENCH_MONITORING_DASHBOARD_PARAM_ID, WORKBENCH_MONITORING_MONITOR_PARAM_ID, WORKBENCHES_CREATE_REL_PATH, @@ -114,11 +120,7 @@ export function WorkbenchMonitoring() { /> ) ) : ( - - - - - + )} {updateTarget && ( @@ -134,6 +136,57 @@ export function WorkbenchMonitoring() { ) } +function MonitoringEmptyState({ workbenchId }: { workbenchId: string }) { + const navigate = useNavigate() + const variables = { id: workbenchId, first: 1 } + const { data: dashboardsData } = useWorkbenchDashboardsQuery({ + variables, + fetchPolicy: 'cache-and-network', + }) + const { data: monitorsData } = useWorkbenchMonitorsQuery({ + variables, + fetchPolicy: 'cache-and-network', + }) + + if (!dashboardsData || !monitorsData) return null + + const hasAny = + !isEmpty(dashboardsData.workbench?.workbenchDashboards?.edges) || + !isEmpty(monitorsData.workbench?.monitors?.edges) + + return hasAny ? ( + + ) : ( + + + + + ) +} + const DetailSC = styled.div({ display: 'flex', flexDirection: 'column', diff --git a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoringSidebar.tsx b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoringSidebar.tsx index 736bc15be0..c1dea956f0 100644 --- a/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoringSidebar.tsx +++ b/js/console/src/components/workbenches/workbench/monitoring/WorkbenchMonitoringSidebar.tsx @@ -3,13 +3,13 @@ import { Button, CheckIcon, CloseIcon, - EmptyState, Flex, IconFrame, Input, SearchIcon, TrashCanIcon, } from '@pluralsh/design-system' +import { WorkbenchEmptyText } from 'components/workbenches/common/WorkbenchEmptyState' import { useThrottle } from 'components/hooks/useThrottle' import { GqlError } from 'components/utils/Alert' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' @@ -146,7 +146,9 @@ export function WorkbenchMonitoringSidebar({ {showNoMatches ? ( - + + No dashboards or monitors match “{trimmedFilter}”. + ) : ( <> diff --git a/js/console/src/components/workbenches/workbench/prompts/SavedPromptForm.tsx b/js/console/src/components/workbenches/workbench/prompts/SavedPromptForm.tsx index 293179b301..0cb78fc102 100644 --- a/js/console/src/components/workbenches/workbench/prompts/SavedPromptForm.tsx +++ b/js/console/src/components/workbenches/workbench/prompts/SavedPromptForm.tsx @@ -1,12 +1,12 @@ import { Button, - EmptyState, Flex, FormField, Input, ReturnIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { GqlError } from 'components/utils/Alert' import { useSimpleToast } from 'components/utils/SimpleToastContext' import { RectangleSkeleton } from 'components/utils/SkeletonLoaders' @@ -153,14 +153,18 @@ export function SavedPromptForm({ mode }: { mode: 'create' | 'edit' }) { if (mode === 'edit' && !promptsLoading && !savedPrompt) return ( - + - + ) const isLoading = diff --git a/js/console/src/components/workbenches/workbench/prompts/SavedPrompts.tsx b/js/console/src/components/workbenches/workbench/prompts/SavedPrompts.tsx index 3ff601eccd..c27fe2ca9c 100644 --- a/js/console/src/components/workbenches/workbench/prompts/SavedPrompts.tsx +++ b/js/console/src/components/workbenches/workbench/prompts/SavedPrompts.tsx @@ -1,8 +1,6 @@ import { ArrowUpIcon, Button, - Card, - EmptyState, Flex, IconFrame, PencilIcon, @@ -10,6 +8,7 @@ import { TrashCanIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { createColumnHelper } from '@tanstack/react-table' import { GqlError } from 'components/utils/Alert' import { prettifyPrompt } from 'components/utils/contentEditableChips' @@ -144,22 +143,20 @@ export function SavedPrompts() { css={{ maxWidth: 750 }} > {!!data && isEmpty(prompts) ? ( - - + - - + Add saved prompt + + ) : ( + - + ) const isLoading = diff --git a/js/console/src/components/workbenches/workbench/webhooks/WebhookTriggers.tsx b/js/console/src/components/workbenches/workbench/webhooks/WebhookTriggers.tsx index 0d9a49ab45..73a55e0b0b 100644 --- a/js/console/src/components/workbenches/workbench/webhooks/WebhookTriggers.tsx +++ b/js/console/src/components/workbenches/workbench/webhooks/WebhookTriggers.tsx @@ -1,8 +1,6 @@ import { ArrowTopRightIcon, Button, - Card, - EmptyState, Flex, IconFrame, PencilIcon, @@ -10,6 +8,7 @@ import { TrashCanIcon, useSetBreadcrumbs, } from '@pluralsh/design-system' +import { WorkbenchEmptyState } from 'components/workbenches/common/WorkbenchEmptyState' import { createColumnHelper } from '@tanstack/react-table' import { GqlError } from 'components/utils/Alert' import { StretchedFlex } from 'components/utils/StretchedFlex' @@ -124,34 +123,30 @@ export function WebhookTriggers() { gap="xxsmall" /> {!!data && isEmpty(webhooks) ? ( - - - - - - - - + + + + + + ) : (