mirror of https://github.com/harness/drone.git
feat: [code-2337] add support for webhook execution logs (#2681)
* fix: [code-2337] address comments * fix: [code-2337] increase drawer width to 50% * fix: [code-2337] editor to scroll beyond last line * fix: [code-2337] event title * fix: [code-2337] remove commented code * feat: [code-2337] add support for webhook execution logsCODE-2402
parent
bdc01e85f7
commit
55c6cacbde
|
@ -108,9 +108,9 @@ const BranchProtectionForm = (props: {
|
||||||
const { data: statuses } = useGet<string[]>({
|
const { data: statuses } = useGet<string[]>({
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/checks/recent`,
|
path: `/api/v1/repos/${repoMetadata?.path}/+/checks/recent`,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
query: searchStatusTerm,
|
query: searchStatusTerm
|
||||||
|
},
|
||||||
debounce: 500
|
debounce: 500
|
||||||
}
|
|
||||||
})
|
})
|
||||||
const statusOptions: SelectOption[] = useMemo(
|
const statusOptions: SelectOption[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
|
@ -41,6 +41,7 @@ export interface StringsMap {
|
||||||
ascending: string
|
ascending: string
|
||||||
assignPeople: string
|
assignPeople: string
|
||||||
at: string
|
at: string
|
||||||
|
atSubTitle: string
|
||||||
attachText: string
|
attachText: string
|
||||||
basedOn: string
|
basedOn: string
|
||||||
behindDivergence: string
|
behindDivergence: string
|
||||||
|
@ -358,6 +359,9 @@ export interface StringsMap {
|
||||||
enterUser: string
|
enterUser: string
|
||||||
error: string
|
error: string
|
||||||
error404Text: string
|
error404Text: string
|
||||||
|
event: string
|
||||||
|
executionHistory: string
|
||||||
|
executionId: string
|
||||||
'executions.canceledTime': string
|
'executions.canceledTime': string
|
||||||
'executions.completedTime': string
|
'executions.completedTime': string
|
||||||
'executions.description': string
|
'executions.description': string
|
||||||
|
@ -565,6 +569,7 @@ export interface StringsMap {
|
||||||
'labels.updateLabel': string
|
'labels.updateLabel': string
|
||||||
'labels.updated': string
|
'labels.updated': string
|
||||||
language: string
|
language: string
|
||||||
|
lastTriggeredAt: string
|
||||||
leaveAComment: string
|
leaveAComment: string
|
||||||
license: string
|
license: string
|
||||||
lineBreaks: string
|
lineBreaks: string
|
||||||
|
@ -626,6 +631,8 @@ export interface StringsMap {
|
||||||
noCommits: string
|
noCommits: string
|
||||||
noCommitsMessage: string
|
noCommitsMessage: string
|
||||||
noCommitsPR: string
|
noCommitsPR: string
|
||||||
|
noExecutionsFound: string
|
||||||
|
noExecutionsFoundForWebhook: string
|
||||||
noExpiration: string
|
noExpiration: string
|
||||||
noExpirationDate: string
|
noExpirationDate: string
|
||||||
noFilterResultMessage: string
|
noFilterResultMessage: string
|
||||||
|
@ -637,6 +644,7 @@ export interface StringsMap {
|
||||||
noWebHooks: string
|
noWebHooks: string
|
||||||
none: string
|
none: string
|
||||||
noneYet: string
|
noneYet: string
|
||||||
|
notRetriggerableMessage: string
|
||||||
off: string
|
off: string
|
||||||
ok: string
|
ok: string
|
||||||
on: string
|
on: string
|
||||||
|
@ -855,6 +863,7 @@ export interface StringsMap {
|
||||||
pullRequestalreadyExists: string
|
pullRequestalreadyExists: string
|
||||||
pullRequests: string
|
pullRequests: string
|
||||||
quote: string
|
quote: string
|
||||||
|
reTriggeredExecution: string
|
||||||
reactivate: string
|
reactivate: string
|
||||||
readMe: string
|
readMe: string
|
||||||
reader: string
|
reader: string
|
||||||
|
@ -903,6 +912,7 @@ export interface StringsMap {
|
||||||
repositoryName: string
|
repositoryName: string
|
||||||
reqChanges: string
|
reqChanges: string
|
||||||
requestChanges: string
|
requestChanges: string
|
||||||
|
requestPayload: string
|
||||||
required: string
|
required: string
|
||||||
resetZoom: string
|
resetZoom: string
|
||||||
resolve: string
|
resolve: string
|
||||||
|
@ -911,6 +921,7 @@ export interface StringsMap {
|
||||||
resolvedComments: string
|
resolvedComments: string
|
||||||
restoreBranch: string
|
restoreBranch: string
|
||||||
results: string
|
results: string
|
||||||
|
retriggerExecution: string
|
||||||
reviewProjectSettings: string
|
reviewProjectSettings: string
|
||||||
reviewerNotFound: string
|
reviewerNotFound: string
|
||||||
reviewers: string
|
reviewers: string
|
||||||
|
@ -973,6 +984,7 @@ export interface StringsMap {
|
||||||
selectToViewMore: string
|
selectToViewMore: string
|
||||||
selectUsers: string
|
selectUsers: string
|
||||||
'semanticSearch.sampleQueries': string
|
'semanticSearch.sampleQueries': string
|
||||||
|
serverResponse: string
|
||||||
setAsAdmin: string
|
setAsAdmin: string
|
||||||
setting: string
|
setting: string
|
||||||
settings: string
|
settings: string
|
||||||
|
@ -1048,6 +1060,7 @@ export interface StringsMap {
|
||||||
token: string
|
token: string
|
||||||
tooltipRepoEdit: string
|
tooltipRepoEdit: string
|
||||||
top: string
|
top: string
|
||||||
|
triggeredEvent: string
|
||||||
'triggers.actions': string
|
'triggers.actions': string
|
||||||
'triggers.createSuccess': string
|
'triggers.createSuccess': string
|
||||||
'triggers.createTrigger': string
|
'triggers.createTrigger': string
|
||||||
|
@ -1142,9 +1155,11 @@ export interface StringsMap {
|
||||||
webhookPRMerged: string
|
webhookPRMerged: string
|
||||||
webhookPRReopened: string
|
webhookPRReopened: string
|
||||||
webhookPRUpdated: string
|
webhookPRUpdated: string
|
||||||
|
webhookPage: string
|
||||||
webhookSelectAllEvents: string
|
webhookSelectAllEvents: string
|
||||||
webhookSelectIndividualEvents: string
|
webhookSelectIndividualEvents: string
|
||||||
webhookSelectPushEvents: string
|
webhookSelectPushEvents: string
|
||||||
|
webhookTabs: string
|
||||||
webhookTagCreated: string
|
webhookTagCreated: string
|
||||||
webhookTagDeleted: string
|
webhookTagDeleted: string
|
||||||
webhookTagUpdated: string
|
webhookTagUpdated: string
|
||||||
|
|
|
@ -420,7 +420,22 @@ webhookPRClosed: PR closed
|
||||||
webhookPRCommentCreated: PR comment created
|
webhookPRCommentCreated: PR comment created
|
||||||
webhookPRMerged: PR merged
|
webhookPRMerged: PR merged
|
||||||
nameYourWebhook: Name your webhook
|
nameYourWebhook: Name your webhook
|
||||||
|
noExecutionsFound: No Executions found
|
||||||
|
noExecutionsFoundForWebhook: No executions found for the given webhook
|
||||||
|
executionHistory: Execution History
|
||||||
|
serverResponse: Server Response
|
||||||
|
requestPayload: Request Payload
|
||||||
|
triggeredEvent: Triggered Event
|
||||||
|
event: Event
|
||||||
|
lastTriggeredAt: Last Triggered At
|
||||||
|
executionId: Execution ID
|
||||||
submitReview: Submit Review
|
submitReview: Submit Review
|
||||||
|
webhookPage: Webhook page
|
||||||
|
webhookTabs: WebhookTabs
|
||||||
|
reTriggeredExecution: Re-triggered Execution
|
||||||
|
retriggerExecution: Re-trigger Execution
|
||||||
|
atSubTitle: At
|
||||||
|
notRetriggerableMessage: This webhook execution cannot be re-triggered
|
||||||
approve: Approve
|
approve: Approve
|
||||||
requestChanges: Changes Requested
|
requestChanges: Changes Requested
|
||||||
repoEmptyMarkdown: |
|
repoEmptyMarkdown: |
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-height: calc(var(--page-height) - 160px);
|
||||||
|
background-color: var(--primary-bg) !important;
|
||||||
|
width: 100%;
|
||||||
|
margin: var(--spacing-small);
|
||||||
|
:global {
|
||||||
|
.bp3-tab {
|
||||||
|
width: fit-content !important;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-list .bp3-tab[aria-selected='true'] {
|
||||||
|
background-color: var(--grey-0);
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 2px solid var(--primary-7);
|
||||||
|
border-bottom-left-radius: 0px !important;
|
||||||
|
border-bottom-right-radius: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabsContainer {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--primary-bg) !important;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tablist'] {
|
||||||
|
background-color: var(--white) !important;
|
||||||
|
padding-left: var(--spacing-large) !important;
|
||||||
|
padding-right: var(--spacing-xlarge) !important;
|
||||||
|
border-bottom: 1px solid var(--grey-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tabpanel'] {
|
||||||
|
margin-top: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
[aria-selected='true'] {
|
||||||
|
.tabTitle,
|
||||||
|
.tabTitle:hover {
|
||||||
|
color: var(--grey-900) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--grey-700);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle:not:first-child {
|
||||||
|
margin-left: var(--spacing-8) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerContainer {
|
||||||
|
border-bottom: unset !important;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// This is an auto-generated file
|
||||||
|
export declare const headerContainer: string
|
||||||
|
export declare const main: string
|
||||||
|
export declare const tabsContainer: string
|
||||||
|
export declare const tabTitle: string
|
|
@ -14,52 +14,75 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { Container, PageBody } from '@harnessio/uicore'
|
import cx from 'classnames'
|
||||||
import { useGet } from 'restful-react'
|
import { PageBody, Container, Tabs } from '@harnessio/uicore'
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
import type { OpenapiWebhookType } from 'services/code'
|
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
|
||||||
import { WehookForm } from 'pages/WebhookNew/WehookForm'
|
import { PageBrowserProps, getErrorMessage, voidFn } from 'utils/Utils'
|
||||||
import { useAppContext } from 'AppContext'
|
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
import { WebhookTabs } from 'utils/GitUtils'
|
||||||
|
import WebhookDetailsTab from 'pages/WebhookDetailsTab/WebhookDetailsTab'
|
||||||
|
import WebhookExecutions from 'pages/WebhookExecutions/WebhookExecutions'
|
||||||
|
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
|
||||||
|
import { useQueryParams } from 'hooks/useQueryParams'
|
||||||
|
import css from './Webhook.module.scss'
|
||||||
|
|
||||||
export default function WebhookDetails() {
|
export default function WebhookDetails() {
|
||||||
|
const { repoMetadata, error, loading, refetch, webhookId } = useGetRepositoryMetadata()
|
||||||
|
const queryParams = useQueryParams<PageBrowserProps>()
|
||||||
|
const { replaceQueryParams } = useUpdateQueryParams()
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { routes } = useAppContext()
|
|
||||||
const { repoMetadata, error, loading, webhookId, refetch: refreshMetadata } = useGetRepositoryMetadata()
|
useEffect(() => {
|
||||||
const {
|
if (!queryParams.tab) {
|
||||||
data,
|
replaceQueryParams({ ...queryParams, tab: WebhookTabs.DETAILS })
|
||||||
loading: webhookLoading,
|
}
|
||||||
error: webhookError,
|
|
||||||
refetch: refetchWebhook
|
|
||||||
} = useGet<OpenapiWebhookType>({
|
|
||||||
path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks/${webhookId}`,
|
|
||||||
lazy: !repoMetadata
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const tabListArray = [
|
||||||
<Container>
|
|
||||||
<RepositoryPageHeader
|
|
||||||
repoMetadata={repoMetadata}
|
|
||||||
title={getString('webhookDetails')}
|
|
||||||
dataTooltipId="webhookDetails"
|
|
||||||
extraBreadcrumbLinks={
|
|
||||||
repoMetadata && [
|
|
||||||
{
|
{
|
||||||
label: getString('webhooks'),
|
id: WebhookTabs.DETAILS,
|
||||||
url: routes.toCODEWebhooks({ repoPath: repoMetadata.path as string })
|
title: getString('details'),
|
||||||
|
panel: (
|
||||||
|
<Container padding={'large'}>
|
||||||
|
<WebhookDetailsTab />
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: WebhookTabs.EXECUTIONS,
|
||||||
|
title: getString('pageTitle.executions'),
|
||||||
|
panel: <WebhookExecutions />
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
return (
|
||||||
|
<Container className={css.main}>
|
||||||
|
<RepositoryPageHeader
|
||||||
|
className={css.headerContainer}
|
||||||
|
repoMetadata={repoMetadata}
|
||||||
|
title={`${getString('webhook')} : ${webhookId}`}
|
||||||
|
dataTooltipId={getString('webhookPage')}
|
||||||
/>
|
/>
|
||||||
<PageBody
|
<PageBody error={getErrorMessage(error)} retryOnError={voidFn(refetch)}>
|
||||||
error={error || webhookError}
|
<LoadingSpinner visible={loading} />
|
||||||
retryOnError={() => (repoMetadata ? refetchWebhook() : refreshMetadata())}>
|
{repoMetadata && (
|
||||||
<LoadingSpinner visible={loading || webhookLoading} withBorder={!!data && webhookLoading} />
|
<Container className={cx(css.main, css.tabsContainer)}>
|
||||||
|
<Tabs
|
||||||
{repoMetadata && data && <WehookForm isEdit webhook={data} repoMetadata={repoMetadata} />}
|
id={getString('webhookTabs')}
|
||||||
|
large={false}
|
||||||
|
selectedTabId={queryParams.tab}
|
||||||
|
animate={false}
|
||||||
|
onChange={(id: WebhookTabs) => {
|
||||||
|
if (id === WebhookTabs.DETAILS) {
|
||||||
|
delete queryParams.page
|
||||||
|
}
|
||||||
|
replaceQueryParams({ ...queryParams, tab: id })
|
||||||
|
}}
|
||||||
|
tabList={tabListArray}></Tabs>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import { Container, PageBody } from '@harnessio/uicore'
|
||||||
|
import { useGet } from 'restful-react'
|
||||||
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
|
import type { OpenapiWebhookType } from 'services/code'
|
||||||
|
import { WehookForm } from 'pages/WebhookNew/WehookForm'
|
||||||
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
|
||||||
|
export default function WebhookDetails() {
|
||||||
|
const { repoMetadata, error, loading, webhookId, refetch: refreshMetadata } = useGetRepositoryMetadata()
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading: webhookLoading,
|
||||||
|
error: webhookError,
|
||||||
|
refetch: refetchWebhook
|
||||||
|
} = useGet<OpenapiWebhookType>({
|
||||||
|
path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks/${webhookId}`,
|
||||||
|
lazy: !repoMetadata
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<PageBody
|
||||||
|
error={error || webhookError}
|
||||||
|
retryOnError={() => (repoMetadata ? refetchWebhook() : refreshMetadata())}>
|
||||||
|
<LoadingSpinner visible={loading || webhookLoading} withBorder={!!data && webhookLoading} />
|
||||||
|
{repoMetadata && data && <WehookForm isEdit webhook={data} repoMetadata={repoMetadata} />}
|
||||||
|
</PageBody>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-height: calc(var(--page-height) - 160px);
|
||||||
|
width: 100%;
|
||||||
|
:global {
|
||||||
|
.bp3-tab {
|
||||||
|
width: fit-content !important;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-tab-list .bp3-tab[aria-selected='true'] {
|
||||||
|
background-color: var(--grey-0);
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-bottom: 2px solid var(--primary-7);
|
||||||
|
border-bottom-left-radius: 0px !important;
|
||||||
|
border-bottom-right-radius: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabsContainer {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tablist'] {
|
||||||
|
background-color: var(--white) !important;
|
||||||
|
padding-left: var(--spacing-large) !important;
|
||||||
|
padding-right: var(--spacing-xlarge) !important;
|
||||||
|
border-bottom: 1px solid var(--grey-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div > div[role='tabpanel'] {
|
||||||
|
margin-top: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
[aria-selected='true'] {
|
||||||
|
.tabTitle,
|
||||||
|
.tabTitle:hover {
|
||||||
|
color: var(--grey-900) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--grey-700);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 24px;
|
||||||
|
margin-top: var(--spacing-8);
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabTitle:not:first-child {
|
||||||
|
margin-left: var(--spacing-8) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageBody {
|
||||||
|
min-height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.executionContext {
|
||||||
|
padding: 2rem 1.8rem 1rem 1.8rem !important;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorMessage {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
height: 100% !important;
|
||||||
|
:global(.bp3-drawer-header) {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding-top: var(--spacing-5) !important;
|
||||||
|
padding-bottom: var(--spacing-5) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logsContainer {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 34;
|
||||||
|
}
|
27
web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts
vendored
Normal file
27
web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// This is an auto-generated file
|
||||||
|
export declare const copyButton: string
|
||||||
|
export declare const editor: string
|
||||||
|
export declare const errorMessage: string
|
||||||
|
export declare const executionContext: string
|
||||||
|
export declare const logsContainer: string
|
||||||
|
export declare const main: string
|
||||||
|
export declare const pageBody: string
|
||||||
|
export declare const tabsContainer: string
|
||||||
|
export declare const tabTitle: string
|
|
@ -0,0 +1,234 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo, useState } from 'react'
|
||||||
|
import { Drawer, Position } from '@blueprintjs/core'
|
||||||
|
import cx from 'classnames'
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonVariation,
|
||||||
|
Container,
|
||||||
|
Layout,
|
||||||
|
PageBody,
|
||||||
|
PageHeader,
|
||||||
|
Tabs,
|
||||||
|
Text,
|
||||||
|
useToaster
|
||||||
|
} from '@harnessio/uicore'
|
||||||
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
import MonacoEditor from 'react-monaco-editor'
|
||||||
|
import { defaultTo, isEmpty } from 'lodash-es'
|
||||||
|
import { useMutate } from 'restful-react'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { Render } from 'react-jsx-match'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { CopyButton } from 'components/CopyButton/CopyButton'
|
||||||
|
import { getErrorMessage } from 'utils/Utils'
|
||||||
|
import { getConfig } from 'services/config'
|
||||||
|
import { DateTimeWithLocalContentInline } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||||
|
import { CodeIcon, ExecutionTabs, WebhookIndividualEvent, getEventDescription } from 'utils/GitUtils'
|
||||||
|
import type { RepoRepositoryOutput, TypesWebhookExecution, TypesWebhookExecutionResponse } from 'services/code'
|
||||||
|
import { useModalHook } from 'hooks/useModalHook'
|
||||||
|
import css from './useWebhookLogDrawer.module.scss'
|
||||||
|
|
||||||
|
interface LogViewerProps {
|
||||||
|
data?: string | TypesWebhookExecutionResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWeebhookLogDrawer(refetchExecutionList: () => Promise<void>) {
|
||||||
|
const [executionData, setExecutionData] = useState<TypesWebhookExecution>()
|
||||||
|
const [activeTab, setActiveTab] = useState<string>(ExecutionTabs.PAYLOAD)
|
||||||
|
const [path, setPath] = useState('')
|
||||||
|
const { mutate: retriggerExection } = useMutate({
|
||||||
|
verb: 'POST',
|
||||||
|
base: getConfig('code/api/v1'),
|
||||||
|
path
|
||||||
|
})
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const LogViewer = (props: LogViewerProps) => {
|
||||||
|
const { data } = props
|
||||||
|
return (
|
||||||
|
<Container padding={'medium'} className={css.logsContainer}>
|
||||||
|
<CopyButton
|
||||||
|
content={JSON.stringify(data, null, 2)}
|
||||||
|
className={css.copyButton}
|
||||||
|
icon={CodeIcon.Copy}
|
||||||
|
color={Color.PRIMARY_7}
|
||||||
|
iconProps={{ size: 20 }}
|
||||||
|
/>
|
||||||
|
<MonacoEditor
|
||||||
|
className={css.editor}
|
||||||
|
height={'100vh'}
|
||||||
|
language="json"
|
||||||
|
value={JSON.stringify(data, null, 2)}
|
||||||
|
data-testid="monaco-editor"
|
||||||
|
theme="vs-dark"
|
||||||
|
options={{
|
||||||
|
fontFamily: "'Roboto Mono', monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
scrollBeyondLastLine: true,
|
||||||
|
minimap: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
unicodeHighlight: {
|
||||||
|
ambiguousCharacters: false
|
||||||
|
},
|
||||||
|
lineNumbers: 'on',
|
||||||
|
glyphMargin: true,
|
||||||
|
folding: false,
|
||||||
|
lineDecorationsWidth: 60,
|
||||||
|
wordWrap: 'on',
|
||||||
|
scrollbar: {
|
||||||
|
verticalScrollbarSize: 0
|
||||||
|
},
|
||||||
|
renderLineHighlight: 'none',
|
||||||
|
wordWrapBreakBeforeCharacters: '',
|
||||||
|
lineNumbersMinChars: 0,
|
||||||
|
wordBasedSuggestions: 'off',
|
||||||
|
readOnly: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabListArray = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: ExecutionTabs.PAYLOAD,
|
||||||
|
title: ExecutionTabs.PAYLOAD,
|
||||||
|
panel: <LogViewer data={JSON.parse(executionData?.request?.body ?? '{}')} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ExecutionTabs.SERVER_RESPONSE,
|
||||||
|
title: ExecutionTabs.SERVER_RESPONSE,
|
||||||
|
panel: <LogViewer data={executionData?.response} />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[activeTab, executionData]
|
||||||
|
)
|
||||||
|
const { showSuccess, showError } = useToaster()
|
||||||
|
const [openModal, hideModal] = useModalHook(
|
||||||
|
() => (
|
||||||
|
<Drawer position={Position.RIGHT} isOpen={true} isCloseButtonShown={true} size={'50%'} onClose={hideModal}>
|
||||||
|
<PageHeader
|
||||||
|
title={
|
||||||
|
<Text icon={'execution'} iconProps={{ size: 24 }} font={{ variation: FontVariation.H4 }}>
|
||||||
|
{executionData?.id}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
<Button
|
||||||
|
disabled={!executionData?.retriggerable}
|
||||||
|
tooltipProps={{
|
||||||
|
disabled: executionData?.retriggerable,
|
||||||
|
position: Position.TOP,
|
||||||
|
interactionKind: 'hover'
|
||||||
|
}}
|
||||||
|
tooltip={getString('notRetriggerableMessage')}
|
||||||
|
variation={ButtonVariation.SECONDARY}
|
||||||
|
onClick={() =>
|
||||||
|
retriggerExection({})
|
||||||
|
.then(() => {
|
||||||
|
showSuccess(getString('reTriggeredExecution'))
|
||||||
|
refetchExecutionList()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
showError(getErrorMessage(err))
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{getString('retriggerExecution')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageBody className={cx(css.pageBody)}>
|
||||||
|
<Layout.Vertical className={css.executionContext}>
|
||||||
|
<Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
|
||||||
|
<Text font={{ variation: FontVariation.H6 }}>{getString('triggeredEvent')}: </Text>
|
||||||
|
<Text color={Color.GREY_600}>
|
||||||
|
{getEventDescription(executionData?.trigger_type as WebhookIndividualEvent)}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
<Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
|
||||||
|
<Text font={{ variation: FontVariation.H6 }}>{getString('atSubTitle')}: </Text>
|
||||||
|
<Container>
|
||||||
|
<DateTimeWithLocalContentInline time={defaultTo(executionData?.created as number, 0)} />
|
||||||
|
</Container>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
<Layout.Horizontal spacing={'small'}>
|
||||||
|
<Text font={{ variation: FontVariation.H6 }}>Duration:</Text>
|
||||||
|
<Text color={Color.GREY_600}>
|
||||||
|
{Math.ceil(
|
||||||
|
moment
|
||||||
|
.duration((executionData?.duration ? executionData?.duration / 1_000_000 : 0) as number)
|
||||||
|
.asSeconds()
|
||||||
|
)}
|
||||||
|
{'s'}
|
||||||
|
</Text>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Layout.Vertical>
|
||||||
|
<Render when={!isEmpty(executionData?.error)}>
|
||||||
|
<Container
|
||||||
|
intent="danger"
|
||||||
|
background="red100"
|
||||||
|
border={{
|
||||||
|
color: 'red500'
|
||||||
|
}}
|
||||||
|
margin={{ top: 'small', right: 'medium', left: 'medium' }}>
|
||||||
|
<Text
|
||||||
|
className={css.errorMessage}
|
||||||
|
icon="error-outline"
|
||||||
|
iconProps={{ size: 16, margin: { right: 'small' } }}
|
||||||
|
padding={{ left: 'large', right: 'large', top: 'small', bottom: 'small' }}
|
||||||
|
color={Color.ERROR}>
|
||||||
|
{executionData?.error}
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
</Render>
|
||||||
|
<Container className={cx(css.main, css.tabsContainer)}>
|
||||||
|
<Tabs
|
||||||
|
id="WebhookExecutionLogs"
|
||||||
|
large={false}
|
||||||
|
defaultSelectedTabId={activeTab}
|
||||||
|
animate={false}
|
||||||
|
onChange={(id: string) => {
|
||||||
|
setActiveTab(id)
|
||||||
|
}}
|
||||||
|
tabList={tabListArray}></Tabs>
|
||||||
|
</Container>
|
||||||
|
</PageBody>
|
||||||
|
</Drawer>
|
||||||
|
),
|
||||||
|
[executionData, activeTab, path]
|
||||||
|
)
|
||||||
|
|
||||||
|
const openExecutionLogs = (
|
||||||
|
webhookExecution: TypesWebhookExecution,
|
||||||
|
logTab: ExecutionTabs,
|
||||||
|
repoMetadata?: RepoRepositoryOutput
|
||||||
|
) => {
|
||||||
|
setExecutionData(webhookExecution)
|
||||||
|
setActiveTab(logTab)
|
||||||
|
setPath(
|
||||||
|
`/repos/${repoMetadata?.path}/+/webhooks/${webhookExecution.webhook_id}/executions/${webhookExecution.id}/retrigger`
|
||||||
|
)
|
||||||
|
openModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { openModal, hideModal, openExecutionLogs }
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.main {
|
||||||
|
.table {
|
||||||
|
.row {
|
||||||
|
height: fit-content !important;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[class*='TableV2--row'] {
|
||||||
|
padding: 3px var(--spacing-medium);
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
// This is an auto-generated file
|
||||||
|
export declare const main: string
|
||||||
|
export declare const row: string
|
||||||
|
export declare const table: string
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import React, { useEffect, useMemo } from 'react'
|
||||||
|
import { Container, TableV2, Text, Button, ButtonVariation, useToaster } from '@harnessio/uicore'
|
||||||
|
|
||||||
|
import type { CellProps, Column } from 'react-table'
|
||||||
|
import { useGet } from 'restful-react'
|
||||||
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
import { useHistory } from 'react-router-dom'
|
||||||
|
import { defaultTo, isEmpty } from 'lodash-es'
|
||||||
|
import { useQueryParams } from 'hooks/useQueryParams'
|
||||||
|
import { usePageIndex } from 'hooks/usePageIndex'
|
||||||
|
import { LIST_FETCHING_LIMIT, type PageBrowserProps } from 'utils/Utils'
|
||||||
|
import { ExecutionTabs, WebhookIndividualEvent, getEventDescription } from 'utils/GitUtils'
|
||||||
|
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
|
||||||
|
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||||
|
import { useStrings } from 'framework/strings'
|
||||||
|
import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams'
|
||||||
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
import { getConfig } from 'services/config'
|
||||||
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
|
import type { TypesWebhookExecution } from 'services/code'
|
||||||
|
import { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||||
|
import { useWeebhookLogDrawer } from 'pages/WebhookExecutions/WebhookExecutionLogs/useWeebhookLogDrawer'
|
||||||
|
import { ExecutionStatusLabel } from 'components/ExecutionStatusLabel/ExecutionStatusLabel'
|
||||||
|
import css from './WebhookExecutions.module.scss'
|
||||||
|
|
||||||
|
const WebhookExecutions = () => {
|
||||||
|
const { repoMetadata, webhookId } = useGetRepositoryMetadata()
|
||||||
|
const { getString } = useStrings()
|
||||||
|
const { showError, showSuccess } = useToaster()
|
||||||
|
const history = useHistory()
|
||||||
|
const pageBrowser = useQueryParams<PageBrowserProps>()
|
||||||
|
const { updateQueryParams, replaceQueryParams } = useUpdateQueryParams()
|
||||||
|
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
|
||||||
|
const [page, setPage] = usePageIndex(pageInit)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const params = {
|
||||||
|
...pageBrowser,
|
||||||
|
...(page > 1 && { page: page.toString() })
|
||||||
|
}
|
||||||
|
updateQueryParams(params, undefined, true)
|
||||||
|
|
||||||
|
if (page <= 1) {
|
||||||
|
const updateParams = { ...params }
|
||||||
|
delete updateParams.page
|
||||||
|
replaceQueryParams(updateParams, undefined, true)
|
||||||
|
}
|
||||||
|
}, [page]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (parseInt(pageBrowser.page ?? '1') !== page) {
|
||||||
|
setPage(parseInt(pageBrowser.page ?? '1'))
|
||||||
|
}
|
||||||
|
}, [pageBrowser])
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: executionList,
|
||||||
|
loading: executionListLoading,
|
||||||
|
refetch: refetchExecutionList,
|
||||||
|
response
|
||||||
|
} = useGet<TypesWebhookExecution[]>({
|
||||||
|
base: getConfig('code/api/v1'),
|
||||||
|
path: `/repos/${repoMetadata?.path}/+/webhooks/${webhookId}/executions`,
|
||||||
|
queryParams: {
|
||||||
|
limit: LIST_FETCHING_LIMIT,
|
||||||
|
page: page
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { openExecutionLogs } = useWeebhookLogDrawer(refetchExecutionList)
|
||||||
|
|
||||||
|
const columns: Column<TypesWebhookExecution>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: getString('executionId'),
|
||||||
|
id: getString('executionId'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return (
|
||||||
|
<Text color={Color.GREY_900} icon={'execution'}>
|
||||||
|
{row.original.id}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('lastTriggeredAt'),
|
||||||
|
id: getString('lastTriggeredAt'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
<TimePopoverWithLocal
|
||||||
|
time={defaultTo(row.original.created as number, 0)}
|
||||||
|
inline={false}
|
||||||
|
font={{ variation: FontVariation.BODY2_SEMI }}
|
||||||
|
color={Color.GREY_400}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('event'),
|
||||||
|
id: getString('event'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return <Text>{getEventDescription(row.original.trigger_type as WebhookIndividualEvent)}</Text>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('requestPayload'),
|
||||||
|
id: getString('requestPayload'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variation={ButtonVariation.LINK}
|
||||||
|
font={{ variation: FontVariation.FORM_LABEL }}
|
||||||
|
text={'View'}
|
||||||
|
iconProps={{ size: 16 }}
|
||||||
|
icon={'file'}
|
||||||
|
onClick={() => openExecutionLogs(row.original, ExecutionTabs.PAYLOAD, repoMetadata)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('serverResponse'),
|
||||||
|
id: getString('serverResponse'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variation={ButtonVariation.LINK}
|
||||||
|
font={{ variation: FontVariation.FORM_LABEL }}
|
||||||
|
text={'View'}
|
||||||
|
iconProps={{ size: 18 }}
|
||||||
|
icon={'sto-dast'}
|
||||||
|
onClick={() => openExecutionLogs(row.original, ExecutionTabs.SERVER_RESPONSE, repoMetadata)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: getString('status'),
|
||||||
|
id: getString('status'),
|
||||||
|
sort: 'true',
|
||||||
|
width: '16.66%',
|
||||||
|
Cell: ({ row }: CellProps<TypesWebhookExecution>) => {
|
||||||
|
return (
|
||||||
|
<ExecutionStatusLabel
|
||||||
|
data={row.original.result === 'success' ? { state: 'success' } : { state: 'failed' }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
], // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[history, getString, repoMetadata?.path, setPage, showError, showSuccess]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Container className={css.main} padding={{ bottom: 'large', right: 'xlarge', left: 'xlarge' }}>
|
||||||
|
{executionList && !executionListLoading && executionList.length && (
|
||||||
|
<TableV2<TypesWebhookExecution>
|
||||||
|
className={css.table}
|
||||||
|
columns={columns}
|
||||||
|
data={executionList}
|
||||||
|
sortable
|
||||||
|
autoResetExpanded={true}
|
||||||
|
getRowClassName={() => css.row}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<LoadingSpinner visible={executionListLoading} />
|
||||||
|
<ResourceListingPagination response={response} page={page} setPage={setPage} />
|
||||||
|
</Container>
|
||||||
|
<NoResultCard
|
||||||
|
showWhen={() => !executionListLoading && isEmpty(executionList)}
|
||||||
|
forSearch={true}
|
||||||
|
title={getString('noExecutionsFound')}
|
||||||
|
emptySearchMessage={getString('noExecutionsFoundForWebhook')}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebhookExecutions
|
|
@ -35,33 +35,11 @@ import React from 'react'
|
||||||
import type { OpenapiUpdateWebhookRequest, EnumWebhookTrigger, OpenapiWebhookType } from 'services/code'
|
import type { OpenapiUpdateWebhookRequest, EnumWebhookTrigger, OpenapiWebhookType } from 'services/code'
|
||||||
import { getErrorMessage, permissionProps } from 'utils/Utils'
|
import { getErrorMessage, permissionProps } from 'utils/Utils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import { WebhookIndividualEvent, type GitInfoProps, WebhookEventType } from 'utils/GitUtils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import css from './WehookForm.module.scss'
|
import css from './WehookForm.module.scss'
|
||||||
|
|
||||||
enum WebhookEventType {
|
|
||||||
PUSH = 'push',
|
|
||||||
ALL = 'all',
|
|
||||||
INDIVIDUAL = 'individual'
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WebhookIndividualEvent {
|
|
||||||
BRANCH_CREATED = 'branch_created',
|
|
||||||
BRANCH_UPDATED = 'branch_updated',
|
|
||||||
BRANCH_DELETED = 'branch_deleted',
|
|
||||||
TAG_CREATED = 'tag_created',
|
|
||||||
TAG_UPDATED = 'tag_updated',
|
|
||||||
TAG_DELETED = 'tag_deleted',
|
|
||||||
PR_CREATED = 'pullreq_created',
|
|
||||||
PR_UPDATED = 'pullreq_updated',
|
|
||||||
PR_REOPENED = 'pullreq_reopened',
|
|
||||||
PR_BRANCH_UPDATED = 'pullreq_branch_updated',
|
|
||||||
PR_CLOSED = 'pullreq_closed',
|
|
||||||
PR_COMMENT_CREATED = 'pullreq_comment_created',
|
|
||||||
PR_MERGED = 'pullreq_merged'
|
|
||||||
}
|
|
||||||
|
|
||||||
const SECRET_MASK = '********'
|
const SECRET_MASK = '********'
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
|
|
|
@ -50,7 +50,7 @@ import { ResourceListingPagination } from 'components/ResourceListingPagination/
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
import { NoResultCard } from 'components/NoResultCard/NoResultCard'
|
||||||
import type { OpenapiWebhookType } from 'services/code'
|
import type { OpenapiWebhookType } from 'services/code'
|
||||||
import { formatTriggers } from 'utils/GitUtils'
|
import { WebhookTabs, formatTriggers } from 'utils/GitUtils'
|
||||||
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
|
||||||
import { WebhooksHeader } from './WebhooksHeader/WebhooksHeader'
|
import { WebhooksHeader } from './WebhooksHeader/WebhooksHeader'
|
||||||
import css from './Webhooks.module.scss'
|
import css from './Webhooks.module.scss'
|
||||||
|
@ -256,6 +256,20 @@ export default function Webhooks() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hasIcon: true,
|
||||||
|
iconName: 'execution',
|
||||||
|
iconSize: 16,
|
||||||
|
text: getString('executionHistory'),
|
||||||
|
onClick: () => {
|
||||||
|
history.push(
|
||||||
|
`${routes.toCODEWebhookDetails({
|
||||||
|
repoPath: repoMetadata?.path as string,
|
||||||
|
webhookId: String(row.original?.identifier)
|
||||||
|
})}?tab=${WebhookTabs.EXECUTIONS}`
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -133,6 +133,16 @@ export enum SpaceSettingsTab {
|
||||||
labels = 'labels'
|
labels = 'labels'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WebhookTabs {
|
||||||
|
DETAILS = 'details',
|
||||||
|
EXECUTIONS = 'executions'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExecutionTabs {
|
||||||
|
PAYLOAD = 'Payload',
|
||||||
|
SERVER_RESPONSE = 'Server Response'
|
||||||
|
}
|
||||||
|
|
||||||
export enum VulnerabilityScanningType {
|
export enum VulnerabilityScanningType {
|
||||||
DETECT = 'detect',
|
DETECT = 'detect',
|
||||||
BLOCK = 'block',
|
BLOCK = 'block',
|
||||||
|
@ -579,3 +589,61 @@ export const dryMerge = (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WebhookEventType {
|
||||||
|
PUSH = 'push',
|
||||||
|
ALL = 'all',
|
||||||
|
INDIVIDUAL = 'individual'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookIndividualEvent {
|
||||||
|
BRANCH_CREATED = 'branch_created',
|
||||||
|
BRANCH_UPDATED = 'branch_updated',
|
||||||
|
BRANCH_DELETED = 'branch_deleted',
|
||||||
|
TAG_CREATED = 'tag_created',
|
||||||
|
TAG_UPDATED = 'tag_updated',
|
||||||
|
TAG_DELETED = 'tag_deleted',
|
||||||
|
PR_CREATED = 'pullreq_created',
|
||||||
|
PR_UPDATED = 'pullreq_updated',
|
||||||
|
PR_REOPENED = 'pullreq_reopened',
|
||||||
|
PR_BRANCH_UPDATED = 'pullreq_branch_updated',
|
||||||
|
PR_CLOSED = 'pullreq_closed',
|
||||||
|
PR_COMMENT_CREATED = 'pullreq_comment_created',
|
||||||
|
PR_MERGED = 'pullreq_merged'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookEventMap {
|
||||||
|
BRANCH_CREATED = 'Branch created',
|
||||||
|
BRANCH_UPDATED = 'Branch updated',
|
||||||
|
BRANCH_DELETED = 'Branch deleted',
|
||||||
|
TAG_CREATED = 'Tag created',
|
||||||
|
TAG_UPDATED = 'Tag updated',
|
||||||
|
TAG_DELETED = 'Tag deleted',
|
||||||
|
PR_CREATED = 'PR created',
|
||||||
|
PR_UPDATED = 'PR updated',
|
||||||
|
PR_REOPENED = 'PR reopened',
|
||||||
|
PR_BRANCH_UPDATED = 'PR updated',
|
||||||
|
PR_CLOSED = 'PR closed',
|
||||||
|
PR_COMMENT_CREATED = 'PR created',
|
||||||
|
PR_MERGED = 'PR merged'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eventMapping: Record<WebhookIndividualEvent, WebhookEventMap> = {
|
||||||
|
[WebhookIndividualEvent.BRANCH_CREATED]: WebhookEventMap.BRANCH_CREATED,
|
||||||
|
[WebhookIndividualEvent.BRANCH_UPDATED]: WebhookEventMap.BRANCH_UPDATED,
|
||||||
|
[WebhookIndividualEvent.BRANCH_DELETED]: WebhookEventMap.BRANCH_DELETED,
|
||||||
|
[WebhookIndividualEvent.TAG_CREATED]: WebhookEventMap.TAG_CREATED,
|
||||||
|
[WebhookIndividualEvent.TAG_UPDATED]: WebhookEventMap.TAG_UPDATED,
|
||||||
|
[WebhookIndividualEvent.TAG_DELETED]: WebhookEventMap.TAG_DELETED,
|
||||||
|
[WebhookIndividualEvent.PR_CREATED]: WebhookEventMap.PR_CREATED,
|
||||||
|
[WebhookIndividualEvent.PR_UPDATED]: WebhookEventMap.PR_UPDATED,
|
||||||
|
[WebhookIndividualEvent.PR_REOPENED]: WebhookEventMap.PR_REOPENED,
|
||||||
|
[WebhookIndividualEvent.PR_BRANCH_UPDATED]: WebhookEventMap.PR_BRANCH_UPDATED,
|
||||||
|
[WebhookIndividualEvent.PR_CLOSED]: WebhookEventMap.PR_CLOSED,
|
||||||
|
[WebhookIndividualEvent.PR_COMMENT_CREATED]: WebhookEventMap.PR_COMMENT_CREATED,
|
||||||
|
[WebhookIndividualEvent.PR_MERGED]: WebhookEventMap.PR_MERGED
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEventDescription(event: WebhookIndividualEvent): string {
|
||||||
|
return eventMapping[event]
|
||||||
|
}
|
||||||
|
|
|
@ -98,6 +98,7 @@ export const getErrorMessage = (error: Unknown): string | undefined =>
|
||||||
export interface PageBrowserProps {
|
export interface PageBrowserProps {
|
||||||
page?: string
|
page?: string
|
||||||
state?: string
|
state?: string
|
||||||
|
tab?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extractInfoFromRuleViolationArr = (ruleViolationArr: TypesRuleViolations[]) => {
|
export const extractInfoFromRuleViolationArr = (ruleViolationArr: TypesRuleViolations[]) => {
|
||||||
|
|
|
@ -40,7 +40,7 @@ enum TimeZone {
|
||||||
export function DateTimeWithLocalContentInline({ time }: { time: number }): JSX.Element {
|
export function DateTimeWithLocalContentInline({ time }: { time: number }): JSX.Element {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
return (
|
return (
|
||||||
<Layout.Vertical>
|
<Layout.Vertical margin={{ right: '4px' }}>
|
||||||
<Layout.Horizontal className={css.timeWrapper}>
|
<Layout.Horizontal className={css.timeWrapper}>
|
||||||
<Text color={Color.GREY_600} className={css.time}>
|
<Text color={Color.GREY_600} className={css.time}>
|
||||||
{moment(time).format(DATE_PARSE_FORMAT)}
|
{moment(time).format(DATE_PARSE_FORMAT)}
|
||||||
|
|
Loading…
Reference in New Issue