From 56c214128f65a7b1bd85323a4db81d02a3e803b1 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Thu, 3 Oct 2024 15:34:24 +0530 Subject: [PATCH] feat: add reducer for deployment template --- .../MainContent/ConfigDryRun.tsx | 3 +- .../DeploymentTemplate/DTChartSelector.tsx | 6 +- .../DeleteOverrideDialog.tsx | 7 +- .../DeploymentTemplate/DeploymentTemplate.tsx | 1193 ++++++++--------- .../DeploymentTemplateForm.tsx | 4 - .../DeploymentTemplateGUIView.tsx | 5 - .../DeploymentTemplate/constants.ts | 6 - .../MainContent/DeploymentTemplate/types.ts | 298 +++- .../MainContent/DeploymentTemplate/utils.ts | 54 - .../MainContent/DeploymentTemplate/utils.tsx | 689 ++++++++++ .../AppConfigurations/MainContent/types.ts | 1 + 11 files changed, 1542 insertions(+), 724 deletions(-) delete mode 100644 src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.ts create mode 100644 src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.tsx diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/ConfigDryRun.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/ConfigDryRun.tsx index 981e897dbb..16148adb67 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/ConfigDryRun.tsx +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/ConfigDryRun.tsx @@ -31,6 +31,7 @@ const ConfigDryRun = ({ handleChangeDryRunEditorMode, isDraftPresent, isPublishedConfigPresent, + isApprovalPending, }: ConfigDryRunProps) => { const { envId, appId } = useParams() @@ -72,7 +73,7 @@ const ConfigDryRun = ({
- {DryRunEditorModeSelect && isDraftPresent ? ( + {DryRunEditorModeSelect && isApprovalPending ? ( - + <> {customCharts.length > 0 && (
onSelectChartType(chart.name)} diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeleteOverrideDialog.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeleteOverrideDialog.tsx index b33f71d144..fe66297341 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeleteOverrideDialog.tsx +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeleteOverrideDialog.tsx @@ -18,7 +18,7 @@ const DeleteOverrideDialog = ({ environmentConfigId, handleReload, handleClose, - handleShowDeleteDraftOverrideDialog, + handleProtectionError, reloadEnvironments, }: DeleteOverrideDialogProps) => { const { appId, envId } = useParams() @@ -35,12 +35,9 @@ const DeleteOverrideDialog = ({ handleClose() handleReload() } catch (error) { - // TODO: Remove this later showError(error) - // handleConfigProtectionError(2, error, dispatch, reloadEnvironments) - // TODO: Use util from above if (error.code === 423) { - handleShowDeleteDraftOverrideDialog() + handleProtectionError() reloadEnvironments() } } finally { diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplate.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplate.tsx index 4519823e81..f262c6eea7 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplate.tsx +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplate.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, SyntheticEvent, useMemo } from 'react' +import { useEffect, SyntheticEvent, useMemo, useReducer, Reducer } from 'react' import ReactGA from 'react-ga4' import { BaseURLParams, @@ -9,13 +9,10 @@ import { useMainContext, YAMLStringify, DeploymentTemplateConfigState, - ServerErrors, Progressing, ErrorScreenManager, getResolvedDeploymentTemplate, GetResolvedDeploymentTemplateProps, - ConfigKeysWithLockType, - applyCompareDiffOnUneditedDocument, ModuleStatus, useAsync, ModuleNameMap, @@ -25,18 +22,17 @@ import { ValuesAndManifestFlagDTO, CompareFromApprovalOptionsValuesType, noop, - SelectedChartDetailsType, logExceptionToSentry, ConfigHeaderTabType, ProtectConfigTabsType, - CONFIG_HEADER_TAB_VALUES, - ConfigToolbarPopupNodeType, Button, ComponentSizeType, ButtonStyleType, ButtonVariantType, DryRunEditorMode, usePrompt, + DraftMetadataDTO, + DEFAULT_LOCKED_KEYS_CONFIG, } from '@devtron-labs/devtron-fe-common-lib' import { Prompt, useParams } from 'react-router-dom' import YAML from 'yaml' @@ -47,21 +43,29 @@ import { URLS } from '@Config/routes' import { DEFAULT_ROUTE_PROMPT_MESSAGE } from '@Config/constants' import { ReactComponent as ICClose } from '@Icons/ic-close.svg' import { - DeploymentTemplateChartStateType, - DeploymentTemplateEditorDataStateType, + DeploymentTemplateActionState, + DeploymentTemplateActionType, DeploymentTemplateProps, - ResolvedEditorTemplateType, + DeploymentTemplateStateType, + GetChartListReturnType, + GetPublishedAndBaseDeploymentTemplateReturnType, + HandleInitializeTemplatesWithoutDraftParamsType, } from './types' import { BASE_DEPLOYMENT_TEMPLATE_ENV_ID, - DEFAULT_LOCKED_KEYS_CONFIG, NO_SCOPED_VARIABLES_MESSAGE, PROTECT_BASE_DEPLOYMENT_TEMPLATE_IDENTIFIER_DTO, } from './constants' import DeploymentTemplateOptionsHeader from './DeploymentTemplateOptionsHeader' import DeploymentTemplateForm from './DeploymentTemplateForm' import DeploymentTemplateCTA from './DeploymentTemplateCTA' -import { applyCompareDiffOfTempFormDataOnOriginalData } from './utils' +import { + deploymentTemplateReducer, + getCurrentTemplateWithLockedKeys, + getDeploymentTemplateInitialState, + getEditorTemplateAndLockedKeys, + getLockedDiffModalDocuments, +} from './utils' import DeleteOverrideDialog from './DeleteOverrideDialog' import { updateBaseDeploymentTemplate, @@ -85,8 +89,6 @@ import NoPublishedVersionEmptyState from '../NoPublishedVersionEmptyState' // TODO: Verify null checks for all imports const getDraftByResourceName = importComponentFromFELibrary('getDraftByResourceName', null, 'function') const getJsonPath = importComponentFromFELibrary('getJsonPath', null, 'function') -const removeLockedKeysFromYaml = importComponentFromFELibrary('removeLockedKeysFromYaml', null, 'function') -const reapplyRemovedLockedKeysToYaml = importComponentFromFELibrary('reapplyRemovedLockedKeysToYaml', null, 'function') const getLockConfigEligibleAndIneligibleChanges: (props: { documents: Record<'edited' | 'unedited', object> lockedConfigKeysWithLockType: { config: string[]; allowed: boolean } @@ -115,104 +117,50 @@ const DeploymentTemplate = ({ const { appId, envId } = useParams() const { isSuperAdmin } = useMainContext() - const [isLoadingInitialData, setIsLoadingInitialData] = useState(true) - const [initialLoadError, setInitialLoadError] = useState(null) - // TODO: Constant - /** - * publishedChartDetails is the chart details of saved chart - */ - const [chartDetails, setChartDetails] = useState({ - charts: [], - chartsMetadata: {}, - globalChartDetails: null, - }) - /** - * Last saved template data, meant to be readonly no changes should be made to this state when saved - */ - const [publishedTemplateData, setPublishedTemplateData] = useState(null) - /** - * Last saved draft template data - * Only present in case of protected config - * Meant to be readonly - * TODO: Add null checks - * TODO: Will have to put as empty string in case draft is not available - */ - const [draftTemplateData, setDraftTemplateData] = useState(null) - const [baseDeploymentTemplateData, setBaseDeploymentTemplateData] = useState(null) - /** - * The config that we are going to feed the editor, this can be modified - */ - const [currentEditorTemplateData, setCurrentEditorTemplateData] = - useState(null) - /** - * Action to resolve scoped variables - */ - const [resolveScopedVariables, setResolveScopedVariables] = useState(false) - const [isResolvingVariables, setIsResolvingVariables] = useState(false) + const [state, dispatch] = useReducer>( + deploymentTemplateReducer, + getDeploymentTemplateInitialState({ isSuperAdmin, isEnvView: !!envId }), + ) - // TODO: Add simpleKeys: true for locked - /** - * The resolved scoped variables for editorTemplate in currentEditorTemplateData - */ - const [resolvedEditorTemplate, setResolvedEditorTemplate] = useState({ - originalTemplate: '', - templateWithoutLockedKeys: '', - }) - /** - * In case of GUI, it is of actual template, in case of compare mode, it is Default value of editor - */ - const [resolvedOriginalTemplate, setResolvedOriginalTemplate] = useState({ - originalTemplate: '', - templateWithoutLockedKeys: '', - }) - const [wasGuiOrHideLockedKeysEdited, setWasGuiOrHideLockedKeysEdited] = useState(false) - const [showDraftComments, setShowDraftComments] = useState(false) + const { + chartDetails, + lockedConfigKeysWithLockType, + publishedTemplateData, + baseDeploymentTemplateData, + draftTemplateData, + resolveScopedVariables, + isResolvingVariables, + resolvedEditorTemplate, + resolvedOriginalTemplate, + showDraftComments, + wasGuiOrHideLockedKeysEdited, + hideLockedKeys, + editMode, + configHeaderTab, + shouldMergeTemplateWithPatches, + selectedProtectionViewTab, + dryRunEditorMode, + popupNodeType, + showReadMe, + compareFromSelectedOptionValue, + lockedDiffModalState: { showLockedDiffForApproval, showLockedTemplateDiffModal }, + currentEditorTemplateData, + showDeleteDraftOverrideDialog, + showDeleteOverrideDialog, + isSaving, + showSaveChangesModal, + } = state - const [hideLockedKeys, setHideLockedKeys] = useState(false) - const [lockedConfigKeysWithLockType, setLockedConfigKeysWithLockType] = useState( - structuredClone(DEFAULT_LOCKED_KEYS_CONFIG), - ) /** - * State to show locked changes modal in case user is non super admin and is changing locked keys - * Would be showing an info bar in locked modal - * TODO: Maybe can combine state with showLockedTemplateDiffModal + * TODO: Add null checks for all draftTemplateData + * TODO: Will have to put as empty string in case draft is not available */ - const [showLockedDiffForApproval, setShowLockedDiffForApproval] = useState(false) - const [isSaving, setIsSaving] = useState(false) - const [showLockedTemplateDiffModal, setShowLockedTemplateDiffModal] = useState(false) - const [showSaveChangesModal, setShowSaveChangesModal] = useState(false) - const [popupNodeType, setPopupNodeType] = useState(null) - - // FIXME: Need clean up as well on reload for states below - const [compareFromSelectedOptionValue, setCompareFromSelectedOptionValue] = - useState(CompareFromApprovalOptionsValuesType.APPROVAL_PENDING) - - const [dryRunEditorMode, setDryRunEditorMode] = useState(DryRunEditorMode.PUBLISHED_VALUES) - - const [showDeleteOverrideDialog, setShowDeleteOverrideDialog] = useState(false) - const [showDeleteDraftOverrideDialog, setShowDeleteDraftOverrideDialog] = useState(false) + // TODO: Add simpleKeys: true for locked in resolved states const [, grafanaModuleStatus] = useAsync(() => getModuleInfo(ModuleNameMap.GRAFANA), []) - const [showReadMe, setShowReadMe] = useState(false) - const [editMode, setEditMode] = useState( - isSuperAdmin ? ConfigurationType.YAML : ConfigurationType.GUI, - ) - // Initially will set here but in case of protected will change the tab - const [configHeaderTab, setConfigHeaderTab] = useState( - envId ? CONFIG_HEADER_TAB_VALUES.OVERRIDE[0] : CONFIG_HEADER_TAB_VALUES.BASE_DEPLOYMENT_TEMPLATE[0], - ) - - const [shouldMergeTemplateWithPatches, setShouldMergeTemplateWithPatches] = useState(false) - const [selectedProtectionViewTab, setSelectedProtectionViewTab] = useState( - ProtectConfigTabsType.EDIT_DRAFT, - ) - - const isDryRunView = configHeaderTab === ConfigHeaderTabType.DRY_RUN + const isDryRunView = configHeaderTab === ConfigHeaderTabType.DRY_RUN && !showReadMe const isInheritedView = configHeaderTab === ConfigHeaderTabType.INHERITED && !showReadMe - - const isGuiSupported = !isInheritedView && !isDryRunView - const isPublishedValuesView: boolean = !!( configHeaderTab === ConfigHeaderTabType.VALUES && selectedProtectionViewTab === ProtectConfigTabsType.PUBLISHED && @@ -220,20 +168,21 @@ const DeploymentTemplate = ({ isProtected && draftTemplateData?.latestDraft ) - const isCompareView = configHeaderTab === ConfigHeaderTabType.VALUES && selectedProtectionViewTab === ProtectConfigTabsType.COMPARE && !showReadMe && isProtected - const isApprovalView = - isCompareView && - !!draftTemplateData?.latestDraft && - draftTemplateData.latestDraft.draftState === DraftState.AwaitApproval + const isGuiSupported = !isInheritedView && !isDryRunView const isDraftAvailable: boolean = isProtected && !!draftTemplateData?.latestDraft + const isApprovalPending: boolean = + isDraftAvailable && draftTemplateData.latestDraft.draftState === DraftState.AwaitApproval + + const isApprovalView = isCompareView && isApprovalPending + const showNoOverrideEmptyState = !!envId && !isDraftAvailable && @@ -244,40 +193,13 @@ const DeploymentTemplate = ({ const isApprovalPendingOptionSelected = isApprovalView && compareFromSelectedOptionValue === CompareFromApprovalOptionsValuesType.APPROVAL_PENDING - // TODO: Can rename as publishedOverriddenState const isPublishedConfigPresent = !(envId && !publishedTemplateData?.isOverridden) - const baseDeploymentTemplateURL = `${URLS.APP}/${appId}/${URLS.APP_CONFIG}/${URLS.APP_DEPLOYMENT_CONFIG}` - - const getCurrentTemplateWithLockedKeys = (): string => { - if (!currentEditorTemplateData.removedPatches.length) { - return currentEditorTemplateData.editorTemplate - } - - try { - const originalDocument = currentEditorTemplateData.originalTemplate - const parsedDocument = YAML.parse(currentEditorTemplateData.editorTemplate) - - const updatedEditorObject = reapplyRemovedLockedKeysToYaml( - parsedDocument, - currentEditorTemplateData.removedPatches, - ) - if (wasGuiOrHideLockedKeysEdited) { - return YAMLStringify(applyCompareDiffOnUneditedDocument(originalDocument, updatedEditorObject), { - simpleKeys: true, - }) - } - return YAMLStringify(updatedEditorObject, { simpleKeys: true }) - } catch { - ToastManager.showToast({ - variant: ToastVariantType.error, - description: 'Something went wrong while parsing locked keys', - }) - } - - return currentEditorTemplateData.editorTemplate - } + const baseDeploymentTemplateURL = envId + ? `${URLS.APPLICATION_GROUP}/${envId}/${URLS.APP_CONFIG}/${appId}/${URLS.APP_DEPLOYMENT_CONFIG}` + : `${URLS.APP}/${appId}/${URLS.APP_CONFIG}/${URLS.APP_DEPLOYMENT_CONFIG}` + // TODO: Can we move all handlers into utils by sending state and dispatch as params? const areChangesPresent: boolean = useMemo(() => { if (!currentEditorTemplateData) { return false @@ -288,7 +210,10 @@ const DeploymentTemplate = ({ } if (hideLockedKeys) { - const finalEditorValue = getCurrentTemplateWithLockedKeys() + const finalEditorValue = getCurrentTemplateWithLockedKeys({ + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited, + }) const isEditorTemplateChanged = finalEditorValue !== currentEditorTemplateData.originalTemplateState.editorTemplate if (isEditorTemplateChanged) { @@ -327,17 +252,20 @@ const DeploymentTemplate = ({ }) const handleRemoveResolvedVariables = () => { - setIsResolvingVariables(false) - setResolveScopedVariables(false) - ReactGA.event({ category: 'devtronapp-configuration-dt', action: 'clicked-unresolve-scoped-variable', }) + + dispatch({ + type: DeploymentTemplateActionType.UN_RESOLVE_SCOPED_VARIABLES, + }) } const handleToggleShowTemplateMergedWithPatch = () => { - setShouldMergeTemplateWithPatches((prev) => !prev) + dispatch({ + type: DeploymentTemplateActionType.TOGGLE_SHOW_COMPARISON_WITH_MERGED_PATCHES, + }) } const handleUpdateProtectedTabSelection = (tab: ProtectConfigTabsType, triggerGA: boolean = true) => { @@ -352,18 +280,24 @@ const DeploymentTemplate = ({ }) } - handleRemoveResolvedVariables() - setSelectedProtectionViewTab(tab) + dispatch({ + type: DeploymentTemplateActionType.UPDATE_PROTECTION_VIEW_TAB, + payload: { + selectedProtectionViewTab: tab, + }, + }) } const handleCompareFromOptionSelection = (option: SelectPickerOptionType) => { - setCompareFromSelectedOptionValue(option.value as CompareFromApprovalOptionsValuesType) + dispatch({ + type: DeploymentTemplateActionType.CHANGE_COMPARE_FROM_SELECTED_OPTION, + payload: { + compareFromSelectedOptionValue: option.value as CompareFromApprovalOptionsValuesType, + }, + }) } - const getChartList = async (): Promise< - Pick & - SelectedChartDetailsType - > => { + const getChartList = async (): Promise => { const chartRefResp = await getChartReferences(+appId, +envId) const { chartRefs, latestAppChartRef, latestChartRef, latestEnvChartRef, chartMetadata } = chartRefResp.result @@ -384,10 +318,6 @@ const DeploymentTemplate = ({ } } - const handleEnableWasGuiOrHideLockedKeysEdited = () => { - setWasGuiOrHideLockedKeysEdited(true) - } - const getRawEditorValueForDryRunMode = (): string => { if (!isDryRunView) { logExceptionToSentry(new Error('getRawEditorValueForDryRunMode called in non dry run mode')) @@ -423,7 +353,10 @@ const DeploymentTemplate = ({ if (hideLockedKeys) { try { - const templateWithLockedKeys = getCurrentTemplateWithLockedKeys() + const templateWithLockedKeys = getCurrentTemplateWithLockedKeys({ + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited, + }) return templateWithLockedKeys } catch { // Do nothing @@ -462,82 +395,28 @@ const DeploymentTemplate = ({ } } - const getEditorTemplateAndLockedKeys = ( - template: string, - lockedConfigKeys: string[] = lockedConfigKeysWithLockType.config, - ): Pick => { - const removedPatches: DeploymentTemplateEditorDataStateType['removedPatches'] = [] - - if (!removeLockedKeysFromYaml || !lockedConfigKeys.length) { - return { editorTemplate: template, removedPatches } - } - - // QUESTION: Should we wrap try catch here or at usage? - try { - const { document, addOperations } = removeLockedKeysFromYaml(template, lockedConfigKeys) - if (addOperations.length) { - removedPatches.push(...addOperations) - } - - const updatedTemplate = YAMLStringify(document, { - simpleKeys: true, - }) - return { editorTemplate: updatedTemplate, removedPatches } - } catch { - return { editorTemplate: template, removedPatches: [] } - } - } - const handleSetHideLockedKeys = (value: boolean) => { - if (!removeLockedKeysFromYaml || !reapplyRemovedLockedKeysToYaml) { - return - } - ReactGA.event({ category: 'devtronapp-configuration-dt', action: value ? 'clicked-hide-locked-keys' : 'clicked-show-locked-keys', }) - if (value) { - handleEnableWasGuiOrHideLockedKeysEdited() - const { editorTemplate, removedPatches } = getEditorTemplateAndLockedKeys( - currentEditorTemplateData.editorTemplate, - ) - setCurrentEditorTemplateData({ - ...currentEditorTemplateData, - editorTemplate, - removedPatches, - }) - setHideLockedKeys(true) - - return - } - - try { - const updatedEditorValue = getCurrentTemplateWithLockedKeys() - setCurrentEditorTemplateData({ - ...currentEditorTemplateData, - editorTemplate: updatedEditorValue, - removedPatches: [], - }) - setHideLockedKeys(false) - } catch { - ToastManager.showToast({ - variant: ToastVariantType.error, - description: 'Something went wrong while parsing locked keys', - }) - } + dispatch({ + type: DeploymentTemplateActionType.UPDATE_HIDE_LOCKED_KEYS, + payload: { + hideLockedKeys, + }, + }) } const handleLoadScopedVariables = async () => { // TODO: can think about adding abort controller try { - setIsResolvingVariables(true) - // TODO: check if need to enhance this - const shouldFetchDefaultTemplate: boolean = !!isGuiSupported + const shouldFetchOriginalTemplate: boolean = !!isGuiSupported + const shouldFetchPublishedTemplate: boolean = isPublishedConfigPresent && isApprovalView - const [currentEditorTemplate, defaultTemplate] = await Promise.all([ + const [currentEditorTemplate, defaultTemplate, publishedTemplate] = await Promise.all([ getResolvedDeploymentTemplate({ appId: +appId, chartRefId: currentEditorTemplateData.selectedChartRefId, @@ -545,56 +424,78 @@ const DeploymentTemplate = ({ valuesAndManifestFlag: ValuesAndManifestFlagDTO.DEPLOYMENT_TEMPLATE, ...(envId && { envId: +envId }), }), - shouldFetchDefaultTemplate + shouldFetchOriginalTemplate ? getResolvedDeploymentTemplate(getPayloadForOriginalTemplateVariables()) : null, + shouldFetchPublishedTemplate + ? getResolvedDeploymentTemplate({ + appId: +appId, + chartRefId: publishedTemplateData.selectedChartRefId, + values: publishedTemplateData.editorTemplate, + valuesAndManifestFlag: ValuesAndManifestFlagDTO.DEPLOYMENT_TEMPLATE, + ...(envId && { envId: +envId }), + }) + : null, ]) - // FIXME: In case of compare, we have to fix this - if (!currentEditorTemplate.areVariablesPresent) { + const areNoVariablesPresent = shouldFetchPublishedTemplate + ? !publishedTemplate.areVariablesPresent && !currentEditorTemplate.areVariablesPresent + : !currentEditorTemplate.areVariablesPresent + + if (areNoVariablesPresent) { ToastManager.showToast({ variant: ToastVariantType.error, description: NO_SCOPED_VARIABLES_MESSAGE, }) - handleRemoveResolvedVariables() - return - } - - // Recalculate locked keys since even variable values can be object - const { editorTemplate: resolvedEditorTemplateWithoutLockedKeys } = getEditorTemplateAndLockedKeys( - currentEditorTemplate.resolvedData, - // No need to send locked keys here since on load we do not resolve scoped variables - ) - setResolvedEditorTemplate({ - originalTemplate: currentEditorTemplate.resolvedData, - templateWithoutLockedKeys: resolvedEditorTemplateWithoutLockedKeys, - }) - - if (shouldFetchDefaultTemplate) { - const { editorTemplate: resolvedOriginalTemplateWithoutLockedKeys } = getEditorTemplateAndLockedKeys( - defaultTemplate.resolvedData, - // No need to send locked keys here since on load we do not resolve scoped variables - ) - - setResolvedOriginalTemplate({ - originalTemplate: defaultTemplate.resolvedData, - templateWithoutLockedKeys: resolvedOriginalTemplateWithoutLockedKeys, + dispatch({ + type: DeploymentTemplateActionType.UN_RESOLVE_SCOPED_VARIABLES, }) + return } - setIsResolvingVariables(false) - } catch { - handleRemoveResolvedVariables() + dispatch({ + type: DeploymentTemplateActionType.RESOLVE_SCOPED_VARIABLES, + payload: { + resolvedEditorTemplate: { + originalTemplateString: currentEditorTemplate.resolvedData, + templateWithoutLockedKeys: getEditorTemplateAndLockedKeys( + currentEditorTemplate.resolvedData, + lockedConfigKeysWithLockType.config, + ).editorTemplate, + }, + resolvedOriginalTemplate: shouldFetchOriginalTemplate + ? { + originalTemplateString: defaultTemplate.resolvedData, + templateWithoutLockedKeys: getEditorTemplateAndLockedKeys( + defaultTemplate.resolvedData, + lockedConfigKeysWithLockType.config, + ).editorTemplate, + } + : null, + resolvedPublishedTemplate: shouldFetchPublishedTemplate + ? { + originalTemplateString: publishedTemplate.resolvedData, + templateWithoutLockedKeys: getEditorTemplateAndLockedKeys( + publishedTemplate.resolvedData, + lockedConfigKeysWithLockType.config, + ).editorTemplate, + } + : null, + }, + }) + } catch (error) { + showError(error) + dispatch({ + type: DeploymentTemplateActionType.UN_RESOLVE_SCOPED_VARIABLES, + }) } } const handleToggleDraftComments = () => { - setShowDraftComments((prev) => !prev) - } - - const handleToggleShowSaveChangesModal = () => { - setShowSaveChangesModal((prev) => !prev) + dispatch({ + type: DeploymentTemplateActionType.TOGGLE_DRAFT_COMMENTS, + }) } const handleResolveScopedVariables = async () => { @@ -603,29 +504,21 @@ const DeploymentTemplate = ({ action: 'clicked-resolve-scoped-variable', }) - setResolveScopedVariables(true) + dispatch({ + type: DeploymentTemplateActionType.INITIATE_RESOLVE_SCOPED_VARIABLES, + }) await handleLoadScopedVariables() } - const handleEditorChange = (value: string) => { + const handleEditorChange = (template: string) => { if (resolveScopedVariables || isApprovalView) { return } - try { - YAML.parse(value) - setCurrentEditorTemplateData({ - ...currentEditorTemplateData, - editorTemplate: value, - unableToParseYaml: false, - }) - } catch { - setCurrentEditorTemplateData({ - ...currentEditorTemplateData, - editorTemplate: value, - unableToParseYaml: true, - }) - } + dispatch({ + type: DeploymentTemplateActionType.CURRENT_EDITOR_VALUE_CHANGE, + payload: { template }, + }) } const handleToggleResolveScopedVariables = () => { @@ -639,35 +532,33 @@ const DeploymentTemplate = ({ } const handleChangeToGUIMode = () => { - setEditMode(ConfigurationType.GUI) + dispatch({ + type: DeploymentTemplateActionType.CHANGE_TO_GUI_MODE, + }) } const handleChangeToYAMLMode = () => { - if (editMode === ConfigurationType.GUI && wasGuiOrHideLockedKeysEdited) { - try { - applyCompareDiffOfTempFormDataOnOriginalData( - // TODO: Should add simpleKeys here as well? - YAMLStringify(currentEditorTemplateData.originalTemplate), - currentEditorTemplateData.editorTemplate, - handleEditorChange, - ) - } catch { - // Do nothing - } - } - - setEditMode(ConfigurationType.YAML) + dispatch({ + type: DeploymentTemplateActionType.CHANGE_TO_YAML_MODE, + }) } const handleChangeDryRunEditorMode = (mode: DryRunEditorMode) => { - setDryRunEditorMode(mode) + dispatch({ + type: DeploymentTemplateActionType.UPDATE_DRY_RUN_EDITOR_MODE, + payload: { + dryRunEditorMode: mode, + }, + }) } const handleUpdateReadmeMode = (value: boolean) => { - handleChangeToYAMLMode() - handleRemoveResolvedVariables() - - setShowReadMe(value) + dispatch({ + type: DeploymentTemplateActionType.UPDATE_README_MODE, + payload: { + showReadMe: value, + }, + }) } const handleEnableReadmeView = () => { @@ -690,8 +581,12 @@ const DeploymentTemplate = ({ }) } - handleRemoveResolvedVariables() - setConfigHeaderTab(tab) + dispatch({ + type: DeploymentTemplateActionType.UPDATE_CONFIG_HEADER_TAB, + payload: { + configHeaderTab: tab, + }, + }) } const handleViewInheritedConfig = () => { @@ -827,7 +722,6 @@ const DeploymentTemplate = ({ readme, guiSchema, isAppMetricsEnabled, - // FIXME: IF i remove chartConfig TS is not throwing error need to check chartConfig: { id, refChartTemplate, refChartTemplateVersion, chartRefId, readme }, editorTemplate: stringifiedTemplate, editorTemplateWithoutLockedKeys, @@ -850,7 +744,6 @@ const DeploymentTemplate = ({ const { result: { - // This is the base deployment template globalConfig, // TODO: Check if null check is needed here for environmentConfig environmentConfig: { id, status, manualReviewed, active, namespace, envOverrideValues }, @@ -878,7 +771,6 @@ const DeploymentTemplate = ({ isAppMetricsEnabled: appMetrics, editorTemplate: stringifiedTemplate, isOverridden: !!IsOverride, - // FIXME: IF i remove environmentConfig TS is not throwing error need to check environmentConfig: { id, status, @@ -894,10 +786,10 @@ const DeploymentTemplate = ({ } } - const handleInitializePublishedData = async ( + const getPublishedAndBaseDeploymentTemplate = async ( chartRefsData: Awaited>, lockedConfigKeys: string[], - ): Promise => { + ): Promise => { const shouldFetchBaseDeploymentData = !envId const [templateData, baseDeploymentTemplateDataResponse] = await Promise.all([ handleFetchDeploymentTemplate(chartRefsData.selectedChart, lockedConfigKeys), @@ -906,36 +798,65 @@ const DeploymentTemplate = ({ : null, ]) - setBaseDeploymentTemplateData(shouldFetchBaseDeploymentData ? baseDeploymentTemplateDataResponse : templateData) - setPublishedTemplateData(templateData) - return templateData + return { + publishedTemplateState: templateData, + baseDeploymentTemplateState: shouldFetchBaseDeploymentData + ? baseDeploymentTemplateDataResponse + : templateData, + } } - const handleInitializeCurrentEditorWithPublishedData = (publishedData: DeploymentTemplateConfigState) => { - const clonedTemplateData = structuredClone(publishedData) + const handleInitializeTemplatesWithoutDraft = ({ + baseDeploymentTemplateState, + publishedTemplateState, + chartDetailsState, + lockedConfigKeysWithLockTypeState, + }: HandleInitializeTemplatesWithoutDraftParamsType) => { + const clonedTemplateData = structuredClone(publishedTemplateState) delete clonedTemplateData.editorTemplateWithoutLockedKeys // Since hideLockedKeys is initially false so saving it as whole - setCurrentEditorTemplateData({ + const currentEditorState: typeof state.currentEditorTemplateData = { ...clonedTemplateData, unableToParseYaml: false, removedPatches: [], - originalTemplateState: publishedData, + originalTemplateState: publishedTemplateState, + } + + dispatch({ + type: DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITHOUT_DRAFT, + payload: { + baseDeploymentTemplateData: baseDeploymentTemplateState, + publishedTemplateData: publishedTemplateState, + chartDetails: chartDetailsState, + lockedConfigKeysWithLockType: lockedConfigKeysWithLockTypeState, + currentEditorTemplateData: currentEditorState, + }, }) } - // Should it be method or should duplicate? - const handleInitializePublishedDataAndCurrentEditorData = async ( + const handleInitializePublishedAndCurrentEditorData = async ( chartRefsData: Awaited>, - lockedConfigKeys: string[], + lockedKeysConfig: typeof lockedConfigKeysWithLockType, ) => { - const publishedData = await handleInitializePublishedData(chartRefsData, lockedConfigKeys) - handleInitializeCurrentEditorWithPublishedData(publishedData) + const { publishedTemplateState, baseDeploymentTemplateState } = await getPublishedAndBaseDeploymentTemplate( + chartRefsData, + lockedKeysConfig.config, + ) + handleInitializeTemplatesWithoutDraft({ + baseDeploymentTemplateState, + publishedTemplateState, + chartDetailsState: { + charts: chartRefsData.charts, + chartsMetadata: chartRefsData.chartsMetadata, + globalChartDetails: chartRefsData.globalChartDetails, + }, + lockedConfigKeysWithLockTypeState: lockedKeysConfig, + }) } - // TODO: Needs to confirm type with BE const handleInitializeDraftData = ( - latestDraft: any, + latestDraft: DraftMetadataDTO, guiSchema: string, chartRefsData: Awaited>, lockedConfigKeys: string[], @@ -973,7 +894,6 @@ const DeploymentTemplate = ({ editorTemplateWithoutLockedKeys, } - setDraftTemplateData(response) return response } @@ -1020,17 +940,16 @@ const DeploymentTemplate = ({ mergeStrategy: mergeStrategy || DEFAULT_MERGE_STRATEGY, } - setDraftTemplateData(response) return response } // Should remove edit draft mode in case of error and show normal edit values view with zero drafts where user can save as draft? const handleLoadProtectedDeploymentTemplate = async ( chartRefsData: Awaited>, - lockedConfigKeys: string[], + lockedKeysConfig: typeof lockedConfigKeysWithLockType, ) => { // In case of error of draftResponse - const [draftPromiseResponse, publishedDataPromiseResponse] = await Promise.allSettled([ + const [draftPromiseResponse, publishedAndBaseTemplateDataResponse] = await Promise.allSettled([ getDraftByResourceName( +appId, +envId || BASE_DEPLOYMENT_TEMPLATE_ENV_ID, @@ -1040,68 +959,86 @@ const DeploymentTemplate = ({ `${environmentName}-DeploymentTemplateOverride` : PROTECT_BASE_DEPLOYMENT_TEMPLATE_IDENTIFIER_DTO, ), - handleInitializePublishedData(chartRefsData, lockedConfigKeys), + getPublishedAndBaseDeploymentTemplate(chartRefsData, lockedKeysConfig.config), ]) - if (publishedDataPromiseResponse.status === 'rejected') { - throw publishedDataPromiseResponse.reason + if (publishedAndBaseTemplateDataResponse.status === 'rejected') { + throw publishedAndBaseTemplateDataResponse.reason } - if (draftPromiseResponse.status === 'rejected') { - handleInitializeCurrentEditorWithPublishedData(publishedDataPromiseResponse.value) + const { publishedTemplateState, baseDeploymentTemplateState } = publishedAndBaseTemplateDataResponse.value + + const shouldInitializeWithoutDraft = + draftPromiseResponse.status === 'rejected' || + (draftPromiseResponse.value?.result && + (draftPromiseResponse.value.result.draftState === DraftState.Init || + draftPromiseResponse.value.result.draftState === DraftState.AwaitApproval)) + + if (shouldInitializeWithoutDraft) { + handleInitializeTemplatesWithoutDraft({ + baseDeploymentTemplateState, + publishedTemplateState, + chartDetailsState: { + charts: chartRefsData.charts, + chartsMetadata: chartRefsData.chartsMetadata, + globalChartDetails: chartRefsData.globalChartDetails, + }, + lockedConfigKeysWithLockTypeState: lockedKeysConfig, + }) return } const draftResponse = draftPromiseResponse.value // NOTE: In case of support for version based guiSchema this won't work // Since we do not have guiSchema for draft, we are using published guiSchema - const { guiSchema } = publishedDataPromiseResponse.value - - if ( - draftResponse?.result && - (draftResponse.result.draftState === DraftState.Init || - draftResponse.result.draftState === DraftState.AwaitApproval) - ) { - const latestDraft = draftResponse.result - const response = handleInitializeDraftData(latestDraft, guiSchema, chartRefsData, lockedConfigKeys) - - const clonedTemplateData = structuredClone(response) - delete clonedTemplateData.editorTemplateWithoutLockedKeys - - // Since hideLockedKeys is initially false so saving it as whole - setCurrentEditorTemplateData({ - ...clonedTemplateData, - unableToParseYaml: false, - removedPatches: [], - originalTemplateState: response, - }) + const { guiSchema } = publishedTemplateState - const isApprovalPending = latestDraft.draftState === DraftState.AwaitApproval - if (isApprovalPending) { - handleConfigHeaderTabChange(ConfigHeaderTabType.VALUES) - handleUpdateProtectedTabSelection(ProtectConfigTabsType.COMPARE, false) - return - } + const latestDraft = draftResponse.result + const draftTemplateState = handleInitializeDraftData( + latestDraft, + guiSchema, + chartRefsData, + lockedKeysConfig.config, + ) - handleConfigHeaderTabChange(ConfigHeaderTabType.INHERITED) - return - } + const clonedTemplateData = structuredClone(draftTemplateState) + delete clonedTemplateData.editorTemplateWithoutLockedKeys - // TODO: Can we move above this if - handleInitializeCurrentEditorWithPublishedData(publishedDataPromiseResponse.value) + dispatch({ + type: DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITH_DRAFT, + payload: { + baseDeploymentTemplateData: baseDeploymentTemplateState, + publishedTemplateData: publishedTemplateState, + chartDetails: { + charts: chartRefsData.charts, + chartsMetadata: chartRefsData.chartsMetadata, + globalChartDetails: chartRefsData.globalChartDetails, + }, + lockedConfigKeysWithLockType: lockedKeysConfig, + draftTemplateData: draftTemplateState, + // Since hideLockedKeys is initially false so saving it as whole + currentEditorTemplateData: { + ...clonedTemplateData, + unableToParseYaml: false, + removedPatches: [], + originalTemplateState: draftTemplateState, + }, + configHeaderTab: + isApprovalPending || !envId ? ConfigHeaderTabType.VALUES : ConfigHeaderTabType.INHERITED, + selectedProtectionViewTab: isApprovalPending + ? ProtectConfigTabsType.COMPARE + : ProtectConfigTabsType.EDIT_DRAFT, + }, + }) } // TODO: Check why inf loading is happening in case of null check error const handleInitialDataLoad = async () => { - // TODO: Can be collected together - setPublishedTemplateData(null) - setCurrentEditorTemplateData(null) - setDraftTemplateData(null) - setIsLoadingInitialData(true) - setInitialLoadError(null) + dispatch({ + type: DeploymentTemplateActionType.INITIATE_INITIAL_DATA_LOAD, + }) // TODO: Handle case where we have draft as delete override then set set draft from base deployment template - try { reloadEnvironments() const [chartRefsDataResponse, lockedKeysConfigResponse] = await Promise.allSettled([ @@ -1114,34 +1051,29 @@ const DeploymentTemplate = ({ } const chartRefsData = chartRefsDataResponse.value - // TODO: Can move block somewhere to make const - let lockedKeysConfig: typeof lockedConfigKeysWithLockType = structuredClone(DEFAULT_LOCKED_KEYS_CONFIG) - // Not handling error since user can save without locked keys - if (lockedKeysConfigResponse.status === 'fulfilled' && lockedKeysConfigResponse.value?.result) { - lockedKeysConfig = structuredClone(lockedKeysConfigResponse.value.result) - } - - setLockedConfigKeysWithLockType(lockedKeysConfig) + const isLockedConfigResponseValid = + lockedKeysConfigResponse.status === 'fulfilled' && lockedKeysConfigResponse.value?.result - setChartDetails({ - charts: chartRefsData.charts, - chartsMetadata: chartRefsData.chartsMetadata, - globalChartDetails: chartRefsData.globalChartDetails, - }) + const lockedKeysConfig: typeof lockedConfigKeysWithLockType = isLockedConfigResponseValid + ? structuredClone(lockedKeysConfigResponse.value.result) + : structuredClone(DEFAULT_LOCKED_KEYS_CONFIG) const shouldFetchDraftDetails = isProtected && typeof getDraftByResourceName === 'function' if (shouldFetchDraftDetails) { - await handleLoadProtectedDeploymentTemplate(chartRefsData, lockedKeysConfig.config) + await handleLoadProtectedDeploymentTemplate(chartRefsData, lockedKeysConfig) return } - await handleInitializePublishedDataAndCurrentEditorData(chartRefsData, lockedKeysConfig.config) + await handleInitializePublishedAndCurrentEditorData(chartRefsData, lockedKeysConfig) } catch (error) { showError(error) - setInitialLoadError(error) - } finally { - setIsLoadingInitialData(false) + dispatch({ + type: DeploymentTemplateActionType.INITIAL_DATA_ERROR, + payload: { + error, + }, + }) } } @@ -1151,22 +1083,16 @@ const DeploymentTemplate = ({ }, []) const handleReload = async () => { - setHideLockedKeys(false) - setResolveScopedVariables(false) - setWasGuiOrHideLockedKeysEdited(false) - setShowLockedDiffForApproval(false) - setShowLockedTemplateDiffModal(false) - setIsLoadingInitialData(true) - setShowReadMe(false) - // TODO: UTIL - setEditMode(isSuperAdmin ? ConfigurationType.YAML : ConfigurationType.GUI) - // TODO: Can be util BTW just fallback in case of error in data load - setConfigHeaderTab( - envId ? CONFIG_HEADER_TAB_VALUES.OVERRIDE[0] : CONFIG_HEADER_TAB_VALUES.BASE_DEPLOYMENT_TEMPLATE[0], - ) + dispatch({ + type: DeploymentTemplateActionType.RESET_ALL, + payload: { + isSuperAdmin, + isEnvView: !!envId, + }, + }) + fetchEnvConfig(+envId || BASE_DEPLOYMENT_TEMPLATE_ENV_ID) - // TODO: Check if async, and on change of isProtected maybe should re-call this - await reloadEnvironments() + reloadEnvironments() await handleInitialDataLoad() } @@ -1176,10 +1102,13 @@ const DeploymentTemplate = ({ * @param skipReadmeAndSchema - true only while doing handleSave */ const prepareDataToSave = (skipReadmeAndSchema: boolean = false, fromDeleteOverride: boolean = false) => { - if (!envId) { - const editorTemplate = getCurrentTemplateWithLockedKeys() - const editorTemplateObject: Record = YAML.parse(editorTemplate) + const editorTemplate = getCurrentTemplateWithLockedKeys({ + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited, + }) + const editorTemplateObject: Record = YAML.parse(editorTemplate) + if (!envId) { const baseRequestData = { ...(currentEditorTemplateData.chartConfig.chartRefId === currentEditorTemplateData.selectedChart.id ? currentEditorTemplateData.chartConfig @@ -1204,10 +1133,7 @@ const DeploymentTemplate = ({ if (showLockedTemplateDiffModal) { // FIXME: In case of draft edit should we do this or approval as unedited? const { eligibleChanges } = getLockConfigEligibleAndIneligibleChanges({ - documents: { - unedited: currentEditorTemplateData.originalTemplate, - edited: editorTemplateObject, - }, + documents: getLockedDiffModalDocuments(false, state), lockedConfigKeysWithLockType, }) @@ -1231,9 +1157,9 @@ const DeploymentTemplate = ({ envOverrideValues: baseDeploymentTemplateData.originalTemplate, chartRefId: chartDetails.globalChartDetails.id, IsOverride: false, - // FIXME: - isAppMetricsEnabled: currentEditorTemplateData.isAppMetricsEnabled, + isAppMetricsEnabled: baseDeploymentTemplateData.isAppMetricsEnabled, saveEligibleChanges: false, + // FIXME: Check again with old service ...(currentEditorTemplateData.environmentConfig.id > 0 ? { id: currentEditorTemplateData.environmentConfig.id, @@ -1248,17 +1174,13 @@ const DeploymentTemplate = ({ id: currentEditorTemplateData.environmentConfig.id, globalConfig: baseDeploymentTemplateData.originalTemplate, isDraftOverriden: false, - // FIXME: Even this is wrong - readme: currentEditorTemplateData.readme, - schema: currentEditorTemplateData.schema, + readme: baseDeploymentTemplateData.readme, + schema: baseDeploymentTemplateData.schema, } : {}), } } - const editorTemplate = getCurrentTemplateWithLockedKeys() - const editorTemplateObject: Record = YAML.parse(editorTemplate) - const baseObject = { environmentId: +envId, chartRefId: currentEditorTemplateData.selectedChartRefId, @@ -1291,10 +1213,7 @@ const DeploymentTemplate = ({ if (showLockedTemplateDiffModal) { const { eligibleChanges } = getLockConfigEligibleAndIneligibleChanges({ - documents: { - unedited: currentEditorTemplateData.originalTemplate, - edited: editorTemplateObject, - }, + documents: getLockedDiffModalDocuments(false, state), lockedConfigKeysWithLockType, }) @@ -1329,19 +1248,29 @@ const DeploymentTemplate = ({ } const handleSaveTemplate = async () => { - setIsSaving(true) + dispatch({ + type: DeploymentTemplateActionType.INITIATE_SAVE, + }) + try { - // TODO: Check in case of envOverrides the service names are same but are different entities const apiService = getSaveAPIService() + // TODO: Test concurrency, maybe would have to re-compute all the values // TODO: Can send signal const response = await apiService(prepareDataToSave(true), null) - if (response?.result?.isLockConfigError) { - // TODO: Can think of concurrency, maybe would have to re-compute all the values - setShowLockedTemplateDiffModal(true) + + const isLockConfigError = !!response?.result?.isLockConfigError + + dispatch({ + type: DeploymentTemplateActionType.FINISH_SAVE, + payload: { + isLockConfigError, + }, + }) + + if (isLockConfigError) { return } - setIsSaving(false) await handleReload() respondOnSuccess(!isCiPipeline) @@ -1351,17 +1280,19 @@ const DeploymentTemplate = ({ description: 'Changes will be reflected after next deployment.', }) } catch (error) { - // TODO: Check concurrency error due to this - // TODO: Check when adding protected config - // handleConfigProtectionError(2, error, dispatch, reloadEnvironments) - // TODO: Use util from above - if (error.code === 423) { - setShowSaveChangesModal(true) + const isProtectionError = error.code === 423 + + showError(error) + dispatch({ + type: DeploymentTemplateActionType.SAVE_ERROR, + payload: { + isProtectionError, + }, + }) + + if (isProtectionError) { reloadEnvironments() } - // TODO: Remove this later - showError(error) - setIsSaving(false) } } @@ -1373,28 +1304,29 @@ const DeploymentTemplate = ({ action: editMode === ConfigurationType.GUI ? 'clicked-saved-via-gui' : 'clicked-saved-via-yaml', }) + // TODO: Not handle in case of isUnset const shouldValidateLockChanges = lockedConfigKeysWithLockType.config.length > 0 && !isSuperAdmin - // TODO: Try catch if (shouldValidateLockChanges) { - const editorTemplate = getCurrentTemplateWithLockedKeys() - const { ineligibleChanges } = getLockConfigEligibleAndIneligibleChanges({ - documents: { - unedited: currentEditorTemplateData.originalTemplate, - edited: YAML.parse(editorTemplate), - }, + documents: getLockedDiffModalDocuments(false, state), lockedConfigKeysWithLockType, }) if (Object.keys(ineligibleChanges || {}).length) { - setShowLockedTemplateDiffModal(true) + dispatch({ + type: DeploymentTemplateActionType.LOCKED_CHANGES_DETECTED_ON_SAVE, + }) + return } } if (isProtected) { - setShowSaveChangesModal(true) + dispatch({ + type: DeploymentTemplateActionType.SHOW_PROTECTED_SAVE_MODAL, + }) + return } @@ -1408,19 +1340,17 @@ const DeploymentTemplate = ({ const shouldValidateLockChanges = lockedConfigKeysWithLockType.config.length > 0 && !isSuperAdmin if (shouldValidateLockChanges) { // We are going to test the draftData not the current edited data and for this the computation has already been done - // TODO: Can think of some concurrent behaviors - // TODO: Can common documents: { unedited, edited } for both + // TODO: Test concurrent behavior for api validation const { ineligibleChanges } = getLockConfigEligibleAndIneligibleChanges({ - documents: { - unedited: publishedTemplateData.originalTemplate, - edited: draftTemplateData.originalTemplate, - }, + documents: getLockedDiffModalDocuments(true, state), lockedConfigKeysWithLockType, }) if (Object.keys(ineligibleChanges || {}).length) { - setShowLockedDiffForApproval(true) - setShowLockedTemplateDiffModal(true) + dispatch({ + type: DeploymentTemplateActionType.SHOW_LOCKED_DIFF_FOR_APPROVAL, + }) + return false } } @@ -1429,93 +1359,55 @@ const DeploymentTemplate = ({ } const restoreLastSavedTemplate = () => { - handleRemoveResolvedVariables() - setWasGuiOrHideLockedKeysEdited(hideLockedKeys) - - const originalTemplateData = currentEditorTemplateData.originalTemplateState - - const stringifiedYAML = originalTemplateData.editorTemplate - // Since have'nt stored removed patches in global scope so had to re-calculate - const { editorTemplate, removedPatches } = getEditorTemplateAndLockedKeys(stringifiedYAML) - - // When restoring would restore everything, including schema, readme, etc, that is why not using originalTemplate from currentEditorTemplate - - setCurrentEditorTemplateData({ - ...originalTemplateData, - editorTemplate: hideLockedKeys ? editorTemplate : stringifiedYAML, - removedPatches: hideLockedKeys ? removedPatches : [], - unableToParseYaml: false, - originalTemplateState: originalTemplateData, + dispatch({ + type: DeploymentTemplateActionType.RESTORE_LAST_SAVED_TEMPLATE, }) } const handleChartChange = async (selectedChart: DeploymentChartVersionType) => { - // FIXME: Should only update config, and not editor template - // TODO: Not the intended loading state, will change later - setIsLoadingInitialData(true) - setInitialLoadError(null) - try { - const { id, name, isAppMetricsSupported } = selectedChart - // No need to send locked config here since won't call this method on load - // TODO: Can be a util for whole process itself - const templateData = await handleFetchBaseDeploymentTemplate(selectedChart) - // TODO: Sync with product for which values to retains!!! - // FIXME: From tech POV, it should be simple, - // Since we want to retain edited values so, not changing editorTemplate - // TODO: Ask if to retain app config - - // If user changing chart type then we should reset the editor template in case of version change we won't change edited template - const isChartTypeChanged = currentEditorTemplateData?.selectedChart.name !== name - - const updatedEditorTemplateData: typeof currentEditorTemplateData = { - ...currentEditorTemplateData, - isAppMetricsEnabled: isAppMetricsSupported ? currentEditorTemplateData.isAppMetricsEnabled : false, - selectedChart, - selectedChartRefId: +id, - schema: templateData.schema, - readme: templateData.readme, - guiSchema: templateData.guiSchema, - ...(isChartTypeChanged && { - editorTemplate: templateData.editorTemplate, - // Not resetting originalTemplate since we are not changing it - }), - } - - // TODO: Need to confirm with product once - if (isChartTypeChanged) { - handleRemoveResolvedVariables() - setHideLockedKeys(false) - } + dispatch({ + type: DeploymentTemplateActionType.INITIATE_CHART_CHANGE, + }) - setCurrentEditorTemplateData(updatedEditorTemplateData) + try { + const selectedChartTemplateDetails = await handleFetchBaseDeploymentTemplate(selectedChart) + dispatch({ + type: DeploymentTemplateActionType.CHART_CHANGE_SUCCESS, + payload: { + selectedChart, + selectedChartTemplateDetails, + }, + }) } catch (error) { showError(error) - setInitialLoadError(error) - } finally { - setIsLoadingInitialData(false) + dispatch({ + type: DeploymentTemplateActionType.CHART_CHANGE_ERROR, + }) } } const handleCloseLockedDiffModal = () => { - setShowLockedTemplateDiffModal(false) + dispatch({ + type: DeploymentTemplateActionType.CLOSE_LOCKED_DIFF_MODAL, + }) } const handleCloseSaveChangesModal = () => { - setShowSaveChangesModal(false) - handleCloseLockedDiffModal() + dispatch({ + type: DeploymentTemplateActionType.CLOSE_SAVE_CHANGES_MODAL, + }) } const handleAppMetricsToggle = () => { - setCurrentEditorTemplateData((prevTemplateData) => ({ - ...prevTemplateData, - isAppMetricsEnabled: !prevTemplateData.isAppMetricsEnabled, - })) + dispatch({ + type: DeploymentTemplateActionType.TOGGLE_APP_METRICS, + }) } // We don't have options for locked keys here const getDryRunModeEditorValue = (): string => { if (resolveScopedVariables) { - return resolvedEditorTemplate.originalTemplate + return resolvedEditorTemplate.originalTemplateString } return getRawEditorValueForDryRunMode() @@ -1531,13 +1423,13 @@ const DeploymentTemplate = ({ if (isApprovalPendingOptionSelected) { return hideLockedKeys ? resolvedOriginalTemplate.templateWithoutLockedKeys - : resolvedOriginalTemplate.originalTemplate + : resolvedOriginalTemplate.originalTemplateString } // Since editor is disabled for scoped variables we do'nt need to worry about keys changing in editor return hideLockedKeys ? resolvedEditorTemplate.templateWithoutLockedKeys - : resolvedEditorTemplate.originalTemplate + : resolvedEditorTemplate.originalTemplateString } if (isInheritedView) { @@ -1568,7 +1460,7 @@ const DeploymentTemplate = ({ */ const getUneditedDocument = (): string => { if (resolveScopedVariables) { - return resolvedOriginalTemplate.originalTemplate + return resolvedOriginalTemplate.originalTemplateString } if (isPublishedValuesView) { @@ -1584,34 +1476,25 @@ const DeploymentTemplate = ({ return '' } - const getLockedDiffModalDocuments = () => { - if (isApprovalView) { - return { - unedited: publishedTemplateData.originalTemplate, - edited: YAML.parse(draftTemplateData.editorTemplate), - } - } - - const editorTemplate = getCurrentTemplateWithLockedKeys() - - return { - unedited: currentEditorTemplateData.originalTemplate, - edited: YAML.parse(editorTemplate), - } - } - const handleCloseDeleteOverrideDialog = () => { - setShowDeleteOverrideDialog(false) + dispatch({ + type: DeploymentTemplateActionType.CLOSE_OVERRIDE_DIALOG, + }) } - const handleShowDeleteDraftOverrideDialog = () => { - setShowDeleteDraftOverrideDialog(true) + const handleDeleteOverrideProtectionError = () => { + dispatch({ + type: DeploymentTemplateActionType.DELETE_OVERRIDE_CONCURRENT_PROTECTION_ERROR, + }) } const handleToggleDeleteDraftOverrideDialog = () => { - setShowDeleteDraftOverrideDialog((prev) => !prev) + dispatch({ + type: DeploymentTemplateActionType.CLOSE_DELETE_DRAFT_OVERRIDE_DIALOG, + }) } + // TODO: Check if can break this method const handleOverride = () => { if (!envId) { logExceptionToSentry(new Error('Trying to access override without envId in DeploymentTemplate')) @@ -1619,29 +1502,30 @@ const DeploymentTemplate = ({ } if (currentEditorTemplateData.originalTemplateState.isOverridden) { - // Not directly overriding state since user can use cancel - if (isProtected) { - handleShowDeleteDraftOverrideDialog() - return - } - ReactGA.event({ category: 'devtronapp-configuration-dt', action: 'clicked-delete-override', }) - setShowDeleteOverrideDialog(true) + dispatch({ + type: DeploymentTemplateActionType.SHOW_DELETE_OVERRIDE_DIALOG, + payload: { + isProtected, + }, + }) return } + // TODO: Check if can be removed if (currentEditorTemplateData.isOverridden) { - restoreLastSavedTemplate() + dispatch({ + type: DeploymentTemplateActionType.DELETE_LOCAL_OVERRIDE, + }) return } - setCurrentEditorTemplateData({ - ...currentEditorTemplateData, - isOverridden: true, + dispatch({ + type: DeploymentTemplateActionType.OVERRIDE_TEMPLATE, }) } @@ -1705,8 +1589,24 @@ const DeploymentTemplate = ({ } } + const getPublishedTemplate = (): string => { + if (!isPublishedConfigPresent) { + return '' + } + + if (state.resolveScopedVariables) { + return state.hideLockedKeys + ? state.resolvedPublishedTemplate.templateWithoutLockedKeys + : state.resolvedPublishedTemplate.originalTemplateString + } + + return state.hideLockedKeys + ? publishedTemplateData.editorTemplateWithoutLockedKeys + : publishedTemplateData.editorTemplate + } + const renderEditorComponent = () => { - if (isLoadingInitialData || isResolvingVariables) { + if (isResolvingVariables || state.isLoadingChangedChartDetails) { return (
@@ -1714,11 +1614,6 @@ const DeploymentTemplate = ({ ) } - if (initialLoadError) { - // TODO: re-visit reload mechanism - return - } - if (showNoOverrideEmptyState) { return ( ) @@ -1782,14 +1676,12 @@ const DeploymentTemplate = ({ schema={getCurrentEditorSchema()} isOverridden={getIsCurrentTemplateOverridden()} isUnSet={isUnSet} - wasGuiOrHideLockedKeysEdited={wasGuiOrHideLockedKeysEdited} - handleEnableWasGuiOrHideLockedKeysEdited={handleEnableWasGuiOrHideLockedKeysEdited} handleChangeToYAMLMode={handleChangeToYAMLMode} editorOnChange={handleEditorChange} editedDocument={getCurrentEditorValue()} uneditedDocument={getUneditedDocument()} showReadMe={showReadMe} - // TODO: Confirm with product which editor to show + // TODO: Show readme only in case of edit/values mode readMe={currentEditorTemplateData?.readme} environmentName={environmentName} latestDraft={draftTemplateData?.latestDraft} @@ -1799,10 +1691,10 @@ const DeploymentTemplate = ({ ) } - // TODO: Need to implement when we have support for merge patches + // NOTE: Need to implement when we have support for merge patches const getShouldShowMergePatchesButton = (): boolean => false - const handleMergeStrategyChange: ConfigToolbarProps['handleMergeStrategyChange'] = (strategy) => { + const handleMergeStrategyChange: ConfigToolbarProps['handleMergeStrategyChange'] = (mergeStrategy) => { ReactGA.event({ category: 'devtronapp-configuration-dt', action: 'clicked-merge-strategy-dropdown', @@ -1813,26 +1705,30 @@ const DeploymentTemplate = ({ return } - const currentEditorTemplateClone = structuredClone(currentEditorTemplateData) - - const newTemplateData: typeof currentEditorTemplateClone = { - ...currentEditorTemplateClone, - mergeStrategy: strategy, - } - setCurrentEditorTemplateData(newTemplateData) + dispatch({ + type: DeploymentTemplateActionType.UPDATE_MERGE_STRATEGY, + payload: { + mergeStrategy, + }, + }) } const handleOpenDiscardDraftPopup = () => { - setPopupNodeType(ConfigToolbarPopupNodeType.DISCARD_DRAFT) + dispatch({ + type: DeploymentTemplateActionType.SHOW_DISCARD_DRAFT_POPUP, + }) } const handleShowEditHistory = () => { - setPopupNodeType(ConfigToolbarPopupNodeType.EDIT_HISTORY) + dispatch({ + type: DeploymentTemplateActionType.SHOW_EDIT_HISTORY, + }) } - // TODO: Product req is to close modal not go back to reverted popup menu, check if can be easily done const handleClearPopupNode = () => { - setPopupNodeType(null) + dispatch({ + type: DeploymentTemplateActionType.CLEAR_POPUP_NODE, + }) } const renderCTA = () => { @@ -1855,14 +1751,14 @@ const DeploymentTemplate = ({ ? !!publishedTemplateData?.isAppMetricsEnabled : !!currentEditorTemplateData?.isAppMetricsEnabled - const isLoading = isLoadingInitialData || isResolvingVariables || isSaving + const isLoading = isResolvingVariables || isSaving || state.isLoadingChangedChartDetails const isDisabled = resolveScopedVariables || currentEditorTemplateData.unableToParseYaml || - isLoadingInitialData || isResolvingVariables || - isSaving + isSaving || + state.isLoadingChangedChartDetails if (isProtected && ProtectedDeploymentTemplateCTA) { return ( @@ -1938,7 +1834,7 @@ const DeploymentTemplate = ({ isPublishedConfigPresent, handleDeleteOverride: handleOverride, unableToParseData: currentEditorTemplateData?.unableToParseYaml, - isLoading: isLoadingInitialData || isResolvingVariables || isSaving, + isLoading: isResolvingVariables || isSaving || state.isLoadingChangedChartDetails, isDraftAvailable, handleDiscardDraft: handleOpenDiscardDraftPopup, handleShowEditHistory, @@ -2001,20 +1897,26 @@ const DeploymentTemplate = ({ resolveScopedVariables={resolveScopedVariables} // TODO: Can make variable disableAllActions={ - currentEditorTemplateData?.unableToParseYaml || isResolvingVariables || isSaving + currentEditorTemplateData?.unableToParseYaml || + isResolvingVariables || + isSaving || + state.isLoadingChangedChartDetails } configHeaderTab={configHeaderTab} isProtected={isProtected} - isApprovalPending={draftTemplateData?.latestDraft?.draftState === DraftState.AwaitApproval} + isApprovalPending={isApprovalPending} isDraftPresent={isDraftAvailable} approvalUsers={draftTemplateData?.latestDraft?.approvers} - isLoadingInitialData={isLoadingInitialData} + isLoadingInitialData={false} isPublishedConfigPresent={isPublishedConfigPresent} handleClearPopupNode={handleClearPopupNode} > )} @@ -2045,75 +1947,86 @@ const DeploymentTemplate = ({ ) + const renderDeploymentTemplate = () => { + if (state.isLoadingInitialData) { + return + } + + if (state.initialLoadError) { + return + } + + return ( +
+ {renderBody()} + + {showDeleteOverrideDialog && ( + + )} + + {DeleteOverrideDraftModal && showDeleteDraftOverrideDialog && ( + + )} + + {DeploymentTemplateLockedDiff && showLockedTemplateDiffModal && ( + + )} + + {/* FIXME: Can move this as well in ProtectedDeploymentTemplateCTA */} + {SaveChangesModal && showSaveChangesModal && ( + + )} +
+ ) + } + return ( <>
-
- {renderBody()} - - {showDeleteOverrideDialog && ( - - )} - - {DeleteOverrideDraftModal && showDeleteDraftOverrideDialog && ( - - )} - - {DeploymentTemplateLockedDiff && showLockedTemplateDiffModal && ( - - )} - - {/* FIXME: Can move this as well in ProtectedDeploymentTemplateCTA */} - {SaveChangesModal && showSaveChangesModal && ( - - )} -
+ {renderDeploymentTemplate()} {DraftComments && showDraftComments && ( diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplateGUIView.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplateGUIView.tsx index aef3cb5440..6c82df0dce 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplateGUIView.tsx +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/DeploymentTemplateGUIView.tsx @@ -56,8 +56,6 @@ const DeploymentTemplateGUIView = ({ uneditedDocument, editedDocument, isUnSet, - handleEnableWasGuiOrHideLockedKeysEdited, - wasGuiOrHideLockedKeysEdited, handleChangeToYAMLMode, guiSchema, selectedChart, @@ -117,9 +115,6 @@ const DeploymentTemplateGUIView = ({ }, [guiSchema, hideLockedKeys]) const handleFormChange: FormProps['onChange'] = (data) => { - if (!wasGuiOrHideLockedKeysEdited) { - handleEnableWasGuiOrHideLockedKeysEdited() - } editorOnChange?.(YAML.stringify(data.formData)) } diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/constants.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/constants.ts index a0debbd7d7..37f58bea07 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/constants.ts +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/constants.ts @@ -1,4 +1,3 @@ -import { ConfigKeysWithLockType } from '@devtron-labs/devtron-fe-common-lib' import { DOCUMENTATION } from '@Config/constants' export const BASE_DEPLOYMENT_TEMPLATE_ENV_ID = -1 @@ -50,8 +49,3 @@ export const DEPLOYMENT_TEMPLATE_LABELS_KEYS = { } export const NO_SCOPED_VARIABLES_MESSAGE = 'No valid variable found on this page' - -export const DEFAULT_LOCKED_KEYS_CONFIG: Readonly = { - config: [], - allowed: false, -} diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/types.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/types.ts index d45d14dc77..05a157fc35 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/types.ts +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/types.ts @@ -12,6 +12,11 @@ import { SelectedChartDetailsType, CompareFromApprovalOptionsValuesType, ConfigurationType, + ServerErrors, + ConfigToolbarPopupNodeType, + DryRunEditorMode, + ConfigHeaderTabType, + ProtectConfigTabsType, } from '@devtron-labs/devtron-fe-common-lib' export interface DeploymentTemplateProps { @@ -116,9 +121,7 @@ export interface DeploymentTemplateFormProps readMe: string isPublishedValuesView: boolean handleOverride: () => void - wasGuiOrHideLockedKeysEdited: boolean handleChangeToYAMLMode: () => void - handleEnableWasGuiOrHideLockedKeysEdited: () => void hideLockedKeys: boolean editMode: ConfigurationType showReadMe: boolean @@ -132,8 +135,6 @@ export interface DeploymentTemplateGUIViewProps value: string readOnly: boolean isUnSet: boolean - handleEnableWasGuiOrHideLockedKeysEdited: () => void - wasGuiOrHideLockedKeysEdited: boolean handleChangeToYAMLMode: () => void guiSchema: string selectedChart: DeploymentChartVersionType @@ -141,7 +142,7 @@ export interface DeploymentTemplateGUIViewProps } export interface ResolvedEditorTemplateType { - originalTemplate: string + originalTemplateString: string templateWithoutLockedKeys: string } @@ -189,7 +190,7 @@ export interface DeleteOverrideDialogProps { environmentConfigId: number handleReload: () => void handleClose: () => void - handleShowDeleteDraftOverrideDialog: () => void + handleProtectionError: () => void reloadEnvironments: () => void } @@ -281,3 +282,288 @@ export interface DeploymentTemplateConfigDTO { globalConfig: DeploymentTemplateGlobalConfigDTO guiSchema: string } + +export interface DeploymentTemplateStateType { + isLoadingInitialData: boolean + initialLoadError: ServerErrors + /** + * (Readonly) + */ + chartDetails: DeploymentTemplateChartStateType + + /** + * Template state that would be used in case actual deployment happens + * (Readonly) + */ + publishedTemplateData: DeploymentTemplateConfigState + /** + * Last saved draft template data + * Only present in case of protected config + * (Readonly) + */ + draftTemplateData: DeploymentTemplateConfigState + /** + * Template state of base configuration + * (Readonly) + */ + baseDeploymentTemplateData: DeploymentTemplateConfigState + /** + * The state of current editor + */ + currentEditorTemplateData: DeploymentTemplateEditorDataStateType + + /** + * If true, would resolve scoped variables + */ + resolveScopedVariables: boolean + isResolvingVariables: boolean + + /** + * Contains resolved editor template for current editor - has two keys 1. Complete template 2. Template without locked keys + */ + resolvedEditorTemplate: ResolvedEditorTemplateType + /** + * Contains resolved original template for current editor for us to feed to GUI View + */ + resolvedOriginalTemplate: ResolvedEditorTemplateType + + /** + * Used in case of approval view since we need to compare with published template + */ + resolvedPublishedTemplate: ResolvedEditorTemplateType + + /** + * Used to identify whether we are going to maintain sorting order of keys in editor + */ + wasGuiOrHideLockedKeysEdited: boolean + showDraftComments: boolean + hideLockedKeys: boolean + lockedConfigKeysWithLockType: ConfigKeysWithLockType + lockedDiffModalState: { + showLockedTemplateDiffModal: boolean + /** + * State to show locked changes modal in case user is non super admin and is changing locked keys + * Would be showing an info bar in locked modal + */ + showLockedDiffForApproval: boolean + } + isSaving: boolean + /** + * Would show modal to save in case of config protection to propose changes / save as draft + */ + showSaveChangesModal: boolean + /** + * To replace the opened popup menu body in config toolbar + */ + popupNodeType: ConfigToolbarPopupNodeType + /** + * In case of approval pending mode, we would be showing a select to compare from, this is its selected value + */ + compareFromSelectedOptionValue: CompareFromApprovalOptionsValuesType + /** + * There is a select in dry run mode in case isDraftPresent for us to toggle between draft/published/approval-pending + */ + dryRunEditorMode: DryRunEditorMode + /** + * Triggered on changing chart/version + */ + isLoadingChangedChartDetails: boolean + showDeleteOverrideDialog: boolean + showDeleteDraftOverrideDialog: boolean + showReadMe: boolean + editMode: ConfigurationType + configHeaderTab: ConfigHeaderTabType + shouldMergeTemplateWithPatches: boolean + selectedProtectionViewTab: ProtectConfigTabsType +} + +export interface GetDeploymentTemplateInitialStateParamsType { + isSuperAdmin: boolean + isEnvView: boolean +} + +export interface GetPublishedAndBaseDeploymentTemplateReturnType { + publishedTemplateState: DeploymentTemplateConfigState + baseDeploymentTemplateState: DeploymentTemplateConfigState +} + +export interface GetChartListReturnType + extends SelectedChartDetailsType, + Pick {} + +export interface HandleInitializeTemplatesWithoutDraftParamsType { + baseDeploymentTemplateState: DeploymentTemplateStateType['baseDeploymentTemplateData'] + publishedTemplateState: DeploymentTemplateStateType['publishedTemplateData'] + chartDetailsState: DeploymentTemplateStateType['chartDetails'] + lockedConfigKeysWithLockTypeState: DeploymentTemplateStateType['lockedConfigKeysWithLockType'] +} + +interface InitializeStateBasePayloadType + extends Pick< + DeploymentTemplateStateType, + 'baseDeploymentTemplateData' | 'publishedTemplateData' | 'chartDetails' | 'lockedConfigKeysWithLockType' + > {} + +export enum DeploymentTemplateActionType { + RESET_ALL = 'RESET_ALL', + INITIATE_INITIAL_DATA_LOAD = 'INITIATE_INITIAL_DATA_LOAD', + INITIAL_DATA_ERROR = 'INITIAL_DATA_ERROR', + INITIALIZE_TEMPLATES_WITHOUT_DRAFT = 'INITIALIZE_TEMPLATES_WITHOUT_DRAFT', + INITIALIZE_TEMPLATES_WITH_DRAFT = 'INITIALIZE_TEMPLATES_WITH_DRAFT', + INITIATE_CHART_CHANGE = 'INITIATE_CHART_CHANGE', + CHART_CHANGE_SUCCESS = 'CHART_CHANGE_SUCCESS', + CHART_CHANGE_ERROR = 'CHART_CHANGE_ERROR', + INITIATE_RESOLVE_SCOPED_VARIABLES = 'INITIATE_RESOLVE_SCOPED_VARIABLES', + RESOLVE_SCOPED_VARIABLES = 'RESOLVE_SCOPED_VARIABLES', + UN_RESOLVE_SCOPED_VARIABLES = 'UN_RESOLVE_SCOPED_VARIABLES', + TOGGLE_DRAFT_COMMENTS = 'TOGGLE_DRAFT_COMMENTS', + UPDATE_README_MODE = 'UPDATE_README_MODE', + RESTORE_LAST_SAVED_TEMPLATE = 'RESTORE_LAST_SAVED_TEMPLATE', + CURRENT_EDITOR_VALUE_CHANGE = 'CURRENT_EDITOR_VALUE_CHANGE', + UPDATE_HIDE_LOCKED_KEYS = 'UPDATE_HIDE_LOCKED_KEYS', + CHANGE_TO_GUI_MODE = 'CHANGE_TO_GUI_MODE', + CHANGE_TO_YAML_MODE = 'CHANGE_TO_YAML_MODE', + UPDATE_CONFIG_HEADER_TAB = 'UPDATE_CONFIG_HEADER_TAB', + TOGGLE_SHOW_COMPARISON_WITH_MERGED_PATCHES = 'TOGGLE_SHOW_COMPARISON_WITH_MERGED_PATCHES', + UPDATE_PROTECTION_VIEW_TAB = 'UPDATE_PROTECTION_VIEW_TAB', + UPDATE_DRY_RUN_EDITOR_MODE = 'UPDATE_DRY_RUN_EDITOR_MODE', + INITIATE_SAVE = 'INITIATE_SAVE', + SAVE_ERROR = 'SAVE_ERROR', + FINISH_SAVE = 'FINISH_SAVE', + SHOW_EDIT_HISTORY = 'SHOW_EDIT_HISTORY', + SHOW_DISCARD_DRAFT_POPUP = 'SHOW_DISCARD_DRAFT_POPUP', + CLEAR_POPUP_NODE = 'CLEAR_POPUP_NODE', + CHANGE_COMPARE_FROM_SELECTED_OPTION = 'CHANGE_COMPARE_FROM_SELECTED_OPTION', + SHOW_LOCKED_DIFF_FOR_APPROVAL = 'SHOW_LOCKED_DIFF_FOR_APPROVAL', + TOGGLE_APP_METRICS = 'TOGGLE_APP_METRICS', + UPDATE_MERGE_STRATEGY = 'UPDATE_MERGE_STRATEGY', + SHOW_DELETE_OVERRIDE_DIALOG = 'SHOW_DELETE_OVERRIDE_DIALOG', + DELETE_LOCAL_OVERRIDE = 'DELETE_LOCAL_OVERRIDE', + OVERRIDE_TEMPLATE = 'OVERRIDE_TEMPLATE', + DELETE_OVERRIDE_CONCURRENT_PROTECTION_ERROR = 'DELETE_OVERRIDE_CONCURRENT_PROTECTION_ERROR', + CLOSE_DELETE_DRAFT_OVERRIDE_DIALOG = 'CLOSE_DELETE_DRAFT_OVERRIDE_DIALOG', + CLOSE_OVERRIDE_DIALOG = 'CLOSE_OVERRIDE_DIALOG', + LOCKED_CHANGES_DETECTED_ON_SAVE = 'LOCKED_CHANGES_DETECTED_ON_SAVE', + SHOW_PROTECTED_SAVE_MODAL = 'SHOW_PROTECTED_SAVE_MODAL', + CLOSE_SAVE_CHANGES_MODAL = 'CLOSE_SAVE_CHANGES_MODAL', + CLOSE_LOCKED_DIFF_MODAL = 'CLOSE_LOCKED_DIFF_MODAL', +} + +type DeploymentTemplateNoPayloadActions = + | DeploymentTemplateActionType.INITIATE_INITIAL_DATA_LOAD + | DeploymentTemplateActionType.INITIATE_CHART_CHANGE + | DeploymentTemplateActionType.CHART_CHANGE_ERROR + | DeploymentTemplateActionType.INITIATE_RESOLVE_SCOPED_VARIABLES + | DeploymentTemplateActionType.UN_RESOLVE_SCOPED_VARIABLES + | DeploymentTemplateActionType.TOGGLE_DRAFT_COMMENTS + | DeploymentTemplateActionType.RESTORE_LAST_SAVED_TEMPLATE + | DeploymentTemplateActionType.CHANGE_TO_GUI_MODE + | DeploymentTemplateActionType.CHANGE_TO_YAML_MODE + | DeploymentTemplateActionType.TOGGLE_SHOW_COMPARISON_WITH_MERGED_PATCHES + | DeploymentTemplateActionType.INITIATE_SAVE + | DeploymentTemplateActionType.SHOW_EDIT_HISTORY + | DeploymentTemplateActionType.SHOW_DISCARD_DRAFT_POPUP + | DeploymentTemplateActionType.CLEAR_POPUP_NODE + | DeploymentTemplateActionType.SHOW_LOCKED_DIFF_FOR_APPROVAL + | DeploymentTemplateActionType.TOGGLE_APP_METRICS + | DeploymentTemplateActionType.DELETE_LOCAL_OVERRIDE + | DeploymentTemplateActionType.OVERRIDE_TEMPLATE + | DeploymentTemplateActionType.DELETE_OVERRIDE_CONCURRENT_PROTECTION_ERROR + | DeploymentTemplateActionType.CLOSE_DELETE_DRAFT_OVERRIDE_DIALOG + | DeploymentTemplateActionType.CLOSE_OVERRIDE_DIALOG + | DeploymentTemplateActionType.LOCKED_CHANGES_DETECTED_ON_SAVE + | DeploymentTemplateActionType.SHOW_PROTECTED_SAVE_MODAL + | DeploymentTemplateActionType.CLOSE_SAVE_CHANGES_MODAL + | DeploymentTemplateActionType.CLOSE_LOCKED_DIFF_MODAL + +export type DeploymentTemplateActionState = + | { + type: DeploymentTemplateActionType.RESET_ALL + payload: GetDeploymentTemplateInitialStateParamsType + } + | { + type: DeploymentTemplateNoPayloadActions + payload?: never + } + | { + type: DeploymentTemplateActionType.INITIAL_DATA_ERROR + payload: { + error: ServerErrors + } + } + | { + type: DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITHOUT_DRAFT + payload: InitializeStateBasePayloadType & Pick + } + | { + type: DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITH_DRAFT + payload: InitializeStateBasePayloadType & + Pick< + DeploymentTemplateStateType, + 'currentEditorTemplateData' | 'draftTemplateData' | 'configHeaderTab' | 'selectedProtectionViewTab' + > + } + | { + type: DeploymentTemplateActionType.CHART_CHANGE_SUCCESS + payload: { + selectedChart: DeploymentChartVersionType + selectedChartTemplateDetails: DeploymentTemplateConfigState + } + } + | { + type: DeploymentTemplateActionType.RESOLVE_SCOPED_VARIABLES + payload: Pick< + DeploymentTemplateStateType, + 'resolvedEditorTemplate' | 'resolvedOriginalTemplate' | 'resolvedPublishedTemplate' + > + } + | { + type: DeploymentTemplateActionType.UPDATE_README_MODE + payload: Pick + } + | { + type: DeploymentTemplateActionType.CURRENT_EDITOR_VALUE_CHANGE + payload: { + template: string + } + } + | { + type: DeploymentTemplateActionType.UPDATE_HIDE_LOCKED_KEYS + payload: Pick + } + | { + type: DeploymentTemplateActionType.UPDATE_CONFIG_HEADER_TAB + payload: Pick + } + | { + type: DeploymentTemplateActionType.UPDATE_PROTECTION_VIEW_TAB + payload: Pick + } + | { + type: DeploymentTemplateActionType.UPDATE_DRY_RUN_EDITOR_MODE + payload: Pick + } + | { + type: DeploymentTemplateActionType.CHANGE_COMPARE_FROM_SELECTED_OPTION + payload: Pick + } + | { + type: DeploymentTemplateActionType.UPDATE_MERGE_STRATEGY + payload: Pick + } + | { + type: DeploymentTemplateActionType.SHOW_DELETE_OVERRIDE_DIALOG + payload: Pick + } + | { + type: DeploymentTemplateActionType.SAVE_ERROR + payload: { + isProtectionError: boolean + } + } + | { + type: DeploymentTemplateActionType.FINISH_SAVE + payload: { + isLockConfigError: boolean + } + } diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.ts deleted file mode 100644 index 9ec48088ea..0000000000 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - applyCompareDiffOnUneditedDocument, - getGuiSchemaFromChartName, - ResponseType, - YAMLStringify, -} from '@devtron-labs/devtron-fe-common-lib' -import YAML from 'yaml' - -export const makeObjectFromJsonPathArray = (index: number, paths: string[]) => { - if (index >= paths.length) { - return { - 'ui:widget': 'hidden', - } - } - if (paths[index] === '$') { - return makeObjectFromJsonPathArray(index + 1, paths) - } - const key = paths[index] - const isKeyNumber = !Number.isNaN(Number(key)) - if (isKeyNumber) { - return { items: makeObjectFromJsonPathArray(index + 1, paths) } - } - return { [key]: makeObjectFromJsonPathArray(index + 1, paths) } -} - -/** - * This method will compare and calculate the diffs between @unedited and @edited - * documents and apply these diffs onto the @unedited document and return this new document - * @param {string} unedited - The unedited document onto which we want to patch the changes from @edited - * @param {string} edited - The edited document whose changes we want to patch onto @unedited - */ -export const applyCompareDiffOfTempFormDataOnOriginalData = ( - unedited: string, - edited: string, - updateTempFormData?: (data: string) => void, -) => { - const updated = applyCompareDiffOnUneditedDocument(YAML.parse(unedited), YAML.parse(edited)) - // TODO: Can add simpleKeys? - updateTempFormData?.(YAMLStringify(updated)) - return updated -} - -export const addGUISchemaIfAbsent = (response: ResponseType, chartName: string) => { - if (response && response.result && !response.result.guiSchema) { - return { - ...response, - result: { - ...response.result, - guiSchema: JSON.stringify(getGuiSchemaFromChartName(chartName)), - }, - } - } - return response -} diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.tsx b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.tsx new file mode 100644 index 0000000000..7076e6379d --- /dev/null +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/DeploymentTemplate/utils.tsx @@ -0,0 +1,689 @@ +import { + applyCompareDiffOnUneditedDocument, + CompareFromApprovalOptionsValuesType, + CONFIG_HEADER_TAB_VALUES, + ConfigurationType, + DryRunEditorMode, + getGuiSchemaFromChartName, + ProtectConfigTabsType, + ResponseType, + YAMLStringify, + DEFAULT_LOCKED_KEYS_CONFIG, + ToastVariantType, + ToastManager, + logExceptionToSentry, + ConfigToolbarPopupNodeType, +} from '@devtron-labs/devtron-fe-common-lib' +import { importComponentFromFELibrary } from '@Components/common' +import YAML from 'yaml' +import { + DeploymentTemplateActionState, + DeploymentTemplateActionType, + DeploymentTemplateEditorDataStateType, + DeploymentTemplateStateType, + GetDeploymentTemplateInitialStateParamsType, +} from './types' + +const removeLockedKeysFromYaml = importComponentFromFELibrary('removeLockedKeysFromYaml', null, 'function') +const reapplyRemovedLockedKeysToYaml = importComponentFromFELibrary('reapplyRemovedLockedKeysToYaml', null, 'function') + +export const makeObjectFromJsonPathArray = (index: number, paths: string[]) => { + if (index >= paths.length) { + return { + 'ui:widget': 'hidden', + } + } + if (paths[index] === '$') { + return makeObjectFromJsonPathArray(index + 1, paths) + } + const key = paths[index] + const isKeyNumber = !Number.isNaN(Number(key)) + if (isKeyNumber) { + return { items: makeObjectFromJsonPathArray(index + 1, paths) } + } + return { [key]: makeObjectFromJsonPathArray(index + 1, paths) } +} + +/** + * This method will compare and calculate the diffs between @unedited and @edited + * documents and apply these diffs onto the @unedited document and return this new document + * @param {string} unedited - The unedited document onto which we want to patch the changes from @edited + * @param {string} edited - The edited document whose changes we want to patch onto @unedited + */ +export const applyCompareDiffOfTempFormDataOnOriginalData = ( + unedited: string, + edited: string, + updateTempFormData?: (data: string) => void, +) => { + const updated = applyCompareDiffOnUneditedDocument(YAML.parse(unedited), YAML.parse(edited)) + updateTempFormData?.(YAMLStringify(updated, { simpleKeys: true })) + return updated +} + +export const addGUISchemaIfAbsent = (response: ResponseType, chartName: string) => { + if (response && response.result && !response.result.guiSchema) { + return { + ...response, + result: { + ...response.result, + guiSchema: JSON.stringify(getGuiSchemaFromChartName(chartName)), + }, + } + } + return response +} + +export const getEditorTemplateAndLockedKeys = ( + template: string, + lockedConfigKeys: string[], +): Pick => { + const removedPatches: DeploymentTemplateEditorDataStateType['removedPatches'] = [] + + if (!removeLockedKeysFromYaml || !lockedConfigKeys.length) { + return { editorTemplate: template, removedPatches } + } + + try { + const { document, addOperations } = removeLockedKeysFromYaml(template, lockedConfigKeys) + if (addOperations.length) { + removedPatches.push(...addOperations) + } + + const updatedTemplate = YAMLStringify(document, { + simpleKeys: true, + }) + return { editorTemplate: updatedTemplate, removedPatches } + } catch { + return { editorTemplate: template, removedPatches: [] } + } +} + +export const getCurrentTemplateWithLockedKeys = ({ + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited, +}: Pick): string => { + if (!currentEditorTemplateData.removedPatches.length || !reapplyRemovedLockedKeysToYaml) { + return currentEditorTemplateData.editorTemplate + } + + try { + const originalDocument = currentEditorTemplateData.originalTemplate + const parsedDocument = YAML.parse(currentEditorTemplateData.editorTemplate) + + const updatedEditorObject = reapplyRemovedLockedKeysToYaml( + parsedDocument, + currentEditorTemplateData.removedPatches, + ) + + if (wasGuiOrHideLockedKeysEdited) { + return YAMLStringify(applyCompareDiffOnUneditedDocument(originalDocument, updatedEditorObject), { + simpleKeys: true, + }) + } + return YAMLStringify(updatedEditorObject, { simpleKeys: true }) + } catch { + ToastManager.showToast({ + variant: ToastVariantType.error, + description: 'Something went wrong while parsing locked keys', + }) + } + + return currentEditorTemplateData.editorTemplate +} + +export const getDeploymentTemplateInitialState = ({ + isSuperAdmin, + isEnvView, +}: GetDeploymentTemplateInitialStateParamsType): DeploymentTemplateStateType => ({ + isLoadingInitialData: true, + initialLoadError: null, + chartDetails: { + charts: [], + chartsMetadata: {}, + globalChartDetails: null, + }, + publishedTemplateData: null, + draftTemplateData: null, + baseDeploymentTemplateData: null, + currentEditorTemplateData: null, + resolveScopedVariables: false, + isResolvingVariables: false, + resolvedEditorTemplate: { + originalTemplateString: '', + templateWithoutLockedKeys: '', + }, + resolvedOriginalTemplate: { + originalTemplateString: '', + templateWithoutLockedKeys: '', + }, + resolvedPublishedTemplate: { + originalTemplateString: '', + templateWithoutLockedKeys: '', + }, + wasGuiOrHideLockedKeysEdited: false, + showDraftComments: false, + hideLockedKeys: false, + lockedConfigKeysWithLockType: structuredClone(DEFAULT_LOCKED_KEYS_CONFIG), + isSaving: false, + lockedDiffModalState: { + showLockedDiffForApproval: false, + showLockedTemplateDiffModal: false, + }, + showSaveChangesModal: false, + popupNodeType: null, + compareFromSelectedOptionValue: CompareFromApprovalOptionsValuesType.APPROVAL_PENDING, + dryRunEditorMode: DryRunEditorMode.PUBLISHED_VALUES, + showDeleteOverrideDialog: false, + showDeleteDraftOverrideDialog: false, + showReadMe: false, + editMode: isSuperAdmin ? ConfigurationType.YAML : ConfigurationType.GUI, + configHeaderTab: isEnvView + ? CONFIG_HEADER_TAB_VALUES.OVERRIDE[0] + : CONFIG_HEADER_TAB_VALUES.BASE_DEPLOYMENT_TEMPLATE[0], + shouldMergeTemplateWithPatches: false, + selectedProtectionViewTab: ProtectConfigTabsType.EDIT_DRAFT, + isLoadingChangedChartDetails: false, +}) + +const handleSwitchToYAMLMode = (state: DeploymentTemplateStateType): DeploymentTemplateStateType => { + if (state.editMode === ConfigurationType.GUI && state.wasGuiOrHideLockedKeysEdited) { + try { + const editorTemplate = YAMLStringify( + applyCompareDiffOfTempFormDataOnOriginalData( + YAMLStringify(state.currentEditorTemplateData.originalTemplate), + state.currentEditorTemplateData.editorTemplate, + ), + { simpleKeys: true }, + ) + + return { + ...state, + editMode: ConfigurationType.YAML, + currentEditorTemplateData: { + ...state.currentEditorTemplateData, + editorTemplate, + }, + } + } catch { + // Do nothing + } + } + + return { + ...state, + editMode: ConfigurationType.YAML, + } +} + +const handleUnResolveScopedVariables = (): Pick< + DeploymentTemplateStateType, + 'isResolvingVariables' | 'resolveScopedVariables' +> => ({ + isResolvingVariables: false, + resolveScopedVariables: false, +}) + +const handleRestoreLastSavedTemplate = (state: DeploymentTemplateStateType): DeploymentTemplateStateType => { + const originalTemplateData = state.currentEditorTemplateData.originalTemplateState + const stringifiedYAML = originalTemplateData.editorTemplate + + // Since have'nt stored removed patches in global scope so had to re-calculate + const { editorTemplate, removedPatches } = getEditorTemplateAndLockedKeys( + stringifiedYAML, + state.lockedConfigKeysWithLockType.config, + ) + + const currentEditorTemplateData: typeof state.currentEditorTemplateData = { + ...originalTemplateData, + editorTemplate: state.hideLockedKeys ? editorTemplate : stringifiedYAML, + removedPatches: state.hideLockedKeys ? removedPatches : [], + unableToParseYaml: false, + originalTemplateState: originalTemplateData, + } + + // When restoring would restore everything, including schema, readme, etc, that is why not using originalTemplate from currentEditorTemplate + return { + ...state, + ...handleUnResolveScopedVariables(), + wasGuiOrHideLockedKeysEdited: state.hideLockedKeys, + currentEditorTemplateData, + } +} + +// TODO: Return type +// TODO: Check if needs try catch +export const getLockedDiffModalDocuments = (isApprovalView: boolean, state: DeploymentTemplateStateType) => ({ + unedited: state.publishedTemplateData.originalTemplate, + edited: isApprovalView + ? state.draftTemplateData.originalTemplate + : YAML.parse( + getCurrentTemplateWithLockedKeys({ + currentEditorTemplateData: state.currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited: state.wasGuiOrHideLockedKeysEdited, + }), + ), +}) + +export const deploymentTemplateReducer = ( + state: DeploymentTemplateStateType, + action: DeploymentTemplateActionState, +): DeploymentTemplateStateType => { + switch (action.type) { + case DeploymentTemplateActionType.RESET_ALL: + return getDeploymentTemplateInitialState(action.payload) + + case DeploymentTemplateActionType.INITIATE_INITIAL_DATA_LOAD: + return { + ...state, + isLoadingInitialData: true, + initialLoadError: null, + } + + case DeploymentTemplateActionType.INITIAL_DATA_ERROR: + return { + ...state, + isLoadingInitialData: false, + initialLoadError: action.payload.error, + } + + case DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITHOUT_DRAFT: { + const { + baseDeploymentTemplateData, + publishedTemplateData, + chartDetails, + lockedConfigKeysWithLockType, + currentEditorTemplateData, + } = action.payload + + return { + ...state, + baseDeploymentTemplateData, + publishedTemplateData, + chartDetails, + lockedConfigKeysWithLockType, + currentEditorTemplateData, + isLoadingInitialData: false, + initialLoadError: null, + } + } + + case DeploymentTemplateActionType.INITIALIZE_TEMPLATES_WITH_DRAFT: { + const { + baseDeploymentTemplateData, + publishedTemplateData, + chartDetails, + lockedConfigKeysWithLockType, + draftTemplateData, + currentEditorTemplateData, + configHeaderTab, + selectedProtectionViewTab, + } = action.payload + + return { + ...state, + baseDeploymentTemplateData, + publishedTemplateData, + chartDetails, + lockedConfigKeysWithLockType, + draftTemplateData, + currentEditorTemplateData, + configHeaderTab, + selectedProtectionViewTab, + isLoadingInitialData: false, + initialLoadError: null, + } + } + + case DeploymentTemplateActionType.INITIATE_CHART_CHANGE: + return { + ...state, + isLoadingChangedChartDetails: true, + } + + case DeploymentTemplateActionType.CHART_CHANGE_SUCCESS: { + const { selectedChart, selectedChartTemplateDetails } = action.payload + const { id, name, isAppMetricsSupported } = selectedChart + + // We will retain editor values in all cases except when chart type is changed + const isChartTypeChanged = state.currentEditorTemplateData.selectedChart.name !== name + const currentEditorTemplateData: typeof state.currentEditorTemplateData = { + ...state.currentEditorTemplateData, + isAppMetricsEnabled: isAppMetricsSupported + ? state.currentEditorTemplateData.isAppMetricsEnabled + : false, + selectedChart, + selectedChartRefId: +id, + schema: selectedChartTemplateDetails.schema, + readme: selectedChartTemplateDetails.readme, + guiSchema: selectedChartTemplateDetails.guiSchema, + + ...(isChartTypeChanged && { + editorTemplate: selectedChartTemplateDetails.editorTemplate, + unableToParseYaml: false, + // Not resetting originalTemplate since we are not changing it + }), + } + + return { + ...state, + isLoadingChangedChartDetails: false, + currentEditorTemplateData, + hideLockedKeys: isChartTypeChanged ? false : state.hideLockedKeys, + isResolvingVariables: isChartTypeChanged ? false : state.isResolvingVariables, + resolveScopedVariables: isChartTypeChanged ? false : state.resolveScopedVariables, + } + } + + case DeploymentTemplateActionType.CHART_CHANGE_ERROR: + return { + ...state, + isLoadingChangedChartDetails: false, + } + + case DeploymentTemplateActionType.INITIATE_RESOLVE_SCOPED_VARIABLES: + return { + ...state, + isResolvingVariables: true, + resolveScopedVariables: true, + } + + case DeploymentTemplateActionType.UN_RESOLVE_SCOPED_VARIABLES: + return { + ...state, + ...handleUnResolveScopedVariables(), + } + + case DeploymentTemplateActionType.RESOLVE_SCOPED_VARIABLES: { + const { resolvedEditorTemplate, resolvedOriginalTemplate, resolvedPublishedTemplate } = action.payload + + return { + ...state, + isResolvingVariables: false, + resolvedEditorTemplate, + resolvedOriginalTemplate, + resolvedPublishedTemplate, + } + } + + case DeploymentTemplateActionType.TOGGLE_DRAFT_COMMENTS: + return { + ...state, + showDraftComments: !state.showDraftComments, + } + + case DeploymentTemplateActionType.RESTORE_LAST_SAVED_TEMPLATE: + return handleRestoreLastSavedTemplate(state) + + case DeploymentTemplateActionType.CURRENT_EDITOR_VALUE_CHANGE: { + const wasGuiOrHideLockedKeysEdited = + state.wasGuiOrHideLockedKeysEdited || state.editMode === ConfigurationType.GUI + const { template } = action.payload + + const currentEditorTemplateData: typeof state.currentEditorTemplateData = { + ...state.currentEditorTemplateData, + editorTemplate: template, + unableToParseYaml: false, + } + + try { + YAML.parse(template) + } catch { + currentEditorTemplateData.unableToParseYaml = true + } + + return { + ...state, + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited, + } + } + + case DeploymentTemplateActionType.UPDATE_HIDE_LOCKED_KEYS: { + if (!reapplyRemovedLockedKeysToYaml) { + logExceptionToSentry( + new Error( + `reapplyRemovedLockedKeysToYaml is not available inside ${DeploymentTemplateActionType.UPDATE_HIDE_LOCKED_KEYS} action`, + ), + ) + return state + } + + const { hideLockedKeys } = action.payload + + if (hideLockedKeys) { + const { editorTemplate, removedPatches } = getEditorTemplateAndLockedKeys( + state.currentEditorTemplateData.editorTemplate, + state.lockedConfigKeysWithLockType.config, + ) + + const currentEditorTemplateData: typeof state.currentEditorTemplateData = { + ...state.currentEditorTemplateData, + editorTemplate, + removedPatches, + } + + return { + ...state, + currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited: true, + hideLockedKeys, + } + } + + try { + const updatedEditorValue = getCurrentTemplateWithLockedKeys({ + currentEditorTemplateData: state.currentEditorTemplateData, + wasGuiOrHideLockedKeysEdited: state.wasGuiOrHideLockedKeysEdited, + }) + + const currentEditorTemplateData: typeof state.currentEditorTemplateData = { + ...state.currentEditorTemplateData, + editorTemplate: updatedEditorValue, + removedPatches: [], + } + + return { + ...state, + hideLockedKeys, + currentEditorTemplateData, + } + } catch { + ToastManager.showToast({ + variant: ToastVariantType.error, + description: 'Something went wrong while parsing locked keys', + }) + + return state + } + } + + case DeploymentTemplateActionType.CHANGE_TO_GUI_MODE: + return { + ...state, + editMode: ConfigurationType.GUI, + } + + case DeploymentTemplateActionType.CHANGE_TO_YAML_MODE: + return handleSwitchToYAMLMode(state) + + case DeploymentTemplateActionType.UPDATE_CONFIG_HEADER_TAB: + return { + ...state, + ...handleUnResolveScopedVariables(), + configHeaderTab: action.payload.configHeaderTab, + } + + case DeploymentTemplateActionType.TOGGLE_SHOW_COMPARISON_WITH_MERGED_PATCHES: + return { + ...state, + shouldMergeTemplateWithPatches: !state.shouldMergeTemplateWithPatches, + } + + case DeploymentTemplateActionType.UPDATE_PROTECTION_VIEW_TAB: + return { + ...state, + ...handleUnResolveScopedVariables(), + selectedProtectionViewTab: action.payload.selectedProtectionViewTab, + } + + case DeploymentTemplateActionType.UPDATE_DRY_RUN_EDITOR_MODE: + return { + ...state, + ...handleUnResolveScopedVariables(), + dryRunEditorMode: action.payload.dryRunEditorMode, + } + + case DeploymentTemplateActionType.INITIATE_SAVE: + return { + ...state, + isSaving: true, + } + + case DeploymentTemplateActionType.SHOW_EDIT_HISTORY: + return { + ...state, + popupNodeType: ConfigToolbarPopupNodeType.EDIT_HISTORY, + } + + case DeploymentTemplateActionType.SHOW_DISCARD_DRAFT_POPUP: + return { + ...state, + popupNodeType: ConfigToolbarPopupNodeType.DISCARD_DRAFT, + } + + case DeploymentTemplateActionType.CLEAR_POPUP_NODE: + return { + ...state, + popupNodeType: null, + } + + case DeploymentTemplateActionType.UPDATE_README_MODE: + return { + ...handleSwitchToYAMLMode(state), + ...handleUnResolveScopedVariables(), + showReadMe: action.payload.showReadMe, + } + + case DeploymentTemplateActionType.CHANGE_COMPARE_FROM_SELECTED_OPTION: + return { + ...state, + compareFromSelectedOptionValue: action.payload.compareFromSelectedOptionValue, + } + + case DeploymentTemplateActionType.SHOW_LOCKED_DIFF_FOR_APPROVAL: + return { + ...state, + lockedDiffModalState: { + showLockedDiffForApproval: true, + showLockedTemplateDiffModal: true, + }, + } + + case DeploymentTemplateActionType.TOGGLE_APP_METRICS: + return { + ...state, + currentEditorTemplateData: { + ...state.currentEditorTemplateData, + isAppMetricsEnabled: !state.currentEditorTemplateData.isAppMetricsEnabled, + }, + } + + case DeploymentTemplateActionType.UPDATE_MERGE_STRATEGY: + return { + ...state, + currentEditorTemplateData: { + ...state.currentEditorTemplateData, + mergeStrategy: action.payload.mergeStrategy, + }, + } + + case DeploymentTemplateActionType.SHOW_DELETE_OVERRIDE_DIALOG: + return { + ...state, + showDeleteDraftOverrideDialog: action.payload.isProtected, + showDeleteOverrideDialog: !action.payload.isProtected, + } + + case DeploymentTemplateActionType.DELETE_LOCAL_OVERRIDE: + return handleRestoreLastSavedTemplate(state) + + case DeploymentTemplateActionType.OVERRIDE_TEMPLATE: + return { + ...state, + currentEditorTemplateData: { + ...state.currentEditorTemplateData, + isOverridden: true, + }, + } + + case DeploymentTemplateActionType.DELETE_OVERRIDE_CONCURRENT_PROTECTION_ERROR: + return { + ...state, + showDeleteDraftOverrideDialog: true, + showDeleteOverrideDialog: false, + } + + case DeploymentTemplateActionType.CLOSE_DELETE_DRAFT_OVERRIDE_DIALOG: + return { + ...state, + showDeleteDraftOverrideDialog: false, + } + + case DeploymentTemplateActionType.CLOSE_OVERRIDE_DIALOG: + return { + ...state, + showDeleteOverrideDialog: false, + } + + case DeploymentTemplateActionType.SAVE_ERROR: + return { + ...state, + isSaving: false, + showSaveChangesModal: action.payload.isProtectionError ? true : state.showSaveChangesModal, + } + + case DeploymentTemplateActionType.FINISH_SAVE: + return { + ...state, + isSaving: false, + lockedDiffModalState: { + showLockedTemplateDiffModal: action.payload.isLockConfigError, + showLockedDiffForApproval: false, + }, + } + + case DeploymentTemplateActionType.LOCKED_CHANGES_DETECTED_ON_SAVE: { + return { + ...state, + lockedDiffModalState: { + showLockedTemplateDiffModal: true, + showLockedDiffForApproval: false, + }, + } + } + + case DeploymentTemplateActionType.SHOW_PROTECTED_SAVE_MODAL: + return { + ...state, + showSaveChangesModal: true, + } + + case DeploymentTemplateActionType.CLOSE_SAVE_CHANGES_MODAL: + return { + ...state, + showSaveChangesModal: false, + } + + case DeploymentTemplateActionType.CLOSE_LOCKED_DIFF_MODAL: + return { + ...state, + lockedDiffModalState: { + showLockedTemplateDiffModal: false, + showLockedDiffForApproval: false, + }, + } + + default: + return state + } +} diff --git a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/types.ts b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/types.ts index 4aff7b8290..a5beed881a 100644 --- a/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/types.ts +++ b/src/Pages/Applications/DevtronApps/Details/AppConfigurations/MainContent/types.ts @@ -162,6 +162,7 @@ export interface ConfigDryRunProps { dryRunEditorMode: string handleChangeDryRunEditorMode: (mode: string) => void isDraftPresent: boolean + isApprovalPending: boolean isPublishedConfigPresent: boolean }