Skip to content

Commit 724d211

Browse files
sans-harnesscjlee01cjlee1
authored
Webhook execution details (harness#994)
* feat: add header * feat: show data, add util for ns to s conversion * feat: add loading states, redirects * minor fixes - lint, tsc * feat: add route constants * feat: fix formatting on log viewer (harness#1042) Signed-off-by: Calvin Lee <[email protected]> Co-authored-by: Calvin Lee <[email protected]> * feat: add server reponse * feat: add html/json parsers * fix: review comments * chore: cleanuo * chore: remove logs * fix: lint --------- Signed-off-by: Calvin Lee <[email protected]> Co-authored-by: Calvin Lee <[email protected]> Co-authored-by: Calvin Lee <[email protected]>
1 parent 6808d46 commit 724d211

File tree

14 files changed

+345
-33
lines changed

14 files changed

+345
-33
lines changed

apps/design-system/src/subjects/views/repo-webhooks-list/repo-webhooks-list-store.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const repoWebhooksListStore: RepoWebhooksListStore = {
4646
setTotalPages: (_: Headers | undefined) => {},
4747
setWebhooks: (_data: ListRepoWebhooksOkResponse) => {},
4848
preSetWebhookData: null,
49-
setPreSetWebhookData: (_: CreateWebhookFormFields | null) => {}
49+
setPreSetWebhookData: (_: CreateWebhookFormFields | null) => {},
50+
executionId: null,
51+
setExecutionId: (_: number | null) => {},
52+
updateExecution: () => {}
5053
})
5154
}

apps/gitness/src/framework/routing/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ export enum RouteConstants {
7575
toRepoLabels = 'toRepoLabels',
7676
toProjectLabels = 'toProjectLabels',
7777
toRepoWebhookDetails = 'toRepoWebhookDetails',
78-
toRepoWebhookExecutions = 'toRepoWebhookExecutions'
78+
toRepoWebhookExecutions = 'toRepoWebhookExecutions',
79+
toRepoWebhookExecutionDetails = 'toRepoWebhookExecutionDetails'
7980
}
8081

8182
export interface RouteEntry {

apps/gitness/src/pages-v2/webhooks/stores/webhook-store.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const useWebhookStore = create<WebhookStore>(set => ({
1717
setPage: page => set({ page }),
1818
webhookLoading: false,
1919
executions: null,
20+
executionId: null,
2021
setWebhookLoading: (webhookLoading: boolean) => set({ webhookLoading }),
2122
setWebhookExecutionPage: page => set({ webhookExecutionPage: page }),
2223
setTotalWebhookExecutionPages: headers =>
@@ -40,6 +41,16 @@ export const useWebhookStore = create<WebhookStore>(set => ({
4041
setExecutions: (data: WebhookExecutionType[]) => {
4142
set({ executions: data })
4243
},
44+
// if a webhook execution is already in the list, update it, otherwise add it
45+
updateExecution: (updatedExecution: WebhookExecutionType) => {
46+
set(state => ({
47+
executions: state.executions?.some(exec => exec.id === updatedExecution.id)
48+
? state.executions.map(exec => (exec.id === updatedExecution.id ? updatedExecution : exec))
49+
: [...(state.executions ?? []), updatedExecution]
50+
}))
51+
},
52+
4353
setTotalPages: headers => set({ totalPages: parseInt(headers?.get(PageResponseHeader.xTotalPages) || '0') }),
44-
setPreSetWebhookData: (data: CreateWebhookFormFields | null) => set({ preSetWebhookData: data })
54+
setPreSetWebhookData: (data: CreateWebhookFormFields | null) => set({ preSetWebhookData: data }),
55+
setExecutionId: (id: number | null) => set({ executionId: id })
4556
}))
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useEffect } from 'react'
2+
import { useNavigate, useParams } from 'react-router-dom'
3+
4+
import {
5+
useGetRepoWebhookExecutionQuery,
6+
useRetriggerRepoWebhookExecutionMutation
7+
} from '@harnessio/code-service-client'
8+
import { RepoWebhookExecutionDetailsPage, WebhookExecutionType } from '@harnessio/ui/views'
9+
10+
import { useRoutes } from '../../framework/context/NavigationContext'
11+
import { useThemeStore } from '../../framework/context/ThemeContext'
12+
import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath'
13+
import { useTranslationStore } from '../../i18n/stores/i18n-store'
14+
import { PathParams } from '../../RouteDefinitions'
15+
import { useWebhookStore } from './stores/webhook-store'
16+
17+
export const WebhookExecutionDetailsContainer = () => {
18+
const { repoId, spaceId, webhookId, executionId } = useParams<PathParams>()
19+
const repo_ref = useGetRepoRef()
20+
const { setExecutionId, updateExecution } = useWebhookStore()
21+
const navigate = useNavigate()
22+
const routes = useRoutes()
23+
24+
useEffect(() => {
25+
setExecutionId(parseInt(executionId ?? ''))
26+
}, [setExecutionId, executionId])
27+
28+
const { data: { body: execution } = {} } = useGetRepoWebhookExecutionQuery(
29+
{
30+
repo_ref: repo_ref ?? '',
31+
webhook_identifier: parseInt(webhookId ?? ''),
32+
webhook_execution_id: parseInt(executionId ?? ''),
33+
queryParams: {}
34+
},
35+
{
36+
enabled: !!repo_ref && !!webhookId && !!executionId
37+
}
38+
)
39+
40+
const { mutate: retriggerExecution, isLoading: isTriggeringExecution } = useRetriggerRepoWebhookExecutionMutation(
41+
{},
42+
{
43+
onSuccess: data => {
44+
updateExecution(data.body as WebhookExecutionType)
45+
navigate(routes.toRepoWebhookExecutionDetails({ spaceId, repoId, webhookId, executionId: `${data.body.id}` }))
46+
}
47+
}
48+
)
49+
50+
useEffect(() => {
51+
if (execution) {
52+
updateExecution(execution as WebhookExecutionType)
53+
}
54+
}, [execution, updateExecution])
55+
56+
const handleRetriggerExecution = () => {
57+
retriggerExecution({
58+
repo_ref: repo_ref ?? '',
59+
webhook_identifier: parseInt(webhookId ?? ''),
60+
webhook_execution_id: parseInt(executionId ?? '')
61+
})
62+
}
63+
64+
return (
65+
<RepoWebhookExecutionDetailsPage
66+
useWebhookStore={useWebhookStore}
67+
useTranslationStore={useTranslationStore}
68+
isLoading={isTriggeringExecution}
69+
handleRetriggerExecution={handleRetriggerExecution}
70+
useThemeStore={useThemeStore}
71+
/>
72+
)
73+
}

apps/gitness/src/pages-v2/webhooks/webhook-executions.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { useWebhookStore } from './stores/webhook-store'
1414
export const WebhookExecutionsContainer = () => {
1515
const repo_ref = useGetRepoRef()
1616
const routes = useRoutes()
17-
const { webhookId } = useParams<PathParams>()
17+
const { spaceId, repoId, webhookId } = useParams<PathParams>()
1818
const { webhookExecutionPage, setWebhookExecutionPage, setExecutions, setTotalWebhookExecutionPages } =
1919
useWebhookStore()
2020

@@ -45,9 +45,12 @@ export const WebhookExecutionsContainer = () => {
4545
<RepoWebhookExecutionsPage
4646
useTranslationStore={useTranslationStore}
4747
useWebhookStore={useWebhookStore}
48-
toRepoWebhooks={() => routes.toRepoWebhooks({ repoId: repo_ref })}
48+
toRepoWebhooks={() => routes.toRepoWebhooks({ webhookId })}
4949
repo_ref={repo_ref}
5050
isLoading={isLoading}
51+
toRepoWebookExecutionDetails={(executionId: string) =>
52+
routes.toRepoWebhookExecutionDetails({ spaceId, repoId, webhookId, executionId })
53+
}
5154
/>
5255
)
5356
}

apps/gitness/src/routes.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { SignIn } from './pages-v2/signin'
5858
import { SignUp } from './pages-v2/signup'
5959
import { UserManagementPageContainer } from './pages-v2/user-management/user-management-container'
6060
import { CreateWebhookContainer } from './pages-v2/webhooks/create-webhook-container'
61+
import { WebhookExecutionDetailsContainer } from './pages-v2/webhooks/webhook-execution-details-container'
6162
import { WebhookExecutionsContainer } from './pages-v2/webhooks/webhook-executions'
6263
import WebhookListPage from './pages-v2/webhooks/webhook-list'
6364

@@ -500,6 +501,21 @@ export const repoRoutes: CustomRouteObject[] = [
500501
),
501502
routeName: RouteConstants.toRepoWebhookExecutions
502503
}
504+
},
505+
{
506+
path: 'executions/:executionId',
507+
element: <WebhookExecutionDetailsContainer />,
508+
handle: {
509+
breadcrumb: ({ webhookId, executionId }: { webhookId: string; executionId: string }) => (
510+
<>
511+
<Text>{webhookId}</Text> <Breadcrumb.Separator />
512+
<Text className="ml-1.5">Executions</Text>
513+
<Breadcrumb.Separator className="mx-1.5" />
514+
<Text>{executionId}</Text>
515+
</>
516+
),
517+
routeName: RouteConstants.toRepoWebhookExecutionDetails
518+
}
503519
}
504520
]
505521
}

packages/ui/src/components/list-actions.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ interface DropdownProps {
1919
selectedValue?: string | null
2020
}
2121

22-
function Root({ children }: { children: ReactNode }) {
23-
return <div className="flex items-center justify-between gap-6">{children}</div>
22+
function Root({ children, className }: { children: ReactNode; className?: string }) {
23+
return <div className={cn('flex items-center justify-between gap-6', className)}>{children}</div>
2424
}
2525

26-
function Left({ children }: { children: ReactNode }) {
27-
return <div className="flex grow items-center gap-6">{children}</div>
26+
function Left({ children, className }: { children: ReactNode; className?: string }) {
27+
return <div className={cn('flex grow items-center gap-6', className)}>{children}</div>
2828
}
2929

30-
function Right({ children }: { children: ReactNode }) {
31-
return <div className="flex items-center gap-6">{children}</div>
30+
function Right({ children, className }: { children: ReactNode; className?: string }) {
31+
return <div className={cn('flex items-center gap-6', className)}>{children}</div>
3232
}
3333

3434
function Dropdown({ title, items, onChange, selectedValue }: DropdownProps) {

packages/ui/src/components/navbar-skeleton/item.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export function Item({ icon, text, description, active, submenuItem, className }
1919
<div
2020
className={cn(
2121
'group relative grid cursor-pointer select-none grid-cols-[auto_1fr] items-center gap-3 pb-[0.6875rem] pt-[0.5625rem] py-2 px-3 rounded-md',
22-
// { 'bg-background-4': active },
2322
{ 'gap-0': !icon },
2423
className
2524
)}

packages/ui/src/utils/TimeUtils.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,38 @@ export const getFormattedDuration = (startTs = 0, endTs = 0): string => {
2929
* Formats duration in milliseconds to human-readable duration format.
3030
* For 3723000 is formatted as "1h 2m 3s".
3131
* @param durationInMs
32+
* @param unit
3233
* @returns
3334
*/
34-
export const formatDuration = (durationInMs: number): string => {
35-
if (!durationInMs) return '0s'
36-
const seconds = Math.floor(durationInMs / 1000)
37-
const minutes = Math.floor(seconds / 60)
38-
const hours = Math.floor(minutes / 60)
3935

40-
const remainingSeconds = seconds % 60
41-
const remainingMinutes = minutes % 60
42-
const formatted = []
36+
export const formatDuration = (duration: number, unit: 'ms' | 'ns' = 'ms'): string => {
37+
if (!duration) return '0s'
4338

44-
if (hours > 0) {
45-
formatted.push(new Intl.NumberFormat().format(hours) + 'h')
46-
}
47-
if (remainingMinutes > 0) {
48-
formatted.push(new Intl.NumberFormat().format(remainingMinutes) + 'm')
39+
let totalSeconds: number
40+
let remainingMs = 0
41+
42+
if (unit === 'ms') {
43+
totalSeconds = Math.floor(duration / 1000)
44+
if (totalSeconds === 0) remainingMs = duration
45+
} else {
46+
totalSeconds = Math.floor(duration / 1e9)
47+
if (totalSeconds === 0) remainingMs = (duration % 1e9) / 1e6
4948
}
50-
if (remainingSeconds > 0) {
51-
formatted.push(new Intl.NumberFormat().format(remainingSeconds) + 's')
49+
50+
if (totalSeconds === 0) {
51+
return remainingMs > 0 ? new Intl.NumberFormat().format(remainingMs) + 'ms' : '0s'
5252
}
5353

54-
return formatted.join(' ')
54+
const hours = Math.floor(totalSeconds / 3600)
55+
const remainingMinutes = Math.floor((totalSeconds % 3600) / 60)
56+
const remainingSeconds = totalSeconds % 60
57+
58+
const formatted = []
59+
if (hours > 0) formatted.push(new Intl.NumberFormat().format(hours) + 'h')
60+
if (remainingMinutes > 0) formatted.push(new Intl.NumberFormat().format(remainingMinutes) + 'm')
61+
if (remainingSeconds > 0) formatted.push(new Intl.NumberFormat().format(remainingSeconds) + 's')
62+
63+
return formatted.join(' ') || '0s'
5564
}
5665

5766
/**

packages/ui/src/views/repo/webhooks/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export * from '@views/repo/webhooks/webhook-create/repo-webhook-create-page'
77
export * from '@views/repo/webhooks/webhook-create/types'
88

99
// webhook executions
10-
export * from '@views/repo/webhooks/webhook-executions/repo-webhook-executions-page'
10+
export * from '@views/repo/webhooks/webhook-executions/repo-webhook-executions-list-page'
11+
export * from '@views/repo/webhooks/webhook-executions/repo-webhook-execution-details-page'

0 commit comments

Comments
 (0)