import React, { 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 { Container, PageHeader, PageBody, Button, Layout, ButtonVariation, Text, useToaster } from '@harnessio/uicore' import { Icon } from '@harnessio/icons' import { Color } 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 pipelineSchema from './schema/pipeline-schema.json' import css from './AddUpdatePipeline.module.scss' const StarterPipeline: Record = { version: 1, stages: [ { type: 'ci', spec: { steps: [ { type: 'script', spec: { run: 'echo hello world' } } ] } } ] } const AddUpdatePipeline = (): JSX.Element => { const { routes } = useAppContext() const { getString } = useStrings() const { pipeline } = useParams() const { repoMetadata } = useGetRepositoryMetadata() const { showError } = useToaster() const [pipelineAsObj, setPipelineAsObj] = useState>(StarterPipeline) const [pipelineAsYAML, setPipelineAsYaml] = useState('') const { openModal: openRunPipelineModal } = useRunPipelineModal() const repoPath = useMemo(() => repoMetadata?.path || '', [repoMetadata]) const [isExistingPipeline, setIsExistingPipeline] = useState(false) const { mutate, loading } = useMutate({ verb: 'POST', path: `/api/v1/repos/${repoPath}/+/commits` }) // Fetch pipeline metadata to fetch pipeline YAML file content const { data: pipelineData, loading: fetchingPipeline } = useGet({ path: `/api/v1/repos/${repoPath}/+/pipelines/${pipeline}`, lazy: !repoMetadata }) const { data: pipelineYAMLFileContent, loading: resourceLoading } = useGetResourceContent({ repoMetadata, gitRef: pipelineData?.default_branch || '', resourcePath: pipelineData?.config_path || '' }) useEffect(() => { if (!resourceLoading) { setIsExistingPipeline(!isEmpty(pipelineYAMLFileContent) && !isUndefined(pipelineYAMLFileContent.content)) } }, [pipelineYAMLFileContent, resourceLoading]) useEffect(() => { if (isExistingPipeline) { setPipelineAsYaml(decodeGitContent((pipelineYAMLFileContent?.content as RepoFileContent)?.data)) } else { try { setPipelineAsYaml(stringify(pipelineAsObj)) } catch (ex) {} } }, [pipelineYAMLFileContent]) const handleSaveAndRun = (): void => { try { const data: OpenapiCommitFilesRequest = { actions: [ { action: isExistingPipeline ? 'UPDATE' : 'CREATE', path: pipelineData?.config_path, payload: pipelineAsYAML, sha: isExistingPipeline ? pipelineYAMLFileContent?.sha : '' } ], branch: repoMetadata?.default_branch, new_branch: '', title: `${isExistingPipeline ? getString('updated') : getString('created')} pipeline ${pipeline}`, message: '' } mutate(data) .then(() => { if (repoMetadata && pipeline) { openRunPipelineModal({ repoMetadata, pipeline }) } }) .catch(error => { showError(getErrorMessage(error), 0, 'pipelines.failedToSavePipeline') }) } catch (exception) { showError(getErrorMessage(exception), 0, 'pipelines.failedToCreatePipeline') } } const updatePipeline = (payload: Record): Record => { const pipelineAsObjClone = { ...pipelineAsObj } let existingSteps: [unknown] = get(pipelineAsObjClone, 'stages.0.spec.steps', []) if (existingSteps.length > 0) { existingSteps.push(payload) } else { existingSteps = [payload] } set(pipelineAsObjClone, 'stages.0.spec.steps', existingSteps) return pipelineAsObjClone } const addUpdatePluginToPipelineYAML = (_isUpdate: boolean, pluginFormData: Record): void => { try { const updatedPipelineAsObj = updatePipeline(pluginFormData) setPipelineAsObj(updatedPipelineAsObj) setPipelineAsYaml(stringify(updatedPipelineAsObj)) } catch (ex) {} } return ( <> {getString('pageTitle.pipelines')} {pipeline} } content={