mirror of
https://github.com/harness/drone.git
synced 2025-05-04 13:22:13 +08:00
Merge branch 'integrate-plugins-api' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#567)
This commit is contained in:
commit
5eff33c75f
@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.form {
|
||||
height: 100%;
|
||||
height: calc(100% - var(--spacing-large) - var(--spacing-xxlarge));
|
||||
width: 100%;
|
||||
:global {
|
||||
.FormikForm--main {
|
||||
@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.plugins {
|
||||
max-height: calc(100vh - var(--header-height));
|
||||
max-height: calc(100vh - var(--header-height) - var(--generate-pipeline-header));
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@ -49,6 +49,18 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.panelContent {
|
||||
height: calc(100% - var(--spacing-large));
|
||||
.search {
|
||||
width: 50%;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.indent {
|
||||
background: var(--grey-100) !important;
|
||||
padding: var(--spacing-medium) var(--spacing-medium) var(--spacing-small) var(--spacing-large) !important;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.configForm {
|
||||
height: calc(100% - var(--spacing-large) - var(--spacing-xxlarge));
|
||||
margin: var(--spacing-large) var(--spacing-xxlarge) var(--spacing-xxlarge) var(--spacing-xxlarge) !important;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const arrow: string
|
||||
export declare const configForm: string
|
||||
export declare const form: string
|
||||
export declare const formFields: string
|
||||
export declare const panelContent: string
|
||||
export declare const indent: string
|
||||
export declare const plugin: string
|
||||
export declare const pluginDesc: string
|
||||
export declare const pluginDetailsPanel: string
|
||||
export declare const pluginIcon: string
|
||||
export declare const plugins: string
|
||||
export declare const search: string
|
||||
|
@ -1,14 +1,25 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Formik } from 'formik'
|
||||
import { parse } from 'yaml'
|
||||
import { capitalize, get, omit, set } from 'lodash-es'
|
||||
import { Classes, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
|
||||
import type { TypesPlugin } from 'services/code'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { Icon, type IconName } from '@harnessio/icons'
|
||||
import { Button, ButtonVariation, Container, FormInput, FormikForm, Layout, Popover, Text } from '@harnessio/uicore'
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
ButtonVariation,
|
||||
Container,
|
||||
ExpandingSearchInput,
|
||||
FormInput,
|
||||
FormikForm,
|
||||
Layout,
|
||||
Popover,
|
||||
Text
|
||||
} from '@harnessio/uicore'
|
||||
import { useStrings } from 'framework/strings'
|
||||
|
||||
import pluginList from './plugins/plugins.json'
|
||||
|
||||
import css from './PluginsPanel.module.scss'
|
||||
|
||||
enum PluginCategory {
|
||||
@ -29,15 +40,6 @@ interface PluginInput {
|
||||
options?: { isExtended?: boolean }
|
||||
}
|
||||
|
||||
interface Plugin {
|
||||
name: string
|
||||
spec: {
|
||||
name: string
|
||||
description?: string
|
||||
inputs: { [key: string]: PluginInput }
|
||||
}
|
||||
}
|
||||
|
||||
interface PluginCategoryInterface {
|
||||
category: PluginCategory
|
||||
name: string
|
||||
@ -45,38 +47,6 @@ interface PluginCategoryInterface {
|
||||
icon: IconName
|
||||
}
|
||||
|
||||
const PluginCategories: PluginCategoryInterface[] = [
|
||||
{
|
||||
category: PluginCategory.Harness,
|
||||
name: 'Run',
|
||||
description: 'Run a script on macOS, Linux, or Windows',
|
||||
icon: 'run-step'
|
||||
},
|
||||
{ category: PluginCategory.Drone, name: 'Drone', description: 'Run Drone plugins', icon: 'ci-infra' }
|
||||
]
|
||||
|
||||
const StepNameInput: PluginInput = {
|
||||
type: 'string',
|
||||
description: 'Name of the step'
|
||||
}
|
||||
|
||||
const RunStep: Plugin = {
|
||||
name: 'run',
|
||||
spec: {
|
||||
name: 'Run',
|
||||
inputs: {
|
||||
name: StepNameInput,
|
||||
image: {
|
||||
type: 'string'
|
||||
},
|
||||
script: {
|
||||
type: 'string',
|
||||
options: { isExtended: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface PluginInsertionTemplateInterface {
|
||||
name?: string
|
||||
type: 'plugin'
|
||||
@ -101,6 +71,12 @@ const PluginInsertionTemplate: PluginInsertionTemplateInterface = {
|
||||
const PluginNameFieldPath = 'spec.name'
|
||||
const PluginInputsFieldPath = 'spec.inputs'
|
||||
|
||||
const LIST_FETCHING_LIMIT = 100
|
||||
|
||||
const RunStep: TypesPlugin = {
|
||||
uid: 'run'
|
||||
}
|
||||
|
||||
export interface PluginsPanelInterface {
|
||||
onPluginAddUpdate: (isUpdate: boolean, pluginFormData: Record<string, any>) => void
|
||||
}
|
||||
@ -109,23 +85,61 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
const { getString } = useStrings()
|
||||
const [category, setCategory] = useState<PluginCategory>()
|
||||
const [panelView, setPanelView] = useState<PluginPanelView>(PluginPanelView.Category)
|
||||
const [plugin, setPlugin] = useState<Plugin>()
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
const [loading] = useState<boolean>(false)
|
||||
const [plugin, setPlugin] = useState<TypesPlugin>()
|
||||
const [plugins, setPlugins] = useState<TypesPlugin[]>([])
|
||||
const [query, setQuery] = useState<string>('')
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
const fetchPlugins = () => {
|
||||
/* temporarily done till api response gets available */
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
setPlugins(pluginList)
|
||||
const PluginCategories: PluginCategoryInterface[] = [
|
||||
{
|
||||
category: PluginCategory.Harness,
|
||||
name: capitalize(getString('run')),
|
||||
description: getString('pluginsPanel.run.helptext'),
|
||||
icon: 'run-step'
|
||||
},
|
||||
{
|
||||
category: PluginCategory.Drone,
|
||||
name: capitalize(getString('plugins.title')),
|
||||
description: getString('pluginsPanel.plugins.helptext'),
|
||||
icon: 'ci-infra'
|
||||
}
|
||||
]
|
||||
|
||||
const fetchPlugins = async (page: number): Promise<TypesPlugin[]> => {
|
||||
const response = await fetch(`/api/v1/plugins?page=${page}&limit=${LIST_FETCHING_LIMIT}`)
|
||||
if (!response.ok) throw new Error('Failed to fetch plugins')
|
||||
return response.json()
|
||||
}
|
||||
|
||||
const fetchAllPlugins = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const pluginsPage1 = await fetchPlugins(1)
|
||||
const pluginsPage2 = await fetchPlugins(2)
|
||||
setPlugins([...pluginsPage1, ...pluginsPage2])
|
||||
} catch (ex) {
|
||||
/* ignore exception */
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (category === PluginCategory.Drone) {
|
||||
fetchPlugins()
|
||||
fetchAllPlugins()
|
||||
}
|
||||
}, [category])
|
||||
|
||||
useEffect(() => {
|
||||
if (panelView !== PluginPanelView.Listing) return
|
||||
|
||||
if (query) {
|
||||
setPlugins(existingPlugins => existingPlugins.filter((item: TypesPlugin) => item.uid?.includes(query)))
|
||||
} else {
|
||||
fetchAllPlugins()
|
||||
}
|
||||
}, [query])
|
||||
|
||||
const renderPluginCategories = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
@ -169,22 +183,32 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
</Container>
|
||||
) : (
|
||||
<Layout.Vertical spacing="small" padding={{ top: 'small' }}>
|
||||
<Layout.Horizontal
|
||||
flex={{ justifyContent: 'flex-start', alignItems: 'center' }}
|
||||
spacing="small"
|
||||
padding={{ top: 'medium', bottom: 'medium', left: 'medium' }}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
size={18}
|
||||
onClick={() => {
|
||||
setPanelView(PluginPanelView.Category)
|
||||
}}
|
||||
className={css.arrow}
|
||||
<Layout.Horizontal flex={{ justifyContent: 'space-between' }} padding={{ left: 'small', right: 'xlarge' }}>
|
||||
<Layout.Horizontal
|
||||
flex={{ justifyContent: 'flex-start', alignItems: 'center' }}
|
||||
spacing="small"
|
||||
padding={{ top: 'medium', bottom: 'medium', left: 'medium' }}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
size={18}
|
||||
onClick={() => {
|
||||
setPanelView(PluginPanelView.Category)
|
||||
}}
|
||||
className={css.arrow}
|
||||
/>
|
||||
<Text font={{ variation: FontVariation.H5 }}>{getString('plugins.select')}</Text>
|
||||
</Layout.Horizontal>
|
||||
<ExpandingSearchInput
|
||||
autoFocus={true}
|
||||
alwaysExpanded={true}
|
||||
defaultValue={query}
|
||||
onChange={setQuery}
|
||||
className={css.search}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
<Container className={css.plugins}>
|
||||
{plugins?.map((pluginItem: Plugin) => {
|
||||
const { name: uid, description } = pluginItem.spec
|
||||
{plugins?.map((pluginItem: TypesPlugin) => {
|
||||
const { uid, description } = pluginItem
|
||||
return (
|
||||
<Layout.Horizontal
|
||||
flex={{ justifyContent: 'flex-start' }}
|
||||
@ -210,7 +234,7 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
)
|
||||
}, [loading, plugins])
|
||||
}, [loading, plugins, query])
|
||||
|
||||
const generateFriendlyName = useCallback((pluginName: string): string => {
|
||||
return capitalize(pluginName.split('_').join(' '))
|
||||
@ -245,7 +269,7 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
)
|
||||
|
||||
const renderPluginFormField = ({ name, properties }: { name: string; properties: PluginInput }): JSX.Element => {
|
||||
const { type, default: defaultValue, options } = properties
|
||||
const { type, options } = properties
|
||||
const { isExtended } = options || {}
|
||||
const WrapperComponent = isExtended ? FormInput.TextArea : FormInput.Text
|
||||
return type === 'string' ? (
|
||||
@ -254,7 +278,6 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
label={generateLabelForPluginField({ name, properties })}
|
||||
style={{ width: '100%' }}
|
||||
key={name}
|
||||
placeholder={defaultValue}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
@ -263,24 +286,29 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
|
||||
const constructPayloadForYAMLInsertion = (
|
||||
pluginFormData: Record<string, any>,
|
||||
pluginMetadata?: Plugin
|
||||
pluginMetadata?: TypesPlugin
|
||||
): Record<string, any> => {
|
||||
const { name, image, script } = pluginFormData
|
||||
const { name, container = {} } = pluginFormData
|
||||
switch (category) {
|
||||
case PluginCategory.Drone:
|
||||
const payload = { ...PluginInsertionTemplate }
|
||||
set(payload, 'name', name)
|
||||
set(payload, PluginNameFieldPath, pluginMetadata?.name)
|
||||
let payload = { ...PluginInsertionTemplate }
|
||||
/* Step name is optional, set only if specified by user */
|
||||
if (name) {
|
||||
set(payload, 'name', name)
|
||||
} else {
|
||||
payload = omit(payload, 'name')
|
||||
}
|
||||
set(payload, PluginNameFieldPath, pluginMetadata?.uid)
|
||||
set(payload, PluginInputsFieldPath, omit(pluginFormData, 'name'))
|
||||
return payload as PluginInsertionTemplateInterface
|
||||
case PluginCategory.Harness:
|
||||
return image || script
|
||||
? {
|
||||
...(name && { name }),
|
||||
type: 'run',
|
||||
spec: { ...(image && { image }), ...(script && { script }) }
|
||||
}
|
||||
: {}
|
||||
return {
|
||||
...(name && { name }),
|
||||
type: 'run',
|
||||
...(Object.keys(container).length === 1 && container?.image
|
||||
? { spec: { container: get(container, 'image') } }
|
||||
: { spec: pluginFormData })
|
||||
}
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
@ -291,20 +319,41 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
}): { [key: string]: PluginInput } => {
|
||||
const inputsClone = Object.assign(
|
||||
{
|
||||
name: StepNameInput
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name of the step'
|
||||
}
|
||||
},
|
||||
existingInputs
|
||||
)
|
||||
return inputsClone
|
||||
}
|
||||
|
||||
const getPluginInputsFromSpec = useCallback((pluginSpec: string): Record<string, any> => {
|
||||
if (!pluginSpec) {
|
||||
return {}
|
||||
}
|
||||
try {
|
||||
const pluginSpecAsObj = parse(pluginSpec)
|
||||
return get(pluginSpecAsObj, 'spec.inputs', {})
|
||||
} catch (ex) {}
|
||||
return {}
|
||||
}, [])
|
||||
|
||||
const getInitialFormValues = useCallback((pluginInputs: Record<string, any>): Record<string, any> => {
|
||||
return Object.entries(pluginInputs).reduce((acc, [field, inputObj]) => {
|
||||
if (inputObj?.default) {
|
||||
acc[field] = inputObj.default
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
}, [])
|
||||
|
||||
const renderPluginConfigForm = useCallback((): JSX.Element => {
|
||||
const inputs: { [key: string]: PluginInput } = insertNameFieldToPluginInputs(get(plugin, 'spec.inputs', {}))
|
||||
const pluginInputs = getPluginInputsFromSpec(get(plugin, 'spec', '') as string)
|
||||
const allPluginInputs = insertNameFieldToPluginInputs(pluginInputs)
|
||||
return (
|
||||
<Layout.Vertical
|
||||
spacing="large"
|
||||
padding={{ left: 'xxlarge', top: 'large', right: 'xxlarge', bottom: 'xxlarge' }}
|
||||
className={css.panelContent}>
|
||||
<Layout.Vertical spacing="large" className={css.configForm}>
|
||||
<Layout.Horizontal spacing="small" flex={{ justifyContent: 'flex-start' }}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
@ -319,28 +368,163 @@ export const PluginsPanel = ({ onPluginAddUpdate }: PluginsPanelInterface): JSX.
|
||||
}}
|
||||
className={css.arrow}
|
||||
/>
|
||||
{plugin?.spec?.name && (
|
||||
{plugin?.uid && (
|
||||
<Text font={{ variation: FontVariation.H5 }}>
|
||||
{getString('addLabel')} {plugin.spec.name} {getString('plugins.stepLabel')}
|
||||
{getString('addLabel')} {plugin.uid} {getString('plugins.stepLabel')}
|
||||
</Text>
|
||||
)}
|
||||
</Layout.Horizontal>
|
||||
<Container className={css.form}>
|
||||
<Formik
|
||||
initialValues={{}}
|
||||
initialValues={getInitialFormValues(pluginInputs)}
|
||||
onSubmit={formData => {
|
||||
onPluginAddUpdate?.(false, constructPayloadForYAMLInsertion(formData, plugin))
|
||||
}}>
|
||||
<FormikForm height="100%" flex={{ justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<Layout.Vertical flex={{ alignItems: 'flex-start' }} height="100%">
|
||||
{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 flex={{ alignItems: 'flex-start' }} height="inherit" spacing="medium">
|
||||
<Layout.Vertical
|
||||
width="100%"
|
||||
className={css.formFields}
|
||||
spacing="xsmall"
|
||||
flex={{ justifyContent: 'space-between' }}>
|
||||
{category === PluginCategory.Harness ? (
|
||||
<Layout.Vertical width="inherit">
|
||||
<FormInput.TextArea
|
||||
name={'script'}
|
||||
label={getString('pluginsPanel.run.script')}
|
||||
style={{ width: '100%' }}
|
||||
key={'script'}
|
||||
/>
|
||||
<FormInput.Select
|
||||
name={'shell'}
|
||||
label={getString('pluginsPanel.run.shell')}
|
||||
style={{ width: '100%' }}
|
||||
key={'shell'}
|
||||
items={[
|
||||
{ label: getString('pluginsPanel.run.sh'), value: 'sh' },
|
||||
{ label: getString('pluginsPanel.run.bash'), value: 'bash' },
|
||||
{ label: getString('pluginsPanel.run.powershell'), value: 'powershell' },
|
||||
{ label: getString('pluginsPanel.run.pwsh'), value: 'pwsh' }
|
||||
]}
|
||||
/>
|
||||
<Accordion activeId="">
|
||||
<Accordion.Panel
|
||||
id="container"
|
||||
summary="Container"
|
||||
details={
|
||||
<Layout.Vertical className={css.indent}>
|
||||
<FormInput.Text
|
||||
name={'container.image'}
|
||||
label={getString('pluginsPanel.run.image')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.image'}
|
||||
/>
|
||||
<Accordion activeId="">
|
||||
<Accordion.Panel
|
||||
id="container.credentials"
|
||||
summary={getString('pluginsPanel.run.credentials')}
|
||||
details={
|
||||
<Layout.Vertical className={css.indent}>
|
||||
<FormInput.Text
|
||||
name={'container.credentials.username'}
|
||||
label={getString('pluginsPanel.run.username')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.credentials.username'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'container.credentials.password'}
|
||||
label={getString('pluginsPanel.run.password')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.credentials.password'}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
}
|
||||
/>
|
||||
</Accordion>
|
||||
<FormInput.Text
|
||||
name={'container.pull'}
|
||||
label={getString('pluginsPanel.run.pull')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.pull'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'container.entrypoint'}
|
||||
label={getString('pluginsPanel.run.entrypoint')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.entrypoint'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'container.network'}
|
||||
label={getString('pluginsPanel.run.network')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.network'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'container.networkMode'}
|
||||
label={getString('pluginsPanel.run.networkMode')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.networkMode'}
|
||||
/>
|
||||
<FormInput.RadioGroup
|
||||
name={'container.privileged'}
|
||||
label={getString('pluginsPanel.run.privileged')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.privileged'}
|
||||
items={[
|
||||
{ label: 'Yes', value: 'true' },
|
||||
{ label: 'No', value: 'false' }
|
||||
]}
|
||||
/>
|
||||
<FormInput.Toggle
|
||||
name={'container.privileged'}
|
||||
label={getString('pluginsPanel.run.privileged')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.privileged'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'container.user'}
|
||||
label={getString('user')}
|
||||
style={{ width: '100%' }}
|
||||
key={'container.user'}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
}
|
||||
/>
|
||||
<Accordion.Panel
|
||||
id="mount"
|
||||
summary="Mount"
|
||||
details={
|
||||
<Layout.Vertical className={css.indent}>
|
||||
<FormInput.Text
|
||||
name={'mount.name'}
|
||||
label={getString('name')}
|
||||
style={{ width: '100%' }}
|
||||
key={'mount.name'}
|
||||
/>
|
||||
<FormInput.Text
|
||||
name={'mount.path'}
|
||||
label={getString('pluginsPanel.run.path')}
|
||||
style={{ width: '100%' }}
|
||||
key={'mount.path'}
|
||||
/>
|
||||
</Layout.Vertical>
|
||||
}
|
||||
/>
|
||||
</Accordion>
|
||||
</Layout.Vertical>
|
||||
) : Object.keys(pluginInputs).length > 0 ? (
|
||||
<Layout.Vertical width="inherit">
|
||||
{Object.keys(allPluginInputs).map((field: string) => {
|
||||
return renderPluginFormField({ name: field, properties: get(allPluginInputs, field) })
|
||||
})}
|
||||
</Layout.Vertical>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
<Container margin={{ top: 'small', bottom: 'small' }}>
|
||||
<Button variation={ButtonVariation.PRIMARY} text={getString('addLabel')} type="submit" />
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
|
@ -426,8 +426,27 @@ export interface StringsMap {
|
||||
'pipelines.updatePipelineSuccess': string
|
||||
'pipelines.updated': string
|
||||
'pipelines.yamlPath': string
|
||||
'plugins.select': string
|
||||
'plugins.stepLabel': string
|
||||
'plugins.title': string
|
||||
'pluginsPanel.plugins.helptext': string
|
||||
'pluginsPanel.run.bash': string
|
||||
'pluginsPanel.run.credentials': string
|
||||
'pluginsPanel.run.entrypoint': string
|
||||
'pluginsPanel.run.helptext': string
|
||||
'pluginsPanel.run.image': string
|
||||
'pluginsPanel.run.network': string
|
||||
'pluginsPanel.run.networkMode': string
|
||||
'pluginsPanel.run.password': string
|
||||
'pluginsPanel.run.path': string
|
||||
'pluginsPanel.run.powershell': string
|
||||
'pluginsPanel.run.privileged': string
|
||||
'pluginsPanel.run.pull': string
|
||||
'pluginsPanel.run.pwsh': string
|
||||
'pluginsPanel.run.script': string
|
||||
'pluginsPanel.run.sh': string
|
||||
'pluginsPanel.run.shell': string
|
||||
'pluginsPanel.run.username': string
|
||||
poweredByAI: string
|
||||
'pr.ableToMerge': string
|
||||
'pr.addDescription': string
|
||||
|
@ -714,6 +714,28 @@ run: Run
|
||||
plugins:
|
||||
title: Plugins
|
||||
stepLabel: step
|
||||
select: Select a plugin
|
||||
pluginsPanel:
|
||||
plugins:
|
||||
helptext: Run a plugin step
|
||||
run:
|
||||
helptext: Run a container or a shell command
|
||||
script: Script
|
||||
shell: Shell
|
||||
sh: Sh
|
||||
bash: Bash
|
||||
powershell: Powershell
|
||||
pwsh: Pwsh
|
||||
image: Image
|
||||
credentials: Credentials
|
||||
username: Username
|
||||
password: Password
|
||||
pull: Pull
|
||||
entrypoint: Entrypoint
|
||||
network: Network
|
||||
networkMode: Network Mode
|
||||
privileged: Privileged
|
||||
path: Path
|
||||
createNewRepo: Create New repository
|
||||
importGitRepo: Import Repository
|
||||
importRepo:
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
.pluginsContainer {
|
||||
width: 30vw;
|
||||
height: calc(100vh - var(--header-height) - var(--generate-pipeline-header)) !important;
|
||||
height: calc(100vh - var(--header-height) - var(--generate-pipeline-header));
|
||||
background-color: var(--grey-50) !important;
|
||||
}
|
||||
|
||||
@ -70,4 +70,5 @@
|
||||
|
||||
.generateHeader {
|
||||
border-bottom: 1px solid var(--grey-100);
|
||||
background: var(--grey-50) !important;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useGet, useMutate } from 'restful-react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { get, isEmpty, isUndefined, set } from 'lodash-es'
|
||||
@ -88,6 +88,7 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
const [isExistingPipeline, setIsExistingPipeline] = useState<boolean>(false)
|
||||
const [isDirty, setIsDirty] = useState<boolean>(false)
|
||||
const [generatingPipeline, setGeneratingPipeline] = useState<boolean>(false)
|
||||
const pipelineAsYAMLRef = useRef<string>('')
|
||||
|
||||
const pipelineSaveOption: PipelineSaveAndRunOption = {
|
||||
title: getString('save'),
|
||||
@ -227,20 +228,29 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
return existingPipeline
|
||||
}
|
||||
|
||||
const handlePluginAddUpdateIntoYAML = useCallback(
|
||||
(_isUpdate: boolean, pluginFormData: Record<string, any>): void => {
|
||||
const handlePluginAddUpdateToPipeline = useCallback(
|
||||
({
|
||||
pluginFormData,
|
||||
existingYAML
|
||||
}: {
|
||||
isUpdate: boolean
|
||||
pluginFormData: Record<string, any>
|
||||
existingYAML: string
|
||||
}): void => {
|
||||
try {
|
||||
const pipelineAsObj = parse(pipelineAsYAML)
|
||||
const pipelineAsObj = parse(existingYAML)
|
||||
const updatedPipelineAsObj = updatePipelineWithPluginData(pipelineAsObj, pluginFormData)
|
||||
if (Object.keys(updatedPipelineAsObj).length > 0) {
|
||||
// avoid setting to empty pipeline in case pipeline update with plugin data fails
|
||||
setPipelineAsYaml(stringify(updatedPipelineAsObj))
|
||||
const updatedPipelineAsYAML = stringify(updatedPipelineAsObj)
|
||||
setPipelineAsYaml(updatedPipelineAsYAML)
|
||||
pipelineAsYAMLRef.current = updatedPipelineAsYAML
|
||||
}
|
||||
} catch (ex) {
|
||||
// ignore exception
|
||||
}
|
||||
},
|
||||
[yamlVersion, isExistingPipeline, originalPipelineYAMLFileContent, pipelineAsYAML]
|
||||
[]
|
||||
)
|
||||
|
||||
const handleGeneratePipeline = useCallback(async (): Promise<void> => {
|
||||
@ -367,7 +377,7 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
/>
|
||||
<PageBody>
|
||||
<Layout.Vertical>
|
||||
{yamlVersion === YamlVersion.V1 && (
|
||||
{!isExistingPipeline && yamlVersion === YamlVersion.V1 && (
|
||||
<Layout.Horizontal
|
||||
padding={{ left: 'medium', bottom: 'medium', top: 'medium' }}
|
||||
className={css.generateHeader}
|
||||
@ -375,16 +385,19 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
flex={{ justifyContent: 'flex-start' }}>
|
||||
<Button
|
||||
text={getString('generate')}
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
className={css.generate}
|
||||
onClick={handleGeneratePipeline}
|
||||
disabled={generatingPipeline}
|
||||
/>
|
||||
<Text font={{ variation: FontVariation.H5 }}>{getString('generateHelptext')}</Text>
|
||||
<Text font={{ weight: 'bold' }}>{getString('generateHelptext')}</Text>
|
||||
</Layout.Horizontal>
|
||||
)}
|
||||
<Layout.Horizontal className={css.layout}>
|
||||
<Container className={cx(css.editorContainer, { [css.extendedHeight]: yamlVersion === YamlVersion.V0 })}>
|
||||
<Container
|
||||
className={cx(css.editorContainer, {
|
||||
[css.extendedHeight]: isExistingPipeline || yamlVersion === YamlVersion.V0
|
||||
})}>
|
||||
<MonacoSourceCodeEditor
|
||||
language={'yaml'}
|
||||
schema={yamlVersion === YamlVersion.V1 ? pipelineSchemaV1 : pipelineSchemaV0}
|
||||
@ -393,8 +406,16 @@ const AddUpdatePipeline = (): JSX.Element => {
|
||||
/>
|
||||
</Container>
|
||||
{yamlVersion === YamlVersion.V1 && (
|
||||
<Container className={css.pluginsContainer}>
|
||||
<PluginsPanel onPluginAddUpdate={handlePluginAddUpdateIntoYAML} />
|
||||
<Container className={cx(css.pluginsContainer, { [css.extendedHeight]: isExistingPipeline })}>
|
||||
<PluginsPanel
|
||||
onPluginAddUpdate={(isUpdate: boolean, pluginFormData: Record<string, any>) =>
|
||||
handlePluginAddUpdateToPipeline({
|
||||
isUpdate,
|
||||
pluginFormData,
|
||||
existingYAML: pipelineAsYAMLRef.current || pipelineAsYAML
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
</Layout.Horizontal>
|
||||
|
Loading…
Reference in New Issue
Block a user