mirror of
https://github.com/harness/drone.git
synced 2025-05-31 11:43:15 +00:00
refactor as per new plugin interfaces
This commit is contained in:
parent
0799c2b0f0
commit
c8ba6321c2
@ -43,3 +43,12 @@
|
||||
text-overflow: ellipsis;
|
||||
width: calc(30vw - 2 * var(--spacing-huge));
|
||||
}
|
||||
|
||||
.formFields {
|
||||
height: calc(100% - var(--spacing-xlarge));
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.panelContent {
|
||||
height: calc(100% - var(--spacing-large));
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
// This is an auto-generated file
|
||||
export declare const arrow: string
|
||||
export declare const form: string
|
||||
export declare const formFields: string
|
||||
export declare const panelContent: string
|
||||
export declare const plugin: string
|
||||
export declare const pluginDesc: string
|
||||
export declare const pluginDetailsPanel: string
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Formik } from 'formik'
|
||||
import { capitalize, get } from 'lodash-es'
|
||||
import { Classes, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { Icon, type IconName } from '@harnessio/icons'
|
||||
import { Button, ButtonVariation, Container, FormInput, FormikForm, Layout, Text } from '@harnessio/uicore'
|
||||
import { Button, ButtonVariation, Container, FormInput, FormikForm, Layout, Popover, Text } from '@harnessio/uicore'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { TypesPlugin } from 'services/code'
|
||||
import { YamlVersion } from 'pages/AddUpdatePipeline/Constants'
|
||||
|
||||
import pluginList from './plugins/plugins.json'
|
||||
|
||||
@ -23,14 +22,30 @@ enum PluginPanelView {
|
||||
Configuration
|
||||
}
|
||||
|
||||
interface PluginInterface {
|
||||
interface PluginInput {
|
||||
type: 'string'
|
||||
description?: string
|
||||
default?: string
|
||||
options?: { isExtended?: boolean }
|
||||
}
|
||||
|
||||
interface Plugin {
|
||||
name: string
|
||||
spec: {
|
||||
name: string
|
||||
description?: string
|
||||
inputs: { [key: string]: PluginInput }
|
||||
}
|
||||
}
|
||||
|
||||
interface PluginCategoryInterface {
|
||||
category: PluginCategory
|
||||
name: string
|
||||
description: string
|
||||
icon: IconName
|
||||
}
|
||||
|
||||
const PluginCategories: PluginInterface[] = [
|
||||
const PluginCategories: PluginCategoryInterface[] = [
|
||||
{
|
||||
category: PluginCategory.Harness,
|
||||
name: 'Run',
|
||||
@ -40,50 +55,34 @@ const PluginCategories: PluginInterface[] = [
|
||||
{ category: PluginCategory.Drone, name: 'Drone', description: 'Run Drone plugins', icon: 'ci-infra' }
|
||||
]
|
||||
|
||||
const dronePluginSpecMockData = {
|
||||
inputs: {
|
||||
channel: {
|
||||
type: 'string'
|
||||
},
|
||||
token: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
type: 'script',
|
||||
spec: {
|
||||
image: 'plugins/slack'
|
||||
},
|
||||
envs: {
|
||||
PLUGIN_CHANNEL: '<+inputs.channel>'
|
||||
const RunStep: Plugin = {
|
||||
name: 'run',
|
||||
spec: {
|
||||
name: 'Run',
|
||||
inputs: {
|
||||
script: {
|
||||
type: 'string',
|
||||
options: { isExtended: true }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const runStepSpec = {
|
||||
inputs: {
|
||||
script: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface PluginsPanelInterface {
|
||||
version?: YamlVersion
|
||||
onPluginAddUpdate: (isUpdate: boolean, pluginFormData: Record<string, any>) => void
|
||||
}
|
||||
|
||||
export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: PluginsPanelInterface): JSX.Element => {
|
||||
export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.Element => {
|
||||
const { getString } = useStrings()
|
||||
const [category, setCategory] = useState<PluginCategory>()
|
||||
const [panelView, setPanelView] = useState<PluginPanelView>(PluginPanelView.Category)
|
||||
const [plugin, setPlugin] = useState<TypesPlugin>()
|
||||
const [plugins, setPlugins] = useState<Record<string, any>[]>()
|
||||
const [plugin, setPlugin] = useState<Plugin>()
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
const [loading] = useState<boolean>(false)
|
||||
|
||||
const fetchPlugins = () => {
|
||||
/* temporarily done till api response gets available */
|
||||
//@ts-ignore
|
||||
setPlugins(pluginList)
|
||||
}
|
||||
|
||||
@ -96,7 +95,7 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
const renderPluginCategories = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
{PluginCategories.map((item: PluginInterface) => {
|
||||
{PluginCategories.map((item: PluginCategoryInterface) => {
|
||||
const { name, category: pluginCategory, description, icon } = item
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
@ -105,7 +104,7 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
if (pluginCategory === PluginCategory.Drone) {
|
||||
setPanelView(PluginPanelView.Listing)
|
||||
} else if (pluginCategory === PluginCategory.Harness) {
|
||||
setPlugin({ uid: getString('run') })
|
||||
setPlugin(RunStep)
|
||||
setPanelView(PluginPanelView.Configuration)
|
||||
}
|
||||
}}
|
||||
@ -150,8 +149,8 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
<Container className={css.plugins}>
|
||||
{plugins?.map((_plugin: Record<string, any>) => {
|
||||
const { name: uid, description } = _plugin.spec
|
||||
{plugins?.map((pluginItem: Plugin) => {
|
||||
const { name: uid, description } = pluginItem.spec
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
flex={{ justifyContent: 'flex-start' }}
|
||||
@ -159,7 +158,7 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
className={css.plugin}
|
||||
onClick={() => {
|
||||
setPanelView(PluginPanelView.Configuration)
|
||||
setPlugin(_plugin)
|
||||
setPlugin(pluginItem)
|
||||
}}
|
||||
key={uid}>
|
||||
<Icon name={'gear'} size={25} />
|
||||
@ -179,40 +178,74 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
)
|
||||
}, [loading, plugins])
|
||||
|
||||
const renderPluginFormField = ({ name, type }: { name: string; type: 'string' }): JSX.Element => {
|
||||
const generateFriendlyName = useCallback((pluginName: string): string => {
|
||||
return capitalize(pluginName.split('_').join(' '))
|
||||
}, [])
|
||||
|
||||
const generateLabelForPluginField = useCallback(
|
||||
({ name, properties }: { name: string; properties: PluginInput }): JSX.Element | string => {
|
||||
const { description } = properties
|
||||
return (
|
||||
<Layout.Horizontal spacing="small" flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
|
||||
{name && <Text font={{ variation: FontVariation.FORM_LABEL }}>{generateFriendlyName(name)}</Text>}
|
||||
{description && (
|
||||
<Popover
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
boundary="viewport"
|
||||
position={PopoverPosition.RIGHT}
|
||||
popoverClassName={Classes.DARK}
|
||||
content={
|
||||
<Container padding="medium">
|
||||
<Text font={{ variation: FontVariation.SMALL }} color={Color.WHITE}>
|
||||
{description}
|
||||
</Text>
|
||||
</Container>
|
||||
}>
|
||||
<Icon name="info" color={Color.PRIMARY_7} size={10} padding={{ bottom: 'small' }} />
|
||||
</Popover>
|
||||
)}
|
||||
</Layout.Horizontal>
|
||||
)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const renderPluginFormField = ({ name, properties }: { name: string; properties: PluginInput }): JSX.Element => {
|
||||
const { type, default: defaultValue, options } = properties
|
||||
const { isExtended } = options || {}
|
||||
const WrapperComponent = isExtended ? FormInput.TextArea : FormInput.Text
|
||||
return type === 'string' ? (
|
||||
<FormInput.Text
|
||||
<WrapperComponent
|
||||
name={name}
|
||||
label={<Text font={{ variation: FontVariation.FORM_INPUT_TEXT }}>{capitalize(name)}</Text>}
|
||||
label={generateLabelForPluginField({ name, properties })}
|
||||
style={{ width: '100%' }}
|
||||
key={name}
|
||||
placeholder={defaultValue}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
const constructPayloadForYAMLInsertion = (isUpdate: boolean, pluginFormData: Record<string, any>) => {
|
||||
const constructPayloadForYAMLInsertion = (pluginFormData: Record<string, any>): Record<string, any> => {
|
||||
let constructedPayload = { ...pluginFormData }
|
||||
switch (category) {
|
||||
case PluginCategory.Drone:
|
||||
return { type: 'script', spec: constructedPayload }
|
||||
case PluginCategory.Harness:
|
||||
constructedPayload =
|
||||
version === YamlVersion.V1
|
||||
? { type: 'script', spec: { run: get(constructedPayload, 'script', '') } }
|
||||
: { name: 'run step', commands: [get(constructedPayload, 'script', '')] }
|
||||
return { type: 'script', spec: { run: get(constructedPayload, 'script', '') } }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
onPluginAddUpdate?.(isUpdate, constructedPayload)
|
||||
}
|
||||
|
||||
const renderPluginConfigForm = useCallback((): JSX.Element => {
|
||||
// TODO obtain plugin input spec by parsing YAML
|
||||
const inputs = get(category === PluginCategory.Drone ? dronePluginSpecMockData : runStepSpec, 'inputs', {})
|
||||
const inputs: { [key: string]: PluginInput } = get(plugin, 'spec.inputs', {})
|
||||
return (
|
||||
<Layout.Vertical
|
||||
spacing="medium"
|
||||
margin={{ left: 'xxlarge', top: 'large', right: 'xxlarge', bottom: 'xxlarge' }}
|
||||
height="95%">
|
||||
spacing="large"
|
||||
padding={{ left: 'xxlarge', top: 'large', right: 'xxlarge', bottom: 'xxlarge' }}
|
||||
className={css.panelContent}>
|
||||
<Layout.Horizontal spacing="small" flex={{ justifyContent: 'flex-start' }}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
@ -227,28 +260,27 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
}}
|
||||
className={css.arrow}
|
||||
/>
|
||||
{plugin?.uid ? (
|
||||
{plugin?.spec?.name && (
|
||||
<Text font={{ variation: FontVariation.H5 }}>
|
||||
{getString('addLabel')} {plugin.uid} {getString('plugins.stepLabel')}
|
||||
{getString('addLabel')} {plugin.spec.name} {getString('plugins.stepLabel')}
|
||||
</Text>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Layout.Horizontal>
|
||||
<Container className={css.form}>
|
||||
<Formik
|
||||
initialValues={{}}
|
||||
onSubmit={formData => {
|
||||
constructPayloadForYAMLInsertion(false, formData)
|
||||
onPluginAddUpdate?.(false, constructPayloadForYAMLInsertion(formData))
|
||||
}}>
|
||||
<FormikForm>
|
||||
<FormikForm height="100%" flex={{ justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<Layout.Vertical flex={{ alignItems: 'flex-start' }} height="100%">
|
||||
<Layout.Vertical width="100%">
|
||||
{Object.keys(inputs).map((field: string) => {
|
||||
const fieldType = get(inputs, `${field}.type`, '') as 'string'
|
||||
return renderPluginFormField({ name: field, type: fieldType })
|
||||
})}
|
||||
</Layout.Vertical>
|
||||
{inputs && (
|
||||
<Layout.Vertical width="100%" className={css.formFields} spacing="xsmall">
|
||||
{Object.keys(inputs).map((field: string) => {
|
||||
return renderPluginFormField({ name: field, properties: get(inputs, field) })
|
||||
})}
|
||||
</Layout.Vertical>
|
||||
)}
|
||||
<Button variation={ButtonVariation.PRIMARY} text={getString('addLabel')} type="submit" />
|
||||
</Layout.Vertical>
|
||||
</FormikForm>
|
||||
@ -272,7 +304,7 @@ export const PluginsPanel = ({ version = YamlVersion.V0, onPluginAddUpdate }: Pl
|
||||
}, [loading, plugins, panelView, category])
|
||||
|
||||
return (
|
||||
<Layout.Vertical>
|
||||
<Layout.Vertical height="100%">
|
||||
{panelView === PluginPanelView.Category ? (
|
||||
<Container padding={{ top: 'medium', bottom: 'medium', left: 'medium' }}>
|
||||
<Text font={{ variation: FontVariation.H5 }}>{getString('step.select')}</Text>
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useGet, useMutate } from 'restful-react'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { get, isEmpty, isUndefined, set } from 'lodash-es'
|
||||
import { stringify } from 'yaml'
|
||||
import { parse, stringify } from 'yaml'
|
||||
import { Menu, PopoverPosition } from '@blueprintjs/core'
|
||||
import {
|
||||
Container,
|
||||
@ -91,7 +91,6 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
const { repoMetadata } = useGetRepositoryMetadata()
|
||||
const { showError, showSuccess, clear: clearToaster } = useToaster()
|
||||
const [yamlVersion, setYAMLVersion] = useState<YamlVersion>()
|
||||
const [pipelineAsObj, setPipelineAsObj] = useState<Record<string, any>>({})
|
||||
const [pipelineAsYAML, setPipelineAsYaml] = useState<string>('')
|
||||
const { openModal: openRunPipelineModal } = useRunPipelineModal()
|
||||
const repoPath = useMemo(() => repoMetadata?.path || '', [repoMetadata])
|
||||
@ -169,7 +168,7 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
// ignore exception
|
||||
}
|
||||
}
|
||||
}, [yamlVersion, isExistingPipeline, originalPipelineYAMLFileContent, pipelineAsObj])
|
||||
}, [yamlVersion, isExistingPipeline, originalPipelineYAMLFileContent])
|
||||
|
||||
// find if editor content was modified
|
||||
useEffect(() => {
|
||||
@ -217,28 +216,40 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
}
|
||||
}
|
||||
|
||||
const updatePipeline = (payload: Record<string, any>): Record<string, any> => {
|
||||
const pipelineAsObjClone = { ...pipelineAsObj }
|
||||
const stepInsertPath = yamlVersion === YamlVersion.V1 ? 'spec.stages.0.spec.steps' : 'steps'
|
||||
let existingSteps: [unknown] = get(pipelineAsObjClone, stepInsertPath, [])
|
||||
if (existingSteps.length > 0) {
|
||||
existingSteps.push(payload)
|
||||
} else {
|
||||
existingSteps = [payload]
|
||||
const updatePipelineWithPluginData = (
|
||||
existingPipeline: Record<string, any>,
|
||||
payload: Record<string, any>
|
||||
): Record<string, any> => {
|
||||
const pipelineAsObjClone = { ...existingPipeline }
|
||||
if (Object.keys(pipelineAsObjClone).length > 0) {
|
||||
const stepInsertPath = 'spec.stages.0.spec.steps'
|
||||
let existingSteps: [unknown] = get(pipelineAsObjClone, stepInsertPath, [])
|
||||
if (existingSteps.length > 0) {
|
||||
existingSteps.push(payload)
|
||||
} else {
|
||||
existingSteps = [payload]
|
||||
}
|
||||
set(pipelineAsObjClone, stepInsertPath, existingSteps)
|
||||
return pipelineAsObjClone
|
||||
}
|
||||
set(pipelineAsObjClone, stepInsertPath, existingSteps)
|
||||
return pipelineAsObjClone
|
||||
return existingPipeline
|
||||
}
|
||||
|
||||
const addUpdatePluginToPipelineYAML = (_isUpdate: boolean, pluginFormData: Record<string, any>): void => {
|
||||
try {
|
||||
const updatedPipelineAsObj = updatePipeline(pluginFormData)
|
||||
setPipelineAsObj(updatedPipelineAsObj)
|
||||
setPipelineAsYaml(stringify(updatedPipelineAsObj))
|
||||
} catch (ex) {
|
||||
// ignore exception
|
||||
}
|
||||
}
|
||||
const handlePluginAddUpdateIntoYAML = useCallback(
|
||||
(_isUpdate: boolean, pluginFormData: Record<string, any>): void => {
|
||||
try {
|
||||
const pipeline = parse(pipelineAsYAML)
|
||||
const updatedPipelineAsObj = updatePipelineWithPluginData(pipeline, pluginFormData)
|
||||
if (Object.keys(updatedPipelineAsObj).length > 0) {
|
||||
// avoid setting to empty pipeline in case pipeline update with plugin data fails
|
||||
setPipelineAsYaml(stringify(updatedPipelineAsObj))
|
||||
}
|
||||
} catch (ex) {
|
||||
// ignore exception
|
||||
}
|
||||
},
|
||||
[yamlVersion, isExistingPipeline, originalPipelineYAMLFileContent, pipelineAsYAML]
|
||||
)
|
||||
|
||||
const renderCTA = useCallback(() => {
|
||||
switch (selectedOption?.action) {
|
||||
@ -300,6 +311,10 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
}
|
||||
}, [loading, fetchingPipeline, isDirty, repoMetadata, pipeline, selectedOption, isExistingPipeline, pipelineAsYAML])
|
||||
|
||||
if (fetchingPipeline || fetchingPipelineYAMLFileContent) {
|
||||
return <LoadingSpinner visible={true} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={css.main}>
|
||||
@ -317,7 +332,6 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
content={<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>{renderCTA()}</Layout.Horizontal>}
|
||||
/>
|
||||
<PageBody>
|
||||
<LoadingSpinner visible={fetchingPipeline || fetchingPipelineYAMLFileContent} />
|
||||
<Layout.Horizontal className={css.container}>
|
||||
<Container className={css.editorContainer}>
|
||||
<MonacoSourceCodeEditor
|
||||
@ -327,9 +341,11 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
onChange={(value: string) => setPipelineAsYaml(value)}
|
||||
/>
|
||||
</Container>
|
||||
<Container className={css.pluginsContainer}>
|
||||
<PluginsPanel onPluginAddUpdate={addUpdatePluginToPipelineYAML} version={yamlVersion} />
|
||||
</Container>
|
||||
{yamlVersion === YamlVersion.V1 && (
|
||||
<Container className={css.pluginsContainer}>
|
||||
<PluginsPanel onPluginAddUpdate={handlePluginAddUpdateIntoYAML} />
|
||||
</Container>
|
||||
)}
|
||||
</Layout.Horizontal>
|
||||
</PageBody>
|
||||
</Container>
|
||||
|
Loading…
x
Reference in New Issue
Block a user