mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
refactor(automations): extract list toolbar and drop dead list projection
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import React from 'react'
|
||||
import { Plus, RefreshCw } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { AutomationHostCatalogEntry } from './automation-host-catalog-types'
|
||||
import type { AutomationListArrowKey } from './automation-list-keyboard-navigation'
|
||||
import { clampAutomationListSearchQueryInput } from './automation-list-search'
|
||||
import type { AutomationListFilter } from './automation-list-view'
|
||||
import { AutomationListSearchField } from './AutomationListSearchField'
|
||||
import { AutomationListFilterMenu } from './AutomationListFilterMenu'
|
||||
import type { AutomationTemplate } from './automation-templates'
|
||||
|
||||
type AutomationListToolbarProps = {
|
||||
/** Focus fallback for the list: the controls that produced the rows. */
|
||||
toolbarRef: React.RefObject<HTMLDivElement | null>
|
||||
listSearchQuery: string
|
||||
isListSearchQueryTooLarge: boolean
|
||||
onListSearchQueryChange: (query: string) => void
|
||||
onSearchArrowNavigate: (key: AutomationListArrowKey) => void
|
||||
onSearchEnter: () => void
|
||||
listFilter: AutomationListFilter
|
||||
onListFilterChange: (filter: AutomationListFilter) => void
|
||||
hostEntries: readonly AutomationHostCatalogEntry[]
|
||||
onRefresh: () => void
|
||||
isRefreshing: boolean
|
||||
openCreateDialog: (template?: AutomationTemplate) => void
|
||||
canCreateAutomation: boolean
|
||||
}
|
||||
|
||||
export function AutomationListToolbar({
|
||||
toolbarRef,
|
||||
listSearchQuery,
|
||||
isListSearchQueryTooLarge,
|
||||
onListSearchQueryChange,
|
||||
onSearchArrowNavigate,
|
||||
onSearchEnter,
|
||||
listFilter,
|
||||
onListFilterChange,
|
||||
hostEntries,
|
||||
onRefresh,
|
||||
isRefreshing,
|
||||
openCreateDialog,
|
||||
canCreateAutomation
|
||||
}: AutomationListToolbarProps): React.JSX.Element {
|
||||
return (
|
||||
<div className="flex shrink-0 items-end justify-between gap-3 pb-4">
|
||||
<div ref={toolbarRef} className="flex min-w-0 flex-1 items-end gap-2">
|
||||
<AutomationListSearchField
|
||||
className="w-full max-w-xs"
|
||||
query={listSearchQuery}
|
||||
isTooLarge={isListSearchQueryTooLarge}
|
||||
onQueryChange={(query) =>
|
||||
onListSearchQueryChange(clampAutomationListSearchQueryInput(query))
|
||||
}
|
||||
onClear={() => onListSearchQueryChange('')}
|
||||
onArrowNavigate={onSearchArrowNavigate}
|
||||
onEnter={onSearchEnter}
|
||||
/>
|
||||
<AutomationListFilterMenu
|
||||
filter={listFilter}
|
||||
onChange={onListFilterChange}
|
||||
hostEntries={hostEntries}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationsPage.19a6e30eae',
|
||||
'Refresh automations'
|
||||
)}
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
className="shrink-0 border border-border bg-background shadow-none hover:bg-muted/50"
|
||||
>
|
||||
<RefreshCw className={cn('size-4', isRefreshing && 'animate-spin')} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationsPage.19a6e30eae',
|
||||
'Refresh automations'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
onClick={() => openCreateDialog()}
|
||||
disabled={!canCreateAutomation}
|
||||
data-contextual-tour-target="automations-create"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
{translate('auto.components.automations.AutomationsPage.newAutomation', 'New Automation')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { Plus, RefreshCw } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type {
|
||||
Automation,
|
||||
AutomationRun,
|
||||
ExternalAutomationAction,
|
||||
ExternalAutomationJob,
|
||||
@@ -19,7 +15,6 @@ import type { RuntimeStatus } from '../../../../shared/runtime-types'
|
||||
import type { TaskSourceHostAvailability } from '../task-source-context-summary'
|
||||
import type { AutomationRowAction } from './automation-captured-owner'
|
||||
import type { AutomationHostTarget } from './automation-host-client'
|
||||
import { clampAutomationListSearchQueryInput } from './automation-list-search'
|
||||
import {
|
||||
createAutomationListEnterHandler,
|
||||
getAutomationListArrowNavigationTarget,
|
||||
@@ -27,8 +22,7 @@ import {
|
||||
} from './automation-list-keyboard-navigation'
|
||||
import type { AutomationListRow } from './automation-list-row-identity'
|
||||
import type { AutomationPaneTab } from './automation-page-state'
|
||||
import { AutomationListSearchField } from './AutomationListSearchField'
|
||||
import { AutomationListFilterMenu, AutomationListFilterPills } from './AutomationListFilterMenu'
|
||||
import { AutomationListFilterPills } from './AutomationListFilterMenu'
|
||||
import { isAutomationListFilterActive, type AutomationListFilter } from './automation-list-view'
|
||||
import { automationHostFilterStableKey } from '../../../../shared/automation-host-filter'
|
||||
import type { AutomationTemplate } from './automation-templates'
|
||||
@@ -48,6 +42,7 @@ import { LIST_TABLE_CONTAINER_CLASS } from '@/lib/list-table-layout'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { AutomationTemplateEmptyState } from './AutomationTemplateEmptyState'
|
||||
import { AutomationListTableHeader } from './AutomationListTableHeader'
|
||||
import { AutomationListToolbar } from './AutomationListToolbar'
|
||||
|
||||
const TEMPLATE_EMPTY_STATES: ReadonlySet<string> = new Set(['host-empty', 'all-hosts-empty'])
|
||||
const EMPTY_AUTOMATION_RUNS: ReadonlyMap<string, AutomationRun> = new Map()
|
||||
@@ -55,9 +50,6 @@ const EMPTY_AUTOMATION_RUNS: ReadonlyMap<string, AutomationRun> = new Map()
|
||||
type AutomationsListPanelProps = {
|
||||
hasListItems: boolean
|
||||
hasFilteredListItems: boolean
|
||||
/** Compatibility projections for consumers that still inspect bare rows. */
|
||||
isListSearchActive?: boolean
|
||||
filteredAutomations?: readonly Automation[]
|
||||
listSearchQuery: string
|
||||
isListSearchQueryTooLarge: boolean
|
||||
onListSearchQueryChange: (query: string) => void
|
||||
@@ -289,61 +281,21 @@ export function AutomationsListPanel(props: AutomationsListPanelProps): React.JS
|
||||
className="flex min-h-0 flex-1 flex-col overflow-hidden px-3 pb-4 md:px-5"
|
||||
data-contextual-tour-target="automations-list"
|
||||
>
|
||||
<div className="flex shrink-0 items-end justify-between gap-3 pb-4">
|
||||
<div ref={toolbarRef} className="flex min-w-0 flex-1 items-end gap-2">
|
||||
<AutomationListSearchField
|
||||
className="w-full max-w-xs"
|
||||
query={listSearchQuery}
|
||||
isTooLarge={isListSearchQueryTooLarge}
|
||||
onQueryChange={(query) =>
|
||||
onListSearchQueryChange(clampAutomationListSearchQueryInput(query))
|
||||
}
|
||||
onClear={() => onListSearchQueryChange('')}
|
||||
onArrowNavigate={handleSearchArrowNavigate}
|
||||
onEnter={handleSearchEnter}
|
||||
/>
|
||||
<AutomationListFilterMenu
|
||||
filter={listFilter}
|
||||
onChange={onListFilterChange}
|
||||
hostEntries={hostCatalog.entries}
|
||||
/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={translate(
|
||||
'auto.components.automations.AutomationsPage.19a6e30eae',
|
||||
'Refresh automations'
|
||||
)}
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
className="shrink-0 border border-border bg-background shadow-none hover:bg-muted/50"
|
||||
>
|
||||
<RefreshCw className={cn('size-4', isRefreshing && 'animate-spin')} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
'auto.components.automations.AutomationsPage.19a6e30eae',
|
||||
'Refresh automations'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
onClick={() => openCreateDialog()}
|
||||
disabled={!canCreateAutomation}
|
||||
data-contextual-tour-target="automations-create"
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
{translate('auto.components.automations.AutomationsPage.newAutomation', 'New Automation')}
|
||||
</Button>
|
||||
</div>
|
||||
<AutomationListToolbar
|
||||
toolbarRef={toolbarRef}
|
||||
listSearchQuery={listSearchQuery}
|
||||
isListSearchQueryTooLarge={isListSearchQueryTooLarge}
|
||||
onListSearchQueryChange={onListSearchQueryChange}
|
||||
onSearchArrowNavigate={handleSearchArrowNavigate}
|
||||
onSearchEnter={handleSearchEnter}
|
||||
listFilter={listFilter}
|
||||
onListFilterChange={onListFilterChange}
|
||||
hostEntries={hostCatalog.entries}
|
||||
onRefresh={onRefresh}
|
||||
isRefreshing={isRefreshing}
|
||||
openCreateDialog={openCreateDialog}
|
||||
canCreateAutomation={canCreateAutomation}
|
||||
/>
|
||||
|
||||
{listFilterActive || legacyScopeStableKey !== null ? (
|
||||
<div className="flex flex-wrap items-center gap-1.5 pb-3">
|
||||
|
||||
@@ -90,10 +90,8 @@ export function AutomationsPageSurface({
|
||||
hostCatalog,
|
||||
hasListItems,
|
||||
hasFilteredListItems,
|
||||
isListSearchActive,
|
||||
isListSearchQueryTooLarge,
|
||||
filteredRows,
|
||||
filteredAutomations,
|
||||
filteredExternalAutomationEntries,
|
||||
selected,
|
||||
selectedRow,
|
||||
@@ -319,7 +317,6 @@ export function AutomationsPageSurface({
|
||||
hasFilteredListItems={hasFilteredListItems}
|
||||
listSearchQuery={listSearchQuery}
|
||||
isListSearchQueryTooLarge={isListSearchQueryTooLarge}
|
||||
isListSearchActive={isListSearchActive}
|
||||
onListSearchQueryChange={setListSearchQuery}
|
||||
listFilter={listFilter}
|
||||
onListFilterChange={onListFilterChange}
|
||||
@@ -337,7 +334,6 @@ export function AutomationsPageSurface({
|
||||
}
|
||||
}}
|
||||
filteredRows={filteredRows}
|
||||
filteredAutomations={filteredAutomations}
|
||||
filteredExternalAutomationEntries={filteredExternalAutomationEntries}
|
||||
selectedRowKey={selectedRow?.key ?? null}
|
||||
selectedExternalKey={local.selectedExternalKey}
|
||||
|
||||
@@ -39,7 +39,6 @@ export const RUNTIME_REPO_ID = RUNTIME_REPO_ID_FIXTURE
|
||||
export const RUNTIME_WORKSPACE_ID = RUNTIME_WORKSPACE_ID_FIXTURE
|
||||
|
||||
export type ListPanelProps = {
|
||||
filteredAutomations: Automation[]
|
||||
filteredExternalAutomationEntries: ExternalAutomationListEntry[]
|
||||
selectedExternal: ExternalAutomationListEntry | null
|
||||
openEditExternalDialog: (
|
||||
|
||||
@@ -61,7 +61,6 @@ export function useAutomationListSearch({
|
||||
selectExternalKey: (externalKey: string | null) => void
|
||||
}): {
|
||||
isListSearchQueryTooLarge: boolean
|
||||
isListSearchActive: boolean
|
||||
filteredRows: readonly AutomationListRow[]
|
||||
filteredExternalAutomationEntries: readonly ExternalAutomationListEntry[]
|
||||
hasListItems: boolean
|
||||
@@ -200,7 +199,6 @@ export function useAutomationListSearch({
|
||||
|
||||
return {
|
||||
isListSearchQueryTooLarge,
|
||||
isListSearchActive,
|
||||
filteredRows,
|
||||
filteredExternalAutomationEntries,
|
||||
hasListItems: hostRowCount > 0,
|
||||
|
||||
@@ -85,7 +85,6 @@ export function useAutomationsPageListState({
|
||||
const selected = selectedRow?.automation ?? null
|
||||
const {
|
||||
isListSearchQueryTooLarge,
|
||||
isListSearchActive,
|
||||
filteredRows,
|
||||
filteredExternalAutomationEntries,
|
||||
hasListItems,
|
||||
@@ -145,11 +144,7 @@ export function useAutomationsPageListState({
|
||||
selected,
|
||||
selectedAutomationRunsWithWorkspaceNames,
|
||||
isListSearchQueryTooLarge,
|
||||
isListSearchActive,
|
||||
filteredRows,
|
||||
// Keep the legacy projection available to integrations that still consume
|
||||
// bare automations; the table itself uses host-qualified rows.
|
||||
filteredAutomations: filteredRows.map((row) => row.automation),
|
||||
filteredExternalAutomationEntries,
|
||||
hasListItems,
|
||||
hasFilteredListItems,
|
||||
|
||||
Reference in New Issue
Block a user