Merge branch 'add-pipeline-editor' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#431)

This commit is contained in:
vardan.bansal@harness.io vardan 2023-09-11 23:38:13 +00:00 committed by Harness
commit 4138ebea1c
28 changed files with 3708 additions and 38 deletions

View File

@ -221,6 +221,18 @@ module.exports = {
'vb',
'xml',
'yaml'
],
globalAPI: true,
filename: '[name].worker.[contenthash:6].js',
customLanguages: [
{
label: 'yaml',
entry: 'monaco-yaml',
worker: {
id: 'monaco-yaml/yamlWorker',
entry: 'monaco-yaml/yaml.worker'
}
}
]
})
]

View File

@ -65,6 +65,7 @@
"moment": "^2.25.3",
"monaco-editor": "^0.40.0",
"monaco-editor-webpack-plugin": "^7.1.0",
"monaco-yaml": "^4.0.4",
"qs": "^6.9.4",
"react": "^17.0.2",
"react-complex-tree": "^1.1.11",

View File

@ -46,7 +46,7 @@ export interface CODERoutes {
toCODESpaceAccessControl: (args: Required<Pick<CODEProps, 'space'>>) => string
toCODESpaceSettings: (args: Required<Pick<CODEProps, 'space'>>) => string
toCODEPipelines: (args: Required<Pick<CODEProps, 'repoPath'>>) => string
toCODEPipelinesNew: (args: Required<Pick<CODEProps, 'space'>>) => string
toCODEPipelineEdit: (args: Required<Pick<CODEProps, 'repoPath' | 'pipeline'>>) => string
toCODESecrets: (args: Required<Pick<CODEProps, 'space'>>) => string
toCODEGlobalSettings: () => string
@ -97,7 +97,7 @@ export const routes: CODERoutes = {
toCODESpaceAccessControl: ({ space }) => `/access-control/${space}`,
toCODESpaceSettings: ({ space }) => `/settings/${space}`,
toCODEPipelines: ({ repoPath }) => `/${repoPath}/pipelines`,
toCODEPipelinesNew: ({ space }) => `/pipelines/${space}/new`,
toCODEPipelineEdit: ({ repoPath, pipeline }) => `/${repoPath}/pipelines/${pipeline}/edit`,
toCODESecrets: ({ space }) => `/secrets/${space}`,
toCODEGlobalSettings: () => '/settings',

View File

@ -31,7 +31,7 @@ import { useFeatureFlag } from 'hooks/useFeatureFlag'
import ExecutionList from 'pages/ExecutionList/ExecutionList'
import Execution from 'pages/Execution/Execution'
import Secret from 'pages/Secret/Secret'
import NewPipeline from 'pages/NewPipeline/NewPipeline'
import AddUpdatePipeline from 'pages/AddUpdatePipeline/AddUpdatePipeline'
export const RouteDestinations: React.FC = React.memo(function RouteDestinations() {
const { getString } = useStrings()
@ -186,9 +186,9 @@ export const RouteDestinations: React.FC = React.memo(function RouteDestinations
)}
{OPEN_SOURCE_PIPELINES && (
<Route path={routes.toCODEPipelinesNew({ space: pathProps.space })} exact>
<Route path={routes.toCODEPipelineEdit({ repoPath, pipeline: pathProps.pipeline })} exact>
<LayoutWithSideNav title={getString('pageTitle.pipelines')}>
<NewPipeline />
<AddUpdatePipeline />
</LayoutWithSideNav>
</Route>
)}

View File

@ -0,0 +1,17 @@
.branchSelect {
:global {
.bp3-popover-wrapper {
width: 100% !important;
.bp3-popover-target {
width: 100% !important;
}
}
.bp3-button {
justify-content: start;
width: 100%;
}
.bp3-icon-chevron-down {
margin-left: auto;
}
}
}

View File

@ -0,0 +1,3 @@
/* eslint-disable */
// This is an auto-generated file
export declare const branchSelect: string

View File

@ -0,0 +1,162 @@
import React, { useMemo, useState } from 'react'
import { useHistory } from 'react-router-dom'
import { useMutate } from 'restful-react'
import * as yup from 'yup'
import { capitalize } from 'lodash'
import {
Button,
ButtonVariation,
Container,
Dialog,
FormInput,
Formik,
FormikForm,
Layout,
Text,
useToaster
} from '@harnessio/uicore'
import { FontVariation } from '@harnessio/design-system'
import { useModalHook } from 'hooks/useModalHook'
import type { OpenapiCreatePipelineRequest, TypesPipeline, TypesRepository } from 'services/code'
import { useStrings } from 'framework/strings'
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
import { useAppContext } from 'AppContext'
import { getErrorMessage } from 'utils/Utils'
import { DEFAULT_YAML_PATH_PREFIX, DEFAULT_YAML_PATH_SUFFIX } from '../../pages/AddUpdatePipeline/Constants'
import css from './NewPipelineModal.module.scss'
interface FormData {
name: string
branch: string
yamlPath: string
}
const useNewPipelineModal = () => {
const { routes } = useAppContext()
const { getString } = useStrings()
const history = useHistory()
const { showError } = useToaster()
const [repo, setRepo] = useState<TypesRepository | undefined>()
const repoPath = useMemo(() => repo?.path || '', [repo])
const { mutate: savePipeline } = useMutate<TypesPipeline>({
verb: 'POST',
path: `/api/v1/repos/${repoPath}/+/pipelines`
})
const handleCreatePipeline = (formData: FormData): void => {
const { name, branch, yamlPath } = formData
try {
const payload: OpenapiCreatePipelineRequest = {
config_path: yamlPath,
default_branch: branch,
uid: name
}
savePipeline(payload, { pathParams: { path: `/api/v1/repos/${repoPath}/+/pipelines` } })
.then(() => {
hideModal()
history.push(routes.toCODEPipelineEdit({ repoPath, pipeline: name }))
})
.catch(error => {
showError(getErrorMessage(error), 0, 'pipelines.failedToCreatePipeline')
})
} catch (exception) {
showError(getErrorMessage(exception), 0, 'pipelines.failedToCreatePipeline')
}
}
const [openModal, hideModal] = useModalHook(() => {
const onClose = () => {
hideModal()
}
return (
<Dialog isOpen enforceFocus={false} onClose={onClose} title={getString('pipelines.createNewPipeline')}>
<Formik<FormData>
initialValues={{ name: '', branch: repo?.default_branch || '', yamlPath: '' }}
formName="createNewPipeline"
enableReinitialize={true}
validationSchema={yup.object().shape({
name: yup
.string()
.trim()
.required(`${getString('name')} ${getString('isRequired')}`),
branch: yup
.string()
.trim()
.required(`${getString('branch')} ${getString('isRequired')}`),
yamlPath: yup
.string()
.trim()
.required(`${getString('pipelines.yamlPath')} ${getString('isRequired')}`)
})}
validateOnChange
validateOnBlur
onSubmit={handleCreatePipeline}>
{formik => {
return (
<FormikForm>
<Layout.Vertical spacing="small">
<Layout.Vertical spacing="small">
<FormInput.Text
name="name"
label={getString('name')}
placeholder={getString('pipelines.enterPipelineName')}
inputGroup={{ autoFocus: true }}
onChange={event => {
const input = (event.target as HTMLInputElement)?.value
formik?.setFieldValue('name', input)
if (input) {
// Keeping minimal validation for now, this could be much more exhaustive
const path = input.trim().replace(/\s/g, '')
formik?.setFieldValue(
'yamlPath',
DEFAULT_YAML_PATH_PREFIX.concat(path).concat(DEFAULT_YAML_PATH_SUFFIX)
)
}
}}
/>
<Layout.Vertical spacing="xsmall" padding={{ bottom: 'medium' }}>
<Text font={{ variation: FontVariation.BODY }}>{capitalize(getString('branch'))}</Text>
<Container className={css.branchSelect}>
<BranchTagSelect
gitRef={formik?.values?.branch || repo?.default_branch || ''}
onSelect={(ref: string) => {
formik?.setFieldValue('branch', ref)
}}
repoMetadata={repo || {}}
disableBranchCreation
disableViewAllBranches
forBranchesOnly
/>
</Container>
</Layout.Vertical>
<FormInput.Text
name="yamlPath"
label={getString('pipelines.yamlPath')}
placeholder={getString('pipelines.enterYAMLPath')}
/>
</Layout.Vertical>
<Layout.Horizontal spacing="medium" width="100%">
<Button variation={ButtonVariation.PRIMARY} text={getString('create')} type="submit" />
<Button variation={ButtonVariation.SECONDARY} text={getString('cancel')} onClick={onClose} />
</Layout.Horizontal>
</Layout.Vertical>
</FormikForm>
)
}}
</Formik>
</Dialog>
)
}, [repo])
return {
openModal: ({ repoMetadata }: { repoMetadata?: TypesRepository }) => {
setRepo(repoMetadata)
openModal()
},
hideModal
}
}
export default useNewPipelineModal

View File

@ -0,0 +1,53 @@
.main {
height: 100%;
:global {
.bp3-tabs {
width: 100%;
height: 100%;
}
}
}
.mainTabPanel {
&:global(.bp3-tab-panel[role='tabpanel']) {
height: calc(100% - 30px);
margin-top: 0 !important;
margin-bottom: 0 !important;
}
}
.pluginDetailsPanel {
height: 100%;
border-top: 1px solid var(--grey-100);
}
.pluginIcon {
background: var(--teal-200) !important;
border-radius: 5px;
}
.plugin {
border: 1px solid var(--grey-100);
&:hover {
cursor: pointer;
}
}
.form {
height: 100%;
width: 100%;
:global {
.FormikForm--main {
height: 100%;
& > div {
height: 100%;
}
}
}
}
.arrow {
&:hover {
cursor: pointer;
}
}

View File

@ -0,0 +1,9 @@
/* eslint-disable */
// This is an auto-generated file
export declare const arrow: string
export declare const form: string
export declare const main: string
export declare const mainTabPanel: string
export declare const plugin: string
export declare const pluginDetailsPanel: string
export declare const pluginIcon: string

View File

@ -0,0 +1,300 @@
import React, { useCallback, useEffect, useState } from 'react'
import { Formik } from 'formik'
import { capitalize, get } from 'lodash-es'
import { useGet } from 'restful-react'
import { useStrings } from 'framework/strings'
import { Button, ButtonVariation, Container, FormInput, FormikForm, Layout, Tab, Tabs, Text } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { Icon, type IconName } from '@harnessio/icons'
import { LIST_FETCHING_LIMIT } from 'utils/Utils'
import type { TypesPlugin } from 'services/code'
import { YamlVersion } from 'pages/AddUpdatePipeline/Constants'
import css from './PluginsPanel.module.scss'
enum PluginCategory {
Harness,
Drone
}
enum PluginPanelView {
Category,
Listing,
Configuration
}
interface PluginInterface {
category: PluginCategory
name: string
description: string
icon: IconName
}
const PluginCategories: PluginInterface[] = [
{
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 dronePluginSpecMockData = {
inputs: {
channel: {
type: 'string'
},
token: {
type: 'string'
}
},
steps: [
{
type: 'script',
spec: {
image: 'plugins/slack'
},
envs: {
PLUGIN_CHANNEL: '<+inputs.channel>'
}
}
]
}
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 => {
const { getString } = useStrings()
const [category, setCategory] = useState<PluginCategory>()
const [panelView, setPanelView] = useState<PluginPanelView>(PluginPanelView.Category)
const [plugin, setPlugin] = useState<TypesPlugin>()
const {
data: plugins,
loading,
refetch: fetchPlugins
} = useGet<TypesPlugin[]>({
path: `/api/v1/plugins`,
queryParams: {
limit: LIST_FETCHING_LIMIT,
page: 1
},
lazy: true
})
useEffect(() => {
if (category === PluginCategory.Drone) {
fetchPlugins()
}
}, [category])
const renderPluginCategories = (): JSX.Element => {
return (
<>
{PluginCategories.map((item: PluginInterface) => {
const { name, category, description, icon } = item
return (
<Layout.Horizontal
onClick={() => {
setCategory(category)
if (category === PluginCategory.Drone) {
setPanelView(PluginPanelView.Listing)
} else if (category === PluginCategory.Harness) {
setPlugin({ uid: getString('run') })
setPanelView(PluginPanelView.Configuration)
}
}}
key={category}
padding={{ left: 'medium', right: 'medium', top: 'medium', bottom: 'medium' }}
flex={{ justifyContent: 'flex-start' }}
className={css.plugin}>
<Container padding="small" className={css.pluginIcon}>
<Icon name={icon} />
</Container>
<Layout.Vertical padding={{ left: 'small' }}>
<Text color={Color.PRIMARY_7} font={{ variation: FontVariation.BODY2 }}>
{name}
</Text>
<Text font={{ variation: FontVariation.SMALL }}>{description}</Text>
</Layout.Vertical>
</Layout.Horizontal>
)
})}
</>
)
}
const renderPlugins = useCallback((): JSX.Element => {
return loading ? (
<Container flex={{ justifyContent: 'center' }} padding="large">
<Icon name="steps-spinner" color={Color.PRIMARY_7} size={25} />
</Container>
) : (
<Layout.Vertical spacing="small" padding={{ top: 'small' }}>
<Layout.Horizontal
flex={{ justifyContent: 'flex-start', alignItems: 'center' }}
spacing="small"
padding={{ left: 'medium' }}>
<Icon
name="arrow-left"
size={18}
onClick={() => {
setPanelView(PluginPanelView.Category)
}}
className={css.arrow}
/>
<Text font={{ variation: FontVariation.H5 }} flex={{ justifyContent: 'center' }}>
{getString('plugins.addAPlugin', { category: PluginCategory[category as PluginCategory] })}
</Text>
</Layout.Horizontal>
<Container>
{plugins?.map((plugin: TypesPlugin) => {
const { uid, description } = plugin
return (
<Layout.Horizontal
flex={{ justifyContent: 'flex-start' }}
padding={{ left: 'large', top: 'medium', bottom: 'medium', right: 'large' }}
className={css.plugin}
onClick={() => {
setPanelView(PluginPanelView.Configuration)
setPlugin(plugin)
}}>
<Icon name={'gear'} size={25} />
<Layout.Vertical padding={{ left: 'small' }}>
<Text font={{ variation: FontVariation.BODY2 }} color={Color.PRIMARY_7}>
{uid}
</Text>
<Text font={{ variation: FontVariation.SMALL }}>{description}</Text>
</Layout.Vertical>
</Layout.Horizontal>
)
})}
</Container>
</Layout.Vertical>
)
}, [loading, plugins])
const renderPluginFormField = ({ name, type }: { name: string; type: 'string' }): JSX.Element => {
return type === 'string' ? (
<FormInput.Text
name={name}
label={<Text font={{ variation: FontVariation.FORM_INPUT_TEXT }}>{capitalize(name)}</Text>}
style={{ width: '100%' }}
key={name}
/>
) : (
<></>
)
}
const constructPayloadForYAMLInsertion = (isUpdate: boolean, pluginFormData: Record<string, any>) => {
let constructedPayload = { ...pluginFormData }
switch (category) {
case PluginCategory.Drone:
case PluginCategory.Harness:
constructedPayload =
version === YamlVersion.V1
? { type: 'script', spec: constructedPayload }
: { name: 'run step', commands: [get(constructedPayload, 'script', '')] }
}
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', {})
return (
<Layout.Vertical
spacing="medium"
margin={{ left: 'xxlarge', top: 'large', right: 'xxlarge', bottom: 'xxlarge' }}
height="95%">
<Layout.Horizontal spacing="small" flex={{ justifyContent: 'flex-start' }}>
<Icon
name="arrow-left"
size={18}
onClick={() => {
setPlugin(undefined)
if (category === PluginCategory.Drone) {
setPanelView(PluginPanelView.Listing)
} else if (category === PluginCategory.Harness) {
setPanelView(PluginPanelView.Category)
}
}}
className={css.arrow}
/>
{plugin?.uid ? (
<Text font={{ variation: FontVariation.H5 }}>
{getString('addLabel')} {plugin.uid} {getString('plugins.stepLabel')}
</Text>
) : (
<></>
)}
</Layout.Horizontal>
<Container className={css.form}>
<Formik
initialValues={{}}
onSubmit={formData => {
constructPayloadForYAMLInsertion(false, formData)
}}>
<FormikForm>
<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>
<Button variation={ButtonVariation.PRIMARY} text={getString('addLabel')} type="submit" />
</Layout.Vertical>
</FormikForm>
</Formik>
</Container>
</Layout.Vertical>
)
}, [plugin, category])
const renderPluginsPanel = useCallback((): JSX.Element => {
switch (panelView) {
case PluginPanelView.Category:
return renderPluginCategories()
case PluginPanelView.Listing:
return renderPlugins()
case PluginPanelView.Configuration:
return renderPluginConfigForm()
default:
return <></>
}
}, [loading, plugins, panelView, category])
return (
<Container className={css.main}>
<Tabs id={'pluginsPanel'} defaultSelectedTabId={'plugins'}>
<Tab
panelClassName={css.mainTabPanel}
id="plugins"
title={
<Text
font={{ variation: FontVariation.BODY2 }}
padding={{ left: 'small', bottom: 'xsmall', top: 'xsmall' }}
color={Color.PRIMARY_7}>
{getString('plugins.title')}
</Text>
}
panel={<Container className={css.pluginDetailsPanel}>{renderPluginsPanel()}</Container>}
/>
</Tabs>
</Container>
)
}

View File

@ -0,0 +1,17 @@
.branchSelect {
:global {
.bp3-popover-wrapper {
width: 100% !important;
.bp3-popover-target {
width: 100% !important;
}
}
.bp3-button {
justify-content: start;
width: 100%;
}
.bp3-icon-chevron-down {
margin-left: auto;
}
}
}

View File

@ -0,0 +1,3 @@
/* eslint-disable */
// This is an auto-generated file
export declare const branchSelect: string

View File

@ -0,0 +1,130 @@
import React, { useMemo, useState } from 'react'
import { useHistory } from 'react-router'
import { useMutate } from 'restful-react'
import * as yup from 'yup'
import { capitalize } from 'lodash'
import { FontVariation } from '@harnessio/design-system'
import {
Button,
ButtonVariation,
Container,
Dialog,
Formik,
FormikForm,
Layout,
Text,
useToaster
} from '@harnessio/uicore'
import { useStrings } from 'framework/strings'
import { useModalHook } from 'hooks/useModalHook'
import type { CreateExecutionQueryParams, TypesExecution, TypesRepository } from 'services/code'
import { getErrorMessage } from 'utils/Utils'
import { useAppContext } from 'AppContext'
import { BranchTagSelect } from 'components/BranchTagSelect/BranchTagSelect'
import css from './RunPipelineModal.module.scss'
interface FormData {
branch: string
}
const useRunPipelineModal = () => {
const { routes } = useAppContext()
const { getString } = useStrings()
const { showSuccess, showError, clear: clearToaster } = useToaster()
const history = useHistory()
const [repo, setRepo] = useState<TypesRepository>()
const [pipeline, setPipeline] = useState<string>('')
const repoPath = useMemo(() => repo?.path || '', [repo])
const { mutate: startExecution } = useMutate<TypesExecution>({
verb: 'POST',
path: `/api/v1/repos/${repoPath}/+/pipelines/${pipeline}/executions`
})
const runPipeline = (formData: FormData): void => {
const { branch } = formData
try {
startExecution(
{},
{
pathParams: { path: `/api/v1/repos/${repoPath}/+/pipelines/${pipeline}/executions` },
queryParams: { branch } as CreateExecutionQueryParams
}
)
.then(response => {
clearToaster()
showSuccess(getString('pipelines.executionStarted'))
if (response?.number && !isNaN(response.number)) {
history.push(routes.toCODEExecution({ repoPath, pipeline, execution: response.number.toString() }))
}
hideModal()
})
.catch(error => {
showError(getErrorMessage(error), 0, 'pipelines.executionCouldNotStart')
})
} catch (exception) {
showError(getErrorMessage(exception), 0, 'pipelines.executionCouldNotStart')
}
}
const [openModal, hideModal] = useModalHook(() => {
const onClose = () => {
hideModal()
}
return (
<Dialog isOpen enforceFocus={false} onClose={onClose} title={getString('pipelines.run')}>
<Formik
formName="run-pipeline-form"
initialValues={{ branch: repo?.default_branch || '' }}
validationSchema={yup.object().shape({
branch: yup
.string()
.trim()
.required(`${getString('branch')} ${getString('isRequired')}`)
})}
onSubmit={runPipeline}
enableReinitialize>
{formik => {
return (
<FormikForm>
<Layout.Vertical spacing="medium">
<Layout.Vertical spacing="xsmall" padding={{ bottom: 'medium' }}>
<Text font={{ variation: FontVariation.BODY }}>{capitalize(getString('branch'))}</Text>
<Container className={css.branchSelect}>
<BranchTagSelect
gitRef={formik?.values?.branch || repo?.default_branch || ''}
onSelect={(ref: string) => {
formik?.setFieldValue('branch', ref)
}}
repoMetadata={repo || {}}
disableBranchCreation
disableViewAllBranches
forBranchesOnly
/>
</Container>
</Layout.Vertical>
<Layout.Horizontal spacing="medium">
<Button variation={ButtonVariation.PRIMARY} type="submit" text={getString('pipelines.run')} />
<Button variation={ButtonVariation.SECONDARY} text={getString('cancel')} onClick={onClose} />
</Layout.Horizontal>
</Layout.Vertical>
</FormikForm>
)
}}
</Formik>
</Dialog>
)
}, [repo?.default_branch, pipeline])
return {
openModal: ({ repoMetadata, pipeline }: { repoMetadata: TypesRepository; pipeline: string }) => {
setRepo(repoMetadata)
setPipeline(pipeline)
openModal()
},
hideModal
}
}
export default useRunPipelineModal

View File

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'
import * as monaco from 'monaco-editor'
import type monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'
import MonacoEditor, { MonacoDiffEditor } from 'react-monaco-editor'
import { setDiagnosticsOptions } from 'monaco-yaml'
import { noop } from 'lodash-es'
import { SourceCodeEditorProps, PLAIN_TEXT } from 'utils/Utils'
import { useEventListener } from 'hooks/useEventListener'
@ -39,7 +40,8 @@ export default function MonacoSourceCodeEditor({
height,
autoHeight,
wordWrap = true,
onChange = noop
onChange = noop,
schema
}: SourceCodeEditorProps) {
const [editor, setEditor] = useState<monacoEditor.editor.IStandaloneCodeEditor>()
const scrollbar = autoHeight ? 'hidden' : 'auto'
@ -50,6 +52,24 @@ export default function MonacoSourceCodeEditor({
monaco.languages.typescript?.typescriptDefaults?.setCompilerOptions?.(compilerOptions)
}, [])
useEffect(() => {
if (language === 'yaml' && schema) {
setDiagnosticsOptions({
validate: true,
enableSchemaRequest: false,
hover: true,
completion: true,
schemas: [
{
fileMatch: ['*'],
schema,
uri: 'https://github.com/harness/harness-schema'
}
]
})
}
}, [language, schema])
useEventListener('resize', () => {
editor?.layout({ width: 0, height: 0 })
window.requestAnimationFrame(() => editor?.layout())

View File

@ -11,6 +11,7 @@ export interface StringsMap {
add: string
addComment: string
addGitIgnore: string
addLabel: string
addLicense: string
addMember: string
addNewFile: string
@ -190,6 +191,7 @@ export interface StringsMap {
failedToCreateSpace: string
failedToDeleteBranch: string
failedToDeleteWebhook: string
failedToSavePipeline: string
fileDeleted: string
fileTooLarge: string
files: string
@ -212,6 +214,7 @@ export interface StringsMap {
'homepage.welcomeText': string
in: string
inactiveBranches: string
isRequired: string
leaveAComment: string
license: string
lineBreaks: string
@ -318,11 +321,26 @@ export interface StringsMap {
payloadUrl: string
payloadUrlLabel: string
pending: string
'pipelines.createNewPipeline': string
'pipelines.created': string
'pipelines.editPipeline': string
'pipelines.enterPipelineName': string
'pipelines.enterYAMLPath': string
'pipelines.executionCouldNotStart': string
'pipelines.executionStarted': string
'pipelines.failedToCreatePipeline': string
'pipelines.lastExecution': string
'pipelines.name': string
'pipelines.newPipelineButton': string
'pipelines.noData': string
'pipelines.run': string
'pipelines.saveAndRun': string
'pipelines.time': string
'pipelines.updated': string
'pipelines.yamlPath': string
'plugins.addAPlugin': string
'plugins.stepLabel': string
'plugins.title': string
'pr.ableToMerge': string
'pr.addDescription': string
'pr.authorCommentedPR': string
@ -455,6 +473,7 @@ export interface StringsMap {
reviewerNotFound: string
reviewers: string
role: string
run: string
running: string
samplePayloadUrl: string
save: string

View File

@ -416,6 +416,7 @@ makeRequired: Make Required
makeOptional: Make Optional
remove: Remove
required: Required
isRequired: is required
noneYet: None Yet
noOptionalReviewers: No Optional Reviewers
noRequiredReviewers: No Required Reviewers
@ -487,6 +488,7 @@ showCommitHistory: Renamed from {file} - Show History
noReviewers: No Reviewers
assignPeople: Assign people
add: Add +
addLabel: Add
users: Users
findAUser: Find a user
reviewerNotFound: 'Reviewer <strong>{{reviewer}}</strong> not found.'
@ -586,6 +588,7 @@ spaceMemberships:
memberAdded: Member added successfully.
failedToCreateSpace: Failed to create Space. Please try again.
failedToCreatePipeline: Failed to create Pipeline. Please try again.
failedToSavePipeline: Failed to save Pipeline. Please try again.
enterName: Enter the name
createASpace: Create a space
createSpace: Create Space
@ -624,8 +627,20 @@ pipelines:
noData: There are no pipelines :(
newPipelineButton: New Pipeline
name: Pipeline Name
createNewPipeline: Create New Pipeline
enterPipelineName: Enter pipeline name
yamlPath: YAML Path
enterYAMLPath: Enter YAML path
failedToCreatePipeline: Failed to create pipeline
saveAndRun: Save and Run
editPipeline: Edit pipeline {{pipeline}}
run: Run pipeline
time: Time
lastExecution: Last Execution
created: Pipeline created successfully
updated: Pipeline updated successfully
executionStarted: Pipeline execution started successfully
executionCouldNotStart: Failure while starting Pipeline execution
executions:
noData: There are no executions :(
newExecutionButton: Run Pipeline
@ -652,3 +667,8 @@ secrets:
failedToDeleteSecret: Failed to delete Secret. Please try again.
deleteSecret: Delete Secrets
userUpdateSuccess: 'User updated successfully'
run: Run
plugins:
title: Plugins
addAPlugin: Add a {{category}} plugin
stepLabel: step

View File

@ -0,0 +1,34 @@
.main {
--header-height: 96px;
--heading-height: 58px;
min-height: var(--page-height);
background-color: var(--primary-bg) !important;
.layout {
align-items: center;
}
}
.editorContainer {
width: calc(100% - 30vw);
height: calc(100vh - var(--header-height)) !important;
}
.pluginsContainer {
width: 30vw;
}
.header {
a {
font-size: var(--font-size-small);
color: var(--primary-7);
}
}
.breadcrumb {
align-items: center;
}
.drawer {
height: calc(100% - 40px);
}

View File

@ -0,0 +1,9 @@
/* eslint-disable */
// This is an auto-generated file
export declare const breadcrumb: string
export declare const drawer: string
export declare const editorContainer: string
export declare const header: string
export declare const layout: string
export declare const main: string
export declare const pluginsContainer: string

View File

@ -0,0 +1,325 @@
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'
import { stringify } from 'yaml'
import { Menu, PopoverPosition } from '@blueprintjs/core'
import {
Container,
PageHeader,
PageBody,
Layout,
ButtonVariation,
Text,
useToaster,
SplitButton,
Button
} from '@harnessio/uicore'
import { Icon } from '@harnessio/icons'
import { Color, FontVariation } from '@harnessio/design-system'
import type { OpenapiCommitFilesRequest, RepoCommitFilesResponse, RepoFileContent, TypesPipeline } from 'services/code'
import { useStrings } from 'framework/strings'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { useGetResourceContent } from 'hooks/useGetResourceContent'
import MonacoSourceCodeEditor from 'components/SourceCodeEditor/MonacoSourceCodeEditor'
import { PluginsPanel } from 'components/PluginsPanel/PluginsPanel'
import useRunPipelineModal from 'components/RunPipelineModal/RunPipelineModal'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { useAppContext } from 'AppContext'
import type { CODEProps } from 'RouteDefinitions'
import { getErrorMessage } from 'utils/Utils'
import { decodeGitContent } from 'utils/GitUtils'
import pipelineSchemaV1 from './schema/pipeline-schema-v1.json'
import pipelineSchemaV0 from './schema/pipeline-schema-v0.json'
import { YamlVersion } from './Constants'
import css from './AddUpdatePipeline.module.scss'
const StarterPipelineV1: Record<string, any> = {
version: 1,
stages: [
{
type: 'ci',
spec: {
steps: [
{
type: 'script',
spec: {
run: 'echo hello world'
}
}
]
}
}
]
}
const StarterPipelineV0: Record<string, any> = {
kind: 'pipeline',
type: 'docker',
name: 'default',
steps: [
{
name: 'test',
image: 'alpine',
commands: ['echo hello world']
}
]
}
enum PipelineSaveAndRunAction {
SAVE,
RUN,
SAVE_AND_RUN
}
interface PipelineSaveAndRunOption {
title: string
action: PipelineSaveAndRunAction
}
const AddUpdatePipeline = (): JSX.Element => {
const version = YamlVersion.V0
const { routes } = useAppContext()
const { getString } = useStrings()
const { pipeline } = useParams<CODEProps>()
const { repoMetadata } = useGetRepositoryMetadata()
const { showError, showSuccess, clear: clearToaster } = useToaster()
const [pipelineAsObj, setPipelineAsObj] = useState<Record<string, any>>(
version === YamlVersion.V0 ? StarterPipelineV0 : StarterPipelineV1
)
const [pipelineAsYAML, setPipelineAsYaml] = useState<string>('')
const { openModal: openRunPipelineModal } = useRunPipelineModal()
const repoPath = useMemo(() => repoMetadata?.path || '', [repoMetadata])
const [isExistingPipeline, setIsExistingPipeline] = useState<boolean>(false)
const [isDirty, setIsDirty] = useState<boolean>(false)
const pipelineSaveOption: PipelineSaveAndRunOption = {
title: getString('save'),
action: PipelineSaveAndRunAction.SAVE
}
const pipelineRunOption: PipelineSaveAndRunOption = {
title: getString('run'),
action: PipelineSaveAndRunAction.RUN
}
const pipelineSaveAndRunOption: PipelineSaveAndRunOption = {
title: getString('pipelines.saveAndRun'),
action: PipelineSaveAndRunAction.SAVE_AND_RUN
}
const pipelineSaveAndRunOptions: PipelineSaveAndRunOption[] = [pipelineSaveAndRunOption, pipelineSaveOption]
const [selectedOption, setSelectedOption] = useState<PipelineSaveAndRunOption>()
const { mutate, loading } = useMutate<RepoCommitFilesResponse>({
verb: 'POST',
path: `/api/v1/repos/${repoPath}/+/commits`
})
// Fetch pipeline metadata to fetch pipeline YAML file content
const { data: pipelineData, loading: fetchingPipeline } = useGet<TypesPipeline>({
path: `/api/v1/repos/${repoPath}/+/pipelines/${pipeline}`,
lazy: !repoMetadata
})
const {
data: pipelineYAMLFileContent,
loading: fetchingPipelineYAMLFileContent,
refetch: fetchPipelineYAMLFileContent
} = useGetResourceContent({
repoMetadata,
gitRef: pipelineData?.default_branch || '',
resourcePath: pipelineData?.config_path || ''
})
const originalPipelineYAMLFileContent = useMemo(
(): string => decodeGitContent((pipelineYAMLFileContent?.content as RepoFileContent)?.data),
[pipelineYAMLFileContent?.content]
)
// check if file already exists and has some content
useEffect(() => {
setIsExistingPipeline(!isEmpty(originalPipelineYAMLFileContent) && !isUndefined(originalPipelineYAMLFileContent))
}, [originalPipelineYAMLFileContent])
// load initial content on the editor
useEffect(() => {
if (isExistingPipeline) {
setPipelineAsYaml(originalPipelineYAMLFileContent)
} else {
// load with starter pipeline
try {
setPipelineAsYaml(stringify(pipelineAsObj))
} catch (ex) {
// ignore exception
}
}
}, [isExistingPipeline, originalPipelineYAMLFileContent, pipelineAsObj])
// find if editor content was modified
useEffect(() => {
setIsDirty(originalPipelineYAMLFileContent !== pipelineAsYAML)
}, [originalPipelineYAMLFileContent, pipelineAsYAML])
// set initial CTA title
useEffect(() => {
setSelectedOption(isDirty ? pipelineSaveAndRunOption : pipelineRunOption)
}, [isDirty])
const handleSaveAndRun = (option: PipelineSaveAndRunOption): void => {
if ([PipelineSaveAndRunAction.SAVE_AND_RUN, PipelineSaveAndRunAction.SAVE].includes(option?.action)) {
try {
const data: OpenapiCommitFilesRequest = {
actions: [
{
action: isExistingPipeline ? 'UPDATE' : 'CREATE',
path: pipelineData?.config_path,
payload: pipelineAsYAML,
sha: isExistingPipeline ? pipelineYAMLFileContent?.sha : ''
}
],
branch: pipelineData?.default_branch || '',
title: `${isExistingPipeline ? getString('updated') : getString('created')} pipeline ${pipeline}`,
message: ''
}
mutate(data)
.then(() => {
fetchPipelineYAMLFileContent()
clearToaster()
showSuccess(getString(isExistingPipeline ? 'pipelines.updated' : 'pipelines.created'))
if (option?.action === PipelineSaveAndRunAction.SAVE_AND_RUN && repoMetadata && pipeline) {
openRunPipelineModal({ repoMetadata, pipeline })
}
setSelectedOption(pipelineRunOption)
})
.catch(error => {
showError(getErrorMessage(error), 0, 'pipelines.failedToSavePipeline')
})
} catch (exception) {
showError(getErrorMessage(exception), 0, 'pipelines.failedToSavePipeline')
}
}
}
const updatePipeline = (payload: Record<string, any>): Record<string, any> => {
const pipelineAsObjClone = { ...pipelineAsObj }
const stepInsertPath = version === YamlVersion.V0 ? 'steps' : '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
}
const addUpdatePluginToPipelineYAML = (_isUpdate: boolean, pluginFormData: Record<string, any>): void => {
try {
const updatedPipelineAsObj = updatePipeline(pluginFormData)
setPipelineAsObj(updatedPipelineAsObj)
setPipelineAsYaml(stringify(updatedPipelineAsObj))
} catch (ex) {}
}
const renderCTA = useCallback(() => {
switch (selectedOption?.action) {
case PipelineSaveAndRunAction.RUN:
return (
<Button
variation={ButtonVariation.PRIMARY}
text={getString('run')}
onClick={() => {
if (repoMetadata && pipeline) {
openRunPipelineModal({ repoMetadata, pipeline })
}
}}
/>
)
case PipelineSaveAndRunAction.SAVE:
case PipelineSaveAndRunAction.SAVE_AND_RUN:
return isExistingPipeline ? (
<Button
variation={ButtonVariation.PRIMARY}
text={getString('save')}
onClick={() => {
handleSaveAndRun(pipelineSaveOption)
}}
disabled={loading || !isDirty}
/>
) : (
<SplitButton
text={selectedOption?.title}
disabled={loading || !isDirty}
variation={ButtonVariation.PRIMARY}
popoverProps={{
interactionKind: 'click',
usePortal: true,
position: PopoverPosition.BOTTOM_RIGHT,
transitionDuration: 1000
}}
intent="primary"
onClick={() => handleSaveAndRun(selectedOption)}>
{pipelineSaveAndRunOptions.map(option => {
return (
<Menu.Item
key={option.title}
text={
<Text color={Color.BLACK} font={{ variation: FontVariation.SMALL_BOLD }}>
{option.title}
</Text>
}
onClick={() => {
setSelectedOption(option)
}}
/>
)
})}
</SplitButton>
)
default:
return <></>
}
}, [loading, fetchingPipeline, isDirty, repoMetadata, pipeline, selectedOption, isExistingPipeline, pipelineAsYAML])
return (
<>
<Container className={css.main}>
<PageHeader
title={getString('pipelines.editPipeline', { pipeline })}
breadcrumbs={
<Container className={css.header}>
<Layout.Horizontal spacing="small" className={css.breadcrumb}>
<Link to={routes.toCODEPipelines({ repoPath })}>{getString('pageTitle.pipelines')}</Link>
<Icon name="main-chevron-right" size={8} color={Color.GREY_500} />
<Text font={{ size: 'small' }}>{pipeline}</Text>
</Layout.Horizontal>
</Container>
}
content={<Layout.Horizontal flex={{ justifyContent: 'space-between' }}>{renderCTA()}</Layout.Horizontal>}
/>
<PageBody>
<LoadingSpinner visible={fetchingPipeline || fetchingPipelineYAMLFileContent} />
<Layout.Horizontal>
<Container className={css.editorContainer}>
<MonacoSourceCodeEditor
language={'yaml'}
schema={version === YamlVersion.V0 ? pipelineSchemaV0 : pipelineSchemaV1}
source={pipelineAsYAML}
onChange={(value: string) => setPipelineAsYaml(value)}
/>
</Container>
<Container className={css.pluginsContainer}>
<PluginsPanel onPluginAddUpdate={addUpdatePluginToPipelineYAML} />
</Container>
</Layout.Horizontal>
</PageBody>
</Container>
</>
)
}
export default AddUpdatePipeline

View File

@ -0,0 +1,7 @@
export enum YamlVersion {
V0,
V1
}
export const DEFAULT_YAML_PATH_PREFIX = '.harness/'
export const DEFAULT_YAML_PATH_SUFFIX = '.yaml'

View File

@ -0,0 +1,72 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": ["pipeline"]
},
"name": {
"type": "string"
},
"trigger": {
"type": "object",
"properties": {
"event": {
"type": "string"
},
"branch": {
"type": "array",
"items": {
"type": "string"
}
},
"path": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["event"]
},
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"image": {
"type": "string"
},
"commands": {
"type": "array",
"items": {
"type": "string"
}
},
"when": {
"type": "string",
"enum": ["on_success", "on_failure", "always"]
},
"depends_on": {
"type": "array",
"items": {
"type": "string"
}
},
"environment": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": ["name", "commands"]
}
}
},
"required": ["kind", "name", "steps"]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
.main {
min-height: var(--page-height);
background-color: var(--primary-bg) !important;
.layout {
align-items: center;
}
}

View File

@ -1,4 +0,0 @@
/* eslint-disable */
// This is an auto-generated file
export declare const layout: string
export declare const main: string

View File

@ -1,15 +0,0 @@
import React from 'react'
import { Container, PageHeader } from '@harnessio/uicore'
import { useStrings } from 'framework/strings'
import css from './NewPipeline.module.scss'
const NewPipeline = () => {
const { getString } = useStrings()
return (
<Container className={css.main}>
<PageHeader title={getString('pipelines.newPipelineButton')} />
</Container>
)
}
export default NewPipeline

View File

@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react'
import { Classes, Menu, MenuItem, Popover, Position } from '@blueprintjs/core'
import {
Avatar,
Button,
@ -33,6 +34,7 @@ import { RepositoryPageHeader } from 'components/RepositoryPageHeader/Repository
import { ExecutionStatus, ExecutionState } from 'components/ExecutionStatus/ExecutionStatus'
import { getStatus } from 'utils/PipelineUtils'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import useNewPipelineModal from 'components/NewPipelineModal/NewPipelineModal'
import { useGetSpaceParam } from 'hooks/useGetSpaceParam'
import usePipelineEventStream from 'hooks/usePipelineEventStream'
import noPipelineImage from '../RepositoriesListing/no-repo.svg'
@ -40,13 +42,13 @@ import css from './PipelineList.module.scss'
const PipelineList = () => {
const { routes } = useAppContext()
const space = useGetSpaceParam()
const history = useHistory()
const { getString } = useStrings()
const [searchTerm, setSearchTerm] = useState<string | undefined>()
const pageBrowser = useQueryParams<PageBrowserProps>()
const pageInit = pageBrowser.page ? parseInt(pageBrowser.page) : 1
const [page, setPage] = usePageIndex(pageInit)
const space = useGetSpaceParam()
const { repoMetadata, error, loading, refetch } = useGetRepositoryMetadata()
@ -62,6 +64,7 @@ const PipelineList = () => {
debounce: 500
})
const { openModal } = useNewPipelineModal()
//TODO - do not want to show load between refetchs - remove if/when we move to event stream method
const [isInitialLoad, setIsInitialLoad] = useState(true)
@ -90,15 +93,17 @@ const PipelineList = () => {
variation={ButtonVariation.PRIMARY}
icon="plus"
onClick={() => {
history.push(routes.toCODEPipelinesNew({ space }))
}}></Button>
openModal({ repoMetadata })
}}
disabled={loading}
/>
)
const columns: Column<TypesPipeline>[] = useMemo(
() => [
{
Header: getString('pipelines.name'),
width: 'calc(50% - 90px)',
width: 'calc(100% - 210px)',
Cell: ({ row }: CellProps<TypesPipeline>) => {
const record = row.original
return (
@ -188,6 +193,46 @@ const PipelineList = () => {
)
},
disableSortBy: true
},
{
Header: ' ',
width: '30px',
Cell: ({ row }: CellProps<TypesPipeline>) => {
const [menuOpen, setMenuOpen] = useState(false)
const record = row.original
const { uid } = record
return (
<Popover
isOpen={menuOpen}
onInteraction={nextOpenState => {
setMenuOpen(nextOpenState)
}}
className={Classes.DARK}
position={Position.BOTTOM_RIGHT}>
<Button
variation={ButtonVariation.ICON}
icon="Options"
data-testid={`menu-${record.uid}`}
onClick={e => {
e.stopPropagation()
setMenuOpen(true)
}}
/>
<Menu>
<MenuItem
icon="edit"
text={getString('edit')}
onClick={e => {
e.stopPropagation()
history.push(
routes.toCODEPipelineEdit({ repoPath: repoMetadata?.path || '', pipeline: uid as string })
)
}}
/>
</Menu>
</Popover>
)
}
}
],
[getString, repoMetadata?.path, routes, searchTerm]

View File

@ -88,6 +88,7 @@ export interface SourceCodeEditorProps {
autoHeight?: boolean
wordWrap?: boolean
onChange?: (value: string) => void
schema?: Record<string, any>
}
// Monaco editor has a bug where when its value is set, the value

View File

@ -1387,6 +1387,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json-schema@^7.0.0":
version "7.0.12"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@ -6809,6 +6814,11 @@ json5@^1.0.1, json5@^1.0.2:
dependencies:
minimist "^1.2.0"
jsonc-parser@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@ -7718,7 +7728,7 @@ moment@^2.25.3:
monaco-editor-webpack-plugin@^7.1.0:
version "7.1.0"
resolved "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz#16f265c2b5dbb5fe08681b6b3b7d00d3c5b2ee97"
resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz#16f265c2b5dbb5fe08681b6b3b7d00d3c5b2ee97"
integrity sha512-ZjnGINHN963JQkFqjjcBtn1XBtUATDZBMgNQhDQwd78w2ukRhFXAPNgWuacaQiDZsUr4h1rWv5Mv6eriKuOSzA==
dependencies:
loader-utils "^2.0.2"
@ -7728,6 +7738,32 @@ monaco-editor@^0.40.0:
resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.40.0.tgz#d10834e15ad50a15ec61fd01892e508464ebe2fe"
integrity sha512-1wymccLEuFSMBvCk/jT1YDW/GuxMLYwnFwF9CDyYCxoTw2Pt379J3FUhwy9c43j51JdcxVPjwk0jm0EVDsBS2g==
monaco-marker-data-provider@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/monaco-marker-data-provider/-/monaco-marker-data-provider-1.1.1.tgz#0ca69f367152f5aa12cec2bda95f32b7403e876f"
integrity sha512-PGB7TJSZE5tmHzkxv/OEwK2RGNC2A7dcq4JRJnnj31CUAsfmw0Gl+1QTrH0W0deKhcQmQM0YVPaqgQ+0wCt8Mg==
monaco-worker-manager@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/monaco-worker-manager/-/monaco-worker-manager-2.0.1.tgz#f67c54dfca34ed4b225d5de84e77b24b4e36de8a"
integrity sha512-kdPL0yvg5qjhKPNVjJoym331PY/5JC11aPJXtCZNwWRvBr6jhkIamvYAyiY5P1AWFmNOy0aRDRoMdZfa71h8kg==
monaco-yaml@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/monaco-yaml/-/monaco-yaml-4.0.4.tgz#b05283a5539bf109d228982bd25772664651af96"
integrity sha512-qbM36fY1twpDUs4lhhxoXDQGUPVyYAFCPJi3E0JKgLioD8wzsD/pawgauFFXSzpMa09z8wbt/DTLXjXEehnVFA==
dependencies:
"@types/json-schema" "^7.0.0"
jsonc-parser "^3.0.0"
monaco-marker-data-provider "^1.0.0"
monaco-worker-manager "^2.0.0"
path-browserify "^1.0.0"
prettier "^2.0.0"
vscode-languageserver-textdocument "^1.0.0"
vscode-languageserver-types "^3.0.0"
vscode-uri "^3.0.0"
yaml "^2.0.0"
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@ -8336,6 +8372,11 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
path-browserify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-case@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"
@ -8589,6 +8630,11 @@ prepend-http@^1.0.1:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==
prettier@^2.0.0:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prettier@^2.3.2, prettier@^2.6.2:
version "2.8.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
@ -10979,6 +11025,21 @@ vfile@^5.0.0:
unist-util-stringify-position "^3.0.0"
vfile-message "^3.0.0"
vscode-languageserver-textdocument@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0"
integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==
vscode-languageserver-types@^3.0.0:
version "3.17.3"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
vscode-uri@^3.0.0:
version "3.0.7"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8"
integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
@ -11417,6 +11478,11 @@ yaml@^1.10.0, yaml@^1.7.2, yaml@^1.8.3:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.0.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144"
integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==
yargs-parser@20.x:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"