From 55c6cacbde6699f6b5eb8431561d5eb6ddf3e21c Mon Sep 17 00:00:00 2001 From: Ritik Kapoor Date: Fri, 13 Sep 2024 06:59:06 +0000 Subject: [PATCH] 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 logs --- .../BranchProtectionForm.tsx | 6 +- web/src/framework/strings/stringTypes.ts | 15 ++ web/src/i18n/strings.en.yaml | 15 ++ .../pages/WebhookDetails/Webhook.module.scss | 102 ++++++++ .../WebhookDetails/Webhook.module.scss.d.ts | 22 ++ .../pages/WebhookDetails/WebhookDetails.tsx | 89 ++++--- .../WebhookDetailsTab/WebhookDetailsTab.tsx | 47 ++++ .../useWebhookLogDrawer.module.scss | 130 ++++++++++ .../useWebhookLogDrawer.module.scss.d.ts | 27 ++ .../useWeebhookLogDrawer.tsx | 234 ++++++++++++++++++ .../WebhookExecutions.module.scss | 31 +++ .../WebhookExecutions.module.scss.d.ts | 21 ++ .../WebhookExecutions/WebhookExecutions.tsx | 208 ++++++++++++++++ web/src/pages/WebhookNew/WehookForm.tsx | 24 +- web/src/pages/Webhooks/Webhooks.tsx | 16 +- web/src/utils/GitUtils.ts | 68 +++++ web/src/utils/Utils.ts | 1 + .../timePopoverLocal/TimePopoverWithLocal.tsx | 2 +- 18 files changed, 997 insertions(+), 61 deletions(-) create mode 100644 web/src/pages/WebhookDetails/Webhook.module.scss create mode 100644 web/src/pages/WebhookDetails/Webhook.module.scss.d.ts create mode 100644 web/src/pages/WebhookDetailsTab/WebhookDetailsTab.tsx create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWeebhookLogDrawer.tsx create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutions.module.scss create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutions.module.scss.d.ts create mode 100644 web/src/pages/WebhookExecutions/WebhookExecutions.tsx diff --git a/web/src/components/BranchProtection/BranchProtectionForm/BranchProtectionForm.tsx b/web/src/components/BranchProtection/BranchProtectionForm/BranchProtectionForm.tsx index 677598a38..380794b70 100644 --- a/web/src/components/BranchProtection/BranchProtectionForm/BranchProtectionForm.tsx +++ b/web/src/components/BranchProtection/BranchProtectionForm/BranchProtectionForm.tsx @@ -108,9 +108,9 @@ const BranchProtectionForm = (props: { const { data: statuses } = useGet({ path: `/api/v1/repos/${repoMetadata?.path}/+/checks/recent`, queryParams: { - query: searchStatusTerm, - debounce: 500 - } + query: searchStatusTerm + }, + debounce: 500 }) const statusOptions: SelectOption[] = useMemo( () => diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 82d5e8fe8..a5de59b3d 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -41,6 +41,7 @@ export interface StringsMap { ascending: string assignPeople: string at: string + atSubTitle: string attachText: string basedOn: string behindDivergence: string @@ -358,6 +359,9 @@ export interface StringsMap { enterUser: string error: string error404Text: string + event: string + executionHistory: string + executionId: string 'executions.canceledTime': string 'executions.completedTime': string 'executions.description': string @@ -565,6 +569,7 @@ export interface StringsMap { 'labels.updateLabel': string 'labels.updated': string language: string + lastTriggeredAt: string leaveAComment: string license: string lineBreaks: string @@ -626,6 +631,8 @@ export interface StringsMap { noCommits: string noCommitsMessage: string noCommitsPR: string + noExecutionsFound: string + noExecutionsFoundForWebhook: string noExpiration: string noExpirationDate: string noFilterResultMessage: string @@ -637,6 +644,7 @@ export interface StringsMap { noWebHooks: string none: string noneYet: string + notRetriggerableMessage: string off: string ok: string on: string @@ -855,6 +863,7 @@ export interface StringsMap { pullRequestalreadyExists: string pullRequests: string quote: string + reTriggeredExecution: string reactivate: string readMe: string reader: string @@ -903,6 +912,7 @@ export interface StringsMap { repositoryName: string reqChanges: string requestChanges: string + requestPayload: string required: string resetZoom: string resolve: string @@ -911,6 +921,7 @@ export interface StringsMap { resolvedComments: string restoreBranch: string results: string + retriggerExecution: string reviewProjectSettings: string reviewerNotFound: string reviewers: string @@ -973,6 +984,7 @@ export interface StringsMap { selectToViewMore: string selectUsers: string 'semanticSearch.sampleQueries': string + serverResponse: string setAsAdmin: string setting: string settings: string @@ -1048,6 +1060,7 @@ export interface StringsMap { token: string tooltipRepoEdit: string top: string + triggeredEvent: string 'triggers.actions': string 'triggers.createSuccess': string 'triggers.createTrigger': string @@ -1142,9 +1155,11 @@ export interface StringsMap { webhookPRMerged: string webhookPRReopened: string webhookPRUpdated: string + webhookPage: string webhookSelectAllEvents: string webhookSelectIndividualEvents: string webhookSelectPushEvents: string + webhookTabs: string webhookTagCreated: string webhookTagDeleted: string webhookTagUpdated: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index d866cd6d6..17524e5df 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -420,7 +420,22 @@ webhookPRClosed: PR closed webhookPRCommentCreated: PR comment created webhookPRMerged: PR merged 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 +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 requestChanges: Changes Requested repoEmptyMarkdown: | diff --git a/web/src/pages/WebhookDetails/Webhook.module.scss b/web/src/pages/WebhookDetails/Webhook.module.scss new file mode 100644 index 000000000..1649c7abe --- /dev/null +++ b/web/src/pages/WebhookDetails/Webhook.module.scss @@ -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; +} diff --git a/web/src/pages/WebhookDetails/Webhook.module.scss.d.ts b/web/src/pages/WebhookDetails/Webhook.module.scss.d.ts new file mode 100644 index 000000000..e5b62e3a9 --- /dev/null +++ b/web/src/pages/WebhookDetails/Webhook.module.scss.d.ts @@ -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 diff --git a/web/src/pages/WebhookDetails/WebhookDetails.tsx b/web/src/pages/WebhookDetails/WebhookDetails.tsx index fec621e22..1378b1ea1 100644 --- a/web/src/pages/WebhookDetails/WebhookDetails.tsx +++ b/web/src/pages/WebhookDetails/WebhookDetails.tsx @@ -14,52 +14,75 @@ * limitations under the License. */ -import React from 'react' -import { Container, PageBody } from '@harnessio/uicore' -import { useGet } from 'restful-react' +import React, { useEffect } from 'react' +import cx from 'classnames' +import { PageBody, Container, Tabs } from '@harnessio/uicore' import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' -import type { OpenapiWebhookType } from 'services/code' import { useStrings } from 'framework/strings' import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader' -import { WehookForm } from 'pages/WebhookNew/WehookForm' -import { useAppContext } from 'AppContext' +import { PageBrowserProps, getErrorMessage, voidFn } from 'utils/Utils' 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() { + const { repoMetadata, error, loading, refetch, webhookId } = useGetRepositoryMetadata() + const queryParams = useQueryParams() + const { replaceQueryParams } = useUpdateQueryParams() const { getString } = useStrings() - const { routes } = useAppContext() - const { repoMetadata, error, loading, webhookId, refetch: refreshMetadata } = useGetRepositoryMetadata() - const { - data, - loading: webhookLoading, - error: webhookError, - refetch: refetchWebhook - } = useGet({ - path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks/${webhookId}`, - lazy: !repoMetadata + + useEffect(() => { + if (!queryParams.tab) { + replaceQueryParams({ ...queryParams, tab: WebhookTabs.DETAILS }) + } }) + const tabListArray = [ + { + id: WebhookTabs.DETAILS, + title: getString('details'), + panel: ( + + + + ) + }, + { + id: WebhookTabs.EXECUTIONS, + title: getString('pageTitle.executions'), + panel: + } + ] return ( - + - (repoMetadata ? refetchWebhook() : refreshMetadata())}> - - - {repoMetadata && data && } + + + {repoMetadata && ( + + { + if (id === WebhookTabs.DETAILS) { + delete queryParams.page + } + replaceQueryParams({ ...queryParams, tab: id }) + }} + tabList={tabListArray}> + + )} ) diff --git a/web/src/pages/WebhookDetailsTab/WebhookDetailsTab.tsx b/web/src/pages/WebhookDetailsTab/WebhookDetailsTab.tsx new file mode 100644 index 000000000..d66b85655 --- /dev/null +++ b/web/src/pages/WebhookDetailsTab/WebhookDetailsTab.tsx @@ -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({ + path: `/api/v1/repos/${repoMetadata?.path}/+/webhooks/${webhookId}`, + lazy: !repoMetadata + }) + + return ( + + (repoMetadata ? refetchWebhook() : refreshMetadata())}> + + {repoMetadata && data && } + + + ) +} diff --git a/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss new file mode 100644 index 000000000..4a786c19e --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss @@ -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; +} diff --git a/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts new file mode 100644 index 000000000..0cb7e095d --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWebhookLogDrawer.module.scss.d.ts @@ -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 diff --git a/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWeebhookLogDrawer.tsx b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWeebhookLogDrawer.tsx new file mode 100644 index 000000000..2a92abfe9 --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutionLogs/useWeebhookLogDrawer.tsx @@ -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) { + const [executionData, setExecutionData] = useState() + const [activeTab, setActiveTab] = useState(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 ( + + + + + ) + } + + const tabListArray = useMemo( + () => [ + { + id: ExecutionTabs.PAYLOAD, + title: ExecutionTabs.PAYLOAD, + panel: + }, + { + id: ExecutionTabs.SERVER_RESPONSE, + title: ExecutionTabs.SERVER_RESPONSE, + panel: + } + ], + [activeTab, executionData] + ) + const { showSuccess, showError } = useToaster() + const [openModal, hideModal] = useModalHook( + () => ( + + + {executionData?.id} + + } + content={ + + } + /> + + + + + {getString('triggeredEvent')}: + + {getEventDescription(executionData?.trigger_type as WebhookIndividualEvent)} + + + + {getString('atSubTitle')}: + + + + + + Duration: + + {Math.ceil( + moment + .duration((executionData?.duration ? executionData?.duration / 1_000_000 : 0) as number) + .asSeconds() + )} + {'s'} + + + + + + + {executionData?.error} + + + + + { + setActiveTab(id) + }} + tabList={tabListArray}> + + + + ), + [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 } +} diff --git a/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss b/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss new file mode 100644 index 000000000..3a866d738 --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss @@ -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; + } + } +} diff --git a/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss.d.ts b/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss.d.ts new file mode 100644 index 000000000..a0bbd780c --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutions.module.scss.d.ts @@ -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 diff --git a/web/src/pages/WebhookExecutions/WebhookExecutions.tsx b/web/src/pages/WebhookExecutions/WebhookExecutions.tsx new file mode 100644 index 000000000..719a38afe --- /dev/null +++ b/web/src/pages/WebhookExecutions/WebhookExecutions.tsx @@ -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() + 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({ + 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[] = useMemo( + () => [ + { + Header: getString('executionId'), + id: getString('executionId'), + sort: 'true', + width: '16.66%', + Cell: ({ row }: CellProps) => { + return ( + + {row.original.id} + + ) + } + }, + { + Header: getString('lastTriggeredAt'), + id: getString('lastTriggeredAt'), + sort: 'true', + width: '16.66%', + Cell: ({ row }: CellProps) => { + return ( + + + + ) + } + }, + { + Header: getString('event'), + id: getString('event'), + sort: 'true', + width: '16.66%', + Cell: ({ row }: CellProps) => { + return {getEventDescription(row.original.trigger_type as WebhookIndividualEvent)} + } + }, + { + Header: getString('requestPayload'), + id: getString('requestPayload'), + sort: 'true', + width: '16.66%', + Cell: ({ row }: CellProps) => { + return ( +