From c6dc2c566ea604a55816348a69a8ff5c510c70b8 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Thu, 10 Oct 2024 16:29:04 +0800 Subject: [PATCH] feat: workflow run logs (#29678) * feat: workflow run logs * progress * adds logs modal * remove menu item * output raw text * update parseAs * accept text/plain * cleanup ui * feature flag new logs * fix types * fix condition * cleanup spacing * loading spacing * cheeky console.log * more log * split logs properly * add workflow logs to prod branches * address some feedback * Some UI tweaks * More UI tweak * More UI tweak --------- Co-authored-by: Joshen Lim --- .../BranchManagement/BranchPanels.tsx | 15 +- .../BranchManagement/CreateBranchModal.tsx | 22 ++- .../BranchManagement/WorkflowLogs.tsx | 141 ++++++++++++++++++ apps/studio/data/workflow-runs/keys.ts | 3 + .../workflow-runs/workflow-run-logs-query.ts | 51 +++++++ .../data/workflow-runs/workflow-runs-query.ts | 37 +++++ .../ui-patterns/ShimmeringLoader/index.tsx | 8 +- 7 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 apps/studio/components/interfaces/BranchManagement/WorkflowLogs.tsx create mode 100644 apps/studio/data/workflow-runs/keys.ts create mode 100644 apps/studio/data/workflow-runs/workflow-run-logs-query.ts create mode 100644 apps/studio/data/workflow-runs/workflow-runs-query.ts diff --git a/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx b/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx index 91f2e1f81f8..41b2a1bcebe 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchPanels.tsx @@ -24,6 +24,7 @@ import { useBranchResetMutation } from 'data/branches/branch-reset-mutation' import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useFlag } from 'hooks/ui/useFlag' import { Badge, Button, @@ -37,6 +38,7 @@ import { } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import BranchStatusBadge from './BranchStatusBadge' +import WorkflowLogs from './WorkflowLogs' interface BranchManagementSectionProps { header: string @@ -113,8 +115,12 @@ export const BranchRow = ({ const createPullRequestURL = generateCreatePullRequestURL?.(branch.git_branch) ?? 'https://github.com' - const shouldRenderLogsButton = - branch.pr_number !== undefined && branch.latest_check_run_id !== undefined + const branchingWorkflowLogsEnabled = useFlag('branchingWorkflowLogs') + + const shouldRenderGitHubLogsButton = + !branchingWorkflowLogsEnabled && + branch.pr_number !== undefined && + branch.latest_check_run_id !== undefined const checkRunLogsURL = `https://github.com/${repo}/pull/${branch.pr_number}/checks?check_run_id=${branch.latest_check_run_id}` const { ref, inView } = useInView() @@ -228,6 +234,7 @@ export const BranchRow = ({ View Repository + {branchingWorkflowLogsEnabled && } + ) : ( + )} diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx index 6af935d805d..db71fa16422 100644 --- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx @@ -1,6 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod' import { useParams } from 'common' -import { AlertCircle, Check, ExternalLink, Loader2 } from 'lucide-react' +import { Check, ExternalLink, Loader2 } from 'lucide-react' import Link from 'next/link' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' @@ -16,9 +16,6 @@ import { useGitHubConnectionsQuery } from 'data/integrations/github-connections- import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useSelectedProject } from 'hooks/misc/useSelectedProject' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, FormControl_Shadcn_, FormField_Shadcn_, @@ -28,6 +25,7 @@ import { Input_Shadcn_, Modal, } from 'ui' +import { Admonition } from 'ui-patterns' interface CreateBranchModalProps { visible: boolean @@ -204,15 +202,13 @@ const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps) => { - - - - Each Preview branch costs $0.32 per day - - Each preview branch costs $0.32 per day until it is removed. This pricing is for - Early Access and is subject to change. - - + + diff --git a/apps/studio/components/interfaces/BranchManagement/WorkflowLogs.tsx b/apps/studio/components/interfaces/BranchManagement/WorkflowLogs.tsx new file mode 100644 index 00000000000..f631d52c94b --- /dev/null +++ b/apps/studio/components/interfaces/BranchManagement/WorkflowLogs.tsx @@ -0,0 +1,141 @@ +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(undefined) + + const { + data: workflowRunLogs, + isSuccess: isWorkflowRunLogsSuccess, + isLoading: isWorkflowRunLogsLoading, + isError: isWorkflowRunLogsError, + error: workflowRunLogsError, + } = useWorkflowRunLogsQuery( + { + workflowRunId: selectedWorkflowRunId, + }, + { + enabled: isOpen && selectedWorkflowRunId !== undefined, + } + ) + + return ( + + + + + + + + Workflow Logs + + Select a workflow run to view logs + + + + + + {selectedWorkflowRunId === undefined ? ( + <> + {isWorkflowRunsLoading && } + {isWorkflowRunsError && } + {isWorkflowRunsSuccess && + (workflowRuns.length > 0 ? ( +
    + {workflowRuns.map((workflowRun) => ( +
  • + +
  • + ))} +
+ ) : ( +

+ No workflow runs found. +

+ ))} + + ) : ( +
+ + + {isWorkflowRunLogsLoading && } + {isWorkflowRunLogsError && ( + + )} + {isWorkflowRunLogsSuccess && ( +
+                  {workflowRunLogs}
+                
+ )} +
+ )} +
+
+
+ ) +} + +export default WorkflowLogs diff --git a/apps/studio/data/workflow-runs/keys.ts b/apps/studio/data/workflow-runs/keys.ts new file mode 100644 index 00000000000..dfb79399023 --- /dev/null +++ b/apps/studio/data/workflow-runs/keys.ts @@ -0,0 +1,3 @@ +export const workflowRunKeys = { + list: (projectRef: string | undefined) => ['projects', projectRef, 'workflow-runs'] as const, +} diff --git a/apps/studio/data/workflow-runs/workflow-run-logs-query.ts b/apps/studio/data/workflow-runs/workflow-run-logs-query.ts new file mode 100644 index 00000000000..0e51abd0c8c --- /dev/null +++ b/apps/studio/data/workflow-runs/workflow-run-logs-query.ts @@ -0,0 +1,51 @@ +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> +export type WorkflowRunLogsError = ResponseError + +export const useWorkflowRunLogsQuery = ( + { workflowRunId }: WorkflowRunLogsVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + workflowRunKeys.list(workflowRunId), + ({ signal }) => getWorkflowRunLogs({ workflowRunId }, signal), + { enabled: enabled && typeof workflowRunId !== 'undefined', staleTime: 0, ...options } + ) diff --git a/apps/studio/data/workflow-runs/workflow-runs-query.ts b/apps/studio/data/workflow-runs/workflow-runs-query.ts new file mode 100644 index 00000000000..564dea7240a --- /dev/null +++ b/apps/studio/data/workflow-runs/workflow-runs-query.ts @@ -0,0 +1,37 @@ +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> +export type WorkflowRunsError = ResponseError + +export const useWorkflowRunsQuery = ( + { projectRef }: WorkflowRunsVariables, + { enabled = true, ...options }: UseQueryOptions = {} +) => + useQuery( + workflowRunKeys.list(projectRef), + ({ signal }) => getWorkflowRuns({ projectRef }, signal), + { enabled: enabled && typeof projectRef !== 'undefined', staleTime: 0, ...options } + ) diff --git a/packages/ui-patterns/ShimmeringLoader/index.tsx b/packages/ui-patterns/ShimmeringLoader/index.tsx index e7ec3e32a71..17457b88c71 100644 --- a/packages/ui-patterns/ShimmeringLoader/index.tsx +++ b/packages/ui-patterns/ShimmeringLoader/index.tsx @@ -22,8 +22,12 @@ const ShimmeringLoader = ({ ) } -const GenericSkeletonLoader = () => ( -
+interface GenericSkeletonLoaderProps { + className?: string +} + +const GenericSkeletonLoader = ({ className }: GenericSkeletonLoaderProps) => ( +