mirror of https://github.com/harness/drone.git
feat: [AH-882]: Implement create webhook modal (#3288)
* feat: [AH-882]: fix circular dependancy check plugin for ar * feat: [AH-882]: fix PR comments * feat: [AH-882]: Add Generic URL validation * feat: [AH-882]: implement support for extra headers in create webhook form * feat: [AH-882]: bufixes in webhook form content * [AH-882]: Implement create webhook modalpull/3616/head
parent
d6ea2b8de6
commit
3c5a2a4ab0
|
@ -14,7 +14,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { FormikProps } from 'formik'
|
||||
import type { DataTooltipInterface } from '@harnessio/uicore'
|
||||
import type { FormikContextType, FormikProps } from 'formik'
|
||||
|
||||
export interface FormikExtended<T> extends FormikContextType<T> {
|
||||
disabled?: boolean
|
||||
formName: string
|
||||
}
|
||||
|
||||
export interface FormikContextProps<T> {
|
||||
formik?: FormikExtended<T>
|
||||
tooltipProps?: DataTooltipInterface
|
||||
}
|
||||
|
||||
export enum Parent {
|
||||
OSS = 'OSS',
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash-es'
|
||||
import { connect } from 'formik'
|
||||
import { FormGroup, IFormGroupProps, Intent } from '@blueprintjs/core'
|
||||
import { Checkbox, FormError, getFormFieldLabel, errorCheck } from '@harnessio/uicore'
|
||||
|
||||
import type { FormikContextProps } from '@ar/common/types'
|
||||
|
||||
interface CheckboxItem {
|
||||
label: string
|
||||
value: string
|
||||
disabled?: boolean
|
||||
tooltipId?: string
|
||||
}
|
||||
|
||||
export interface CheckboxGroupProps extends Omit<IFormGroupProps, 'labelFor'> {
|
||||
name: string
|
||||
items: CheckboxItem[]
|
||||
}
|
||||
|
||||
function CheckboxGroup(props: CheckboxGroupProps & FormikContextProps<any>) {
|
||||
const { formik, name } = props
|
||||
const hasError = errorCheck(name, formik)
|
||||
const {
|
||||
intent = hasError ? Intent.DANGER : Intent.NONE,
|
||||
helperText = hasError ? <FormError name={name} errorMessage={get(formik?.errors, name)} /> : null,
|
||||
disabled = formik?.disabled,
|
||||
items = [],
|
||||
label,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
const formValue: string[] = get(formik?.values, name, [])
|
||||
|
||||
const handleChange = (val: string, checked: boolean) => {
|
||||
const newValue = checked ? [...formValue, val] : formValue.filter(v => v !== val)
|
||||
formik?.setFieldValue(name, newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={getFormFieldLabel(label, name, props)}
|
||||
labelFor={name}
|
||||
helperText={helperText}
|
||||
intent={intent}
|
||||
disabled={disabled}
|
||||
{...rest}>
|
||||
{items.map(item => {
|
||||
return (
|
||||
<Checkbox
|
||||
key={item.value}
|
||||
value={item.value}
|
||||
disabled={disabled}
|
||||
checked={formValue?.includes(item.value)}
|
||||
onChange={e => {
|
||||
handleChange(item.value, e.currentTarget.checked)
|
||||
}}
|
||||
label={item.label}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FormGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(CheckboxGroup)
|
|
@ -58,6 +58,7 @@ const prodConfig = {
|
|||
}),
|
||||
new CircularDependencyPlugin({
|
||||
exclude: /node_modules/,
|
||||
include: /src\/ar/,
|
||||
failOnError: true
|
||||
}),
|
||||
new RetryChunkLoadPlugin({
|
||||
|
|
|
@ -28,6 +28,7 @@ export const DEFAULT_DATE_TIME_FORMAT = `${DEFAULT_DATE_FORMAT} ${DEFAULT_TIME_F
|
|||
|
||||
export const REPO_KEY_REGEX = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/
|
||||
export const URL_REGEX = /^https:\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:[0-9]{1,5})?(\/[^\s]*)?$/
|
||||
export const GENERIC_URL_REGEX = /^(https?):\/\/([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:[0-9]{1,5})?(\/[^\s]*)?$/
|
||||
|
||||
export enum PreferenceScope {
|
||||
USER = 'USER',
|
||||
|
|
|
@ -24,6 +24,7 @@ export default function getARRouteDefinitions(routeParams: Record<string, string
|
|||
toARRepositories: () => '/',
|
||||
toARRepositoryDetails: params => `/${params?.repositoryIdentifier}`,
|
||||
toARRepositoryDetailsTab: params => `/${params?.repositoryIdentifier}/${params?.tab}`,
|
||||
toARRepositoryWebhookDetails: params => `/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}`,
|
||||
toARArtifacts: () => `/${routeParams?.repositoryIdentifier}?tab=packages`,
|
||||
toARArtifactDetails: params => `/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}`,
|
||||
toARVersionDetails: params =>
|
||||
|
|
|
@ -15,14 +15,38 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Button, ButtonVariation } from '@harnessio/uicore'
|
||||
import { useHistory, useParams } from 'react-router-dom'
|
||||
import type { Webhook } from '@harnessio/react-har-service-client'
|
||||
import { Button, ButtonVariation, useToggleOpen } from '@harnessio/uicore'
|
||||
|
||||
import { useRoutes } from '@ar/hooks'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import type { RepositoryDetailsTabPathParams } from '@ar/routes/types'
|
||||
|
||||
import CreateWebhookModal from './CreateWebhookModal'
|
||||
|
||||
export default function CreateWebhookButton() {
|
||||
const { isOpen, close, open } = useToggleOpen()
|
||||
const { getString } = useStrings()
|
||||
const { repositoryIdentifier } = useParams<RepositoryDetailsTabPathParams>()
|
||||
|
||||
const routes = useRoutes()
|
||||
const history = useHistory()
|
||||
|
||||
const handleAfterCreateWebhook = (data: Webhook) => {
|
||||
history.push(
|
||||
routes.toARRepositoryWebhookDetails({
|
||||
repositoryIdentifier,
|
||||
webhookIdentifier: data.identifier
|
||||
})
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Button variation={ButtonVariation.PRIMARY} icon="plus" iconProps={{ size: 10 }}>
|
||||
{getString('webhookList.newWebhook')}
|
||||
</Button>
|
||||
<>
|
||||
<Button variation={ButtonVariation.PRIMARY} icon="plus" iconProps={{ size: 10 }} onClick={open}>
|
||||
{getString('webhookList.newWebhook')}
|
||||
</Button>
|
||||
<CreateWebhookModal isOpen={isOpen} onClose={close} onSubmit={handleAfterCreateWebhook} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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, { useRef, useState } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import type { IDialogProps } from '@blueprintjs/core'
|
||||
import { createWebhook, Webhook, WebhookRequest } from '@harnessio/react-har-service-client'
|
||||
import { Button, ButtonVariation, Layout, ModalDialog, useToaster } from '@harnessio/uicore'
|
||||
|
||||
import { useGetSpaceRef } from '@ar/hooks'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import WebhookForm from '../Forms/WebhookForm'
|
||||
|
||||
interface CreateWebhookModalProps extends IDialogProps {
|
||||
onSubmit: (res: Webhook) => void
|
||||
}
|
||||
|
||||
export default function CreateWebhookModal(props: CreateWebhookModalProps) {
|
||||
const { onSubmit, isOpen, onClose, title } = props
|
||||
const [isLoading, setLoading] = useState(false)
|
||||
const { getString } = useStrings()
|
||||
|
||||
const registryRef = useGetSpaceRef()
|
||||
|
||||
const { showError, clear, showSuccess } = useToaster()
|
||||
|
||||
const formRef = useRef<FormikProps<WebhookRequest>>(null)
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.current?.submitForm()
|
||||
}
|
||||
|
||||
const handleCreateWebhook = async (formData: WebhookRequest) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const response = await createWebhook({
|
||||
registry_ref: registryRef,
|
||||
body: formData
|
||||
})
|
||||
showSuccess(getString('webhookList.webhookCreated'))
|
||||
onSubmit(response.content.data)
|
||||
} catch (e) {
|
||||
clear()
|
||||
showError(getErrorMessage(e))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
title={title ?? getString('webhookList.newWebhook')}
|
||||
onClose={onClose}
|
||||
isOpen={isOpen}
|
||||
showOverlay={isLoading}
|
||||
footer={
|
||||
<Layout.Horizontal spacing="small">
|
||||
<Button variation={ButtonVariation.PRIMARY} onClick={handleSubmit}>
|
||||
{getString('add')}
|
||||
</Button>
|
||||
<Button variation={ButtonVariation.SECONDARY} onClick={() => onClose?.()}>
|
||||
{getString('cancel')}
|
||||
</Button>
|
||||
</Layout.Horizontal>
|
||||
}>
|
||||
<WebhookForm onSubmit={handleCreateWebhook} ref={formRef} />
|
||||
</ModalDialog>
|
||||
)
|
||||
}
|
|
@ -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 from 'react'
|
||||
import { FieldArray, FormikProps } from 'formik'
|
||||
import { Layout } from '@harnessio/uicore'
|
||||
|
||||
import type { WebhookRequestUI } from '../types'
|
||||
import ExtraHeadersList from './ExtraHeadersList'
|
||||
|
||||
interface ExtraHeadersFormContentProps {
|
||||
formikProps: FormikProps<WebhookRequestUI>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function ExtraHeadersFormContent(props: ExtraHeadersFormContentProps) {
|
||||
const { formikProps, disabled } = props
|
||||
return (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FieldArray
|
||||
name="extraHeaders"
|
||||
render={({ push, remove }) => {
|
||||
return (
|
||||
<ExtraHeadersList
|
||||
onAdd={push}
|
||||
onRemove={remove}
|
||||
name="extraHeaders"
|
||||
formikProps={formikProps}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 type { FormikProps } from 'formik'
|
||||
import type { ExtraHeader } from '@harnessio/react-har-service-client'
|
||||
import { Button, ButtonSize, ButtonVariation, FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
|
||||
import type { WebhookRequestUI } from '../types'
|
||||
|
||||
interface ExtraHeadersListProps {
|
||||
onAdd: (item: ExtraHeader) => void
|
||||
onRemove: (index: number) => void
|
||||
name: keyof WebhookRequestUI
|
||||
formikProps: FormikProps<WebhookRequestUI>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function ExtraHeadersList(props: ExtraHeadersListProps) {
|
||||
const { onAdd, onRemove, name, formikProps, disabled } = props
|
||||
const list = formikProps.values[name] as ExtraHeader[]
|
||||
const { getString } = useStrings()
|
||||
return (
|
||||
<Layout.Vertical flex={{ alignItems: 'flex-start' }}>
|
||||
{list?.map((_each: ExtraHeader, index: number) => (
|
||||
<Layout.Horizontal key={index} spacing="medium">
|
||||
<FormInput.Text
|
||||
inline
|
||||
name={`${name}[${index}].key`}
|
||||
label={getString('webhookList.formFields.extraHeader')}
|
||||
placeholder={getString('webhookList.formFields.extraHeaderPlaceholder')}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormInput.Text
|
||||
inline
|
||||
name={`${name}[${index}].value`}
|
||||
label={getString('webhookList.formFields.extraValue')}
|
||||
placeholder={getString('webhookList.formFields.extraValuePlaceholder')}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Button variation={ButtonVariation.ICON} icon="code-delete" onClick={() => onRemove(index)} />
|
||||
</Layout.Horizontal>
|
||||
))}
|
||||
<Button
|
||||
size={ButtonSize.SMALL}
|
||||
icon="plus"
|
||||
variation={ButtonVariation.LINK}
|
||||
onClick={() => onAdd({ key: '', value: '' })}>
|
||||
{getString('webhookList.formFields.addNewKeyValuePair')}
|
||||
</Button>
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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, { type PropsWithChildren } from 'react'
|
||||
import { Text, type TextProps } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
|
||||
export default function FormLabel(props: PropsWithChildren<TextProps>) {
|
||||
return (
|
||||
<Text font={{ variation: FontVariation.FORM_LABEL, weight: 'bold' }} color={Color.GREY_900} {...props}>
|
||||
{props.children}
|
||||
</Text>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.triggerType {
|
||||
margin-bottom: var(--spacing-xsmall) !important;
|
||||
}
|
|
@ -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 triggerType: string
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 type { FormikProps } from 'formik'
|
||||
import { Container, FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import CheckboxGroup from '@ar/components/Form/CheckboxGroup/CheckboxGroup'
|
||||
|
||||
import FormLabel from './FormLabel'
|
||||
import type { WebhookRequestUI } from './types'
|
||||
import { TriggerLabelOptions } from './constants'
|
||||
|
||||
import css from './Forms.module.scss'
|
||||
|
||||
interface SelectTriggersProps {
|
||||
formikProps: FormikProps<WebhookRequestUI>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function SelectTriggers(props: SelectTriggersProps) {
|
||||
const { formikProps, disabled } = props
|
||||
const triggerType = formikProps.values.triggerType
|
||||
const { getString } = useStrings()
|
||||
return (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormLabel>{getString('webhookList.formFields.triggerLabel')}</FormLabel>
|
||||
<FormInput.RadioGroup
|
||||
key={triggerType}
|
||||
className={css.triggerType}
|
||||
disabled={disabled}
|
||||
name="triggerType"
|
||||
items={[
|
||||
{ label: getString('webhookList.formFields.allTrigger'), value: 'all' },
|
||||
{ label: getString('webhookList.formFields.customTrigger'), value: 'custom' }
|
||||
]}
|
||||
/>
|
||||
{triggerType === 'custom' && (
|
||||
<Container margin={{ left: 'large' }}>
|
||||
<CheckboxGroup
|
||||
disabled={disabled}
|
||||
items={TriggerLabelOptions.map(each => ({ ...each, label: getString(each.label) }))}
|
||||
name="triggers"
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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, { forwardRef, useMemo } from 'react'
|
||||
import * as Yup from 'yup'
|
||||
import { Formik } from '@harnessio/uicore'
|
||||
import type { WebhookRequest } from '@harnessio/react-har-service-client'
|
||||
|
||||
import { useAppStore } from '@ar/hooks'
|
||||
import { GENERIC_URL_REGEX } from '@ar/constants'
|
||||
import { setFormikRef } from '@ar/common/utils'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import type { FormikFowardRef } from '@ar/common/types'
|
||||
|
||||
import type { WebhookRequestUI } from './types'
|
||||
import WebhookFormContent from './WebhookFormContent'
|
||||
import { transformFormValuesToSubmitValues } from './utils'
|
||||
|
||||
interface CreateWebhookFormProps {
|
||||
onSubmit: (values: WebhookRequest) => void
|
||||
readonly?: boolean
|
||||
isEdit?: boolean
|
||||
}
|
||||
|
||||
function WebhookForm(props: CreateWebhookFormProps, formikRef: FormikFowardRef<WebhookRequestUI>) {
|
||||
const { onSubmit, readonly, isEdit } = props
|
||||
const { getString } = useStrings()
|
||||
const { parent, scope } = useAppStore()
|
||||
const initialValues: WebhookRequestUI = useMemo(() => {
|
||||
return {
|
||||
identifier: '',
|
||||
name: '',
|
||||
url: '',
|
||||
triggerType: 'all',
|
||||
enabled: true,
|
||||
insecure: false,
|
||||
extraHeaders: [{ key: '', value: '' }]
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleSubmit = (values: WebhookRequestUI) => {
|
||||
const formValues = transformFormValuesToSubmitValues(values, parent, scope)
|
||||
onSubmit(formValues)
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik<WebhookRequestUI>
|
||||
formName="webhook-form"
|
||||
validationSchema={Yup.object().shape({
|
||||
identifier: Yup.string().required(getString('validationMessages.identifierRequired')),
|
||||
name: Yup.string().required(getString('validationMessages.nameRequired')),
|
||||
url: Yup.string()
|
||||
.required(getString('validationMessages.urlRequired'))
|
||||
.matches(GENERIC_URL_REGEX, getString('validationMessages.genericURLPattern')),
|
||||
triggers: Yup.array().when(['triggerType'], {
|
||||
is: (triggerType: WebhookRequestUI['triggerType']) => triggerType === 'custom',
|
||||
then: (schema: Yup.StringSchema) => schema.required(getString('validationMessages.required')),
|
||||
otherwise: (schema: Yup.StringSchema) => schema.notRequired()
|
||||
})
|
||||
})}
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={initialValues}>
|
||||
{formik => {
|
||||
setFormikRef(formikRef, formik)
|
||||
return <WebhookFormContent formikProps={formik} isEdit={isEdit} readonly={readonly} />
|
||||
}}
|
||||
</Formik>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(WebhookForm)
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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 type { FormikProps } from 'formik'
|
||||
import { Checkbox, FormikForm, FormInput, Layout } from '@harnessio/uicore'
|
||||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { useAppStore, useParentComponents } from '@ar/hooks'
|
||||
|
||||
import FormLabel from './FormLabel'
|
||||
import SelectTriggers from './SelectTriggers'
|
||||
import type { WebhookRequestUI } from './types'
|
||||
import ExtraHeadersFormContent from './ExtraHeadersFormContent/ExtraHeadersFormContent'
|
||||
|
||||
interface WebhookFormContentProps {
|
||||
formikProps: FormikProps<WebhookRequestUI>
|
||||
readonly?: boolean
|
||||
isEdit?: boolean
|
||||
}
|
||||
|
||||
export default function WebhookFormContent(props: WebhookFormContentProps) {
|
||||
const { formikProps, readonly, isEdit } = props
|
||||
const { scope } = useAppStore()
|
||||
const { getString } = useStrings()
|
||||
const { SecretFormInput } = useParentComponents()
|
||||
const values = formikProps.values
|
||||
return (
|
||||
<FormikForm>
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormInput.InputWithIdentifier
|
||||
inputName="name"
|
||||
idName="identifier"
|
||||
inputLabel={getString('webhookList.formFields.name')}
|
||||
idLabel={getString('webhookList.formFields.id')}
|
||||
inputGroupProps={{
|
||||
placeholder: getString('enterPlaceholder', { name: getString('webhookList.formFields.name') }),
|
||||
disabled: readonly
|
||||
}}
|
||||
isIdentifierEditable={!isEdit && !readonly}
|
||||
/>
|
||||
<FormInput.TextArea
|
||||
name="description"
|
||||
label={getString('optionalField', { name: getString('webhookList.formFields.description') })}
|
||||
placeholder={getString('enterPlaceholder', { name: getString('webhookList.formFields.description') })}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name="url"
|
||||
label={getString('webhookList.formFields.url')}
|
||||
placeholder={getString('enterPlaceholder', { name: getString('webhookList.formFields.url') })}
|
||||
disabled={readonly}
|
||||
/>
|
||||
<SecretFormInput
|
||||
name="secretIdentifier"
|
||||
spaceIdFieldName="secretSpaceId"
|
||||
label={getString('webhookList.formFields.secret')}
|
||||
placeholder={getString('enterPlaceholder', { name: getString('webhookList.formFields.secret') })}
|
||||
scope={scope}
|
||||
disabled={readonly}
|
||||
formik={formikProps}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
<Layout.Vertical spacing="large">
|
||||
<SelectTriggers formikProps={formikProps} disabled={readonly} />
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormLabel>{getString('webhookList.formFields.SSLVerification')}</FormLabel>
|
||||
<Checkbox
|
||||
label={getString('webhookList.formFields.enableSSLVerification')}
|
||||
checked={!values.insecure}
|
||||
disabled={readonly}
|
||||
onChange={e => {
|
||||
formikProps.setFieldValue('insecure', !e.currentTarget.checked)
|
||||
}}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
{isEdit && (
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormLabel>{getString('webhookList.formFields.enabled')}</FormLabel>
|
||||
<FormInput.CheckBox
|
||||
disabled={readonly}
|
||||
label={getString('webhookList.formFields.enabled')}
|
||||
name="enabled"
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
)}
|
||||
<Layout.Vertical spacing="small">
|
||||
<FormLabel>{getString('optionalField', { name: getString('webhookList.formFields.advanced') })}</FormLabel>
|
||||
<ExtraHeadersFormContent formikProps={formikProps} disabled={readonly} />
|
||||
</Layout.Vertical>
|
||||
</Layout.Vertical>
|
||||
</FormikForm>
|
||||
)
|
||||
}
|
|
@ -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 type { Trigger } from '@harnessio/react-har-service-client'
|
||||
import type { StringKeys } from '@ar/frameworks/strings'
|
||||
|
||||
interface TriggerLabelOption {
|
||||
label: StringKeys
|
||||
value: Trigger
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const TriggerLabelOptions: TriggerLabelOption[] = [
|
||||
{ label: 'webhookList.triggers.artifactCreation', value: 'ARTIFACT_CREATION' },
|
||||
{ label: 'webhookList.triggers.artifactDeletion', value: 'ARTIFACT_DELETION' },
|
||||
{ label: 'webhookList.triggers.artifactModification', value: 'ARTIFACT_MODIFICATION' }
|
||||
]
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 type { WebhookRequest } from '@harnessio/react-har-service-client'
|
||||
|
||||
export type WebhookRequestUI = WebhookRequest & { triggerType: 'all' | 'custom' }
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 produce from 'immer'
|
||||
import { get, set } from 'lodash-es'
|
||||
import type { WebhookRequest } from '@harnessio/react-har-service-client'
|
||||
|
||||
import type { Scope } from '@ar/MFEAppTypes'
|
||||
import { Parent } from '@ar/common/types'
|
||||
import { getSecretSpacePath } from '@ar/pages/upstream-proxy-details/components/Forms/utils'
|
||||
|
||||
import type { WebhookRequestUI } from './types'
|
||||
|
||||
function convertSecretInputToFormFields(
|
||||
formData: WebhookRequestUI,
|
||||
secretField: keyof WebhookRequestUI,
|
||||
secretSpacePathField: keyof WebhookRequestUI,
|
||||
scope?: Scope
|
||||
) {
|
||||
const secret = get(formData, secretField)
|
||||
set(formData, secretSpacePathField, getSecretSpacePath(get(secret, 'referenceString', ''), scope))
|
||||
set(formData, secretField, get(secret, 'identifier'))
|
||||
}
|
||||
|
||||
export function transformFormValuesToSubmitValues(
|
||||
formValues: WebhookRequestUI,
|
||||
parent: Parent,
|
||||
scope: Scope
|
||||
): WebhookRequest {
|
||||
return produce(formValues, draft => {
|
||||
if (draft.triggerType === 'all') {
|
||||
draft.triggers = []
|
||||
}
|
||||
if (draft.extraHeaders?.length) {
|
||||
draft.extraHeaders = draft.extraHeaders.filter(each => !!each.key && !!each.value)
|
||||
}
|
||||
if (parent === Parent.Enterprise) {
|
||||
convertSecretInputToFormFields(draft, 'secretIdentifier', 'secretSpacePath', scope)
|
||||
}
|
||||
set(draft, 'triggerType', undefined)
|
||||
return draft
|
||||
})
|
||||
}
|
|
@ -23,7 +23,6 @@ import type { ListWebhooks, Webhook } from '@harnessio/react-har-service-client'
|
|||
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import { useParentHooks, useRoutes } from '@ar/hooks'
|
||||
import { RepositoryDetailsTab } from '@ar/pages/repository-details/constants'
|
||||
|
||||
import {
|
||||
WebhookActionsCell,
|
||||
|
@ -115,11 +114,10 @@ export default function WebhookListTable(props: WebhookListTableProps): JSX.Elem
|
|||
sortable
|
||||
getRowClassName={() => css.tableRow}
|
||||
onRowClick={rowDetails => {
|
||||
// TODO: navigate to webhook details page
|
||||
history.push(
|
||||
routes.toARRepositoryDetailsTab({
|
||||
routes.toARRepositoryWebhookDetails({
|
||||
repositoryIdentifier: rowDetails.identifier,
|
||||
tab: RepositoryDetailsTab.WEBHOOKS
|
||||
webhookIdentifier: rowDetails.identifier
|
||||
})
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
newWebhook: New Webhook
|
||||
webhookCreated: Webhook created successfully
|
||||
triggers:
|
||||
artifactCreation: 'Artifact Creation'
|
||||
artifactDeletion: 'Artifact Deletion'
|
||||
|
@ -8,3 +9,21 @@ table:
|
|||
name: Webhook
|
||||
trigger: Event
|
||||
noWebhooksTitle: There are no webhooks available
|
||||
formFields:
|
||||
name: Name
|
||||
id: Id
|
||||
description: Description
|
||||
url: Payload URL
|
||||
secret: Secret
|
||||
triggerLabel: Which events do you like to trigger this webhook?
|
||||
allTrigger: Send me everything
|
||||
customTrigger: Let me select individual events
|
||||
SSLVerification: SSL Verification
|
||||
enableSSLVerification: Enable SSL Verification
|
||||
advanced: Advanced
|
||||
extraHeader: Key
|
||||
extraHeaderPlaceholder: Enter Header
|
||||
extraValue: Value
|
||||
extraValuePlaceholder: Enter Value
|
||||
addNewKeyValuePair: Add New Key Value Pair
|
||||
enabled: Enabled
|
||||
|
|
|
@ -20,6 +20,7 @@ import type {
|
|||
RedirectPageQueryParams,
|
||||
RepositoryDetailsPathParams,
|
||||
RepositoryDetailsTabPathParams,
|
||||
RepositoryWebhookDetailsPathParams,
|
||||
VersionDetailsPathParams,
|
||||
VersionDetailsTabPathParams
|
||||
} from './types'
|
||||
|
@ -34,6 +35,7 @@ export interface ARRouteDefinitionsReturn {
|
|||
toARArtifactDetails: (params: ArtifactDetailsPathParams) => string
|
||||
toARVersionDetails: (params: VersionDetailsPathParams) => string
|
||||
toARVersionDetailsTab: (params: VersionDetailsTabPathParams) => string
|
||||
toARRepositoryWebhookDetails: (params: RepositoryWebhookDetailsPathParams) => string
|
||||
}
|
||||
|
||||
export const routeDefinitions: ARRouteDefinitionsReturn = {
|
||||
|
@ -66,5 +68,7 @@ export const routeDefinitions: ARRouteDefinitionsReturn = {
|
|||
return `/registries/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}/pipelines/${params.pipelineIdentifier}/executions/${params.executionIdentifier}/${params.versionTab}`
|
||||
}
|
||||
return `/registries/${params?.repositoryIdentifier}/artifacts/${params?.artifactIdentifier}/versions/${params?.versionIdentifier}/${params.versionTab}`
|
||||
}
|
||||
},
|
||||
toARRepositoryWebhookDetails: params =>
|
||||
`/registries/${params?.repositoryIdentifier}/webhooks/${params?.webhookIdentifier}`
|
||||
}
|
||||
|
|
|
@ -52,3 +52,7 @@ export interface RedirectPageQueryParams {
|
|||
versionId?: string
|
||||
versionDetailsTab?: VersionDetailsTab
|
||||
}
|
||||
|
||||
export interface RepositoryWebhookDetailsPathParams extends RepositoryDetailsPathParams {
|
||||
webhookIdentifier: string
|
||||
}
|
||||
|
|
|
@ -86,14 +86,17 @@ badges:
|
|||
artifactRegistry: '{{ $.repositoryList.artifactRegistry.label }}'
|
||||
upstreamProxy: '{{ $.repositoryList.upstreamProxy.label }}'
|
||||
validationMessages:
|
||||
required: Required
|
||||
repokeyRegExMessage: Registry name must start with letter and can only contain lowercase alphanumerics, _, . and -
|
||||
nameRequired: Registry name is required
|
||||
nameRequired: Name is required
|
||||
identifierRequired: Identifier is required
|
||||
cleanupPolicy:
|
||||
nameRequired: Cleanup policy name is required
|
||||
expireDaysRequired: Expire days is required
|
||||
positiveExpireDays: Expire days must be greater than 0
|
||||
urlRequired: Remote registry URL is required
|
||||
urlPattern: Remote registry URL must be valid
|
||||
urlRequired: URL is required
|
||||
urlPattern: URL must be valid and start with https://
|
||||
genericURLPattern: URL must be valid and start with http:// or https://
|
||||
userNameRequired: Username is required
|
||||
passwordRequired: Password is required
|
||||
accessKeyRequired: Access key is required
|
||||
|
|
|
@ -228,6 +228,23 @@ export interface StringsMap {
|
|||
'versionList.table.columns.size': string
|
||||
'versionList.table.columns.version': string
|
||||
'versionList.table.noVersionsTitle': string
|
||||
'webhookList.formFields.SSLVerification': string
|
||||
'webhookList.formFields.addNewKeyValuePair': string
|
||||
'webhookList.formFields.advanced': string
|
||||
'webhookList.formFields.allTrigger': string
|
||||
'webhookList.formFields.customTrigger': string
|
||||
'webhookList.formFields.description': string
|
||||
'webhookList.formFields.enableSSLVerification': string
|
||||
'webhookList.formFields.enabled': string
|
||||
'webhookList.formFields.extraHeader': string
|
||||
'webhookList.formFields.extraHeaderPlaceholder': string
|
||||
'webhookList.formFields.extraValue': string
|
||||
'webhookList.formFields.extraValuePlaceholder': string
|
||||
'webhookList.formFields.id': string
|
||||
'webhookList.formFields.name': string
|
||||
'webhookList.formFields.secret': string
|
||||
'webhookList.formFields.triggerLabel': string
|
||||
'webhookList.formFields.url': string
|
||||
'webhookList.newWebhook': string
|
||||
'webhookList.table.columns.name': string
|
||||
'webhookList.table.columns.trigger': string
|
||||
|
@ -235,6 +252,7 @@ export interface StringsMap {
|
|||
'webhookList.triggers.artifactCreation': string
|
||||
'webhookList.triggers.artifactDeletion': string
|
||||
'webhookList.triggers.artifactModification': string
|
||||
'webhookList.webhookCreated': string
|
||||
'actions.delete': string
|
||||
'actions.edit': string
|
||||
'actions.quarantine': string
|
||||
|
@ -318,9 +336,12 @@ export interface StringsMap {
|
|||
'validationMessages.cleanupPolicy.expireDaysRequired': string
|
||||
'validationMessages.cleanupPolicy.nameRequired': string
|
||||
'validationMessages.cleanupPolicy.positiveExpireDays': string
|
||||
'validationMessages.genericURLPattern': string
|
||||
'validationMessages.identifierRequired': string
|
||||
'validationMessages.nameRequired': string
|
||||
'validationMessages.passwordRequired': string
|
||||
'validationMessages.repokeyRegExMessage': string
|
||||
'validationMessages.required': string
|
||||
'validationMessages.secretKeyRequired': string
|
||||
'validationMessages.urlPattern': string
|
||||
'validationMessages.urlRequired': string
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
margin-top: 5px !important;
|
||||
}
|
||||
|
||||
.selectInput {
|
||||
min-width: 265px;
|
||||
}
|
||||
|
||||
&.containerWithoutLabel {
|
||||
align-items: flex-start !important;
|
||||
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
export declare const container: string
|
||||
export declare const containerWithoutLabel: string
|
||||
export declare const createNewBtn: string
|
||||
export declare const selectInput: string
|
||||
|
|
|
@ -81,6 +81,7 @@ export default function SecretFormInput(props: SecretFormInputProps) {
|
|||
spacing="small"
|
||||
flex={{ justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<FormInput.Select
|
||||
className={css.selectInput}
|
||||
name={name}
|
||||
label={label}
|
||||
disabled={disabled}
|
||||
|
|
Loading…
Reference in New Issue