feat: [AH-883]: Implement edit webhook page (#3291)

* feat: [AH-883]: refetch webhook after update and fix route to webhook details page from webhook list page
* feat: [AH-883]: fix breaking css for inline inputs
* feat: [AH-883]: implement Edit flow with enterprise secret input
* feat: [AH-883]: Implement edit webhook page
This commit is contained in:
Shivanand Sonnad 2025-01-21 07:08:51 +00:00 committed by Harness
parent b6c214b9ee
commit b00a8f16b4
22 changed files with 553 additions and 11 deletions

View File

@ -28,6 +28,7 @@ import upstreamProxyDetails from '@ar/pages/upstream-proxy-details/strings/strin
import versionDetails from '@ar/pages/version-details/strings/strings.en.yaml' import versionDetails from '@ar/pages/version-details/strings/strings.en.yaml'
import versionList from '@ar/pages/version-list/strings/strings.en.yaml' import versionList from '@ar/pages/version-list/strings/strings.en.yaml'
import webhookList from '@ar/pages/webhook-list/strings/strings.en.yaml' import webhookList from '@ar/pages/webhook-list/strings/strings.en.yaml'
import webhookDetails from '@ar/pages/webhook-details/strings/strings.en.yaml'
export default function languageLoader() { export default function languageLoader() {
return { return {
@ -39,6 +40,7 @@ export default function languageLoader() {
upstreamProxyDetails, upstreamProxyDetails,
versionDetails, versionDetails,
versionList, versionList,
webhookList webhookList,
webhookDetails
} }
} }

View File

@ -31,6 +31,8 @@ export default function getARRouteDefinitions(routeParams: Record<string, string
`/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}`, `/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}`,
// anything random, as this route will not be used in gitness // anything random, as this route will not be used in gitness
toARVersionDetailsTab: params => toARVersionDetailsTab: params =>
`/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}` `/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}`,
toARRepositoryWebhookDetailsTab: params =>
`/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}/${params?.tab}`
} }
} }

View File

@ -123,7 +123,7 @@ export function getFormattedFormDataForAuthType(
}) })
} }
function getSecretScopeDetailsByIdentifier(identifier: string, secretSpacePath: string) { export function getSecretScopeDetailsByIdentifier(identifier: string, secretSpacePath: string) {
const referenceString = getReferenceStringFromSecretSpacePath(identifier, secretSpacePath) const referenceString = getReferenceStringFromSecretSpacePath(identifier, secretSpacePath)
const [, orgIdentifier, projectIdentifier] = secretSpacePath.split('/') const [, orgIdentifier, projectIdentifier] = secretSpacePath.split('/')
return { return {

View File

@ -0,0 +1,40 @@
/*
* 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.
*/
.tabsContainer {
position: relative !important;
& > :global(.bp3-tabs > .bp3-tab-list) {
border-bottom: 1px solid var(--grey-200);
}
:global {
.bp3-tab-panel {
margin-top: 0;
}
.bp3-tab-list {
background-color: var(--white);
width: 100%;
border-bottom: 0;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.13);
align-items: center;
padding: 0 var(--spacing-xlarge);
position: sticky;
top: 0px;
z-index: 2;
}
}
}

View File

@ -0,0 +1,19 @@
/*
* 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 tabsContainer: string

View File

@ -0,0 +1,128 @@
/*
* 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, { useState } from 'react'
import type { FormikProps } from 'formik'
import { Expander } from '@blueprintjs/core'
import { useGetWebhookQuery, type WebhookRequest } from '@harnessio/react-har-service-client'
import { Redirect, Switch, useHistory, useParams } from 'react-router-dom'
import { Button, ButtonVariation, Container, Layout, Page, Tab, Tabs } from '@harnessio/uicore'
import { useStrings } from '@ar/frameworks/strings'
import RouteProvider from '@ar/components/RouteProvider/RouteProvider'
import type { RepositoryWebhookDetailsPathParams } from '@ar/routes/types'
import { useGetSpaceRef, useParentComponents, useRoutes } from '@ar/hooks'
import { PermissionIdentifier, ResourceType } from '@ar/common/permissionTypes'
import { repositoryWebhookDetailsPathParams, repositoryWebhookDetailsTabPathParams } from '@ar/routes/RouteDestinations'
import { WebhookDetailsTab } from './constants'
import WebhookDetailsTabPage from './WebhookDetailsTabPage'
// import { MOCK_WEBHOK_LIST_TABLE } from '../webhook-list/mockData'
import { WebhookDetailsContext } from './context/WebhookDetailsContext'
import { WebhookDetailsPageHeader } from './components/WebhookDetailsPageHeader/WebhookDetailsPageHeader'
import css from './WebhookDetailsPage.module.scss'
export default function WebhookDetailsPage() {
const params = useParams<RepositoryWebhookDetailsPathParams>()
const { repositoryIdentifier, webhookIdentifier } = params
const history = useHistory()
const routes = useRoutes()
const routeDefinitions = useRoutes(true)
const { RbacButton } = useParentComponents()
const registryRef = useGetSpaceRef()
const { getString } = useStrings()
const stepRef = React.useRef<FormikProps<WebhookRequest> | null>(null)
const [activeTab, setActiveTab] = useState(WebhookDetailsTab.Configuration)
const [isDirty, setIsDirty] = useState(false)
const [isUpdating, setUpdating] = useState(false)
const { isFetching, error, data, refetch } = useGetWebhookQuery({
registry_ref: registryRef,
webhook_identifier: webhookIdentifier
})
const handleChangeTab = (nextTab: WebhookDetailsTab): void => {
setActiveTab(nextTab)
history.push(routes.toARRepositoryWebhookDetailsTab({ ...params, tab: nextTab }))
}
const renderActionBtns = (): JSX.Element => (
<Layout.Horizontal spacing="medium">
<RbacButton
text={getString('save')}
variation={ButtonVariation.PRIMARY}
onClick={stepRef.current?.submitForm}
disabled={!isDirty || isUpdating}
permission={{
permission: PermissionIdentifier.EDIT_ARTIFACT_REGISTRY,
resource: {
resourceType: ResourceType.ARTIFACT_REGISTRY,
resourceIdentifier: repositoryIdentifier
}
}}
/>
<Button
variation={ButtonVariation.SECONDARY}
text={getString('discard')}
onClick={() => stepRef.current?.resetForm()}
disabled={!isDirty}
/>
</Layout.Horizontal>
)
const response = data?.content.data
// const response = MOCK_WEBHOK_LIST_TABLE.webhooks[0]
return (
<WebhookDetailsContext.Provider value={{ data: response, loading: isFetching, setDirty: setIsDirty, setUpdating }}>
<Page.Body loading={isFetching} error={error} retryOnError={() => refetch()}>
{response && !isFetching && (
<Container>
<WebhookDetailsPageHeader data={response} repositoryIdentifier={repositoryIdentifier} />
<Container className={css.tabsContainer}>
<Tabs id="webhookDetailsTabs" selectedTabId={activeTab} onChange={handleChangeTab}>
<Tab id={WebhookDetailsTab.Configuration} title={getString('webhookDetails.tabs.configuration')} />
<Tab id={WebhookDetailsTab.Executions} title={getString('webhookDetails.tabs.executions')} />
<Expander />
{activeTab === WebhookDetailsTab.Configuration && renderActionBtns()}
</Tabs>
</Container>
<Switch>
<RouteProvider
exact
path={routeDefinitions.toARRepositoryWebhookDetails({ ...repositoryWebhookDetailsPathParams })}>
<Redirect
to={routes.toARRepositoryWebhookDetailsTab({
...params,
tab: WebhookDetailsTab.Configuration
})}
/>
</RouteProvider>
<RouteProvider
exact
path={routeDefinitions.toARRepositoryWebhookDetailsTab({ ...repositoryWebhookDetailsTabPathParams })}>
<WebhookDetailsTabPage onInit={setActiveTab} formRef={stepRef} />
</RouteProvider>
</Switch>
</Container>
)}
</Page.Body>
</WebhookDetailsContext.Provider>
)
}

View File

@ -0,0 +1,49 @@
/*
* 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, { useEffect } from 'react'
import type { FormikProps } from 'formik'
import { useParams } from 'react-router-dom'
import type { WebhookRequest } from '@harnessio/react-har-service-client'
import type { RepositoryWebhookDetailsTabPathParams } from '@ar/routes/types'
import { WebhookDetailsTab } from './constants'
import WebhookConfigurationForm from './components/WebhookConfigurationForm/WebhookConfigurationForm'
interface WebhookDetailsTabPageProps {
onInit: (tab: WebhookDetailsTab) => void
formRef: React.RefObject<FormikProps<WebhookRequest>>
}
export default function WebhookDetailsTabPage(props: WebhookDetailsTabPageProps): JSX.Element {
const { onInit, formRef } = props
const params = useParams<RepositoryWebhookDetailsTabPathParams>()
const { tab } = params
useEffect(() => {
onInit(tab)
}, [tab])
switch (tab) {
case WebhookDetailsTab.Configuration:
return <WebhookConfigurationForm formRef={formRef} />
case WebhookDetailsTab.Executions:
return <>Executions Page</>
default:
return <>Not Found</>
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.
*/
.formContainer {
:global(.bp3-form-group .bp3-input),
:global(.bp3-form-group .bp3-input-group),
:global(.bp3-form-group .bp3-select-popover) {
width: var(--input-element-width);
}
:global(.bp3-form-group.bp3-inline .bp3-input),
:global(.bp3-form-group.bp3-inline .bp3-input-group),
:global(.bp3-form-group.bp3-inline .bp3-select-popover) {
width: unset;
}
div[class*='InputWithIdentifier--txtNameContainer-'] {
width: var(--input-element-width);
}
}

View File

@ -0,0 +1,19 @@
/*
* 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 formContainer: string

View File

@ -0,0 +1,69 @@
/*
* 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, { useContext } from 'react'
import type { FormikProps } from 'formik'
import { useParams } from 'react-router-dom'
import { updateWebhook, WebhookRequest } from '@harnessio/react-har-service-client'
import { Container, getErrorInfoFromErrorObject, useToaster } from '@harnessio/uicore'
import { useGetSpaceRef } from '@ar/hooks'
import { queryClient } from '@ar/utils/queryClient'
import { useStrings } from '@ar/frameworks/strings'
import WebhookForm from '@ar/pages/webhook-list/components/Forms/WebhookForm'
import type { RepositoryWebhookDetailsTabPathParams } from '@ar/routes/types'
import { WebhookDetailsContext } from '../../context/WebhookDetailsContext'
import css from './WebhookConfigurationForm.module.scss'
interface WebhookConfigurationFormProps {
formRef: React.RefObject<FormikProps<WebhookRequest>>
}
export default function WebhookConfigurationForm(props: WebhookConfigurationFormProps): JSX.Element {
const { formRef } = props
const { showError, showSuccess, clear } = useToaster()
const { getString } = useStrings()
const { data, setDirty, setUpdating } = useContext(WebhookDetailsContext)
const registryRef = useGetSpaceRef()
const { webhookIdentifier } = useParams<RepositoryWebhookDetailsTabPathParams>()
const handleUpdateWebhook = async (values: WebhookRequest) => {
try {
setUpdating?.(true)
await updateWebhook({
registry_ref: registryRef,
webhook_identifier: webhookIdentifier,
body: values
})
showSuccess(getString('webhookList.webhookUpdated'))
queryClient.invalidateQueries(['GetWebhook'])
} catch (e) {
clear()
showError(getErrorInfoFromErrorObject(e as Error))
} finally {
setUpdating?.(false)
}
}
if (!data) return <></>
return (
<Container className={css.formContainer} padding="xlarge">
<WebhookForm data={data} ref={formRef} isEdit onSubmit={handleUpdateWebhook} setDirty={setDirty} />
</Container>
)
}

View File

@ -0,0 +1,64 @@
/*
* 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 from 'react'
import { Page, Text } from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import type { Webhook } from '@harnessio/react-har-service-client'
import { useStrings } from '@ar/frameworks/strings'
import { useParentComponents, useRoutes } from '@ar/hooks'
import { getIdentifierStringForBreadcrumb } from '@ar/common/utils'
import { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
interface WebhookDetailsPageHeaderProps {
data: Webhook
repositoryIdentifier: string
}
export function WebhookDetailsPageHeader(props: WebhookDetailsPageHeaderProps) {
const { data, repositoryIdentifier } = props
const { NGBreadcrumbs } = useParentComponents()
const routes = useRoutes()
const { getString } = useStrings()
return (
<Page.Header
title={
<Text font={{ variation: FontVariation.H4 }} lineClamp={1}>
{data.name}
</Text>
}
size="large"
breadcrumbs={
<NGBreadcrumbs
links={[
{
url: routes.toARRepositories(),
label: getString('breadcrumbs.repositories')
},
{
url: routes.toARRepositoryDetailsTab({
repositoryIdentifier,
tab: RepositoryDetailsTab.WEBHOOKS
}),
label: getIdentifierStringForBreadcrumb(getString('breadcrumbs.repositories'), repositoryIdentifier)
}
]}
/>
}
/>
)
}

View File

@ -0,0 +1,20 @@
/*
* 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.
*/
export enum WebhookDetailsTab {
Configuration = 'configuration',
Executions = 'executions'
}

View File

@ -0,0 +1,30 @@
/*
* 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 { createContext } from 'react'
import type { Webhook } from '@harnessio/react-har-service-client'
interface WebhookDetailsContextSpec {
data?: Webhook
loading?: boolean
setDirty?: (dirty: boolean) => void
setUpdating?: (updating: boolean) => void
}
export const WebhookDetailsContext = createContext<WebhookDetailsContextSpec>({
data: {} as Webhook,
loading: false
})

View File

@ -0,0 +1,3 @@
tabs:
configuration: Configuration
executions: Executions

View File

@ -17,7 +17,7 @@
import React, { forwardRef, useMemo } from 'react' import React, { forwardRef, useMemo } from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'
import { Formik } from '@harnessio/uicore' import { Formik } from '@harnessio/uicore'
import type { WebhookRequest } from '@harnessio/react-har-service-client' import type { Webhook, WebhookRequest } from '@harnessio/react-har-service-client'
import { useAppStore } from '@ar/hooks' import { useAppStore } from '@ar/hooks'
import { GENERIC_URL_REGEX } from '@ar/constants' import { GENERIC_URL_REGEX } from '@ar/constants'
@ -27,19 +27,24 @@ import type { FormikFowardRef } from '@ar/common/types'
import type { WebhookRequestUI } from './types' import type { WebhookRequestUI } from './types'
import WebhookFormContent from './WebhookFormContent' import WebhookFormContent from './WebhookFormContent'
import { transformFormValuesToSubmitValues } from './utils' import { transformFormValuesToSubmitValues, transformWebhookDataToFormValues } from './utils'
interface CreateWebhookFormProps { interface CreateWebhookFormProps {
data?: Webhook
onSubmit: (values: WebhookRequest) => void onSubmit: (values: WebhookRequest) => void
setDirty?: (dirty: boolean) => void
readonly?: boolean readonly?: boolean
isEdit?: boolean isEdit?: boolean
} }
function WebhookForm(props: CreateWebhookFormProps, formikRef: FormikFowardRef<WebhookRequestUI>) { function WebhookForm(props: CreateWebhookFormProps, formikRef: FormikFowardRef<WebhookRequestUI>) {
const { onSubmit, readonly, isEdit } = props const { onSubmit, readonly, isEdit, data, setDirty } = props
const { getString } = useStrings() const { getString } = useStrings()
const { parent, scope } = useAppStore() const { parent, scope } = useAppStore()
const initialValues: WebhookRequestUI = useMemo(() => { const initialValues: WebhookRequestUI = useMemo(() => {
if (isEdit && data) {
return transformWebhookDataToFormValues(data, parent)
}
return { return {
identifier: '', identifier: '',
name: '', name: '',
@ -74,6 +79,7 @@ function WebhookForm(props: CreateWebhookFormProps, formikRef: FormikFowardRef<W
onSubmit={handleSubmit} onSubmit={handleSubmit}
initialValues={initialValues}> initialValues={initialValues}>
{formik => { {formik => {
setDirty?.(formik.dirty)
setFormikRef(formikRef, formik) setFormikRef(formikRef, formik)
return <WebhookFormContent formikProps={formik} isEdit={isEdit} readonly={readonly} /> return <WebhookFormContent formikProps={formik} isEdit={isEdit} readonly={readonly} />
}} }}

View File

@ -16,11 +16,14 @@
import produce from 'immer' import produce from 'immer'
import { get, set } from 'lodash-es' import { get, set } from 'lodash-es'
import type { WebhookRequest } from '@harnessio/react-har-service-client' import type { WebhookRequest, Webhook } from '@harnessio/react-har-service-client'
import type { Scope } from '@ar/MFEAppTypes' import type { Scope } from '@ar/MFEAppTypes'
import { Parent } from '@ar/common/types' import { Parent } from '@ar/common/types'
import { getSecretSpacePath } from '@ar/pages/upstream-proxy-details/components/Forms/utils' import {
getSecretScopeDetailsByIdentifier,
getSecretSpacePath
} from '@ar/pages/upstream-proxy-details/components/Forms/utils'
import type { WebhookRequestUI } from './types' import type { WebhookRequestUI } from './types'
@ -54,3 +57,26 @@ export function transformFormValuesToSubmitValues(
return draft return draft
}) })
} }
function convertFormFieldsToSecreteInput(formData: Webhook, secretField: string, secretSpacePathField: string) {
const secretIdentifier = get(formData, secretField, '')
const secretSpacePath = get(formData, secretSpacePathField, '')
set(formData, secretField, getSecretScopeDetailsByIdentifier(secretIdentifier, secretSpacePath))
}
export function transformWebhookDataToFormValues(data: Webhook, parent: Parent): WebhookRequestUI {
return produce(data, draft => {
if (draft.triggers?.length) {
set(draft, 'triggerType', 'custom')
} else {
set(draft, 'triggerType', 'all')
}
if (!draft.extraHeaders?.length) {
set(draft, 'extraHeaders', [{ key: '', value: '' }])
}
if (parent === Parent.Enterprise) {
convertFormFieldsToSecreteInput(draft, 'secretIdentifier', 'secretSpacePath')
}
return draft
}) as WebhookRequestUI
}

View File

@ -17,12 +17,13 @@
import React from 'react' import React from 'react'
import classNames from 'classnames' import classNames from 'classnames'
import type { Column } from 'react-table' import type { Column } from 'react-table'
import { useHistory } from 'react-router-dom' import { useHistory, useParams } from 'react-router-dom'
import { type PaginationProps, TableV2 } from '@harnessio/uicore' import { type PaginationProps, TableV2 } from '@harnessio/uicore'
import type { ListWebhooks, Webhook } from '@harnessio/react-har-service-client' import type { ListWebhooks, Webhook } from '@harnessio/react-har-service-client'
import { useStrings } from '@ar/frameworks/strings' import { useStrings } from '@ar/frameworks/strings'
import { useParentHooks, useRoutes } from '@ar/hooks' import { useParentHooks, useRoutes } from '@ar/hooks'
import type { RepositoryDetailsTabPathParams } from '@ar/routes/types'
import { import {
WebhookActionsCell, WebhookActionsCell,
@ -50,6 +51,7 @@ export interface WebhookListTableProps extends WebhookListColumnActions {
export default function WebhookListTable(props: WebhookListTableProps): JSX.Element { export default function WebhookListTable(props: WebhookListTableProps): JSX.Element {
const { data, gotoPage, onPageSizeChange, readonly, sortBy, setSortBy } = props const { data, gotoPage, onPageSizeChange, readonly, sortBy, setSortBy } = props
const { useDefaultPaginationProps } = useParentHooks() const { useDefaultPaginationProps } = useParentHooks()
const { repositoryIdentifier } = useParams<RepositoryDetailsTabPathParams>()
const { getString } = useStrings() const { getString } = useStrings()
const history = useHistory() const history = useHistory()
const routes = useRoutes() const routes = useRoutes()
@ -116,7 +118,7 @@ export default function WebhookListTable(props: WebhookListTableProps): JSX.Elem
onRowClick={rowDetails => { onRowClick={rowDetails => {
history.push( history.push(
routes.toARRepositoryWebhookDetails({ routes.toARRepositoryWebhookDetails({
repositoryIdentifier: rowDetails.identifier, repositoryIdentifier,
webhookIdentifier: rowDetails.identifier webhookIdentifier: rowDetails.identifier
}) })
) )

View File

@ -1,5 +1,6 @@
newWebhook: New Webhook newWebhook: New Webhook
webhookCreated: Webhook created successfully webhookCreated: Webhook created successfully
webhookUpdated: Webhook updated successfully
triggers: triggers:
artifactCreation: 'Artifact Creation' artifactCreation: 'Artifact Creation'
artifactDeletion: 'Artifact Deletion' artifactDeletion: 'Artifact Deletion'

View File

@ -21,6 +21,7 @@ import type {
RepositoryDetailsPathParams, RepositoryDetailsPathParams,
RepositoryDetailsTabPathParams, RepositoryDetailsTabPathParams,
RepositoryWebhookDetailsPathParams, RepositoryWebhookDetailsPathParams,
RepositoryWebhookDetailsTabPathParams,
VersionDetailsPathParams, VersionDetailsPathParams,
VersionDetailsTabPathParams VersionDetailsTabPathParams
} from './types' } from './types'
@ -36,6 +37,7 @@ export interface ARRouteDefinitionsReturn {
toARVersionDetails: (params: VersionDetailsPathParams) => string toARVersionDetails: (params: VersionDetailsPathParams) => string
toARVersionDetailsTab: (params: VersionDetailsTabPathParams) => string toARVersionDetailsTab: (params: VersionDetailsTabPathParams) => string
toARRepositoryWebhookDetails: (params: RepositoryWebhookDetailsPathParams) => string toARRepositoryWebhookDetails: (params: RepositoryWebhookDetailsPathParams) => string
toARRepositoryWebhookDetailsTab: (params: RepositoryWebhookDetailsTabPathParams) => string
} }
export const routeDefinitions: ARRouteDefinitionsReturn = { export const routeDefinitions: ARRouteDefinitionsReturn = {
@ -70,5 +72,7 @@ export const routeDefinitions: ARRouteDefinitionsReturn = {
return `/registries/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}/${params.versionTab}` return `/registries/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}/${params.versionTab}`
}, },
toARRepositoryWebhookDetails: params => toARRepositoryWebhookDetails: params =>
`/registries/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}` `/registries/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}`,
toARRepositoryWebhookDetailsTab: params =>
`/registries/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}/${params.tab}`
} }

View File

@ -20,12 +20,15 @@ import { Redirect, Switch } from 'react-router-dom'
import { Parent } from '@ar/common/types' import { Parent } from '@ar/common/types'
import { useAppStore, useRoutes } from '@ar/hooks' import { useAppStore, useRoutes } from '@ar/hooks'
import RedirectPage from '@ar/pages/redirect-page/RedirectPage' import RedirectPage from '@ar/pages/redirect-page/RedirectPage'
import type { WebhookDetailsTab } from '@ar/pages/webhook-details/constants'
import type { RepositoryDetailsTab } from '@ar/pages/repository-details/constants' import type { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
import type { import type {
ArtifactDetailsPathParams, ArtifactDetailsPathParams,
RepositoryDetailsPathParams, RepositoryDetailsPathParams,
RepositoryDetailsTabPathParams, RepositoryDetailsTabPathParams,
RepositoryWebhookDetailsPathParams,
RepositoryWebhookDetailsTabPathParams,
VersionDetailsPathParams, VersionDetailsPathParams,
VersionDetailsTabPathParams VersionDetailsTabPathParams
} from './types' } from './types'
@ -37,6 +40,7 @@ const ArtifactDetailsPage = React.lazy(() => import('@ar/pages/artifact-details/
const VersionDetailsPage = React.lazy(() => import('@ar/pages/version-details/VersionDetailsPage')) const VersionDetailsPage = React.lazy(() => import('@ar/pages/version-details/VersionDetailsPage'))
const OSSVersionDetailsPage = React.lazy(() => import('@ar/pages/version-details/OSSVersionDetailsPage')) const OSSVersionDetailsPage = React.lazy(() => import('@ar/pages/version-details/OSSVersionDetailsPage'))
const RouteProvider = React.lazy(() => import('@ar/components/RouteProvider/RouteProvider')) const RouteProvider = React.lazy(() => import('@ar/components/RouteProvider/RouteProvider'))
const WebhookDetailsPage = React.lazy(() => import('@ar/pages/webhook-details/WebhookDetailsPage'))
export const repositoryDetailsPathProps: RepositoryDetailsPathParams = { export const repositoryDetailsPathProps: RepositoryDetailsPathParams = {
repositoryIdentifier: ':repositoryIdentifier' repositoryIdentifier: ':repositoryIdentifier'
@ -74,6 +78,16 @@ export const versionDetailsTabWithSSCADetailsPathParams: VersionDetailsTabPathPa
artifactId: ':artifactId' artifactId: ':artifactId'
} }
export const repositoryWebhookDetailsPathParams: RepositoryWebhookDetailsPathParams = {
...repositoryDetailsPathProps,
webhookIdentifier: ':webhookIdentifier'
}
export const repositoryWebhookDetailsTabPathParams: RepositoryWebhookDetailsTabPathParams = {
...repositoryWebhookDetailsPathParams,
tab: ':tab' as WebhookDetailsTab
}
const RouteDestinations = (): JSX.Element => { const RouteDestinations = (): JSX.Element => {
const routes = useRoutes(true) const routes = useRoutes(true)
const { parent } = useAppStore() const { parent } = useAppStore()
@ -110,6 +124,9 @@ const RouteDestinations = (): JSX.Element => {
<VersionDetailsPage /> <VersionDetailsPage />
</RouteProvider> </RouteProvider>
)} )}
<RouteProvider path={routes.toARRepositoryWebhookDetails({ ...repositoryWebhookDetailsPathParams })}>
<WebhookDetailsPage />
</RouteProvider>
<RouteProvider path={routes.toARRepositoryDetails({ ...repositoryDetailsPathProps })}> <RouteProvider path={routes.toARRepositoryDetails({ ...repositoryDetailsPathProps })}>
<RepositoryDetailsPage /> <RepositoryDetailsPage />
</RouteProvider> </RouteProvider>

View File

@ -17,6 +17,7 @@
import type { RepositoryPackageType } from '@ar/common/types' import type { RepositoryPackageType } from '@ar/common/types'
import type { RepositoryDetailsTab } from '@ar/pages/repository-details/constants' import type { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
import type { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants' import type { VersionDetailsTab } from '@ar/pages/version-details/components/VersionDetailsTabs/constants'
import type { WebhookDetailsTab } from '@ar/pages/webhook-details/constants'
export interface RepositoryDetailsPathParams { export interface RepositoryDetailsPathParams {
repositoryIdentifier: string repositoryIdentifier: string
@ -56,3 +57,7 @@ export interface RedirectPageQueryParams {
export interface RepositoryWebhookDetailsPathParams extends RepositoryDetailsPathParams { export interface RepositoryWebhookDetailsPathParams extends RepositoryDetailsPathParams {
webhookIdentifier: string webhookIdentifier: string
} }
export interface RepositoryWebhookDetailsTabPathParams extends RepositoryWebhookDetailsPathParams {
tab: WebhookDetailsTab
}

View File

@ -229,6 +229,8 @@ export interface StringsMap {
'versionList.table.columns.size': string 'versionList.table.columns.size': string
'versionList.table.columns.version': string 'versionList.table.columns.version': string
'versionList.table.noVersionsTitle': string 'versionList.table.noVersionsTitle': string
'webhookDetails.tabs.configuration': string
'webhookDetails.tabs.executions': string
'webhookList.formFields.SSLVerification': string 'webhookList.formFields.SSLVerification': string
'webhookList.formFields.addNewKeyValuePair': string 'webhookList.formFields.addNewKeyValuePair': string
'webhookList.formFields.advanced': string 'webhookList.formFields.advanced': string
@ -254,6 +256,7 @@ export interface StringsMap {
'webhookList.triggers.artifactDeletion': string 'webhookList.triggers.artifactDeletion': string
'webhookList.triggers.artifactModification': string 'webhookList.triggers.artifactModification': string
'webhookList.webhookCreated': string 'webhookList.webhookCreated': string
'webhookList.webhookUpdated': string
'actions.delete': string 'actions.delete': string
'actions.edit': string 'actions.edit': string
'actions.quarantine': string 'actions.quarantine': string