diff --git a/package-lock.json b/package-lock.json index 60db5a5e7..a91665901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.2.3", + "version": "1.2.3-beta-7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.2.3", + "version": "1.2.3-beta-7", "license": "ISC", "dependencies": { "@types/react-dates": "^21.8.6", diff --git a/package.json b/package.json index fced3982a..01ac9e1c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.2.3", + "version": "1.2.3-beta-7", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Assets/Icon/ic-file-code.svg b/src/Assets/Icon/ic-file-code.svg index 304ac012f..981543bf7 100644 --- a/src/Assets/Icon/ic-file-code.svg +++ b/src/Assets/Icon/ic-file-code.svg @@ -2,6 +2,6 @@ - Copyright (c) 2024. Devtron Inc. --> - - + + diff --git a/src/Assets/Icon/ic-medal.svg b/src/Assets/Icon/ic-medal.svg new file mode 100644 index 000000000..e16ec3dc9 --- /dev/null +++ b/src/Assets/Icon/ic-medal.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Assets/Icon/ic-stamp.svg b/src/Assets/Icon/ic-stamp.svg index 573cb0a0c..3295d60e3 100644 --- a/src/Assets/Icon/ic-stamp.svg +++ b/src/Assets/Icon/ic-stamp.svg @@ -2,8 +2,6 @@ - Copyright (c) 2024. Devtron Inc. --> - - - - + + diff --git a/src/Assets/Icon/ic-visibility-on.svg b/src/Assets/Icon/ic-visibility-on.svg deleted file mode 100644 index 5649af020..000000000 --- a/src/Assets/Icon/ic-visibility-on.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/src/Common/CodeEditor/CodeEditor.reducer.ts b/src/Common/CodeEditor/CodeEditor.reducer.ts index 8b22e1923..582f76e0f 100644 --- a/src/Common/CodeEditor/CodeEditor.reducer.ts +++ b/src/Common/CodeEditor/CodeEditor.reducer.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import YAML from 'yaml' +import { noop, YAMLStringify } from '@Common/Helper' import { MODES } from '../Constants' import { Action, CodeEditorInitialValueType, CodeEditorState, CodeEditorThemesKeys } from './types' @@ -34,16 +36,39 @@ export const CodeEditorReducer = (state: CodeEditorState, action: Action) => { } } +export const parseValueToCode = (value: string, mode: string, tabSize: number) => { + let obj = null + + try { + obj = JSON.parse(value) + } catch { + try { + obj = YAML.parse(value) + } catch { + noop() + } + } + + let final = value + + if (obj) { + final = mode === MODES.JSON ? JSON.stringify(obj, null, tabSize) : YAMLStringify(obj) + } + + return final +} + export const initialState = ({ mode, theme, value, diffView, noParsing, + tabSize, }: CodeEditorInitialValueType): CodeEditorState => ({ mode: mode as MODES, theme: (theme || CodeEditorThemesKeys.vs) as CodeEditorThemesKeys, - code: value, + code: parseValueToCode(value, mode, tabSize), diffMode: diffView, noParsing: [MODES.JSON, MODES.YAML].includes(mode as MODES) ? noParsing : true, }) diff --git a/src/Common/CodeEditor/CodeEditor.tsx b/src/Common/CodeEditor/CodeEditor.tsx index 62128992a..05e23ca53 100644 --- a/src/Common/CodeEditor/CodeEditor.tsx +++ b/src/Common/CodeEditor/CodeEditor.tsx @@ -16,7 +16,6 @@ import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react' import MonacoEditor, { MonacoDiffEditor } from 'react-monaco-editor' -import YAML from 'yaml' import ReactGA from 'react-ga4' import * as monaco from 'monaco-editor/esm/vs/editor/editor.api' import { configureMonacoYaml } from 'monaco-yaml' @@ -27,7 +26,7 @@ import { ReactComponent as ErrorIcon } from '../../Assets/Icon/ic-error-exclamat import './codeEditor.scss' import 'monaco-editor' -import { YAMLStringify, cleanKubeManifest, useJsonYaml } from '../Helper' +import { cleanKubeManifest, useEffectAfterMount, useJsonYaml } from '../Helper' import { useWindowSize } from '../Hooks' import Select from '../Select/Select' import RadioGroup from '../RadioGroup/RadioGroup' @@ -41,8 +40,8 @@ import { CodeEditorThemesKeys, InformationBarProps, } from './types' -import { CodeEditorReducer, initialState } from './CodeEditor.reducer' -import { MODES } from '../Constants' +import { CodeEditorReducer, initialState, parseValueToCode } from './CodeEditor.reducer' +import { DEFAULT_JSON_SCHEMA_URI, MODES } from '../Constants' const CodeEditorContext = React.createContext(null) @@ -76,7 +75,7 @@ const CodeEditor: React.FC & CodeEditorComposition = React. customLoader, focus, validatorSchema, - chartVersion, + schemaURI = DEFAULT_JSON_SCHEMA_URI, isKubernetes = true, cleanData = false, onBlur, @@ -93,16 +92,13 @@ const CodeEditor: React.FC & CodeEditorComposition = React. const monacoRef = useRef(null) const { width, height: windowHeight } = useWindowSize() const memoisedReducer = React.useCallback(CodeEditorReducer, []) - const [state, dispatch] = useReducer(memoisedReducer, initialState({ mode, theme, value, diffView, noParsing })) + const [state, dispatch] = useReducer(memoisedReducer, initialState({ mode, theme, value, diffView, noParsing, tabSize })) const [, json, yamlCode, error] = useJsonYaml(state.code, tabSize, state.mode, !state.noParsing) const [, originalJson, originlaYaml] = useJsonYaml(defaultValue, tabSize, state.mode, !state.noParsing) const [contentHeight, setContentHeight] = useState( adjustEditorHeightToContent ? INITIAL_HEIGHT_WHEN_DYNAMIC_HEIGHT : height, ) - /** - * TODO: can be removed with this new merge into react-monaco-editor :) - * see: https://github.com/react-monaco-editor/react-monaco-editor/pull/955 - * */ + // TODO: upgrade to 0.56.2 to remove this const onChangeRef = useRef(onChange) onChangeRef.current = onChange monaco.editor.defineTheme(CodeEditorThemesKeys.vsDarkDT, { @@ -230,7 +226,7 @@ const CodeEditor: React.FC & CodeEditorComposition = React. isKubernetes, schemas: [ { - uri: `https://github.com/devtron-labs/devtron/tree/main/scripts/devtron-reference-helm-charts/reference-chart_${chartVersion}/schema.json`, // id of the first schema + uri: schemaURI, fileMatch: ['*'], // associate with our model schema: validatorSchema, }, @@ -240,7 +236,7 @@ const CodeEditor: React.FC & CodeEditorComposition = React. config.dispose() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [validatorSchema, chartVersion]) + }, [validatorSchema, schemaURI]) useEffect(() => { if (!editorRef.current) { return @@ -260,28 +256,18 @@ const CodeEditor: React.FC & CodeEditorComposition = React. onChangeRef.current?.(value) } - useEffect(() => { + useEffectAfterMount(() => { if (noParsing) { setCode(value) return } - let obj + if (value === state.code) { return } - try { - obj = JSON.parse(value) - } catch (err) { - try { - obj = YAML.parse(value) - } catch (err) {} - } - let final = value - if (obj) { - final = state.mode === 'json' ? JSON.stringify(obj, null, tabSize) : YAMLStringify(obj) - } - setCode(final) + + setCode(parseValueToCode(value, state.mode, tabSize)) }, [value, noParsing]) useEffect(() => { diff --git a/src/Common/CodeEditor/types.ts b/src/Common/CodeEditor/types.ts index 280c51a2c..2c7910cf6 100644 --- a/src/Common/CodeEditor/types.ts +++ b/src/Common/CodeEditor/types.ts @@ -46,7 +46,7 @@ interface CodeEditorBaseInterface { validatorSchema?: any isKubernetes?: boolean cleanData?: boolean - chartVersion?: any + schemaURI?: string /** * If true, disable the in-built search of monaco editor * @default false @@ -109,6 +109,7 @@ export interface CodeEditorInitialValueType { theme?: string value: string noParsing?: boolean + tabSize: number } export interface CodeEditorState { diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 1cb08f694..4fc0796fc 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -23,6 +23,7 @@ export const Host = window?.__ORCHESTRATOR_ROOT__ ?? '/orchestrator' export const DOCUMENTATION_HOME_PAGE = 'https://docs.devtron.ai' export const DOCUMENTATION_VERSION = '/v/v0.7' export const DISCORD_LINK = 'https://discord.devtron.ai/' +export const DEFAULT_JSON_SCHEMA_URI = 'https://json-schema.org/draft/2020-12/schema' export const DOCUMENTATION = { APP_METRICS: `${DOCUMENTATION_HOME_PAGE}${DOCUMENTATION_VERSION}/usage/applications/app-details/app-metrics`, APP_TAGS: `${DOCUMENTATION_HOME_PAGE}${DOCUMENTATION_VERSION}/usage/applications/create-application#tags`, @@ -514,6 +515,7 @@ export const API_STATUS_CODES = { EXPECTATION_FAILED: 417, UNPROCESSABLE_ENTITY: 422, LOCKED: 423, + UNPROCESSABLE_CONTENT: 422, } export enum SERVER_MODE { diff --git a/src/Common/CustomTagSelector/ResizableTagTextArea.tsx b/src/Common/CustomTagSelector/ResizableTagTextArea.tsx index e4a765e04..020d17d09 100644 --- a/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +++ b/src/Common/CustomTagSelector/ResizableTagTextArea.tsx @@ -48,11 +48,11 @@ export const ResizableTagTextArea = ({ const reInitHeight = () => { refVar.current.style.height = `${minHeight}px` - if (dependentRef) { + if (dependentRef?.current) { dependentRef.current.style.height = `${minHeight}px` } let nextHeight = refVar.current.scrollHeight - if (dependentRef && nextHeight < dependentRef.current.scrollHeight) { + if (dependentRef?.current && nextHeight < dependentRef.current.scrollHeight) { nextHeight = dependentRef.current.scrollHeight } if (minHeight && nextHeight < minHeight) { @@ -62,7 +62,7 @@ export const ResizableTagTextArea = ({ nextHeight = maxHeight } refVar.current.style.height = `${nextHeight}px` - if (dependentRef) { + if (dependentRef?.current) { dependentRef.current.style.height = `${nextHeight}px` } } diff --git a/src/Common/Helper.tsx b/src/Common/Helper.tsx index 6c7f4869b..48c86d97e 100644 --- a/src/Common/Helper.tsx +++ b/src/Common/Helper.tsx @@ -17,7 +17,7 @@ import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react' import DOMPurify from 'dompurify' import { JSONPath, JSONPathOptions } from 'jsonpath-plus' -import { compare as compareJSON, applyPatch, unescapePathComponent } from 'fast-json-patch' +import { compare as compareJSON, applyPatch, unescapePathComponent,deepClone } from 'fast-json-patch' import { components } from 'react-select' import * as Sentry from '@sentry/browser' import moment from 'moment' @@ -667,7 +667,7 @@ export const flatMapOfJSONPaths = ( export const applyCompareDiffOnUneditedDocument = (uneditedDocument: object, editedDocument: object) => { const patch = compareJSON(uneditedDocument, editedDocument) - return applyPatch(uneditedDocument, patch).newDocument + return applyPatch(deepClone(uneditedDocument), patch).newDocument } /** diff --git a/src/Common/Markdown/MarkDown.tsx b/src/Common/Markdown/MarkDown.tsx index 251d8b9b8..c13db8853 100644 --- a/src/Common/Markdown/MarkDown.tsx +++ b/src/Common/Markdown/MarkDown.tsx @@ -22,7 +22,15 @@ import './markdown.scss' const renderer = new marked.Renderer() -const MarkDown = ({ setExpandableIcon, markdown, className, breaks, disableEscapedText, ...props }: MarkDownProps) => { +const MarkDown = ({ + setExpandableIcon, + markdown: markdownProp, + className, + breaks, + disableEscapedText, + ...props +}: MarkDownProps) => { + const markdown = markdownProp || '' const mdeRef = useRef(null) const getHeight = () => { diff --git a/src/Common/RJSF/index.ts b/src/Common/RJSF/index.ts index 884e688da..b60efb258 100644 --- a/src/Common/RJSF/index.ts +++ b/src/Common/RJSF/index.ts @@ -15,6 +15,6 @@ */ export { RJSFForm } from './Form' -export * from './types' +export type * from './types' export { getInferredTypeFromValueType, getRedirectionProps } from './utils' export { HIDE_SUBMIT_BUTTON_UI_SCHEMA } from './constants' diff --git a/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/index.ts b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/index.ts new file mode 100644 index 000000000..c818465fe --- /dev/null +++ b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/index.ts @@ -0,0 +1,2 @@ +export { ViewError as GUIViewError } from './utils' +export type { ViewErrorType as GUIViewErrorType } from './types' diff --git a/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/types.ts b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/types.ts new file mode 100644 index 000000000..49d33baa5 --- /dev/null +++ b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/types.ts @@ -0,0 +1 @@ +export interface ViewErrorType extends Record<'title' | 'subTitle', string> {} diff --git a/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/utils.ts b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/utils.ts new file mode 100644 index 000000000..624d45e91 --- /dev/null +++ b/src/Pages/App/AppConfiguration/DeploymentTemplate/GUIView/utils.ts @@ -0,0 +1,12 @@ +import { ViewErrorType } from './types' + +export class ViewError implements ViewErrorType { + title: string = '' + + subTitle: string = '' + + constructor(title: string, subTitle: string) { + this.title = title + this.subTitle = subTitle + } +} diff --git a/src/Pages/App/AppConfiguration/DeploymentTemplate/index.ts b/src/Pages/App/AppConfiguration/DeploymentTemplate/index.ts new file mode 100644 index 000000000..8719f43d5 --- /dev/null +++ b/src/Pages/App/AppConfiguration/DeploymentTemplate/index.ts @@ -0,0 +1 @@ +export * from './GUIView' diff --git a/src/Pages/App/AppConfiguration/index.ts b/src/Pages/App/AppConfiguration/index.ts new file mode 100644 index 000000000..1dab5ba37 --- /dev/null +++ b/src/Pages/App/AppConfiguration/index.ts @@ -0,0 +1 @@ +export * from './DeploymentTemplate' diff --git a/src/Pages/App/index.ts b/src/Pages/App/index.ts new file mode 100644 index 000000000..308b9d9ae --- /dev/null +++ b/src/Pages/App/index.ts @@ -0,0 +1 @@ +export * from './AppConfiguration' diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/service.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/service.ts index effb28d2a..06cd0af09 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/service.ts +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/service.ts @@ -24,7 +24,11 @@ export const getDeploymentManifest = async ( valuesAndManifestFlag: ValuesAndManifestFlagDTO.MANIFEST, } - return post(ROUTES.APP_TEMPLATE_DATA, payload, { signal: abortSignal }) + const response = await post(ROUTES.APP_TEMPLATE_DATA, payload, { + signal: abortSignal, + }) + + return response } catch (error) { if (!getIsRequestAborted(error)) { showError(error) diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/types.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/types.ts index 0d4cbbcc1..f7660c177 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/types.ts +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/DeploymentTemplate/types.ts @@ -1,4 +1,5 @@ import { DraftMetadataDTO, TemplateListType } from '@Shared/Services' +import { ServerErrors } from '@Common/ServerError' import { OverrideMergeStrategyType } from '../types' export type DeploymentChartOptionkind = 'base' | 'env' | 'chartVersion' | 'deployment' @@ -127,7 +128,7 @@ interface BaseDeploymentTemplateConfigState { mergeStrategy?: never } -interface EnvironmentOverrideDeploymentTemplateConfigState { +type EnvironmentOverrideDeploymentTemplateConfigState = { chartConfig?: never isOverridden: boolean environmentConfig: EnvironmentConfigType @@ -136,7 +137,7 @@ interface EnvironmentOverrideDeploymentTemplateConfigState { export interface DeploymentTemplateConfigCommonState extends SelectedChartDetailsType { /** - * The first ever state of the deployment template + * The first ever state of the deployment template on editor */ originalTemplate: Record isAppMetricsEnabled: boolean @@ -146,6 +147,25 @@ export interface DeploymentTemplateConfigCommonState extends SelectedChartDetail latestDraft?: DraftMetadataDTO editorTemplate: string editorTemplateWithoutLockedKeys: string + // Have added the mergedTemplate states in base as well so as to have a single source of truth to get the final template + /** + * This final template to be applied on the deployment in string format + * In current editor, this may be null initially + */ + mergedTemplate: string | null + /** + * This final template to be applied on the deployment without locked keys in string format + * In current editor, this may be null initially + */ + mergedTemplateWithoutLockedKeys: string | null + /** + * This final template to be applied on the deployment in object format + * In current editor, this may be null initially + */ + mergedTemplateObject: Record | null + + isLoadingMergedTemplate: boolean + mergedTemplateError: ServerErrors | null } export type DeploymentTemplateConfigState = DeploymentTemplateConfigCommonState & diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/OverrideStrategyTippyContent.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/OverrideStrategyTippyContent.tsx index d0a00e7bc..efdc78954 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/OverrideStrategyTippyContent.tsx +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/OverrideStrategyTippyContent.tsx @@ -1,4 +1,6 @@ -const OverrideStrategyTippyContent = () => ( +import { OverrideStrategyTippyContentProps } from './types' + +const OverrideStrategyTippyContent = ({ children }: OverrideStrategyTippyContentProps) => (

Merge strategy determines how environment configurations are combined with inherited configurations @@ -6,6 +8,8 @@ const OverrideStrategyTippyContent = () => (