Merge branch 'add-delete-secret-modal' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#444)

This commit is contained in:
Dan Wilson 2023-09-13 08:42:51 +00:00 committed by Harness
commit 0c7f62d444
6 changed files with 179 additions and 22 deletions

View File

@ -21,7 +21,7 @@ import { useStrings } from 'framework/strings'
import type { OpenapiCreateSecretRequest, TypesSecret } from 'services/code' import type { OpenapiCreateSecretRequest, TypesSecret } from 'services/code'
import { getErrorMessage } from 'utils/Utils' import { getErrorMessage } from 'utils/Utils'
interface SecretFormData { export interface SecretFormData {
value: string value: string
description: string description: string
name: string name: string
@ -82,10 +82,7 @@ export const NewSecretModalButton: React.FC<NewSecretModalButtonProps> = ({
onClose={hideModal} onClose={hideModal}
title={''} title={''}
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}> style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
<Layout.Vertical <Layout.Vertical padding={{ left: 'xxlarge' }} style={{ height: '100%' }} data-testid="add-secret-modal">
padding={{ left: 'xxlarge' }}
style={{ height: '100%' }}
data-testid="add-target-to-flag-modal">
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}> <Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
{modalTitle} {modalTitle}
</Heading> </Heading>

View File

@ -0,0 +1,156 @@
import React, { useRef, useState } from 'react'
import * as yup from 'yup'
import { useMutate } from 'restful-react'
import { FontVariation, Intent } from '@harnessio/design-system'
import {
Button,
Dialog,
Layout,
Heading,
Container,
Formik,
FormikForm,
FormInput,
FlexExpander,
useToaster,
StringSubstitute
} from '@harnessio/uicore'
import { Icon } from '@harnessio/icons'
import { useStrings } from 'framework/strings'
import { useModalHook } from 'hooks/useModalHook'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import type { OpenapiUpdateSecretRequest, TypesSecret } from 'services/code'
import type { SecretFormData } from 'components/NewSecretModalButton/NewSecretModalButton'
import { getErrorMessage } from 'utils/Utils'
const useUpdateSecretModal = () => {
const { getString } = useStrings()
const space = useGetSpaceParam()
const { showError, showSuccess } = useToaster()
const [secret, setSecret] = useState<TypesSecret>()
const postUpdate = useRef<Function>()
const { mutate: updateSecret, loading } = useMutate<TypesSecret>({
verb: 'PATCH',
path: `/api/v1/secrets/${space}/${secret?.uid}/+`
})
const handleSubmit = async (formData: SecretFormData) => {
try {
const payload: OpenapiUpdateSecretRequest = {
data: formData.value,
description: formData.description,
uid: formData.name
}
await updateSecret(payload)
hideModal()
showSuccess(
<StringSubstitute
str={getString('secrets.secretUpdated')}
vars={{
uid: formData.name
}}
/>
)
postUpdate.current?.()
} catch (exception) {
showError(getErrorMessage(exception), 0, getString('secrets.failedToUpdateSecret'))
}
}
const [openModal, hideModal] = useModalHook(() => {
const onClose = () => {
hideModal()
}
return (
<Dialog
isOpen
enforceFocus={false}
onClose={hideModal}
title={''}
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
<Layout.Vertical padding={{ left: 'xxlarge' }} style={{ height: '100%' }} data-testid="add-secret-modal">
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
{getString('secrets.updateSecret')}
</Heading>
<Container margin={{ right: 'xxlarge' }}>
<Formik
initialValues={{ name: secret?.uid || '', description: secret?.description || '', value: '' }}
formName="addSecret"
enableReinitialize={true}
validationSchema={yup.object().shape({
name: yup.string().trim().required(),
value: yup.string().trim().required()
})}
validateOnChange
validateOnBlur
onSubmit={handleSubmit}>
<FormikForm>
<FormInput.Text
name="name"
label={getString('name')}
placeholder={getString('secrets.enterSecretName')}
tooltipProps={{
dataTooltipId: 'secretNameTextField'
}}
inputGroup={{ autoFocus: true }}
/>
<FormInput.Text
name="value"
label={getString('value')}
placeholder={getString('secrets.value')}
tooltipProps={{
dataTooltipId: 'secretDescriptionTextField'
}}
inputGroup={{ type: 'password' }}
/>
<FormInput.Text
name="description"
label={getString('description')}
placeholder={getString('enterDescription')}
tooltipProps={{
dataTooltipId: 'secretDescriptionTextField'
}}
isOptional
/>
<Layout.Horizontal
spacing="small"
padding={{ right: 'xxlarge', top: 'xxxlarge', bottom: 'large' }}
style={{ alignItems: 'center' }}>
<Button
type="submit"
text={getString('secrets.updateSecret')}
intent={Intent.PRIMARY}
disabled={loading}
/>
<Button text={getString('cancel')} minimal onClick={onClose} />
<FlexExpander />
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
</Layout.Horizontal>
</FormikForm>
</Formik>
</Container>
</Layout.Vertical>
</Dialog>
)
}, [secret])
return {
openModal: ({
secretToUpdate,
openSecretUpdate
}: {
secretToUpdate: TypesSecret
openSecretUpdate: () => Promise<void>
}) => {
setSecret(secretToUpdate)
postUpdate.current = openSecretUpdate
openModal()
}
}
}
export default useUpdateSecretModal

View File

@ -496,10 +496,13 @@ export interface StringsMap {
'secrets.enterSecretName': string 'secrets.enterSecretName': string
'secrets.failedToCreate': string 'secrets.failedToCreate': string
'secrets.failedToDeleteSecret': string 'secrets.failedToDeleteSecret': string
'secrets.failedToUpdateSecret': string
'secrets.name': string 'secrets.name': string
'secrets.newSecretButton': string 'secrets.newSecretButton': string
'secrets.noData': string 'secrets.noData': string
'secrets.secretDeleted': string 'secrets.secretDeleted': string
'secrets.secretUpdated': string
'secrets.updateSecret': string
'secrets.value': string 'secrets.value': string
selectBranchPlaceHolder: string selectBranchPlaceHolder: string
selectRange: string selectRange: string

View File

@ -666,9 +666,12 @@ secrets:
createSecret: Create Secret createSecret: Create Secret
createSuccess: Secret created successfully createSuccess: Secret created successfully
secretDeleted: Secret {uid} deleted. secretDeleted: Secret {uid} deleted.
secretUpdated: Secret {uid} updated.
deleteSecretConfirm: Are you sure you want to delete secret <strong>{{uid}}</strong>? You can't undo this action. deleteSecretConfirm: Are you sure you want to delete secret <strong>{{uid}}</strong>? You can't undo this action.
failedToDeleteSecret: Failed to delete Secret. Please try again. failedToDeleteSecret: Failed to delete Secret. Please try again.
deleteSecret: Delete Secrets failedToUpdateSecret: Failed to update Secret. Please try again.
deleteSecret: Delete secret
updateSecret: Update secret
userUpdateSuccess: 'User updated successfully' userUpdateSuccess: 'User updated successfully'
viewFile: View File viewFile: View File
searchResult: 'Search Result {count}' searchResult: 'Search Result {count}'

View File

@ -8,14 +8,13 @@ import {
PageBody, PageBody,
TableV2 as Table, TableV2 as Table,
Text, Text,
Utils, Utils
useToaster
} from '@harnessio/uicore' } from '@harnessio/uicore'
import { Color } from '@harnessio/design-system' import { Color } from '@harnessio/design-system'
import cx from 'classnames' import cx from 'classnames'
import type { CellProps, Column } from 'react-table' import type { CellProps, Column } from 'react-table'
import { useHistory, useParams } from 'react-router-dom' import { useHistory, useParams } from 'react-router-dom'
import { useGet, useMutate } from 'restful-react' import { useGet } from 'restful-react'
import { Timer, Calendar } from 'iconoir-react' import { Timer, Calendar } from 'iconoir-react'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
@ -33,6 +32,7 @@ import { ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import { getStatus } from 'utils/PipelineUtils' import { getStatus } from 'utils/PipelineUtils'
import useSpaceSSE from 'hooks/useSpaceSSE' import useSpaceSSE from 'hooks/useSpaceSSE'
import { ExecutionText, ExecutionTrigger } from 'components/ExecutionText/ExecutionText' import { ExecutionText, ExecutionTrigger } from 'components/ExecutionText/ExecutionText'
import useRunPipelineModal from 'components/RunPipelineModal/RunPipelineModal'
import noExecutionImage from '../RepositoriesListing/no-repo.svg' import noExecutionImage from '../RepositoriesListing/no-repo.svg'
import css from './ExecutionList.module.scss' import css from './ExecutionList.module.scss'
@ -44,7 +44,6 @@ const ExecutionList = () => {
const pageBrowser = useQueryParams<PageBrowserProps>() const pageBrowser = useQueryParams<PageBrowserProps>()
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1 const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
const [page, setPage] = usePageIndex(pageInit) const [page, setPage] = usePageIndex(pageInit)
const { showError, showSuccess } = useToaster()
const { repoMetadata, error, loading, refetch, space } = useGetRepositoryMetadata() const { repoMetadata, error, loading, refetch, space } = useGetRepositoryMetadata()
@ -84,19 +83,11 @@ const ExecutionList = () => {
} }
}) })
const { mutate, loading: mutateLoading } = useMutate<TypesExecution>({ const { openModal: openRunPipelineModal } = useRunPipelineModal()
verb: 'POST',
path: `/api/v1/repos/${repoMetadata?.path}/+/pipelines/${pipeline}/executions`
})
const handleClick = async () => { const handleClick = async () => {
try { if (repoMetadata && pipeline) {
//TODO - this should NOT be hardcoded to master branch - need a modal to insert branch - but useful for testing until then openRunPipelineModal({ repoMetadata, pipeline })
await mutate({ branch: 'master' })
showSuccess('Build started')
executionsRefetch()
} catch {
showError('Failed to start build')
} }
} }
@ -105,7 +96,6 @@ const ExecutionList = () => {
text={getString('executions.newExecutionButton')} text={getString('executions.newExecutionButton')}
variation={ButtonVariation.PRIMARY} variation={ButtonVariation.PRIMARY}
icon="play-outline" icon="play-outline"
disabled={mutateLoading}
onClick={handleClick}></Button> onClick={handleClick}></Button>
) )

View File

@ -29,6 +29,7 @@ import { ResourceListingPagination } from 'components/ResourceListingPagination/
import { NewSecretModalButton } from 'components/NewSecretModalButton/NewSecretModalButton' import { NewSecretModalButton } from 'components/NewSecretModalButton/NewSecretModalButton'
import { useConfirmAct } from 'hooks/useConfirmAction' import { useConfirmAct } from 'hooks/useConfirmAction'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton' import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import useUpdateSecretModal from 'components/UpdateSecretModal/UpdateSecretModal'
import noSecretsImage from '../RepositoriesListing/no-repo.svg' import noSecretsImage from '../RepositoriesListing/no-repo.svg'
import css from './SecretList.module.scss' import css from './SecretList.module.scss'
@ -61,6 +62,8 @@ const SecretList = () => {
onSuccess={() => refetch()}></NewSecretModalButton> onSuccess={() => refetch()}></NewSecretModalButton>
) )
const { openModal: openUpdateSecretModal } = useUpdateSecretModal()
const columns: Column<TypesSecret>[] = useMemo( const columns: Column<TypesSecret>[] = useMemo(
() => [ () => [
{ {
@ -113,6 +116,11 @@ const SecretList = () => {
isDark isDark
width="100px" width="100px"
items={[ items={[
{
text: getString('edit'),
isDanger: true,
onClick: () => openUpdateSecretModal({ secretToUpdate: row.original, openSecretUpdate: refetch })
},
{ {
text: getString('delete'), text: getString('delete'),
isDanger: true, isDanger: true,