diff --git a/ui/src/features/common/code-editor/yaml-editor-lazy.tsx b/ui/src/features/common/code-editor/yaml-editor-lazy.tsx index 68579cd6e..9ce700836 100644 --- a/ui/src/features/common/code-editor/yaml-editor-lazy.tsx +++ b/ui/src/features/common/code-editor/yaml-editor-lazy.tsx @@ -1,9 +1,10 @@ import Editor, { loader } from '@monaco-editor/react'; -import { Typography } from 'antd'; +import { Checkbox, Flex, Spin, Typography } from 'antd'; import type { JSONSchema4 } from 'json-schema'; import * as monaco from 'monaco-editor'; import { configureMonacoYaml } from 'monaco-yaml'; import React, { FC, useEffect, useRef } from 'react'; +import yaml from 'yaml'; import styles from './yaml-editor.module.less'; @@ -18,16 +19,64 @@ export interface YamlEditorProps { height?: string; schema?: JSONSchema4; placeholder?: string; + isLoading?: boolean; + isHideManagedFieldsDisplayed?: boolean; + label?: string; } const YamlEditor: FC = (props) => { const editorRef = useRef(null); - const { value, disabled, onChange, className, width, height, schema, placeholder } = props; + const { + value, + disabled, + onChange, + className, + width, + height, + schema, + placeholder, + isLoading, + isHideManagedFieldsDisplayed, + label + } = props; + const [hideManagedFields, setHideManagedFields] = React.useState(!!isHideManagedFieldsDisplayed); + const [managedFieldsValue, setManagedFieldsValue] = React.useState(null); - const handleOnChange = (value: string | undefined) => { - onChange?.(value); + const handleOnChange = (newValue: string | undefined) => { + onChange?.(newValue); }; + React.useEffect(() => { + try { + const data = yaml.parse(value); + + // Hide managedFields + if (hideManagedFields && data?.metadata?.managedFields) { + setManagedFieldsValue(data?.metadata?.managedFields); + delete data.metadata.managedFields; + + onChange?.(yaml.stringify(data)); + } + + // Restore managedFields + if (!hideManagedFields && managedFieldsValue) { + onChange?.( + yaml.stringify({ + ...data, + metadata: { + ...(typeof data.metadata === 'object' ? data.metadata : {}), + managedFields: managedFieldsValue + } + }) + ); + + setManagedFieldsValue(null); + } + } catch (err) { + // ignore + } + }, [hideManagedFields, value]); + useEffect(() => { configureMonacoYaml(monaco, { enableSchemaRequest: true, @@ -50,8 +99,29 @@ const YamlEditor: FC = (props) => { editorRef.current = editor; }; + if (isLoading) { + return ( + +
+ + ); + } + return ( <> + +
{label}
+ {isHideManagedFieldsDisplayed && ( + + )} +
{ return v; }); - return ; + return ; }; diff --git a/ui/src/features/project/analysis-templates/analysis-templates-list.tsx b/ui/src/features/project/analysis-templates/analysis-templates-list.tsx index 12cd9958a..5a3f17993 100644 --- a/ui/src/features/project/analysis-templates/analysis-templates-list.tsx +++ b/ui/src/features/project/analysis-templates/analysis-templates-list.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@connectrpc/connect-query'; -import { faEye, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faPencil, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, Table } from 'antd'; import { format } from 'date-fns'; @@ -14,14 +14,14 @@ import { } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; import { CreateAnalysisTemplateModal } from './create-analysis-template-modal'; -import { PreviewAnalysisTemplateModal } from './preview-analysis-template-modal'; +import { EditAnalysisTemplateModal } from './edit-analysis-template-modal'; export const AnalysisTemplatesList = () => { const { name } = useParams(); const confirm = useConfirmModal(); - const { data, refetch } = useQuery(listAnalysisTemplates, { project: name }); - const { show: showPreview } = useModal(); + const { data, isLoading, refetch } = useQuery(listAnalysisTemplates, { project: name }); + const { show: showEdit } = useModal(); const { show: showCreate } = useModal((p) => ( )); @@ -35,6 +35,7 @@ export const AnalysisTemplatesList = () => { dataSource={data?.analysisTemplates} pagination={{ hideOnSinglePage: true }} rowKey={(i) => i.metadata?.name || ''} + loading={isLoading} > title='Creation Date' @@ -62,12 +63,19 @@ export const AnalysisTemplatesList = () => { render={(_, template) => (
- } - width={700} - > - - -); diff --git a/ui/src/features/project/credentials/credentials-list.tsx b/ui/src/features/project/credentials/credentials-list.tsx index b8a0320d4..f2e0873f8 100644 --- a/ui/src/features/project/credentials/credentials-list.tsx +++ b/ui/src/features/project/credentials/credentials-list.tsx @@ -27,7 +27,7 @@ export const CredentialsList = () => { const { show: showCreate } = useModal(); const confirm = useConfirmModal(); - const { data, refetch } = useQuery(listCredentials, { project: name }); + const { data, isLoading, refetch } = useQuery(listCredentials, { project: name }); const { mutate } = useMutation(deleteCredentials, { onSuccess: () => { refetch(); @@ -40,6 +40,7 @@ export const CredentialsList = () => { key={data?.credentials?.length} dataSource={data?.credentials || []} rowKey={(record) => record?.metadata?.name || ''} + loading={isLoading} columns={[ { title: 'Name', diff --git a/ui/src/features/stage/analysis-run-modal.tsx b/ui/src/features/stage/analysis-run-modal.tsx index a8f4d314c..ce08dec59 100644 --- a/ui/src/features/stage/analysis-run-modal.tsx +++ b/ui/src/features/stage/analysis-run-modal.tsx @@ -1,14 +1,12 @@ import { useQuery } from '@connectrpc/connect-query'; import { Button, Modal } from 'antd'; import { useParams } from 'react-router-dom'; -import yaml from 'yaml'; import YamlEditor from '@ui/features/common/code-editor/yaml-editor-lazy'; import { ModalProps } from '@ui/features/common/modal/use-modal'; import { getAnalysisRun } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; -import { RawFormat } from "@ui/gen/service/v1alpha1/service_pb"; - -import { LoadingState } from '../common'; +import { RawFormat } from '@ui/gen/service/v1alpha1/service_pb'; +import { decodeRawData } from '@ui/utils/decode-raw-data'; type Props = ModalProps & { name: string; @@ -19,9 +17,8 @@ export const AnalysisRunModal = ({ visible, hide, name }: Props) => { const { data, isLoading } = useQuery(getAnalysisRun, { namespace: projectName, name, - format: RawFormat.YAML, + format: RawFormat.YAML }); - const manifest = new TextDecoder().decode(data?.result?.value ?? new Uint8Array()); return ( { } width={700} > - {isLoading ? ( - - ) : ( - - )} + ); }; diff --git a/ui/src/features/stage/edit-stage-modal.tsx b/ui/src/features/stage/edit-stage-modal.tsx index b3169fda5..f885fafce 100644 --- a/ui/src/features/stage/edit-stage-modal.tsx +++ b/ui/src/features/stage/edit-stage-modal.tsx @@ -10,15 +10,15 @@ import { FieldContainer } from '@ui/features/common/form/field-container'; import { ModalComponentProps } from '@ui/features/common/modal/modal-context'; import schema from '@ui/gen/schema/stages.kargo.akuity.io_v1alpha1.json'; import { - listStages, + getStage, updateResource } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery'; +import { RawFormat } from '@ui/gen/service/v1alpha1/service_pb'; +import { decodeRawData } from '@ui/utils/decode-raw-data'; import { zodValidators } from '@ui/utils/validators'; import { getStageYAMLExample } from '../project/pipelines/utils/stage-yaml-example'; -import { prepareStageToEdit, prepareStageToSave } from './utils/edit-stage-utils'; - type Props = ModalComponentProps & { projectName: string; stageName: string; @@ -29,8 +29,11 @@ const formSchema = z.object({ }); export const EditStageModal = ({ visible, hide, projectName, stageName }: Props) => { - const { data } = useQuery(listStages, { project: projectName }); - const stage = data?.stages.find((item) => item.metadata?.name === stageName); + const { data, isLoading } = useQuery(getStage, { + project: projectName, + name: stageName, + format: RawFormat.YAML + }); const { mutateAsync, isPending } = useMutation(updateResource, { onSuccess: () => hide() @@ -38,7 +41,7 @@ export const EditStageModal = ({ visible, hide, projectName, stageName }: Props) const { control, handleSubmit } = useForm({ values: { - value: prepareStageToEdit(stage) + value: decodeRawData(data) }, resolver: zodResolver(formSchema) }); @@ -46,7 +49,7 @@ export const EditStageModal = ({ visible, hide, projectName, stageName }: Props) const onSubmit = handleSubmit(async (data) => { const textEncoder = new TextEncoder(); await mutateAsync({ - manifest: textEncoder.encode(prepareStageToSave(stage, data.value)) + manifest: textEncoder.encode(data.value) }); }); @@ -74,7 +77,7 @@ export const EditStageModal = ({ visible, hide, projectName, stageName }: Props)
} > - + {({ field: { value, onChange } }) => ( )} diff --git a/ui/src/features/stage/utils/edit-stage-utils.ts b/ui/src/features/stage/utils/edit-stage-utils.ts deleted file mode 100644 index 497dfce44..000000000 --- a/ui/src/features/stage/utils/edit-stage-utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import yaml from 'yaml'; - -import { Stage } from '@ui/gen/v1alpha1/generated_pb'; - -export const prepareStageToEdit = (stage?: Stage) => { - if (!stage) return ''; - - return yaml.stringify({ - metadata: { - annotations: stage.metadata?.annotations || {}, - labels: stage.metadata?.labels || {} - }, - spec: stage.spec - }); -}; - -export const prepareStageToSave = (stage: Stage | undefined, updatedStage: string) => { - if (!stage) return ''; - - const data = yaml.parse(updatedStage); - - return yaml.stringify({ - ...stage, - ...data, - metadata: { - ...stage.metadata, - ...data.metadata - } - }); -}; diff --git a/ui/src/utils/decode-raw-data.ts b/ui/src/utils/decode-raw-data.ts new file mode 100644 index 000000000..d40f556af --- /dev/null +++ b/ui/src/utils/decode-raw-data.ts @@ -0,0 +1,17 @@ +type Data = { + result: + | { + value: unknown; + case: 'stage' | 'project' | 'analysisRun' | 'analysisTemplate'; + } + | { + value: Uint8Array; + case: 'raw'; + } + | { case: undefined; value?: undefined }; +}; + +export const decodeRawData = (data?: Data) => + new TextDecoder().decode( + data?.result?.case === 'raw' ? data?.result?.value ?? new Uint8Array() : new Uint8Array() + );