mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
feat: logflare workflow run logs
This commit is contained in:
1 parent
99c147a56c
commit
4dcd552e49
9 files changed
+43
-235
No files matched your search
@@ -38,7 +38,6 @@ import {
|
||||
} from 'ui'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import BranchStatusBadge from './BranchStatusBadge'
|
||||
import WorkflowLogs from './WorkflowLogs'
|
||||
|
||||
interface BranchManagementSectionProps {
|
||||
header: string
|
||||
@@ -234,7 +233,13 @@ export const BranchRow = ({
|
||||
View Repository
|
||||
</Link>
|
||||
</Button>
|
||||
{branchingWorkflowLogsEnabled && <WorkflowLogs projectRef={branch.project_ref} />}
|
||||
{branchingWorkflowLogsEnabled && (
|
||||
<Button type="default" asChild>
|
||||
<Link href={`/project/${branch.project_ref}/logs/workflow-run-logs`}>
|
||||
View Logs
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="text" icon={<MoreVertical />} className="px-1" />
|
||||
@@ -289,7 +294,11 @@ export const BranchRow = ({
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
<WorkflowLogs projectRef={branch.project_ref} />
|
||||
<Button type="default" asChild>
|
||||
<Link href={`/project/${branch.project_ref}/logs/workflow-run-logs`}>
|
||||
View Logs
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<DropdownMenu modal={false}>
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useWorkflowRunLogsQuery } from 'data/workflow-runs/workflow-run-logs-query'
|
||||
import { useWorkflowRunsQuery } from 'data/workflow-runs/workflow-runs-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { ArrowLeft, ArrowRight } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from 'ui'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import BranchStatusBadge from './BranchStatusBadge'
|
||||
|
||||
interface WorkflowLogsProps {
|
||||
projectRef: string
|
||||
}
|
||||
|
||||
const WorkflowLogs = ({ projectRef }: WorkflowLogsProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const {
|
||||
data: workflowRuns,
|
||||
isSuccess: isWorkflowRunsSuccess,
|
||||
isLoading: isWorkflowRunsLoading,
|
||||
isError: isWorkflowRunsError,
|
||||
error: workflowRunsError,
|
||||
} = useWorkflowRunsQuery(
|
||||
{
|
||||
projectRef,
|
||||
},
|
||||
{
|
||||
enabled: isOpen,
|
||||
}
|
||||
)
|
||||
|
||||
const [selectedWorkflowRunId, setSelectedWorkflowRunId] = useState<string | undefined>(undefined)
|
||||
|
||||
const {
|
||||
data: workflowRunLogs,
|
||||
isSuccess: isWorkflowRunLogsSuccess,
|
||||
isLoading: isWorkflowRunLogsLoading,
|
||||
isError: isWorkflowRunLogsError,
|
||||
error: workflowRunLogsError,
|
||||
} = useWorkflowRunLogsQuery(
|
||||
{
|
||||
workflowRunId: selectedWorkflowRunId,
|
||||
},
|
||||
{
|
||||
enabled: isOpen && selectedWorkflowRunId !== undefined,
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="default">View Logs</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent size="xlarge">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Workflow Logs</DialogTitle>
|
||||
|
||||
<DialogDescription>Select a workflow run to view logs</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogSectionSeparator />
|
||||
|
||||
<DialogSection className={cn('px-0', isWorkflowRunLogsSuccess ? 'py-0 pt-2' : '!py-0')}>
|
||||
{selectedWorkflowRunId === undefined ? (
|
||||
<>
|
||||
{isWorkflowRunsLoading && <GenericSkeletonLoader className="py-2 px-5" />}
|
||||
{isWorkflowRunsError && <AlertError error={workflowRunsError} />}
|
||||
{isWorkflowRunsSuccess &&
|
||||
(workflowRuns.length > 0 ? (
|
||||
<ul className="divide-y">
|
||||
{workflowRuns.map((workflowRun) => (
|
||||
<li key={workflowRun.id} className="py-3 px-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSelectedWorkflowRunId(workflowRun.id)}
|
||||
className="flex items-center gap-2 w-full justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<BranchStatusBadge status={workflowRun.status} />
|
||||
<span className="text-sm">
|
||||
{dayjs(workflowRun.created_at).format('DD MMM, YYYY HH:mm')}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-center text-sm text-foreground-light py-4">
|
||||
No workflow runs found.
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2 py-2">
|
||||
<Button
|
||||
onClick={() => setSelectedWorkflowRunId(undefined)}
|
||||
type="text"
|
||||
icon={<ArrowLeft />}
|
||||
className="self-start mx-5"
|
||||
>
|
||||
Back to workflow runs
|
||||
</Button>
|
||||
|
||||
{isWorkflowRunLogsLoading && <GenericSkeletonLoader className="py-2 px-5" />}
|
||||
{isWorkflowRunLogsError && (
|
||||
<AlertError
|
||||
className="rounded-none"
|
||||
subject="Failed to retrieve workflow logs"
|
||||
error={workflowRunLogsError}
|
||||
/>
|
||||
)}
|
||||
{isWorkflowRunLogsSuccess && (
|
||||
<pre className="whitespace-pre max-h-[500px] overflow-scroll px-5 pb-5">
|
||||
{workflowRunLogs}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DialogSection>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkflowLogs
|
||||
@@ -363,6 +363,7 @@ export enum LogsTableName {
|
||||
POSTGREST = 'postgrest_logs',
|
||||
SUPAVISOR = 'supavisor_logs',
|
||||
WAREHOUSE = 'warehouse_logs',
|
||||
WORKFLOW_RUN = 'workflow_run_logs',
|
||||
}
|
||||
|
||||
export const LOGS_TABLES = {
|
||||
@@ -376,6 +377,7 @@ export const LOGS_TABLES = {
|
||||
postgrest: LogsTableName.POSTGREST,
|
||||
supavisor: LogsTableName.SUPAVISOR,
|
||||
warehouse: LogsTableName.WAREHOUSE,
|
||||
workflow_run: LogsTableName.WORKFLOW_RUN,
|
||||
}
|
||||
|
||||
export const LOGS_SOURCE_DESCRIPTION = {
|
||||
|
||||
@@ -88,6 +88,7 @@ export type QueryType =
|
||||
| 'supavisor'
|
||||
| 'postgrest'
|
||||
| 'warehouse'
|
||||
| 'workflow_run'
|
||||
|
||||
export type Mode = 'simple' | 'custom'
|
||||
|
||||
|
||||
@@ -163,6 +163,12 @@ export function LogsSidebarMenuV2() {
|
||||
url: `/project/${ref}/logs/edge-functions-logs`,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Workflow Run Logs',
|
||||
key: 'workflow-run-logs',
|
||||
url: `/project/${ref}/logs/workflow-run-logs`,
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
|
||||
const filteredLogs = BASE_COLLECTIONS.filter((collection) => {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const workflowRunKeys = {
|
||||
list: (projectRef: string | undefined) => ['projects', projectRef, 'workflow-runs'] as const,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { workflowRunKeys } from './keys'
|
||||
|
||||
export type WorkflowRunLogsVariables = {
|
||||
workflowRunId?: string
|
||||
}
|
||||
|
||||
export async function getWorkflowRunLogs(
|
||||
{ workflowRunId }: WorkflowRunLogsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!workflowRunId) throw new Error('workflowRunId is required')
|
||||
|
||||
const { data, error } = await get(`/platform/workflow-runs/{workflow_run_id}/logs`, {
|
||||
params: {
|
||||
path: {
|
||||
workflow_run_id: workflowRunId,
|
||||
},
|
||||
},
|
||||
parseAs: 'text',
|
||||
headers: {
|
||||
Accept: 'text/plain',
|
||||
},
|
||||
signal,
|
||||
})
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
.split('\n')
|
||||
.flatMap((line) => line.split('\r'))
|
||||
.join('\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export type WorkflowRunLogsData = Awaited<ReturnType<typeof getWorkflowRunLogs>>
|
||||
export type WorkflowRunLogsError = ResponseError
|
||||
|
||||
export const useWorkflowRunLogsQuery = <TData = WorkflowRunLogsData>(
|
||||
{ workflowRunId }: WorkflowRunLogsVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<WorkflowRunLogsData, WorkflowRunLogsError, TData> = {}
|
||||
) =>
|
||||
useQuery<WorkflowRunLogsData, WorkflowRunLogsError, TData>(
|
||||
workflowRunKeys.list(workflowRunId),
|
||||
({ signal }) => getWorkflowRunLogs({ workflowRunId }, signal),
|
||||
{ enabled: enabled && typeof workflowRunId !== 'undefined', staleTime: 0, ...options }
|
||||
)
|
||||
@@ -1,37 +0,0 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { workflowRunKeys } from './keys'
|
||||
|
||||
export type WorkflowRunsVariables = {
|
||||
projectRef?: string
|
||||
}
|
||||
|
||||
export async function getWorkflowRuns({ projectRef }: WorkflowRunsVariables, signal?: AbortSignal) {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
|
||||
const { data, error } = await get(`/platform/workflow-runs`, {
|
||||
params: {
|
||||
query: {
|
||||
project_ref: projectRef,
|
||||
},
|
||||
},
|
||||
signal,
|
||||
})
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
}
|
||||
|
||||
export type WorkflowRunsData = Awaited<ReturnType<typeof getWorkflowRuns>>
|
||||
export type WorkflowRunsError = ResponseError
|
||||
|
||||
export const useWorkflowRunsQuery = <TData = WorkflowRunsData>(
|
||||
{ projectRef }: WorkflowRunsVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<WorkflowRunsData, WorkflowRunsError, TData> = {}
|
||||
) =>
|
||||
useQuery<WorkflowRunsData, WorkflowRunsError, TData>(
|
||||
workflowRunKeys.list(projectRef),
|
||||
({ signal }) => getWorkflowRuns({ projectRef }, signal),
|
||||
{ enabled: enabled && typeof projectRef !== 'undefined', staleTime: 0, ...options }
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useParams } from 'common'
|
||||
import { LogsTableName } from 'components/interfaces/Settings/Logs/Logs.constants'
|
||||
import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
|
||||
import LogsLayout from 'components/layouts/LogsLayout/LogsLayout'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
export const LogPage: NextPageWithLayout = () => {
|
||||
const { ref } = useParams()
|
||||
|
||||
return (
|
||||
<LogsPreviewer
|
||||
projectRef={ref as string}
|
||||
condensedLayout={true}
|
||||
tableName={LogsTableName.WORKFLOW_RUN}
|
||||
queryType={'workflow_run'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
LogPage.getLayout = (page) => <LogsLayout title="Workflow Run Logs">{page}</LogsLayout>
|
||||
|
||||
export default LogPage
|
||||
Reference in new issue
Block a user