mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2025-12-21 08:40:10 -06:00
FE Polish V: Searchable workflows, additional metadata tab (#2342)
* feat: search for workflows * fix: allow fts for workflow name * feat: add back metadata tab to details * feat: dynamic copy * chore: lint
This commit is contained in:
@@ -451,4 +451,12 @@ pre.shiki > code > span::before {
|
||||
align-items: center;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.side-sm\:hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.side-sm\:block {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export enum TabOption {
|
||||
Input = 'input',
|
||||
Logs = 'logs',
|
||||
Waterfall = 'waterfall',
|
||||
AdditionalMetadata = 'additional-metadata',
|
||||
}
|
||||
|
||||
interface TaskRunDetailProps {
|
||||
@@ -220,6 +221,18 @@ export const TaskRunDetail = ({
|
||||
<TabsTrigger variant="underlined" value={TabOption.Logs}>
|
||||
Logs
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
variant="underlined"
|
||||
value={TabOption.AdditionalMetadata}
|
||||
className="side-responsive-layout"
|
||||
>
|
||||
<span className="flex side-responsive-inner">
|
||||
<span className="block side-sm:hidden">Metadata</span>
|
||||
<span className="hidden side-sm:block">
|
||||
Additional Metadata
|
||||
</span>
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value={TabOption.Output}>
|
||||
<V1StepRunOutput taskRunId={taskRunId} />
|
||||
@@ -256,6 +269,15 @@ export const TaskRunDetail = ({
|
||||
<TabsContent value={TabOption.Logs}>
|
||||
<StepRunLogs taskRun={taskRun} />
|
||||
</TabsContent>
|
||||
<TabsContent value={TabOption.AdditionalMetadata}>
|
||||
<CodeHighlighter
|
||||
className="my-4 h-[400px] max-h-[400px] overflow-y-auto"
|
||||
maxHeight="400px"
|
||||
minHeight="400px"
|
||||
language="json"
|
||||
code={JSON.stringify(taskRun.additionalMetadata ?? {}, null, 2)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</TabsContent>
|
||||
{isStandaloneTaskRun && (
|
||||
|
||||
@@ -3,11 +3,21 @@ import { queries } from '@/lib/api';
|
||||
import { usePagination } from '@/hooks/use-pagination';
|
||||
import { useCurrentTenantId } from '@/hooks/use-tenant';
|
||||
import { useRefetchInterval } from '@/contexts/refetch-interval-context';
|
||||
import { z } from 'zod';
|
||||
import { useZodColumnFilters } from '@/hooks/use-zod-column-filters';
|
||||
import { nameKey } from '../components/workflow-columns';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
type UseWorkflowsProps = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
const workflowQuerySchema = z
|
||||
.object({
|
||||
s: z.string().optional(), // search
|
||||
})
|
||||
.default({ s: undefined });
|
||||
|
||||
export const useWorkflows = ({ key }: UseWorkflowsProps) => {
|
||||
const { tenantId } = useCurrentTenantId();
|
||||
const { refetchInterval } = useRefetchInterval();
|
||||
@@ -16,10 +26,22 @@ export const useWorkflows = ({ key }: UseWorkflowsProps) => {
|
||||
key,
|
||||
});
|
||||
|
||||
const paramKey = `workflows-${key}`;
|
||||
|
||||
const {
|
||||
state: { s: search },
|
||||
columnFilters,
|
||||
setColumnFilters,
|
||||
resetFilters,
|
||||
} = useZodColumnFilters(workflowQuerySchema, paramKey, { s: nameKey });
|
||||
|
||||
const [debouncedSearch] = useDebounce(search, 300);
|
||||
|
||||
const listWorkflowQuery = useQuery({
|
||||
...queries.workflows.list(tenantId, {
|
||||
limit,
|
||||
offset,
|
||||
name: debouncedSearch,
|
||||
}),
|
||||
refetchInterval,
|
||||
placeholderData: (data) => data,
|
||||
@@ -38,5 +60,8 @@ export const useWorkflows = ({ key }: UseWorkflowsProps) => {
|
||||
pagination,
|
||||
setPagination,
|
||||
setPageSize,
|
||||
columnFilters,
|
||||
setColumnFilters,
|
||||
resetFilters,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
import { DataTable } from '@/components/v1/molecules/data-table/data-table.tsx';
|
||||
import { Loading } from '@/components/v1/ui/loading.tsx';
|
||||
import { SortingState, VisibilityState } from '@tanstack/react-table';
|
||||
import { VisibilityState } from '@tanstack/react-table';
|
||||
import { useCurrentTenantId } from '@/hooks/use-tenant';
|
||||
import { columns, WorkflowColumn } from './components/workflow-columns';
|
||||
import {
|
||||
columns,
|
||||
nameKey,
|
||||
WorkflowColumn,
|
||||
} from './components/workflow-columns';
|
||||
import { useWorkflows } from './hooks/use-workflows';
|
||||
import { DocsButton } from '@/components/v1/docs/docs-button';
|
||||
import { docsPages } from '@/lib/generated/docs';
|
||||
import { ToolbarType } from '@/components/v1/molecules/data-table/data-table-toolbar';
|
||||
|
||||
export default function WorkflowTable() {
|
||||
const { tenantId } = useCurrentTenantId();
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([
|
||||
{
|
||||
id: 'createdAt',
|
||||
desc: true,
|
||||
},
|
||||
]);
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
|
||||
const {
|
||||
@@ -28,6 +27,9 @@ export default function WorkflowTable() {
|
||||
setPagination,
|
||||
setPageSize,
|
||||
refetch,
|
||||
columnFilters,
|
||||
setColumnFilters,
|
||||
resetFilters,
|
||||
} = useWorkflows({
|
||||
key: 'workflows-table',
|
||||
});
|
||||
@@ -53,6 +55,13 @@ export default function WorkflowTable() {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
filters={[
|
||||
{
|
||||
columnId: nameKey,
|
||||
title: WorkflowColumn.name,
|
||||
type: ToolbarType.Search,
|
||||
},
|
||||
]}
|
||||
columnVisibility={columnVisibility}
|
||||
setColumnVisibility={setColumnVisibility}
|
||||
pagination={pagination}
|
||||
@@ -60,17 +69,16 @@ export default function WorkflowTable() {
|
||||
onSetPageSize={setPageSize}
|
||||
showSelectedRows={false}
|
||||
pageCount={numWorkflows}
|
||||
sorting={sorting}
|
||||
setSorting={setSorting}
|
||||
isLoading={isLoading}
|
||||
manualSorting={false}
|
||||
manualFiltering={false}
|
||||
showColumnToggle={true}
|
||||
columnKeyToName={WorkflowColumn}
|
||||
refetchProps={{
|
||||
isRefetching,
|
||||
onRefetch: refetch,
|
||||
}}
|
||||
columnFilters={columnFilters}
|
||||
setColumnFilters={setColumnFilters}
|
||||
onResetFilters={resetFilters}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
-- name: CountWorkflows :one
|
||||
SELECT
|
||||
count(workflows) OVER() AS total
|
||||
FROM
|
||||
"Workflow" as workflows
|
||||
SELECT COUNT(w.*)
|
||||
FROM "Workflow" w
|
||||
WHERE
|
||||
workflows."tenantId" = $1 AND
|
||||
workflows."deletedAt" IS NULL AND
|
||||
(
|
||||
sqlc.narg('eventKey')::text IS NULL OR
|
||||
workflows."id" IN (
|
||||
w."tenantId" = $1
|
||||
AND w."deletedAt" IS NULL
|
||||
AND (
|
||||
sqlc.narg('eventKey')::TEXT IS NULL OR
|
||||
w."id" IN (
|
||||
SELECT
|
||||
DISTINCT ON(t1."workflowId") t1."workflowId"
|
||||
FROM
|
||||
@@ -31,7 +29,12 @@ WHERE
|
||||
ORDER BY
|
||||
t1."workflowId" DESC, t1."order" DESC
|
||||
)
|
||||
);
|
||||
)
|
||||
AND (
|
||||
sqlc.narg('search')::TEXT IS NULL
|
||||
OR w.name ILIKE CONCAT('%', sqlc.narg('search')::TEXT, '%')
|
||||
)
|
||||
;
|
||||
|
||||
-- name: ListWorkflowsLatestRuns :many
|
||||
SELECT
|
||||
|
||||
@@ -181,16 +181,14 @@ func (q *Queries) CountWorkflowRunsRoundRobin(ctx context.Context, db DBTX, arg
|
||||
}
|
||||
|
||||
const countWorkflows = `-- name: CountWorkflows :one
|
||||
SELECT
|
||||
count(workflows) OVER() AS total
|
||||
FROM
|
||||
"Workflow" as workflows
|
||||
SELECT COUNT(w.*)
|
||||
FROM "Workflow" w
|
||||
WHERE
|
||||
workflows."tenantId" = $1 AND
|
||||
workflows."deletedAt" IS NULL AND
|
||||
(
|
||||
$2::text IS NULL OR
|
||||
workflows."id" IN (
|
||||
w."tenantId" = $1
|
||||
AND w."deletedAt" IS NULL
|
||||
AND (
|
||||
$2::TEXT IS NULL OR
|
||||
w."id" IN (
|
||||
SELECT
|
||||
DISTINCT ON(t1."workflowId") t1."workflowId"
|
||||
FROM
|
||||
@@ -214,18 +212,23 @@ WHERE
|
||||
t1."workflowId" DESC, t1."order" DESC
|
||||
)
|
||||
)
|
||||
AND (
|
||||
$3::TEXT IS NULL
|
||||
OR w.name ILIKE CONCAT('%', $3::TEXT, '%')
|
||||
)
|
||||
`
|
||||
|
||||
type CountWorkflowsParams struct {
|
||||
TenantId pgtype.UUID `json:"tenantId"`
|
||||
EventKey pgtype.Text `json:"eventKey"`
|
||||
Search pgtype.Text `json:"search"`
|
||||
}
|
||||
|
||||
func (q *Queries) CountWorkflows(ctx context.Context, db DBTX, arg CountWorkflowsParams) (int64, error) {
|
||||
row := db.QueryRow(ctx, countWorkflows, arg.TenantId, arg.EventKey)
|
||||
var total int64
|
||||
err := row.Scan(&total)
|
||||
return total, err
|
||||
row := db.QueryRow(ctx, countWorkflows, arg.TenantId, arg.EventKey, arg.Search)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createJob = `-- name: CreateJob :one
|
||||
|
||||
@@ -62,7 +62,9 @@ func (r *workflowAPIRepository) ListWorkflows(tenantId string, opts *repository.
|
||||
}
|
||||
|
||||
if opts.Name != nil {
|
||||
queryParams.Search = pgtype.Text{String: *opts.Name, Valid: true}
|
||||
search := pgtype.Text{String: *opts.Name, Valid: true}
|
||||
queryParams.Search = search
|
||||
countParams.Search = search
|
||||
}
|
||||
|
||||
orderByField := "createdAt"
|
||||
|
||||
Reference in New Issue
Block a user