From fefc9f5d1591012f0642197af2f6a8b8463bba00 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Mon, 2 Dec 2024 16:42:47 +0530
Subject: [PATCH 01/36] feat: VariableDataTable - component integration
---
src/assets/icons/ic-choices-dropdown.svg | 20 +
src/assets/icons/ic-sliders-vertical.svg | 3 +
.../CIPipelineN/InputPluginSelect.tsx | 2 +-
.../CIPipelineN/VariableContainer.tsx | 71 +--
.../VariableDataTable/AddChoicesOverlay.tsx | 191 ++++++++
.../VariableDataTable/VariableDataTable.tsx | 430 ++++++++++++++++++
.../VariableDataTableRowAction.tsx | 73 +++
.../VariableDataTable/constants.ts | 48 ++
.../CIPipelineN/VariableDataTable/helpers.tsx | 12 +
.../CIPipelineN/VariableDataTable/index.ts | 1 +
.../CIPipelineN/VariableDataTable/types.ts | 57 +++
.../CIPipelineN/VariableDataTable/utils.tsx | 215 +++++++++
.../ciPipeline/ciPipeline.service.ts | 3 +-
src/css/base.scss | 4 +
14 files changed, 1059 insertions(+), 71 deletions(-)
create mode 100644 src/assets/icons/ic-choices-dropdown.svg
create mode 100644 src/assets/icons/ic-sliders-vertical.svg
create mode 100644 src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/constants.ts
create mode 100644 src/components/CIPipelineN/VariableDataTable/helpers.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/index.ts
create mode 100644 src/components/CIPipelineN/VariableDataTable/types.ts
create mode 100644 src/components/CIPipelineN/VariableDataTable/utils.tsx
diff --git a/src/assets/icons/ic-choices-dropdown.svg b/src/assets/icons/ic-choices-dropdown.svg
new file mode 100644
index 0000000000..6e7346e1ed
--- /dev/null
+++ b/src/assets/icons/ic-choices-dropdown.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/assets/icons/ic-sliders-vertical.svg b/src/assets/icons/ic-sliders-vertical.svg
new file mode 100644
index 0000000000..0bab3f6efa
--- /dev/null
+++ b/src/assets/icons/ic-sliders-vertical.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/CIPipelineN/InputPluginSelect.tsx b/src/components/CIPipelineN/InputPluginSelect.tsx
index d3a9256109..290ea9e0c9 100644
--- a/src/components/CIPipelineN/InputPluginSelect.tsx
+++ b/src/components/CIPipelineN/InputPluginSelect.tsx
@@ -236,7 +236,7 @@ export const InputPluginSelection = ({
placeholder={placeholder}
refVar={refVar}
tabIndex={selectedVariableIndex}
- handleKeyDown={handleOnKeyDown}
+ onKeyDown={handleOnKeyDown}
/>
{(variableData.refVariableStage ||
(variableData?.variableType && variableData.variableType !== 'NEW')) && (
diff --git a/src/components/CIPipelineN/VariableContainer.tsx b/src/components/CIPipelineN/VariableContainer.tsx
index 7c395e4e70..0323162f1f 100644
--- a/src/components/CIPipelineN/VariableContainer.tsx
+++ b/src/components/CIPipelineN/VariableContainer.tsx
@@ -22,6 +22,7 @@ import { PluginVariableType } from '../ciPipeline/types'
import CustomInputVariableSelect from './CustomInputVariableSelect'
import { ReactComponent as AlertTriangle } from '../../assets/icons/ic-alert-triangle.svg'
import { pipelineContext } from '../workflowEditor/workflowEditor'
+import { VariableDataTable } from './VariableDataTable'
export const VariableContainer = ({ type }: { type: PluginVariableType }) => {
const [collapsedSection, setCollapsedSection] = useState(type !== PluginVariableType.INPUT)
@@ -65,75 +66,7 @@ export const VariableContainer = ({ type }: { type: PluginVariableType }) => {
No {type} variables
)}
- {!collapsedSection && variableLength > 0 && (
-
-
Variable
-
Format
-
- {type === PluginVariableType.INPUT ? 'Value' : 'Description'}
-
- {formData[activeStageName].steps[selectedTaskIndex].pluginRefStepDetail[
- type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
- ]?.map((variable: VariableType, index) => {
- const errorObj =
- formDataErrorObj[activeStageName].steps[selectedTaskIndex]?.pluginRefStepDetail
- .inputVariables[index]
-
- const isInputVariableRequired = type === PluginVariableType.INPUT && !variable.allowEmptyValue
- return (
-
- {type === PluginVariableType.INPUT && variable.description ? (
-
-
- {variable.name}
-
-
- {variable.description}
-
- }
- >
-
-
- {variable.name}
-
-
-
- ) : (
- {variable.name}
- )}
-
- {variable.format}
- {type === PluginVariableType.INPUT ? (
-
-
- {errorObj && !errorObj.isValid && (
-
-
- {errorObj.message}
-
- )}
-
- ) : (
- {variable.description}
- )}
-
- )
- })}
-
- )}
+ {!collapsedSection && variableLength > 0 && }
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
new file mode 100644
index 0000000000..2265b5c696
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
@@ -0,0 +1,191 @@
+import { ChangeEvent } from 'react'
+
+import {
+ Button,
+ ButtonStyleType,
+ ButtonVariantType,
+ Checkbox,
+ CHECKBOX_VALUE,
+ ComponentSizeType,
+ CustomInput,
+ Tooltip,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
+import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
+import { ReactComponent as ICChoicesDropdown } from '@Icons/ic-choices-dropdown.svg'
+
+import { AddChoicesOverlayProps, VariableDataTableActionType } from './types'
+import { validateChoice } from './utils'
+
+export const AddChoicesOverlay = ({
+ choices,
+ askValueAtRuntime,
+ blockCustomValue,
+ rowId,
+ handleRowUpdateAction,
+}: AddChoicesOverlayProps) => {
+ // METHODS
+ const handleAddChoices = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ headerKey: null,
+ actionValue: (currentChoices) => [{ value: '', id: currentChoices.length, error: '' }, ...currentChoices],
+ })
+ }
+
+ const handleChoiceChange = (choiceId: number) => (e: ChangeEvent) => {
+ const choiceValue = e.target.value
+ // TODO: Rethink validation disc with product
+ const error = !validateChoice(choiceValue) ? 'This is a required field' : ''
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ headerKey: null,
+ actionValue: (currentChoices) =>
+ currentChoices.map((choice) =>
+ choice.id === choiceId ? { id: choiceId, value: choiceValue, error } : choice,
+ ),
+ })
+ }
+
+ const handleChoiceDelete = (choiceId: number) => () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ headerKey: null,
+ actionValue: (currentChoices) => currentChoices.filter(({ id }) => id !== choiceId),
+ })
+ }
+
+ const handleAllowCustomInput = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT,
+ rowId,
+ headerKey: null,
+ actionValue: !blockCustomValue,
+ })
+ }
+
+ const handleAskValueAtRuntime = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME,
+ rowId,
+ headerKey: null,
+ actionValue: !askValueAtRuntime,
+ })
+ }
+
+ return (
+
+ {choices.length ? (
+
+
+ }
+ variant={ButtonVariantType.text}
+ size={ComponentSizeType.small}
+ />
+
+
+ {choices.map(({ id, value, error }) => (
+
+
+ }
+ variant={ButtonVariantType.borderLess}
+ size={ComponentSizeType.medium}
+ onClick={handleChoiceDelete(id)}
+ style={ButtonStyleType.negativeGrey}
+ />
+
+ ))}
+
+
+ ) : (
+
+
+
+
+
Set value choices
+
Allow users to select a value from a pre-defined set of choices
+
+
+ }
+ variant={ButtonVariantType.text}
+ size={ComponentSizeType.small}
+ />
+
+
+
+ )}
+
+ {!!choices.length && (
+
+
+ Allow custom input
+ Allow entering any value other than provided choices
+
+ }
+ >
+
Allow Custom input
+
+
+ )}
+
+
+ Ask value at runtime
+
+ Value can be provided at runtime. Entered value will be pre-filled as default
+
+
+ }
+ >
+ Ask value at runtime
+
+
+
+
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
new file mode 100644
index 0000000000..8a848fc806
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -0,0 +1,430 @@
+import { useContext, useState, useEffect } from 'react'
+
+import {
+ DynamicDataTable,
+ DynamicDataTableProps,
+ DynamicDataTableRowDataType,
+ PluginType,
+ RefVariableType,
+ VariableType,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
+import { PluginVariableType } from '@Components/ciPipeline/types'
+
+import { ExtendedOptionType } from '@Components/app/types'
+import { getVariableDataTableHeaders } from './constants'
+import { getSystemVariableIcon } from './helpers'
+import { HandleRowUpdateActionProps, VariableDataKeys, VariableDataRowType, VariableDataTableActionType } from './types'
+import {
+ getEmptyVariableDataTableRow,
+ getFormatColumnRowProps,
+ getValColumnRowProps,
+ getVariableColumnRowProps,
+} from './utils'
+
+import { VariableDataTableRowAction } from './VariableDataTableRowAction'
+
+export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
+ // CONTEXTS
+ const {
+ inputVariablesListFromPrevStep,
+ activeStageName,
+ selectedTaskIndex,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ formDataErrorObj,
+ validateTask,
+ setFormData,
+ setFormDataErrorObj,
+ } = useContext(pipelineContext)
+
+ // CONSTANTS
+ const emptyRowParams = {
+ inputVariablesListFromPrevStep,
+ activeStageName,
+ selectedTaskIndex,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ type,
+ }
+ const currentStepTypeVariable =
+ formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
+ ? 'inlineStepDetail'
+ : 'pluginRefStepDetail'
+
+ const ioVariables: VariableType[] =
+ formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ]
+
+ const ioVariablesError: { isValid: boolean; message: string }[] =
+ formDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ]
+
+ // STATES
+ const [rows, setRows] = useState([])
+
+ // INITIAL ROWS
+ const getInitialRows = (): VariableDataRowType[] =>
+ ioVariables.map(
+ (
+ {
+ name,
+ allowEmptyValue,
+ description,
+ format,
+ variableType,
+ value,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ isRuntimeArg,
+ },
+ id,
+ ) => {
+ const isInputVariableRequired = type === PluginVariableType.INPUT && !allowEmptyValue
+
+ return {
+ data: {
+ variable: {
+ ...getVariableColumnRowProps(),
+ value: name,
+ required: isInputVariableRequired,
+ disabled: true,
+ },
+ format: {
+ ...getFormatColumnRowProps(),
+ type: DynamicDataTableRowDataType.TEXT,
+ value: format,
+ disabled: true,
+ props: {},
+ },
+ val:
+ type === PluginVariableType.INPUT
+ ? {
+ type: DynamicDataTableRowDataType.SELECT_TEXT,
+ value: variableType === RefVariableType.NEW ? value : refVariableName || '',
+ props: {
+ ...getValColumnRowProps(emptyRowParams, id).props,
+ Icon:
+ refVariableStage || variableType !== RefVariableType.NEW
+ ? getSystemVariableIcon()
+ : null,
+ },
+ }
+ : {
+ type: DynamicDataTableRowDataType.TEXT,
+ value: description,
+ disabled: true,
+ props: {},
+ },
+ },
+ customState: {
+ choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
+ id: index,
+ value: choiceValue,
+ error: '',
+ })),
+ askValueAtRuntime: isRuntimeArg ?? false,
+ blockCustomValue: valueConstraint?.blockCustomValue ?? false,
+ selectedValue: null,
+ },
+ id,
+ }
+ },
+ )
+
+ useEffect(() => {
+ setRows(getInitialRows())
+ }, [JSON.stringify(ioVariables)])
+
+ useEffect(() => {
+ if (rows.length) {
+ const updatedFormData = structuredClone(formData)
+ const updatedFormDataErrorObj = structuredClone(formDataErrorObj)
+
+ const updatedIOVariables: VariableType[] = rows.map(({ customState }, index) => {
+ const { askValueAtRuntime, blockCustomValue, choices, selectedValue } = customState
+ let variableDetail
+
+ if (selectedValue) {
+ if (selectedValue.refVariableStepIndex) {
+ variableDetail = {
+ value: '',
+ variableType: RefVariableType.FROM_PREVIOUS_STEP,
+ refVariableStepIndex: selectedValue.refVariableStepIndex,
+ refVariableName: selectedValue.label,
+ format: selectedValue.format,
+ refVariableStage: selectedValue.refVariableStage,
+ }
+ } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
+ variableDetail = {
+ variableType: RefVariableType.GLOBAL,
+ refVariableStepIndex: 0,
+ refVariableName: selectedValue.label,
+ format: selectedValue.format,
+ value: '',
+ refVariableStage: '',
+ }
+ } else {
+ variableDetail = {
+ variableType: RefVariableType.NEW,
+ value: selectedValue.label,
+ refVariableName: '',
+ refVariableStage: '',
+ }
+ }
+ if (formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.PLUGIN_REF) {
+ variableDetail.format = ioVariables[index].format
+ }
+ }
+
+ return {
+ ...ioVariables[index],
+ ...variableDetail,
+ isRuntimeArg: askValueAtRuntime,
+ valueConstraint: {
+ choices: choices.map(({ value }) => value),
+ blockCustomValue,
+ constraint: null,
+ },
+ }
+ })
+
+ updatedFormData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ] = updatedIOVariables
+
+ validateTask(
+ updatedFormData[activeStageName].steps[selectedTaskIndex],
+ updatedFormDataErrorObj[activeStageName].steps[selectedTaskIndex],
+ )
+ setFormDataErrorObj(updatedFormDataErrorObj)
+ setFormData(updatedFormData)
+ }
+ }, [rows])
+
+ // METHODS
+ const handleRowUpdateAction = ({ actionType, actionValue, headerKey, rowId }: HandleRowUpdateActionProps) => {
+ let updatedRows = [...rows]
+
+ switch (actionType) {
+ case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
+ updatedRows = updatedRows.map((row) => {
+ const { id, data, customState } = row
+
+ if (id === rowId) {
+ return {
+ ...row,
+ data: {
+ ...data,
+ val:
+ data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ? {
+ ...data.val,
+ props: {
+ ...data.val.props,
+ options: [
+ {
+ label: 'Default variables',
+ options: customState.choices.map(({ value }) => ({
+ label: value,
+ value,
+ })),
+ },
+ ...data.val.props.options.filter(
+ ({ label }) => label !== 'Default variables',
+ ),
+ ],
+ },
+ }
+ : data.val,
+ },
+ }
+ }
+
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_CHOICES:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowId
+ ? { ...row, customState: { ...row.customState, choices: actionValue(row.customState.choices) } }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowId
+ ? { ...row, customState: { ...row.customState, blockCustomValue: actionValue } }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowId
+ ? { ...row, customState: { ...row.customState, askValueAtRuntime: actionValue } }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ROW:
+ updatedRows = rows.map((row) =>
+ row.id === rowId
+ ? {
+ ...row,
+ data: {
+ ...row.data,
+ [headerKey]: {
+ ...row.data[headerKey],
+ value: actionValue,
+ },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_VAL_COLUMN:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowId && row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ val: {
+ ...row.data.val,
+ value: actionValue.value,
+ props: {
+ ...row.data.val.props,
+ Icon:
+ actionValue.value &&
+ ((actionValue.selectedValue as ExtendedOptionType).refVariableStage ||
+ ((actionValue.selectedValue as ExtendedOptionType)?.variableType &&
+ (actionValue.selectedValue as ExtendedOptionType).variableType !==
+ RefVariableType.NEW))
+ ? getSystemVariableIcon()
+ : null,
+ },
+ },
+ },
+ customState: {
+ ...row.customState,
+ selectedValue: actionValue.selectedValue,
+ },
+ }
+ }
+ return row
+ })
+ break
+
+ default:
+ break
+ }
+
+ setRows(updatedRows)
+ }
+
+ const dataTableHandleAddition = () => {
+ const data = getEmptyVariableDataTableRow(emptyRowParams)
+ const editedRows = [data, ...rows]
+ setRows(editedRows)
+ }
+
+ const dataTableHandleChange = (
+ updatedRow: VariableDataRowType,
+ headerKey: VariableDataKeys,
+ value: string,
+ extraData,
+ ) => {
+ if (headerKey !== 'val') {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: value,
+ headerKey,
+ rowId: updatedRow.id,
+ })
+ } else if (headerKey === 'val') {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
+ actionValue: { value, selectedValue: extraData.selectedValue },
+ headerKey,
+ rowId: updatedRow.id,
+ })
+ }
+ }
+
+ const dataTableHandleDelete = (row: VariableDataRowType) => {
+ const remainingRows = rows.filter(({ id }) => id !== row.id)
+
+ if (remainingRows.length === 0) {
+ const emptyRowData = getEmptyVariableDataTableRow(emptyRowParams)
+ setRows([emptyRowData])
+ return
+ }
+
+ setRows(remainingRows)
+ }
+
+ const handleChoicesAddToValColumn = (rowId: string | number) => () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS,
+ rowId,
+ headerKey: null,
+ actionValue: null,
+ })
+ }
+
+ const validationSchema: DynamicDataTableProps['validationSchema'] = (_, key, { id }) => {
+ if (key === 'val') {
+ const index = rows.findIndex((row) => row.id === id)
+ if (index > -1 && ioVariablesError[index]) {
+ const { isValid, message } = ioVariablesError[index]
+ return { isValid, errorMessages: [message] }
+ }
+ }
+
+ return { isValid: true, errorMessages: [] }
+ }
+
+ const actionButtonRenderer = (row: VariableDataRowType) => (
+
+ )
+
+ const getActionButtonConfig = (): DynamicDataTableProps['actionButtonConfig'] => {
+ if (type === PluginVariableType.INPUT) {
+ return {
+ renderer: actionButtonRenderer,
+ key: 'val',
+ position: 'end',
+ }
+ }
+ return null
+ }
+
+ return (
+
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
new file mode 100644
index 0000000000..d0f7505509
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
@@ -0,0 +1,73 @@
+import { useState } from 'react'
+
+import { TippyCustomized, TippyTheme } from '@devtron-labs/devtron-fe-common-lib'
+
+import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
+
+import { VariableDataTableActionType, VariableDataTableRowActionProps } from './types'
+import { getValidatedChoices } from './utils'
+
+import { AddChoicesOverlay } from './AddChoicesOverlay'
+
+export const VariableDataTableRowAction = ({
+ handleRowUpdateAction,
+ onClose,
+ row,
+}: VariableDataTableRowActionProps) => {
+ const { data, customState, id } = row
+ const [visible, setVisible] = useState(false)
+
+ const handleClose = () => {
+ const { choices, isValid } = getValidatedChoices(customState.choices)
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId: row.id,
+ headerKey: null,
+ actionValue: () => choices,
+ })
+ if (isValid) {
+ setVisible(false)
+ onClose()
+ }
+ }
+
+ const handleAction = () => {
+ if (visible) {
+ handleClose()
+ } else {
+ setVisible(true)
+ }
+ }
+
+ return (
+
+ }
+ appendTo={document.getElementById('visible-modal')}
+ >
+
+
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
new file mode 100644
index 0000000000..163387725c
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -0,0 +1,48 @@
+import { DynamicDataTableHeaderType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib'
+
+import { PluginVariableType } from '@Components/ciPipeline/types'
+
+import { VariableDataKeys } from './types'
+
+export const getVariableDataTableHeaders = (
+ type: PluginVariableType,
+): DynamicDataTableHeaderType[] => [
+ {
+ label: 'VARIABLE',
+ key: 'variable',
+ width: '200px',
+ },
+ {
+ label: 'TYPE',
+ key: 'format',
+ width: '100px',
+ },
+ {
+ label: type === PluginVariableType.INPUT ? 'VALUE' : 'DESCRIPTION',
+ key: 'val',
+ width: '1fr',
+ },
+]
+
+export const FORMAT_COLUMN_OPTIONS: SelectPickerOptionType[] = [
+ {
+ label: 'STRING',
+ value: 'STRING',
+ },
+ {
+ label: 'NUMBER',
+ value: 'NUMBER',
+ },
+ {
+ label: 'BOOLEAN',
+ value: 'BOOLEAN',
+ },
+ {
+ label: 'FILE',
+ value: 'FILE',
+ },
+ {
+ label: 'DATE',
+ value: 'DATE',
+ },
+]
diff --git a/src/components/CIPipelineN/VariableDataTable/helpers.tsx b/src/components/CIPipelineN/VariableDataTable/helpers.tsx
new file mode 100644
index 0000000000..763a3ca6fe
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/helpers.tsx
@@ -0,0 +1,12 @@
+import { Tooltip } from '@devtron-labs/devtron-fe-common-lib'
+
+import { ReactComponent as Var } from '@Icons/ic-var-initial.svg'
+import { TIPPY_VAR_MSG } from '../Constants'
+
+export const getSystemVariableIcon = () => (
+
+
+
+
+
+)
diff --git a/src/components/CIPipelineN/VariableDataTable/index.ts b/src/components/CIPipelineN/VariableDataTable/index.ts
new file mode 100644
index 0000000000..ee1427a9a9
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/index.ts
@@ -0,0 +1 @@
+export * from './VariableDataTable'
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
new file mode 100644
index 0000000000..a339c7741f
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -0,0 +1,57 @@
+import { DynamicDataTableRowType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib'
+
+export type VariableDataKeys = 'variable' | 'format' | 'val'
+
+export type VariableDataCustomState = {
+ choices: { id: number; value: string; error: string }[]
+ askValueAtRuntime: boolean
+ blockCustomValue: boolean
+ selectedValue: Record
+}
+
+export type VariableDataRowType = DynamicDataTableRowType
+
+export enum VariableDataTableActionType {
+ UPDATE_ROW = 'update_row',
+ UPDATE_VAL_COLUMN = 'update_val_column',
+ ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS = 'add_choices_to_value_column_options',
+ UPDATE_CHOICES = 'update_choices',
+ UPDATE_ASK_VALUE_AT_RUNTIME = 'update_ask_value_at_runtime',
+ UPDATE_ALLOW_CUSTOM_INPUT = 'update_allow_custom_input',
+}
+
+type VariableDataTableActionPropsMap = {
+ [VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT]: VariableDataCustomState['blockCustomValue']
+ [VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME]: VariableDataCustomState['askValueAtRuntime']
+ [VariableDataTableActionType.UPDATE_CHOICES]: (
+ currentChoices: VariableDataCustomState['choices'],
+ ) => VariableDataCustomState['choices']
+ [VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS]: null
+ [VariableDataTableActionType.UPDATE_VAL_COLUMN]: {
+ value: string
+ selectedValue: SelectPickerOptionType
+ }
+ [VariableDataTableActionType.UPDATE_ROW]: string
+}
+
+export type VariableDataTableAction<
+ T extends keyof VariableDataTableActionPropsMap = keyof VariableDataTableActionPropsMap,
+> = T extends keyof VariableDataTableActionPropsMap
+ ? { actionType: T; actionValue: VariableDataTableActionPropsMap[T] }
+ : never
+
+export type HandleRowUpdateActionProps = VariableDataTableAction & {
+ rowId: string | number
+ headerKey: VariableDataKeys
+}
+
+export interface VariableDataTableRowActionProps {
+ row: VariableDataRowType
+ onClose: () => void
+ handleRowUpdateAction: (props: HandleRowUpdateActionProps) => void
+}
+
+export type AddChoicesOverlayProps = Pick &
+ Pick & {
+ rowId: VariableDataTableRowActionProps['row']['id']
+ }
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
new file mode 100644
index 0000000000..2eaf20e1ac
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -0,0 +1,215 @@
+import {
+ DynamicDataTableRowDataType,
+ PluginType,
+ RefVariableStageType,
+ VariableType,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { BuildStageVariable, PATTERNS } from '@Config/constants'
+
+import { PipelineContext } from '@Components/workflowEditor/types'
+import { PluginVariableType } from '@Components/ciPipeline/types'
+import { excludeVariables } from '../Constants'
+import { FORMAT_COLUMN_OPTIONS } from './constants'
+import { VariableDataRowType } from './types'
+
+export const getValueColumnOptions = (
+ {
+ inputVariablesListFromPrevStep,
+ activeStageName,
+ selectedTaskIndex,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ type,
+ }: Pick<
+ PipelineContext,
+ | 'activeStageName'
+ | 'selectedTaskIndex'
+ | 'inputVariablesListFromPrevStep'
+ | 'formData'
+ | 'globalVariables'
+ | 'isCdPipeline'
+ > & { type: PluginVariableType },
+ index: number,
+) => {
+ const currentStepTypeVariable =
+ formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
+ ? 'inlineStepDetail'
+ : 'pluginRefStepDetail'
+
+ const ioVariables: VariableType[] =
+ formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ]
+
+ const previousStepVariables = []
+ const defaultVariables = (ioVariables[index]?.valueConstraint?.choices || []).map((value) => ({
+ label: value,
+ value,
+ }))
+
+ if (inputVariablesListFromPrevStep[activeStageName].length > 0) {
+ inputVariablesListFromPrevStep[activeStageName][selectedTaskIndex].forEach((element) => {
+ previousStepVariables.push({
+ ...element,
+ label: element.name,
+ value: element.name,
+ refVariableTaskName: formData[activeStageName]?.steps[element.refVariableStepIndex - 1].name,
+ })
+ })
+ }
+
+ if (activeStageName === BuildStageVariable.PostBuild) {
+ const preBuildStageVariables = []
+ const preBuildTaskLength = formData[BuildStageVariable.PreBuild]?.steps?.length
+ if (preBuildTaskLength >= 1 && !isCdPipeline) {
+ if (inputVariablesListFromPrevStep[BuildStageVariable.PreBuild].length > 0) {
+ inputVariablesListFromPrevStep[BuildStageVariable.PreBuild][preBuildTaskLength - 1].forEach(
+ (element) => {
+ preBuildStageVariables.push({
+ ...element,
+ label: element.name,
+ value: element.name,
+ refVariableTaskName:
+ formData[BuildStageVariable.PreBuild]?.steps[element.refVariableStepIndex - 1].name,
+ })
+ },
+ )
+ }
+
+ const stepTypeVariable =
+ formData[BuildStageVariable.PreBuild].steps[preBuildTaskLength - 1].stepType === PluginType.INLINE
+ ? 'inlineStepDetail'
+ : 'pluginRefStepDetail'
+ const preBuildStageLastTaskOutputVariables =
+ formData[BuildStageVariable.PreBuild].steps[preBuildTaskLength - 1][stepTypeVariable]?.outputVariables
+ const outputVariablesLength = preBuildStageLastTaskOutputVariables?.length || 0
+ for (let j = 0; j < outputVariablesLength; j++) {
+ if (preBuildStageLastTaskOutputVariables[j].name) {
+ const currentVariableDetails = preBuildStageLastTaskOutputVariables[j]
+ preBuildStageVariables.push({
+ ...currentVariableDetails,
+ label: currentVariableDetails.name,
+ value: currentVariableDetails.name,
+ refVariableStepIndex: preBuildTaskLength,
+ refVariableTaskName: formData[BuildStageVariable.PreBuild].steps[preBuildTaskLength - 1].name,
+ refVariableStage: RefVariableStageType.PRE_CI,
+ })
+ }
+ }
+ }
+
+ return [
+ {
+ label: 'Default variables',
+ options: defaultVariables,
+ },
+ {
+ label: 'From Pre-build Stage',
+ options: preBuildStageVariables,
+ },
+ {
+ label: 'From Post-build Stage',
+ options: previousStepVariables,
+ },
+ {
+ label: 'System variables',
+ options: globalVariables,
+ },
+ ]
+ }
+
+ return [
+ {
+ label: 'Default variables',
+ options: defaultVariables,
+ },
+ {
+ label: 'From Previous Steps',
+ options: previousStepVariables,
+ },
+ {
+ label: 'System variables',
+ options: globalVariables.filter(
+ (variable) =>
+ (isCdPipeline && variable.stageType !== 'post-cd') || !excludeVariables.includes(variable.value),
+ ),
+ },
+ ]
+}
+
+export const getVariableColumnRowProps = (): VariableDataRowType['data']['variable'] => {
+ const data: VariableDataRowType['data']['variable'] = {
+ value: '',
+ type: DynamicDataTableRowDataType.TEXT,
+ props: {},
+ }
+
+ return data
+}
+
+export const getFormatColumnRowProps = () => {
+ const data: VariableDataRowType['data']['format'] = {
+ value: FORMAT_COLUMN_OPTIONS[0].value,
+ type: DynamicDataTableRowDataType.DROPDOWN,
+ props: {
+ options: FORMAT_COLUMN_OPTIONS,
+ },
+ }
+
+ return data
+}
+
+export const getValColumnRowProps = (params: Parameters[0], index: number) => {
+ const data: VariableDataRowType['data']['val'] = {
+ value: '',
+ type: DynamicDataTableRowDataType.SELECT_TEXT,
+ props: {
+ placeholder: 'Select source or input value',
+ options: getValueColumnOptions(params, index),
+ },
+ }
+
+ return data
+}
+
+export const getEmptyVariableDataTableRow = (
+ params: Pick<
+ PipelineContext,
+ | 'activeStageName'
+ | 'selectedTaskIndex'
+ | 'inputVariablesListFromPrevStep'
+ | 'formData'
+ | 'globalVariables'
+ | 'isCdPipeline'
+ > & { type: PluginVariableType },
+): VariableDataRowType => {
+ const id = (Date.now() * Math.random()).toString(16)
+ const data: VariableDataRowType = {
+ data: {
+ variable: getVariableColumnRowProps(),
+ format: getFormatColumnRowProps(),
+ val: getValColumnRowProps(params, -1),
+ },
+ id,
+ }
+
+ return data
+}
+
+export const validateChoice = (choice: string) => PATTERNS.STRING.test(choice)
+
+export const getValidatedChoices = (choices: VariableDataRowType['customState']['choices']) => {
+ let isValid = true
+
+ const updatedChoices: VariableDataRowType['customState']['choices'] = choices.map((choice) => {
+ const error = !validateChoice(choice.value) ? 'This is a required field' : ''
+ if (isValid && !!error) {
+ isValid = false
+ }
+ return { ...choice, error }
+ })
+
+ return { isValid, choices: updatedChoices }
+}
diff --git a/src/components/ciPipeline/ciPipeline.service.ts b/src/components/ciPipeline/ciPipeline.service.ts
index 7e777621e0..b8673fc27a 100644
--- a/src/components/ciPipeline/ciPipeline.service.ts
+++ b/src/components/ciPipeline/ciPipeline.service.ts
@@ -23,6 +23,7 @@ import {
PluginType,
RefVariableType,
PipelineBuildStageType,
+ VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
import { Routes, SourceTypeMap, TriggerType, ViewType } from '../../config'
import { getSourceConfig, getWebhookDataMetaConfig } from '../../services/service'
@@ -407,7 +408,7 @@ function migrateOldData(
): PipelineBuildStageType {
const commonFields = {
value: '',
- format: 'STRING',
+ format: VariableTypeFormat.STRING,
description: '',
defaultValue: '',
variableType: RefVariableType.GLOBAL,
diff --git a/src/css/base.scss b/src/css/base.scss
index 725ae3e252..dc60bbe7df 100644
--- a/src/css/base.scss
+++ b/src/css/base.scss
@@ -441,6 +441,10 @@ a:focus {
right: -3px;
}
+.dc__right-8 {
+ right: 8px;
+}
+
.dc__right-10 {
right: 10px;
}
From 69f304ef2fad8a29e4a2af12bb73066f8ac42e56 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 5 Dec 2024 17:55:07 +0530
Subject: [PATCH 02/36] feat: CI/CD Pipeline - Pre/Post Custom Task & Plugin
Integration
---
src/components/CIPipelineN/CIPipeline.tsx | 4 +
.../CIPipelineN/CreatePluginModal/utils.tsx | 1 +
.../CIPipelineN/TaskDetailComponent.tsx | 14 +-
.../CIPipelineN/VariableContainer.tsx | 4 +-
.../VariableDataTable/AddChoicesOverlay.tsx | 191 -----
.../VariableDataTable/ValueConfigOverlay.tsx | 316 +++++++
.../VariableConfigOverlay.tsx | 84 ++
.../VariableDataTable/VariableDataTable.tsx | 778 +++++++++++-------
.../VariableDataTablePopupMenu.tsx | 72 ++
.../VariableDataTableRowAction.tsx | 73 --
.../VariableDataTable/constants.ts | 64 +-
.../CIPipelineN/VariableDataTable/types.ts | 147 +++-
.../CIPipelineN/VariableDataTable/utils.tsx | 487 +++++++++--
src/components/cdPipeline/CDPipeline.tsx | 3 +
src/components/cdPipeline/cdpipeline.util.tsx | 2 +-
.../ciPipeline/ciPipeline.service.ts | 44 +-
src/components/ciPipeline/types.ts | 8 +
src/components/workflowEditor/types.ts | 2 +
src/config/constants.ts | 1 +
src/css/base.scss | 6 +-
20 files changed, 1611 insertions(+), 690 deletions(-)
delete mode 100644 src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
create mode 100644 src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
delete mode 100644 src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index a0496e2dba..9f826b9dd2 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -62,6 +62,7 @@ import {
getInitData,
getInitDataWithCIPipeline,
saveCIPipeline,
+ uploadCIPipelineFile,
} from '../ciPipeline/ciPipeline.service'
import { ValidationRules } from '../ciPipeline/validationRules'
import { CIBuildType, CIPipelineBuildType, CIPipelineDataType, CIPipelineType } from '../ciPipeline/types'
@@ -781,6 +782,8 @@ export default function CIPipeline({
}
}
+ const uploadFile = (file: File[]) => uploadCIPipelineFile({ appId: +appId, ciPipelineId: +ciPipelineId, file })
+
const contextValue = useMemo(
() => ({
formData,
@@ -808,6 +811,7 @@ export default function CIPipeline({
handleDisableParentModalCloseUpdate,
handleValidateMandatoryPlugins,
mandatoryPluginData,
+ uploadFile,
}),
[
formData,
diff --git a/src/components/CIPipelineN/CreatePluginModal/utils.tsx b/src/components/CIPipelineN/CreatePluginModal/utils.tsx
index ed6b9a398e..73b6a77846 100644
--- a/src/components/CIPipelineN/CreatePluginModal/utils.tsx
+++ b/src/components/CIPipelineN/CreatePluginModal/utils.tsx
@@ -156,6 +156,7 @@ const parseInputVariablesIntoCreatePluginPayload = (
valueType: variable.variableType,
referenceVariableName: variable.refVariableName,
isExposed: true,
+ // TODO: handle file type here
})) || []
export const getCreatePluginPayload = ({
diff --git a/src/components/CIPipelineN/TaskDetailComponent.tsx b/src/components/CIPipelineN/TaskDetailComponent.tsx
index 7cfcba114e..bfda4e0bc2 100644
--- a/src/components/CIPipelineN/TaskDetailComponent.tsx
+++ b/src/components/CIPipelineN/TaskDetailComponent.tsx
@@ -31,13 +31,13 @@ import {
PluginDataStoreType,
getUpdatedPluginStore,
} from '@devtron-labs/devtron-fe-common-lib'
-import CustomInputOutputVariables from './CustomInputOutputVariables'
import { PluginDetailHeader } from './PluginDetailHeader'
import { TaskTypeDetailComponent } from './TaskTypeDetailComponent'
import { ValidationRules } from '../ciPipeline/validationRules'
import { pipelineContext } from '../workflowEditor/workflowEditor'
import { PluginDetailHeaderProps, TaskDetailComponentParamsType } from './types'
import { filterInvalidConditionDetails } from '@Components/cdPipeline/cdpipeline.util'
+import { VariableDataTable } from './VariableDataTable'
export const TaskDetailComponent = () => {
const {
@@ -273,7 +273,9 @@ export const TaskDetailComponent = () => {
{selectedStep.stepType === PluginType.INLINE ? (
-
+ <>
+
+ >
) : (
)}{' '}
@@ -289,7 +291,13 @@ export const TaskDetailComponent = () => {
{formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].scriptType !==
ScriptType.CONTAINERIMAGE && (
-
+ <>
+
+ >
)}
>
) : (
diff --git a/src/components/CIPipelineN/VariableContainer.tsx b/src/components/CIPipelineN/VariableContainer.tsx
index 0323162f1f..6abd456c1a 100644
--- a/src/components/CIPipelineN/VariableContainer.tsx
+++ b/src/components/CIPipelineN/VariableContainer.tsx
@@ -26,7 +26,7 @@ import { VariableDataTable } from './VariableDataTable'
export const VariableContainer = ({ type }: { type: PluginVariableType }) => {
const [collapsedSection, setCollapsedSection] = useState(type !== PluginVariableType.INPUT)
- const { formData, selectedTaskIndex, activeStageName, formDataErrorObj } = useContext(pipelineContext)
+ const { formData, selectedTaskIndex, activeStageName, formDataErrorObj } = useContext(pipelineContext)
const variableLength =
formData[activeStageName].steps[selectedTaskIndex].pluginRefStepDetail[
type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
@@ -66,7 +66,7 @@ export const VariableContainer = ({ type }: { type: PluginVariableType }) => {
No {type} variables
)}
- {!collapsedSection && variableLength > 0 && }
+ {!collapsedSection && variableLength > 0 && }
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
deleted file mode 100644
index 2265b5c696..0000000000
--- a/src/components/CIPipelineN/VariableDataTable/AddChoicesOverlay.tsx
+++ /dev/null
@@ -1,191 +0,0 @@
-import { ChangeEvent } from 'react'
-
-import {
- Button,
- ButtonStyleType,
- ButtonVariantType,
- Checkbox,
- CHECKBOX_VALUE,
- ComponentSizeType,
- CustomInput,
- Tooltip,
-} from '@devtron-labs/devtron-fe-common-lib'
-
-import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
-import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
-import { ReactComponent as ICChoicesDropdown } from '@Icons/ic-choices-dropdown.svg'
-
-import { AddChoicesOverlayProps, VariableDataTableActionType } from './types'
-import { validateChoice } from './utils'
-
-export const AddChoicesOverlay = ({
- choices,
- askValueAtRuntime,
- blockCustomValue,
- rowId,
- handleRowUpdateAction,
-}: AddChoicesOverlayProps) => {
- // METHODS
- const handleAddChoices = () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- headerKey: null,
- actionValue: (currentChoices) => [{ value: '', id: currentChoices.length, error: '' }, ...currentChoices],
- })
- }
-
- const handleChoiceChange = (choiceId: number) => (e: ChangeEvent) => {
- const choiceValue = e.target.value
- // TODO: Rethink validation disc with product
- const error = !validateChoice(choiceValue) ? 'This is a required field' : ''
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- headerKey: null,
- actionValue: (currentChoices) =>
- currentChoices.map((choice) =>
- choice.id === choiceId ? { id: choiceId, value: choiceValue, error } : choice,
- ),
- })
- }
-
- const handleChoiceDelete = (choiceId: number) => () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- headerKey: null,
- actionValue: (currentChoices) => currentChoices.filter(({ id }) => id !== choiceId),
- })
- }
-
- const handleAllowCustomInput = () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT,
- rowId,
- headerKey: null,
- actionValue: !blockCustomValue,
- })
- }
-
- const handleAskValueAtRuntime = () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME,
- rowId,
- headerKey: null,
- actionValue: !askValueAtRuntime,
- })
- }
-
- return (
-
- {choices.length ? (
-
-
- }
- variant={ButtonVariantType.text}
- size={ComponentSizeType.small}
- />
-
-
- {choices.map(({ id, value, error }) => (
-
-
- }
- variant={ButtonVariantType.borderLess}
- size={ComponentSizeType.medium}
- onClick={handleChoiceDelete(id)}
- style={ButtonStyleType.negativeGrey}
- />
-
- ))}
-
-
- ) : (
-
-
-
-
-
Set value choices
-
Allow users to select a value from a pre-defined set of choices
-
-
- }
- variant={ButtonVariantType.text}
- size={ComponentSizeType.small}
- />
-
-
-
- )}
-
- {!!choices.length && (
-
-
- Allow custom input
- Allow entering any value other than provided choices
-
- }
- >
-
Allow Custom input
-
-
- )}
-
-
- Ask value at runtime
-
- Value can be provided at runtime. Entered value will be pre-filled as default
-
-
- }
- >
- Ask value at runtime
-
-
-
-
- )
-}
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
new file mode 100644
index 0000000000..2d94cd8667
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
@@ -0,0 +1,316 @@
+import { ChangeEvent } from 'react'
+
+import {
+ Button,
+ ButtonStyleType,
+ ButtonVariantType,
+ Checkbox,
+ CHECKBOX_VALUE,
+ ComponentSizeType,
+ CustomInput,
+ ResizableTextarea,
+ SelectPicker,
+ SelectPickerOptionType,
+ Tooltip,
+ VariableTypeFormat,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
+import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
+import { ReactComponent as ICChoicesDropdown } from '@Icons/ic-choices-dropdown.svg'
+import { ReactComponent as ICInfoOutlineGrey } from '@Icons/ic-info-outline-grey.svg'
+
+import { ConfigOverlayProps, VariableDataTableActionType } from './types'
+import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_OPTIONS_MAP } from './constants'
+import { testValueForNumber } from './utils'
+
+export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlayProps) => {
+ const { id: rowId, data, customState } = row
+ const { choices, askValueAtRuntime, blockCustomValue, fileInfo } = customState
+
+ // CONSTANTS
+ const isFormatNumber = data.format.value === VariableTypeFormat.NUMBER
+ const isFormatBoolOrDate =
+ data.format.value === VariableTypeFormat.BOOL || data.format.value === VariableTypeFormat.DATE
+ const isFormatFile = data.format.value === VariableTypeFormat.FILE
+
+ // METHODS
+ const handleAddChoices = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ actionValue: (currentChoices) => [{ value: '', id: currentChoices.length, error: '' }, ...currentChoices],
+ })
+ }
+
+ const handleChoiceChange = (choiceId: number) => (e: ChangeEvent) => {
+ const choiceValue = e.target.value
+ if (isFormatNumber && !testValueForNumber(choiceValue)) {
+ return
+ }
+
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ actionValue: (currentChoices) =>
+ currentChoices.map((choice) =>
+ choice.id === choiceId ? { id: choiceId, value: choiceValue } : choice,
+ ),
+ })
+ }
+
+ const handleChoiceDelete = (choiceId: number) => () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_CHOICES,
+ rowId,
+ actionValue: (currentChoices) => currentChoices.filter(({ id }) => id !== choiceId),
+ })
+ }
+
+ const handleAllowCustomInput = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT,
+ rowId,
+ actionValue: !blockCustomValue,
+ })
+ }
+
+ const handleAskValueAtRuntime = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME,
+ rowId,
+ actionValue: !askValueAtRuntime,
+ })
+ }
+
+ const handleFileMountChange = (e: ChangeEvent) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_MOUNT,
+ rowId,
+ actionValue: e.target.value,
+ })
+ }
+
+ const handleFileAllowedExtensionsChange = (e: ChangeEvent) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS,
+ rowId,
+ actionValue: e.target.value,
+ })
+ }
+
+ const handleFileMaxSizeChange = (e: ChangeEvent) => {
+ const maxSize = e.target.value
+ if (!testValueForNumber(maxSize)) {
+ return
+ }
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_MAX_SIZE,
+ rowId,
+ actionValue: {
+ size: maxSize,
+ unit: fileInfo.unit,
+ },
+ })
+ }
+
+ const handleFileSizeUnitChange = (unit: SelectPickerOptionType) => {
+ if (fileInfo.unit !== unit) {
+ const maxSize = fileInfo.maxUploadSize
+ ? (parseFloat(fileInfo.maxUploadSize) * unit.value).toString()
+ : fileInfo.maxUploadSize
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_MAX_SIZE,
+ rowId,
+ actionValue: {
+ size: maxSize,
+ unit,
+ },
+ })
+ }
+ }
+
+ // RENDERERS
+ const renderContent = () => {
+ if (isFormatFile) {
+ return (
+
+
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
+
+
+
+
+
+ )
+ }
+
+ if (isFormatBoolOrDate) {
+ return (
+
+
+
+
+
Choices not allowed
+
{`Variable type "${FORMAT_OPTIONS_MAP[data.format.value]}" does not support choices`}
+
+
+
+ )
+ }
+
+ if (choices.length) {
+ return (
+
+
+ }
+ variant={ButtonVariantType.text}
+ size={ComponentSizeType.small}
+ />
+
+
+ {choices.map(({ id, value }) => (
+
+
+ }
+ variant={ButtonVariantType.borderLess}
+ size={ComponentSizeType.medium}
+ onClick={handleChoiceDelete(id)}
+ style={ButtonStyleType.negativeGrey}
+ />
+
+ ))}
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
Set value choices
+
Allow users to select a value from a pre-defined set of choices
+
+
+ }
+ variant={ButtonVariantType.text}
+ size={ComponentSizeType.small}
+ />
+
+
+
+ )
+ }
+
+ return (
+ <>
+ {renderContent()}
+
+ {!!choices.length && (
+
+
+ Allow custom input
+ Allow entering any value other than provided choices
+
+ }
+ >
+ Allow Custom input
+
+
+ )}
+
+
+ Ask value at runtime
+
+ Value can be provided at runtime. Entered value will be pre-filled as default
+
+
+ }
+ >
+ Ask value at runtime
+
+
+
+ >
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
new file mode 100644
index 0000000000..8527ff22a3
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
@@ -0,0 +1,84 @@
+import { ChangeEvent } from 'react'
+
+import { Checkbox, CHECKBOX_VALUE, CustomInput, ResizableTextarea, Tooltip } from '@devtron-labs/devtron-fe-common-lib'
+
+import { ConfigOverlayProps, VariableDataTableActionType } from './types'
+
+export const VariableConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlayProps) => {
+ const { id: rowId, data, customState } = row
+ const { variableDescription, isVariableRequired } = customState
+
+ // METHODS
+ const handleVariableName = (e: ChangeEvent) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ rowId,
+ headerKey: 'variable',
+ actionValue: e.target.value,
+ })
+ }
+
+ const handleVariableDescriptionChange = (e: ChangeEvent) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_VARIABLE_DESCRIPTION,
+ rowId,
+ actionValue: e.target.value,
+ })
+ }
+
+ const handleVariableRequired = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_VARIABLE_REQUIRED,
+ rowId,
+ actionValue: !isVariableRequired,
+ })
+ }
+
+ return (
+ <>
+
+
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
+
+
+
+
+
+
+
+ Value is required
+ Get this tooltip from Utkarsh
+
+ }
+ >
+ Value is required
+
+
+
+ >
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
index 8a848fc806..8cb15c0e43 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -1,4 +1,4 @@
-import { useContext, useState, useEffect } from 'react'
+import { useContext, useState, useEffect, useRef } from 'react'
import {
DynamicDataTable,
@@ -6,26 +6,50 @@ import {
DynamicDataTableRowDataType,
PluginType,
RefVariableType,
+ SelectPickerOptionType,
+ ToastManager,
+ ToastVariantType,
VariableType,
+ VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVariableType } from '@Components/ciPipeline/types'
-
import { ExtendedOptionType } from '@Components/app/types'
-import { getVariableDataTableHeaders } from './constants'
+
+import {
+ FILE_UPLOAD_SIZE_UNIT_OPTIONS,
+ getVariableDataTableHeaders,
+ VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+} from './constants'
import { getSystemVariableIcon } from './helpers'
-import { HandleRowUpdateActionProps, VariableDataKeys, VariableDataRowType, VariableDataTableActionType } from './types'
import {
+ HandleRowUpdateActionProps,
+ VariableDataCustomState,
+ VariableDataKeys,
+ VariableDataRowType,
+ VariableDataTableActionType,
+} from './types'
+import {
+ convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
- getFormatColumnRowProps,
getValColumnRowProps,
- getVariableColumnRowProps,
+ getValColumnRowValue,
+ getVariableDataTableInitialRows,
+ validateMaxFileSize,
} from './utils'
-import { VariableDataTableRowAction } from './VariableDataTableRowAction'
-
-export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
+import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
+import { VariableConfigOverlay } from './VariableConfigOverlay'
+import { ValueConfigOverlay } from './ValueConfigOverlay'
+
+export const VariableDataTable = ({
+ type,
+ isCustomTask = false,
+}: {
+ type: PluginVariableType
+ isCustomTask?: boolean
+}) => {
// CONTEXTS
const {
inputVariablesListFromPrevStep,
@@ -38,6 +62,8 @@ export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
validateTask,
setFormData,
setFormDataErrorObj,
+ calculateLastStepDetail,
+ uploadFile,
} = useContext(pipelineContext)
// CONSTANTS
@@ -49,7 +75,16 @@ export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
globalVariables,
isCdPipeline,
type,
+ description: null,
+ format: VariableTypeFormat.STRING,
+ variableType: RefVariableType.NEW,
+ value: '',
+ refVariableName: null,
+ refVariableStage: null,
+ valueConstraint: null,
+ id: 0,
}
+
const currentStepTypeVariable =
formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
? 'inlineStepDetail'
@@ -68,321 +103,463 @@ export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
// STATES
const [rows, setRows] = useState([])
- // INITIAL ROWS
- const getInitialRows = (): VariableDataRowType[] =>
- ioVariables.map(
- (
- {
- name,
- allowEmptyValue,
- description,
- format,
- variableType,
- value,
- refVariableName,
- refVariableStage,
- valueConstraint,
- isRuntimeArg,
- },
- id,
- ) => {
- const isInputVariableRequired = type === PluginVariableType.INPUT && !allowEmptyValue
-
- return {
- data: {
- variable: {
- ...getVariableColumnRowProps(),
- value: name,
- required: isInputVariableRequired,
- disabled: true,
- },
- format: {
- ...getFormatColumnRowProps(),
- type: DynamicDataTableRowDataType.TEXT,
- value: format,
- disabled: true,
- props: {},
- },
- val:
- type === PluginVariableType.INPUT
- ? {
- type: DynamicDataTableRowDataType.SELECT_TEXT,
- value: variableType === RefVariableType.NEW ? value : refVariableName || '',
- props: {
- ...getValColumnRowProps(emptyRowParams, id).props,
- Icon:
- refVariableStage || variableType !== RefVariableType.NEW
- ? getSystemVariableIcon()
- : null,
- },
- }
- : {
- type: DynamicDataTableRowDataType.TEXT,
- value: description,
- disabled: true,
- props: {},
- },
- },
- customState: {
- choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
- id: index,
- value: choiceValue,
- error: '',
- })),
- askValueAtRuntime: isRuntimeArg ?? false,
- blockCustomValue: valueConstraint?.blockCustomValue ?? false,
- selectedValue: null,
- },
- id,
- }
- },
- )
-
- useEffect(() => {
- setRows(getInitialRows())
- }, [JSON.stringify(ioVariables)])
+ // REFS
+ const initialRowsSet = useRef('')
useEffect(() => {
- if (rows.length) {
- const updatedFormData = structuredClone(formData)
- const updatedFormDataErrorObj = structuredClone(formDataErrorObj)
-
- const updatedIOVariables: VariableType[] = rows.map(({ customState }, index) => {
- const { askValueAtRuntime, blockCustomValue, choices, selectedValue } = customState
- let variableDetail
-
- if (selectedValue) {
- if (selectedValue.refVariableStepIndex) {
- variableDetail = {
- value: '',
- variableType: RefVariableType.FROM_PREVIOUS_STEP,
- refVariableStepIndex: selectedValue.refVariableStepIndex,
- refVariableName: selectedValue.label,
- format: selectedValue.format,
- refVariableStage: selectedValue.refVariableStage,
- }
- } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
- variableDetail = {
- variableType: RefVariableType.GLOBAL,
- refVariableStepIndex: 0,
- refVariableName: selectedValue.label,
- format: selectedValue.format,
- value: '',
- refVariableStage: '',
- }
- } else {
- variableDetail = {
- variableType: RefVariableType.NEW,
- value: selectedValue.label,
- refVariableName: '',
- refVariableStage: '',
- }
- }
- if (formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.PLUGIN_REF) {
- variableDetail.format = ioVariables[index].format
- }
- }
-
- return {
- ...ioVariables[index],
- ...variableDetail,
- isRuntimeArg: askValueAtRuntime,
- valueConstraint: {
- choices: choices.map(({ value }) => value),
- blockCustomValue,
- constraint: null,
- },
- }
- })
+ setRows(
+ ioVariables?.length
+ ? getVariableDataTableInitialRows({ emptyRowParams, ioVariables, isCustomTask, type })
+ : [getEmptyVariableDataTableRow(emptyRowParams)],
+ )
+ initialRowsSet.current = 'set'
+ }, [])
- updatedFormData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
- type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
- ] = updatedIOVariables
-
- validateTask(
- updatedFormData[activeStageName].steps[selectedTaskIndex],
- updatedFormDataErrorObj[activeStageName].steps[selectedTaskIndex],
- )
- setFormDataErrorObj(updatedFormDataErrorObj)
- setFormData(updatedFormData)
- }
- }, [rows])
+ // useEffect(() => {
+ // console.log('meg', rows, ioVariables, formDataErrorObj)
+ // }, [JSON.stringify(ioVariables)])
// METHODS
- const handleRowUpdateAction = ({ actionType, actionValue, headerKey, rowId }: HandleRowUpdateActionProps) => {
- let updatedRows = [...rows]
-
- switch (actionType) {
- case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
- updatedRows = updatedRows.map((row) => {
- const { id, data, customState } = row
-
- if (id === rowId) {
- return {
- ...row,
- data: {
- ...data,
- val:
- data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
- ? {
- ...data.val,
- props: {
- ...data.val.props,
- options: [
- {
- label: 'Default variables',
- options: customState.choices.map(({ value }) => ({
- label: value,
- value,
- })),
- },
- ...data.val.props.options.filter(
- ({ label }) => label !== 'Default variables',
+ const handleRowUpdateAction = (rowAction: HandleRowUpdateActionProps) => {
+ const { actionType } = rowAction
+
+ setRows((prevRows) => {
+ let updatedRows = [...prevRows]
+ switch (actionType) {
+ case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
+ updatedRows = updatedRows.map((row) => {
+ const { id, data, customState } = row
+ const choicesOptions = customState.choices
+ .filter(({ value }) => !!value)
+ .map(({ value }) => ({ label: value, value }))
+
+ if (id === rowAction.rowId && choicesOptions.length > 0) {
+ return {
+ ...row,
+ data: {
+ ...data,
+ val:
+ data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ? {
+ ...data.val,
+ props: {
+ ...data.val.props,
+ options: data.val.props.options.map((option) =>
+ option.label === VAL_COLUMN_CHOICES_DROPDOWN_LABEL
+ ? {
+ label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+ options: choicesOptions,
+ }
+ : option,
),
- ],
- },
- }
- : data.val,
- },
+ },
+ }
+ : data.val,
+ },
+ }
}
- }
- return row
- })
- break
-
- case VariableDataTableActionType.UPDATE_CHOICES:
- updatedRows = updatedRows.map((row) =>
- row.id === rowId
- ? { ...row, customState: { ...row.customState, choices: actionValue(row.customState.choices) } }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
- updatedRows = updatedRows.map((row) =>
- row.id === rowId
- ? { ...row, customState: { ...row.customState, blockCustomValue: actionValue } }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME:
- updatedRows = updatedRows.map((row) =>
- row.id === rowId
- ? { ...row, customState: { ...row.customState, askValueAtRuntime: actionValue } }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_ROW:
- updatedRows = rows.map((row) =>
- row.id === rowId
- ? {
- ...row,
- data: {
- ...row.data,
- [headerKey]: {
- ...row.data[headerKey],
- value: actionValue,
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_CHOICES:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ choices: rowAction.actionValue(row.customState.choices),
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ data: {
+ ...row.data,
+ ...(row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ? {
+ val: {
+ ...row.data.val,
+ props: {
+ ...row.data.val.props,
+ selectPickerProps: {
+ isCreatable:
+ row.data.format.value !== VariableTypeFormat.BOOL &&
+ row.data.format.value !== VariableTypeFormat.DATE &&
+ !row.customState?.blockCustomValue,
+ },
+ },
+ },
+ }
+ : {}),
+ },
+ customState: { ...row.customState, blockCustomValue: rowAction.actionValue },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? { ...row, customState: { ...row.customState, askValueAtRuntime: rowAction.actionValue } }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_VARIABLE_DESCRIPTION:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: { ...row.customState, variableDescription: rowAction.actionValue },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_VARIABLE_REQUIRED:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ data: {
+ ...row.data,
+ variable: { ...row.data.variable, required: rowAction.actionValue },
+ },
+ customState: { ...row.customState, isVariableRequired: rowAction.actionValue },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_MOUNT:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ fileInfo: { ...row.customState.fileInfo, mountDir: rowAction.actionValue },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ data:
+ row.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD
+ ? {
+ ...row.data,
+ val: {
+ ...row.data.val,
+ props: {
+ ...row.data.val.props,
+ fileTypes: rowAction.actionValue.split(','),
+ },
+ },
+ }
+ : row.data,
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ allowedExtensions: rowAction.actionValue,
+ },
},
- },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_VAL_COLUMN:
- updatedRows = updatedRows.map((row) => {
- if (row.id === rowId && row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
- return {
- ...row,
- data: {
- ...row.data,
- val: {
- ...row.data.val,
- value: actionValue.value,
- props: {
- ...row.data.val.props,
- Icon:
- actionValue.value &&
- ((actionValue.selectedValue as ExtendedOptionType).refVariableStage ||
- ((actionValue.selectedValue as ExtendedOptionType)?.variableType &&
- (actionValue.selectedValue as ExtendedOptionType).variableType !==
- RefVariableType.NEW))
- ? getSystemVariableIcon()
- : null,
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_MAX_SIZE:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ maxUploadSize: rowAction.actionValue.size,
+ unit: rowAction.actionValue.unit,
+ },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.ADD_ROW:
+ updatedRows = [
+ getEmptyVariableDataTableRow({ ...emptyRowParams, id: rowAction.actionValue }),
+ ...updatedRows,
+ ]
+ break
+
+ case VariableDataTableActionType.DELETE_ROW:
+ updatedRows = updatedRows.filter((row) => row.id !== rowAction.rowId)
+ if (updatedRows.length === 0) {
+ updatedRows = [getEmptyVariableDataTableRow(emptyRowParams)]
+ }
+ break
+
+ case VariableDataTableActionType.UPDATE_ROW:
+ updatedRows = rows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ data: {
+ ...row.data,
+ [rowAction.headerKey]: {
+ ...row.data[rowAction.headerKey],
+ value: rowAction.actionValue,
+ },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ id: rowAction.actionValue.fileReferenceId,
+ },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_VAL_COLUMN:
+ updatedRows = updatedRows.map((row) => {
+ if (
+ row.id === rowAction.rowId &&
+ row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ) {
+ const { selectedValue, value } = rowAction.actionValue as {
+ selectedValue: SelectPickerOptionType & ExtendedOptionType
+ value: string
+ }
+ const isSystemVariable =
+ !!selectedValue.refVariableStage ||
+ (selectedValue?.variableType && selectedValue.variableType !== RefVariableType.NEW)
+
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ val: {
+ ...row.data.val,
+ value: getValColumnRowValue(
+ row.data.val.value,
+ row.data.format.value as VariableTypeFormat,
+ value,
+ selectedValue,
+ isSystemVariable,
+ ),
+ props: {
+ ...row.data.val.props,
+ Icon: value && isSystemVariable ? getSystemVariableIcon() : null,
+ },
},
},
- },
- customState: {
- ...row.customState,
- selectedValue: actionValue.selectedValue,
- },
+ customState: {
+ ...row.customState,
+ selectedValue: rowAction.actionValue.selectedValue,
+ },
+ }
}
- }
- return row
- })
- break
- default:
- break
- }
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_FORMAT_COLUMN:
+ updatedRows = updatedRows.map((row) => {
+ if (
+ row.id === rowAction.rowId &&
+ row.data.format.type === DynamicDataTableRowDataType.DROPDOWN
+ ) {
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ format: {
+ ...row.data.format,
+ value: rowAction.actionValue.value,
+ },
+ val: getValColumnRowProps({
+ ...emptyRowParams,
+ activeStageName,
+ formData,
+ type,
+ format: rowAction.actionValue.value as VariableTypeFormat,
+ id: rowAction.rowId as number,
+ }),
+ },
+ customState: {
+ isVariableRequired: false,
+ variableDescription: '',
+ selectedValue: rowAction.actionValue.selectedValue,
+ choices: [],
+ blockCustomValue: false,
+ askValueAtRuntime: false,
+ fileInfo: {
+ id: null,
+ allowedExtensions: '',
+ maxUploadSize: '',
+ mountDir: '',
+ unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
+ },
+ },
+ }
+ }
+ return row
+ })
+ break
+
+ default:
+ break
+ }
+
+ const { updatedFormData, updatedFormDataErrorObj } = convertVariableDataTableToFormData({
+ rows: updatedRows,
+ activeStageName,
+ formData,
+ formDataErrorObj,
+ selectedTaskIndex,
+ type,
+ validateTask,
+ calculateLastStepDetail,
+ })
+ setFormDataErrorObj(updatedFormDataErrorObj)
+ setFormData(updatedFormData)
- setRows(updatedRows)
+ return updatedRows
+ })
}
const dataTableHandleAddition = () => {
- const data = getEmptyVariableDataTableRow(emptyRowParams)
- const editedRows = [data, ...rows]
- setRows(editedRows)
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.ADD_ROW,
+ actionValue: rows.length,
+ })
}
- const dataTableHandleChange = (
- updatedRow: VariableDataRowType,
- headerKey: VariableDataKeys,
- value: string,
+ const dataTableHandleChange: DynamicDataTableProps['onRowEdit'] = (
+ updatedRow,
+ headerKey,
+ value,
extraData,
) => {
- if (headerKey !== 'val') {
+ if (headerKey === 'val' && updatedRow.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: value,
- headerKey,
+ actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
+ actionValue: { value, selectedValue: extraData.selectedValue, files: extraData.files },
rowId: updatedRow.id,
})
- } else if (headerKey === 'val') {
+ } else if (
+ headerKey === 'val' &&
+ updatedRow.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD &&
+ extraData.files.length
+ ) {
+ const isFileSizeValid = validateMaxFileSize(
+ extraData.files[0],
+ parseFloat(updatedRow.customState.fileInfo.maxUploadSize),
+ updatedRow.customState.fileInfo.unit.label as string,
+ )
+
+ if (isFileSizeValid) {
+ // TODO: check this merge with UPDATE_FILE_UPLOAD_INFO after loading state
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: value,
+ headerKey,
+ rowId: updatedRow.id,
+ })
+
+ uploadFile(extraData.files)
+ .then((res) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
+ actionValue: { fileReferenceId: res.id },
+ rowId: updatedRow.id,
+ })
+ })
+ .catch(() => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: '',
+ headerKey,
+ rowId: updatedRow.id,
+ })
+ })
+ } else {
+ // TODO: get message from Utkarsh
+ ToastManager.showToast({
+ title: 'Large File Size',
+ description: 'Large File Size',
+ variant: ToastVariantType.error,
+ })
+ }
+ } else if (headerKey === 'format' && updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
+ actionType: VariableDataTableActionType.UPDATE_FORMAT_COLUMN,
actionValue: { value, selectedValue: extraData.selectedValue },
+ rowId: updatedRow.id,
+ })
+ } else {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: value,
headerKey,
rowId: updatedRow.id,
})
}
}
- const dataTableHandleDelete = (row: VariableDataRowType) => {
- const remainingRows = rows.filter(({ id }) => id !== row.id)
-
- if (remainingRows.length === 0) {
- const emptyRowData = getEmptyVariableDataTableRow(emptyRowParams)
- setRows([emptyRowData])
- return
- }
-
- setRows(remainingRows)
+ const dataTableHandleDelete: DynamicDataTableProps['onRowDelete'] = (
+ row,
+ ) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.DELETE_ROW,
+ rowId: row.id,
+ })
}
- const handleChoicesAddToValColumn = (rowId: string | number) => () => {
+ const onActionButtonPopupClose = (rowId: string | number) => () => {
handleRowUpdateAction({
actionType: VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS,
rowId,
- headerKey: null,
- actionValue: null,
})
}
- const validationSchema: DynamicDataTableProps['validationSchema'] = (_, key, { id }) => {
+ const validationSchema: DynamicDataTableProps['validationSchema'] = (
+ _,
+ key,
+ { id },
+ ) => {
if (key === 'val') {
const index = rows.findIndex((row) => row.id === id)
if (index > -1 && ioVariablesError[index]) {
@@ -395,11 +572,12 @@ export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
}
const actionButtonRenderer = (row: VariableDataRowType) => (
-
+
+
+
)
const getActionButtonConfig = (): DynamicDataTableProps['actionButtonConfig'] => {
@@ -413,16 +591,30 @@ export const VariableDataTable = ({ type }: { type: PluginVariableType }) => {
return null
}
+ const getTrailingCellIcon = (): DynamicDataTableProps['trailingCellIcon'] => ({
+ variable:
+ isCustomTask && type === PluginVariableType.INPUT
+ ? (row: VariableDataRowType) => (
+
+
+
+ )
+ : null,
+ })
+
return (
-
+ key={initialRowsSet.current}
headers={getVariableDataTableHeaders(type)}
rows={rows}
- isAdditionNotAllowed
- isDeletionNotAllowed
+ readOnly={!isCustomTask && type === PluginVariableType.OUTPUT}
+ isAdditionNotAllowed={!isCustomTask}
+ isDeletionNotAllowed={!isCustomTask}
+ trailingCellIcon={getTrailingCellIcon()}
onRowEdit={dataTableHandleChange}
onRowDelete={dataTableHandleDelete}
onRowAdd={dataTableHandleAddition}
- showError
+ // showError
validationSchema={validationSchema}
actionButtonConfig={getActionButtonConfig()}
/>
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
new file mode 100644
index 0000000000..9169b6c3d9
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -0,0 +1,72 @@
+import { useState } from 'react'
+
+import {
+ Button,
+ ButtonStyleType,
+ ButtonVariantType,
+ ComponentSizeType,
+ PopupMenu,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
+import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
+
+import { VariableDataTablePopupMenuProps } from './types'
+
+export const VariableDataTablePopupMenu = ({
+ showIcon,
+ heading,
+ children,
+ onClose,
+}: VariableDataTablePopupMenuProps) => {
+ // STATES
+ const [visible, setVisible] = useState(false)
+
+ // METHODS
+ const handleClose = () => {
+ setVisible(false)
+ onClose?.()
+ }
+
+ const handleAction = (open: boolean) => {
+ if (visible !== open) {
+ if (open) {
+ setVisible(true)
+ } else {
+ handleClose()
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+ {visible && (
+
+
+
+ {showIcon &&
}
+
{heading}
+
+
}
+ dataTestId="popup-close-button"
+ ariaLabel="Close Popup"
+ showAriaLabelInTippy={false}
+ onClick={handleClose}
+ />
+
+ {children}
+
+ )}
+
+ {visible && }
+
+ )
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
deleted file mode 100644
index d0f7505509..0000000000
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTableRowAction.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { useState } from 'react'
-
-import { TippyCustomized, TippyTheme } from '@devtron-labs/devtron-fe-common-lib'
-
-import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
-
-import { VariableDataTableActionType, VariableDataTableRowActionProps } from './types'
-import { getValidatedChoices } from './utils'
-
-import { AddChoicesOverlay } from './AddChoicesOverlay'
-
-export const VariableDataTableRowAction = ({
- handleRowUpdateAction,
- onClose,
- row,
-}: VariableDataTableRowActionProps) => {
- const { data, customState, id } = row
- const [visible, setVisible] = useState(false)
-
- const handleClose = () => {
- const { choices, isValid } = getValidatedChoices(customState.choices)
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId: row.id,
- headerKey: null,
- actionValue: () => choices,
- })
- if (isValid) {
- setVisible(false)
- onClose()
- }
- }
-
- const handleAction = () => {
- if (visible) {
- handleClose()
- } else {
- setVisible(true)
- }
- }
-
- return (
-
- }
- appendTo={document.getElementById('visible-modal')}
- >
-
-
- )
-}
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 163387725c..1589e5e9c7 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -1,4 +1,8 @@
-import { DynamicDataTableHeaderType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ DynamicDataTableHeaderType,
+ SelectPickerOptionType,
+ VariableTypeFormat,
+} from '@devtron-labs/devtron-fe-common-lib'
import { PluginVariableType } from '@Components/ciPipeline/types'
@@ -24,25 +28,63 @@ export const getVariableDataTableHeaders = (
},
]
+export const VAL_COLUMN_CHOICES_DROPDOWN_LABEL = 'Default values'
+
+export const FORMAT_OPTIONS_MAP = {
+ [VariableTypeFormat.STRING]: 'String',
+ [VariableTypeFormat.NUMBER]: 'Number',
+ [VariableTypeFormat.BOOL]: 'Boolean',
+ [VariableTypeFormat.DATE]: 'Date',
+ [VariableTypeFormat.FILE]: 'File',
+}
+
export const FORMAT_COLUMN_OPTIONS: SelectPickerOptionType[] = [
{
- label: 'STRING',
- value: 'STRING',
+ label: 'String',
+ value: VariableTypeFormat.STRING,
},
{
- label: 'NUMBER',
- value: 'NUMBER',
+ label: 'Number',
+ value: VariableTypeFormat.NUMBER,
},
{
- label: 'BOOLEAN',
- value: 'BOOLEAN',
+ label: 'Boolean',
+ value: VariableTypeFormat.BOOL,
},
{
- label: 'FILE',
- value: 'FILE',
+ label: 'Date',
+ value: VariableTypeFormat.DATE,
},
{
- label: 'DATE',
- value: 'DATE',
+ label: 'File',
+ value: VariableTypeFormat.FILE,
},
]
+
+export const VAL_COLUMN_BOOL_OPTIONS: SelectPickerOptionType[] = [
+ { label: 'TRUE', value: 'TRUE' },
+ { label: 'FALSE', value: 'FALSE' },
+]
+
+export const VAL_COLUMN_DATE_OPTIONS: SelectPickerOptionType[] = [
+ { label: 'YYYY-MM-DD', value: 'YYYY-MM-DD', description: 'RFC 3339' },
+ { label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm', description: 'RFC 3339 with mins' },
+ { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss', description: 'RFC 3339 with secs' },
+ { label: 'YYYY-MM-DD HH:mm:ssZ', value: 'YYYY-MM-DD HH:mm:ssZ', description: 'RFC 3339 with secs and TZ' },
+ { label: 'YYYY-MM-DDT15Z0700', value: 'ISO', description: 'ISO8601 with hours' },
+ { label: 'YYYY-MM-DDTHH:mm:ss[Z]', value: 'YYYY-MM-DDTHH:mm:ss[Z]', description: 'ISO8601 with secs' },
+ { label: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]', value: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]', description: 'ISO8601 with nanosecs' },
+]
+
+export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
+ {
+ label: 'KB',
+ value: 1024,
+ },
+ {
+ label: 'MB',
+ value: 1 / 1024,
+ },
+]
+
+export const DECIMAL_REGEX = /^\d*\.?\d*$/
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index a339c7741f..a84fb9e9ad 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -1,57 +1,158 @@
-import { DynamicDataTableRowType, SelectPickerOptionType } from '@devtron-labs/devtron-fe-common-lib'
+import { PluginVariableType } from '@Components/ciPipeline/types'
+import { PipelineContext } from '@Components/workflowEditor/types'
+import { DynamicDataTableRowType, SelectPickerOptionType, VariableType } from '@devtron-labs/devtron-fe-common-lib'
export type VariableDataKeys = 'variable' | 'format' | 'val'
export type VariableDataCustomState = {
- choices: { id: number; value: string; error: string }[]
+ variableDescription: string
+ isVariableRequired: boolean
+ choices: { id: number; value: string }[]
askValueAtRuntime: boolean
blockCustomValue: boolean
+ // Check for support in the TableRowTypes
selectedValue: Record
+ fileInfo: {
+ id: number
+ mountDir: string
+ allowedExtensions: string
+ maxUploadSize: string
+ unit: SelectPickerOptionType
+ }
}
export type VariableDataRowType = DynamicDataTableRowType
export enum VariableDataTableActionType {
+ // GENERAL ACTIONS
+ ADD_ROW = 'add-row',
UPDATE_ROW = 'update_row',
+ DELETE_ROW = 'delete_row',
UPDATE_VAL_COLUMN = 'update_val_column',
+ UPDATE_FORMAT_COLUMN = 'update_format_column',
+ UPDATE_FILE_UPLOAD_INFO = 'update_file_upload_info',
+
+ // TABLE ROW ACTIONS
ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS = 'add_choices_to_value_column_options',
UPDATE_CHOICES = 'update_choices',
UPDATE_ASK_VALUE_AT_RUNTIME = 'update_ask_value_at_runtime',
UPDATE_ALLOW_CUSTOM_INPUT = 'update_allow_custom_input',
+ UPDATE_FILE_MOUNT = 'update_file_mount',
+ UPDATE_FILE_ALLOWED_EXTENSIONS = 'update_file_allowed_extensions',
+ UPDATE_FILE_MAX_SIZE = 'update_file_max_size',
+
+ // VARIABLE COLUMN ACTIONS
+ UPDATE_VARIABLE_DESCRIPTION = 'update_variable_description',
+ UPDATE_VARIABLE_REQUIRED = 'update_variable_required',
}
type VariableDataTableActionPropsMap = {
- [VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT]: VariableDataCustomState['blockCustomValue']
- [VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME]: VariableDataCustomState['askValueAtRuntime']
- [VariableDataTableActionType.UPDATE_CHOICES]: (
- currentChoices: VariableDataCustomState['choices'],
- ) => VariableDataCustomState['choices']
- [VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS]: null
+ [VariableDataTableActionType.ADD_ROW]: { actionValue: number }
+ [VariableDataTableActionType.UPDATE_ROW]: {
+ actionValue: string
+ headerKey: VariableDataKeys
+ rowId: string | number
+ }
+ [VariableDataTableActionType.DELETE_ROW]: {
+ rowId: string | number
+ }
[VariableDataTableActionType.UPDATE_VAL_COLUMN]: {
- value: string
- selectedValue: SelectPickerOptionType
+ actionValue: {
+ value: string
+ selectedValue: SelectPickerOptionType
+ files: File[]
+ }
+ rowId: string | number
+ }
+ [VariableDataTableActionType.UPDATE_FORMAT_COLUMN]: {
+ actionValue: {
+ value: string
+ selectedValue: SelectPickerOptionType
+ }
+ rowId: string | number
+ }
+ [VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO]: {
+ actionValue: Pick
+ rowId: string | number
+ }
+
+ [VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT]: {
+ actionValue: VariableDataCustomState['blockCustomValue']
+ rowId: string | number
+ }
+ [VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME]: {
+ actionValue: VariableDataCustomState['askValueAtRuntime']
+ rowId: string | number
}
- [VariableDataTableActionType.UPDATE_ROW]: string
+ [VariableDataTableActionType.UPDATE_CHOICES]: {
+ actionValue: (currentChoices: VariableDataCustomState['choices']) => VariableDataCustomState['choices']
+ rowId: string | number
+ }
+ [VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS]: {
+ rowId: string | number
+ }
+ [VariableDataTableActionType.UPDATE_FILE_MOUNT]: {
+ rowId: string | number
+ actionValue: string
+ }
+ [VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS]: {
+ rowId: string | number
+ actionValue: string
+ }
+ [VariableDataTableActionType.UPDATE_FILE_MAX_SIZE]: {
+ rowId: string | number
+ actionValue: {
+ size: string
+ unit: SelectPickerOptionType
+ }
+ }
+
+ [VariableDataTableActionType.UPDATE_VARIABLE_DESCRIPTION]: { actionValue: string; rowId: string | number }
+ [VariableDataTableActionType.UPDATE_VARIABLE_REQUIRED]: { actionValue: boolean; rowId: string | number }
}
export type VariableDataTableAction<
T extends keyof VariableDataTableActionPropsMap = keyof VariableDataTableActionPropsMap,
-> = T extends keyof VariableDataTableActionPropsMap
- ? { actionType: T; actionValue: VariableDataTableActionPropsMap[T] }
- : never
+> = T extends keyof VariableDataTableActionPropsMap ? { actionType: T } & VariableDataTableActionPropsMap[T] : never
+
+export type HandleRowUpdateActionProps = VariableDataTableAction
-export type HandleRowUpdateActionProps = VariableDataTableAction & {
- rowId: string | number
- headerKey: VariableDataKeys
+export interface VariableDataTablePopupMenuProps {
+ heading: string
+ showIcon?: boolean
+ onClose?: () => void
+ children: JSX.Element
}
-export interface VariableDataTableRowActionProps {
+export interface ConfigOverlayProps {
row: VariableDataRowType
- onClose: () => void
handleRowUpdateAction: (props: HandleRowUpdateActionProps) => void
}
-export type AddChoicesOverlayProps = Pick &
- Pick & {
- rowId: VariableDataTableRowActionProps['row']['id']
- }
+export type GetValColumnRowPropsType = Pick<
+ PipelineContext,
+ | 'activeStageName'
+ | 'formData'
+ | 'globalVariables'
+ | 'isCdPipeline'
+ | 'selectedTaskIndex'
+ | 'inputVariablesListFromPrevStep'
+> &
+ Pick<
+ VariableType,
+ | 'format'
+ | 'value'
+ | 'refVariableName'
+ | 'refVariableStage'
+ | 'valueConstraint'
+ | 'description'
+ | 'variableType'
+ | 'id'
+ > & { type: PluginVariableType }
+
+export interface GetVariableDataTableInitialRowsProps {
+ ioVariables: VariableType[]
+ type: PluginVariableType
+ isCustomTask: boolean
+ emptyRowParams: GetValColumnRowPropsType
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 2eaf20e1ac..c2427e047c 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -1,65 +1,78 @@
+import dayjs from 'dayjs'
+
import {
+ ConditionType,
DynamicDataTableRowDataType,
PluginType,
RefVariableStageType,
+ RefVariableType,
+ SelectPickerOptionType,
VariableType,
+ VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
-import { BuildStageVariable, PATTERNS } from '@Config/constants'
-
+import { BuildStageVariable } from '@Config/constants'
import { PipelineContext } from '@Components/workflowEditor/types'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import { excludeVariables } from '../Constants'
-import { FORMAT_COLUMN_OPTIONS } from './constants'
-import { VariableDataRowType } from './types'
-
-export const getValueColumnOptions = (
- {
- inputVariablesListFromPrevStep,
- activeStageName,
- selectedTaskIndex,
- formData,
- globalVariables,
- isCdPipeline,
- type,
- }: Pick<
- PipelineContext,
- | 'activeStageName'
- | 'selectedTaskIndex'
- | 'inputVariablesListFromPrevStep'
- | 'formData'
- | 'globalVariables'
- | 'isCdPipeline'
- > & { type: PluginVariableType },
- index: number,
-) => {
- const currentStepTypeVariable =
- formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
- ? 'inlineStepDetail'
- : 'pluginRefStepDetail'
- const ioVariables: VariableType[] =
- formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
- type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
- ]
+import { ExtendedOptionType } from '@Components/app/types'
+import { excludeVariables } from '../Constants'
+import {
+ DECIMAL_REGEX,
+ FILE_UPLOAD_SIZE_UNIT_OPTIONS,
+ FORMAT_COLUMN_OPTIONS,
+ VAL_COLUMN_BOOL_OPTIONS,
+ VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+ VAL_COLUMN_DATE_OPTIONS,
+} from './constants'
+import { GetValColumnRowPropsType, GetVariableDataTableInitialRowsProps, VariableDataRowType } from './types'
+import { getSystemVariableIcon } from './helpers'
+export const getOptionsForValColumn = ({
+ inputVariablesListFromPrevStep,
+ activeStageName,
+ selectedTaskIndex,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ format,
+ valueConstraint,
+}: Pick<
+ PipelineContext,
+ | 'activeStageName'
+ | 'selectedTaskIndex'
+ | 'inputVariablesListFromPrevStep'
+ | 'formData'
+ | 'globalVariables'
+ | 'isCdPipeline'
+> &
+ Pick) => {
const previousStepVariables = []
- const defaultVariables = (ioVariables[index]?.valueConstraint?.choices || []).map((value) => ({
+ const defaultValues = (valueConstraint?.choices || []).map>((value) => ({
label: value,
value,
}))
- if (inputVariablesListFromPrevStep[activeStageName].length > 0) {
- inputVariablesListFromPrevStep[activeStageName][selectedTaskIndex].forEach((element) => {
- previousStepVariables.push({
- ...element,
- label: element.name,
- value: element.name,
- refVariableTaskName: formData[activeStageName]?.steps[element.refVariableStepIndex - 1].name,
- })
- })
+ if (format === VariableTypeFormat.BOOL) {
+ defaultValues.push(...VAL_COLUMN_BOOL_OPTIONS)
+ }
+
+ if (format === VariableTypeFormat.DATE) {
+ defaultValues.push(...VAL_COLUMN_DATE_OPTIONS)
}
+ if (format)
+ if (inputVariablesListFromPrevStep[activeStageName].length > 0) {
+ inputVariablesListFromPrevStep[activeStageName][selectedTaskIndex].forEach((element) => {
+ previousStepVariables.push({
+ ...element,
+ label: element.name,
+ value: element.name,
+ refVariableTaskName: formData[activeStageName]?.steps[element.refVariableStepIndex - 1].name,
+ })
+ })
+ }
+
if (activeStageName === BuildStageVariable.PostBuild) {
const preBuildStageVariables = []
const preBuildTaskLength = formData[BuildStageVariable.PreBuild]?.steps?.length
@@ -102,8 +115,8 @@ export const getValueColumnOptions = (
return [
{
- label: 'Default variables',
- options: defaultVariables,
+ label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+ options: defaultValues,
},
{
label: 'From Pre-build Stage',
@@ -122,8 +135,8 @@ export const getValueColumnOptions = (
return [
{
- label: 'Default variables',
- options: defaultVariables,
+ label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+ options: defaultValues,
},
{
label: 'From Previous Steps',
@@ -139,77 +152,369 @@ export const getValueColumnOptions = (
]
}
-export const getVariableColumnRowProps = (): VariableDataRowType['data']['variable'] => {
+export const getVariableColumnRowProps = () => {
const data: VariableDataRowType['data']['variable'] = {
value: '',
type: DynamicDataTableRowDataType.TEXT,
- props: {},
+ props: {
+ placeholder: 'Enter variable name',
+ },
}
return data
}
-export const getFormatColumnRowProps = () => {
- const data: VariableDataRowType['data']['format'] = {
- value: FORMAT_COLUMN_OPTIONS[0].value,
- type: DynamicDataTableRowDataType.DROPDOWN,
- props: {
- options: FORMAT_COLUMN_OPTIONS,
- },
+export const getFormatColumnRowProps = ({
+ format,
+ isCustomTask,
+}: Pick & { isCustomTask: boolean }): VariableDataRowType['data']['format'] => {
+ if (isCustomTask) {
+ return {
+ value: format,
+ type: DynamicDataTableRowDataType.DROPDOWN,
+ props: {
+ options: FORMAT_COLUMN_OPTIONS,
+ },
+ }
}
- return data
+ return {
+ type: DynamicDataTableRowDataType.TEXT,
+ value: format,
+ disabled: true,
+ props: {},
+ }
}
-export const getValColumnRowProps = (params: Parameters[0], index: number) => {
- const data: VariableDataRowType['data']['val'] = {
- value: '',
- type: DynamicDataTableRowDataType.SELECT_TEXT,
- props: {
- placeholder: 'Select source or input value',
- options: getValueColumnOptions(params, index),
- },
+export const getValColumnRowProps = ({
+ format,
+ type,
+ variableType,
+ value,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ description,
+ activeStageName,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+}: GetValColumnRowPropsType): VariableDataRowType['data']['format'] => {
+ if (type === PluginVariableType.INPUT) {
+ if (format === VariableTypeFormat.FILE) {
+ return {
+ type: DynamicDataTableRowDataType.FILE_UPLOAD,
+ value,
+ props: {
+ fileTypes: valueConstraint?.constraint?.fileProperty?.allowedExtensions || [],
+ },
+ }
+ }
+
+ return {
+ type: DynamicDataTableRowDataType.SELECT_TEXT,
+ value: variableType === RefVariableType.NEW ? value : refVariableName || '',
+ props: {
+ placeholder: 'Enter value or variable',
+ options: getOptionsForValColumn({
+ activeStageName,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ format,
+ valueConstraint,
+ }),
+ selectPickerProps: {
+ isCreatable:
+ format !== VariableTypeFormat.BOOL &&
+ (!valueConstraint?.choices?.length || !valueConstraint.blockCustomValue),
+ },
+ Icon:
+ refVariableStage || (variableType && variableType !== RefVariableType.NEW)
+ ? getSystemVariableIcon()
+ : null,
+ },
+ }
}
- return data
+ return {
+ type: DynamicDataTableRowDataType.TEXT,
+ value: description,
+ props: {},
+ }
+}
+
+export const testValueForNumber = (value: string) => !value || DECIMAL_REGEX.test(value)
+
+export const getValColumnRowValue = (
+ currentValue: string,
+ format: VariableTypeFormat,
+ value: string,
+ selectedValue: SelectPickerOptionType & ExtendedOptionType,
+ isSystemVariable: boolean,
+) => {
+ const isNumberFormat = !isSystemVariable && format === VariableTypeFormat.NUMBER
+ if (isNumberFormat && !testValueForNumber(value)) {
+ return currentValue
+ }
+
+ const isDateFormat = !isSystemVariable && value && format === VariableTypeFormat.DATE
+ if (isDateFormat && selectedValue.description) {
+ const now = dayjs()
+ return selectedValue.value !== 'ISO' ? now.format(selectedValue.value) : now.toISOString()
+ }
+
+ return value
}
-export const getEmptyVariableDataTableRow = (
- params: Pick<
- PipelineContext,
- | 'activeStageName'
- | 'selectedTaskIndex'
- | 'inputVariablesListFromPrevStep'
- | 'formData'
- | 'globalVariables'
- | 'isCdPipeline'
- > & { type: PluginVariableType },
-): VariableDataRowType => {
- const id = (Date.now() * Math.random()).toString(16)
+export const getEmptyVariableDataTableRow = (params: GetValColumnRowPropsType): VariableDataRowType => {
const data: VariableDataRowType = {
data: {
variable: getVariableColumnRowProps(),
- format: getFormatColumnRowProps(),
- val: getValColumnRowProps(params, -1),
+ format: getFormatColumnRowProps({ format: VariableTypeFormat.STRING, isCustomTask: true }),
+ val: getValColumnRowProps(params),
+ },
+ id: params.id,
+ customState: {
+ variableDescription: '',
+ isVariableRequired: false,
+ choices: [],
+ askValueAtRuntime: false,
+ blockCustomValue: false,
+ selectedValue: null,
+ fileInfo: {
+ id: null,
+ mountDir: '/devtroncd',
+ allowedExtensions: '',
+ maxUploadSize: '',
+ unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
+ },
},
- id,
}
return data
}
-export const validateChoice = (choice: string) => PATTERNS.STRING.test(choice)
+export const getVariableDataTableInitialRows = ({
+ ioVariables,
+ type,
+ isCustomTask,
+ emptyRowParams,
+}: GetVariableDataTableInitialRowsProps): VariableDataRowType[] =>
+ ioVariables.map(
+ ({
+ name,
+ allowEmptyValue,
+ description,
+ format,
+ variableType,
+ value,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ isRuntimeArg,
+ fileMountDir,
+ fileReferenceId,
+ id,
+ }) => {
+ const isInputVariableRequired = type === PluginVariableType.INPUT && !allowEmptyValue
+
+ return {
+ data: {
+ variable: {
+ ...getVariableColumnRowProps(),
+ value: name,
+ required: isInputVariableRequired,
+ disabled: !isCustomTask,
+ },
+ format: getFormatColumnRowProps({ format, isCustomTask }),
+ val: getValColumnRowProps({
+ ...emptyRowParams,
+ description,
+ format,
+ variableType,
+ value,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ id,
+ }),
+ },
+ customState: {
+ isVariableRequired: isInputVariableRequired,
+ variableDescription: description ?? '',
+ choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
+ id: index,
+ value: choiceValue,
+ })),
+ askValueAtRuntime: isRuntimeArg ?? false,
+ blockCustomValue: valueConstraint?.blockCustomValue ?? false,
+ selectedValue: null,
+ fileInfo: {
+ id: fileReferenceId,
+ mountDir: fileMountDir,
+ allowedExtensions:
+ valueConstraint?.constraint?.fileProperty?.allowedExtensions.join(', ') || '',
+ maxUploadSize: (
+ (valueConstraint?.constraint?.fileProperty?.maxUploadSize || null) / 1024 || ''
+ ).toString(),
+ unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
+ },
+ },
+ id,
+ }
+ },
+ )
+
+export const convertVariableDataTableToFormData = ({
+ rows,
+ type,
+ activeStageName,
+ selectedTaskIndex,
+ formData,
+ formDataErrorObj,
+ validateTask,
+ calculateLastStepDetail,
+}: Pick<
+ PipelineContext,
+ | 'activeStageName'
+ | 'selectedTaskIndex'
+ | 'formData'
+ | 'formDataErrorObj'
+ | 'validateTask'
+ | 'calculateLastStepDetail'
+> & {
+ type: PluginVariableType
+ rows: VariableDataRowType[]
+}) => {
+ const updatedFormData = structuredClone(formData)
+ const updatedFormDataErrorObj = structuredClone(formDataErrorObj)
+
+ const currentStepTypeVariable =
+ updatedFormData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
+ ? 'inlineStepDetail'
+ : 'pluginRefStepDetail'
+
+ const ioVariables: VariableType[] =
+ updatedFormData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ]
-export const getValidatedChoices = (choices: VariableDataRowType['customState']['choices']) => {
- let isValid = true
+ const updatedIOVariables = rows.map(({ data, customState, id }) => {
+ const selectedIOVariable = ioVariables?.find((ioVariable) => ioVariable.id === id)
+ const {
+ askValueAtRuntime,
+ blockCustomValue,
+ choices,
+ selectedValue,
+ isVariableRequired,
+ variableDescription,
+ fileInfo,
+ } = customState
- const updatedChoices: VariableDataRowType['customState']['choices'] = choices.map((choice) => {
- const error = !validateChoice(choice.value) ? 'This is a required field' : ''
- if (isValid && !!error) {
- isValid = false
+ const variableDetail: VariableType = {
+ ...selectedIOVariable,
+ format: data.format.value as VariableTypeFormat,
+ name: data.variable.value,
+ description: variableDescription,
+ allowEmptyValue: !isVariableRequired,
+ isRuntimeArg: askValueAtRuntime,
+ valueConstraint: {
+ choices: choices.map(({ value }) => value),
+ blockCustomValue,
+ constraint: null,
+ },
+ }
+
+ if (fileInfo) {
+ const unitMultiplier = fileInfo.unit.label === 'MB' ? 1024 : 1
+ variableDetail.value = data.val.value
+ variableDetail.fileReferenceId = fileInfo.id
+ variableDetail.fileMountDir = fileInfo.mountDir
+ variableDetail.valueConstraint.constraint = {
+ fileProperty: {
+ allowedExtensions: fileInfo.allowedExtensions
+ .split(',')
+ .map((value) => value.trim())
+ .filter((value) => !!value),
+ maxUploadSize: parseFloat(fileInfo.maxUploadSize) * unitMultiplier * 1024,
+ },
+ }
}
- return { ...choice, error }
+
+ if (selectedValue) {
+ if (selectedValue.refVariableStepIndex) {
+ variableDetail.value = ''
+ variableDetail.variableType = RefVariableType.FROM_PREVIOUS_STEP
+ variableDetail.refVariableStepIndex = selectedValue.refVariableStepIndex
+ variableDetail.refVariableName = selectedValue.label
+ variableDetail.format = selectedValue.format
+ variableDetail.refVariableStage = selectedValue.refVariableStage
+ } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
+ variableDetail.variableType = RefVariableType.GLOBAL
+ variableDetail.refVariableStepIndex = 0
+ variableDetail.refVariableName = selectedValue.label
+ variableDetail.format = selectedValue.format
+ variableDetail.value = ''
+ variableDetail.refVariableStage = null
+ } else {
+ variableDetail.variableType = RefVariableType.NEW
+ variableDetail.value = selectedValue.label
+ variableDetail.refVariableName = ''
+ variableDetail.refVariableStage = null
+ }
+ if (formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.PLUGIN_REF) {
+ variableDetail.format = selectedIOVariable.format
+ }
+ }
+
+ return variableDetail
})
- return { isValid, choices: updatedChoices }
+ updatedFormData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ ] = updatedIOVariables
+
+ if (type === PluginVariableType.OUTPUT) {
+ calculateLastStepDetail(false, updatedFormData, activeStageName, selectedTaskIndex)
+ }
+
+ if (updatedIOVariables.length === 1 && !updatedIOVariables[0].name && !updatedIOVariables[0].value) {
+ updatedIOVariables.pop()
+ }
+
+ if (updatedIOVariables.length === 0) {
+ const { conditionDetails } = updatedFormData[activeStageName].steps[selectedTaskIndex].inlineStepDetail
+ for (let i = 0; i < conditionDetails?.length; i++) {
+ if (
+ (type === PluginVariableType.OUTPUT &&
+ (conditionDetails[i].conditionType === ConditionType.PASS ||
+ conditionDetails[i].conditionType === ConditionType.FAIL)) ||
+ (type === PluginVariableType.INPUT &&
+ (conditionDetails[i].conditionType === ConditionType.TRIGGER ||
+ conditionDetails[i].conditionType === ConditionType.SKIP))
+ ) {
+ conditionDetails.splice(i, 1)
+ i -= 1
+ }
+ }
+ updatedFormData[activeStageName].steps[selectedTaskIndex].inlineStepDetail.conditionDetails = conditionDetails
+ }
+
+ validateTask(
+ updatedFormData[activeStageName].steps[selectedTaskIndex],
+ updatedFormDataErrorObj[activeStageName].steps[selectedTaskIndex],
+ )
+
+ return { updatedFormDataErrorObj, updatedFormData }
+}
+
+// VALIDATIONS
+export const validateMaxFileSize = (file: File, maxFileSizeInMB: number, unit: string = 'KB') => {
+ const unitMultiplier = unit === 'MB' ? 1024 : 1
+ return typeof maxFileSizeInMB !== 'number' || file.size <= maxFileSizeInMB * unitMultiplier * 1024
}
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index 806919c594..0e14761217 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -49,6 +49,7 @@ import {
ProcessPluginDataReturnType,
ResourceKindType,
getEnvironmentListMinPublic,
+ noop,
} from '@devtron-labs/devtron-fe-common-lib'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Redirect, Route, Switch, useParams, useRouteMatch } from 'react-router-dom'
@@ -1237,6 +1238,8 @@ export default function CDPipeline({
handleDisableParentModalCloseUpdate,
handleValidateMandatoryPlugins,
mandatoryPluginData,
+ // TODO: Handle for CD File Upload (Rohit)
+ uploadFile: noop
}
}, [
formData,
diff --git a/src/components/cdPipeline/cdpipeline.util.tsx b/src/components/cdPipeline/cdpipeline.util.tsx
index e9e7aace0d..2a7efa6e1f 100644
--- a/src/components/cdPipeline/cdpipeline.util.tsx
+++ b/src/components/cdPipeline/cdpipeline.util.tsx
@@ -378,4 +378,4 @@ export const getNamespacePlaceholder = (isVirtualEnvironment: boolean, namespace
return 'Not available'
}
return 'Will be auto-populated based on environment'
-}
\ No newline at end of file
+}
diff --git a/src/components/ciPipeline/ciPipeline.service.ts b/src/components/ciPipeline/ciPipeline.service.ts
index b8673fc27a..26750be32b 100644
--- a/src/components/ciPipeline/ciPipeline.service.ts
+++ b/src/components/ciPipeline/ciPipeline.service.ts
@@ -24,13 +24,16 @@ import {
RefVariableType,
PipelineBuildStageType,
VariableTypeFormat,
+ getIsRequestAborted,
+ showError,
} from '@devtron-labs/devtron-fe-common-lib'
import { Routes, SourceTypeMap, TriggerType, ViewType } from '../../config'
import { getSourceConfig, getWebhookDataMetaConfig } from '../../services/service'
import { CiPipelineSourceTypeBaseOptions } from '../CIPipelineN/ciPipeline.utils'
-import { PatchAction } from './types'
+import { PatchAction, UploadCIPipelineFileDTO } from './types'
import { safeTrim } from '../../util/Util'
import { ChangeCIPayloadType } from '../workflowEditor/types'
+import { MutableRefObject } from 'react'
const emptyStepsData = () => {
return { id: 0, steps: [] }
@@ -414,7 +417,14 @@ function migrateOldData(
variableType: RefVariableType.GLOBAL,
refVariableStepIndex: 0,
allowEmptyValue: false,
+ fileMountDir: null,
+ fileReferenceId: null,
+ valueConstraintId: null,
+ valueConstraint: null,
+ isRuntimeArg: null,
+ refVariableUsed: null,
}
+
const updatedData = {
id: 0,
steps: oldDataArr.map((data) => {
@@ -597,3 +607,35 @@ export async function getGlobalVariable(appId: number, isCD?: boolean): Promise<
return { result: variableList }
}
+
+export const uploadCIPipelineFile = async ({
+ file,
+ appId,
+ ciPipelineId,
+ abortControllerRef,
+}: {
+ file: File[]
+ appId: number
+ ciPipelineId: number
+ abortControllerRef?: MutableRefObject
+}): Promise => {
+ const formData = new FormData()
+ formData.append('file', file[0])
+
+ try {
+ const { result } = await post(
+ `${Routes.CI_CONFIG_GET}/${appId}/${ciPipelineId}/${Routes.FILE_UPLOAD}`,
+ formData,
+ { abortControllerRef },
+ true,
+ )
+
+ return result
+ } catch (err) {
+ if (getIsRequestAborted(err)) {
+ return
+ }
+ showError(err)
+ throw err
+ }
+}
diff --git a/src/components/ciPipeline/types.ts b/src/components/ciPipeline/types.ts
index 588495567a..b08001c232 100644
--- a/src/components/ciPipeline/types.ts
+++ b/src/components/ciPipeline/types.ts
@@ -428,3 +428,11 @@ export interface WebhookConditionType {
deleteWebhookCondition: (index: number) => void
canEditSelectorCondition: boolean
}
+
+export interface UploadCIPipelineFileDTO {
+ id: number
+ name: string
+ size: number
+ mimeType: string
+ extension: string
+}
diff --git a/src/components/workflowEditor/types.ts b/src/components/workflowEditor/types.ts
index 7070aef45d..db5a64bdfd 100644
--- a/src/components/workflowEditor/types.ts
+++ b/src/components/workflowEditor/types.ts
@@ -29,6 +29,7 @@ import {
MandatoryPluginDataType,
CiPipeline,
} from '@devtron-labs/devtron-fe-common-lib'
+import { UploadCIPipelineFileDTO } from '@Components/ciPipeline/types'
import { RouteComponentProps } from 'react-router-dom'
import { HostURLConfig } from '../../services/service.types'
import { CIPipelineNodeType, CdPipelineResult } from '../app/details/triggerView/types'
@@ -329,6 +330,7 @@ export interface PipelineContext {
handleDisableParentModalCloseUpdate?: (disableParentModalClose: boolean) => void
handleValidateMandatoryPlugins: (params: HandleValidateMandatoryPluginsParamsType) => void
mandatoryPluginData: MandatoryPluginDataType
+ uploadFile: (file: File[]) => Promise
}
export interface SourceTypeCardProps {
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 1844e17147..99a91660e6 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -128,6 +128,7 @@ export const Routes = {
SSO_LOGIN_SERVICES: 'login-service',
API_TOKEN: 'api-token',
API_TOKEN_WEBHOOK: 'api-token/webhook',
+ FILE_UPLOAD: 'file/upload',
DEPLOYMENT_METRICS: 'deployment-metrics',
APP_CONFIG_MAP_GET: 'configmap/applevel/get',
diff --git a/src/css/base.scss b/src/css/base.scss
index dc60bbe7df..48964df975 100644
--- a/src/css/base.scss
+++ b/src/css/base.scss
@@ -3392,6 +3392,10 @@ textarea,
}
//min width
+.min-w-0 {
+ min-width: 0;
+}
+
.min-w-200 {
min-width: 200px;
}
@@ -5267,4 +5271,4 @@ textarea::placeholder {
background: rgba(0, 0, 0, 0.75);
z-index: var(--modal-index);
backdrop-filter: blur(1px);
-}
\ No newline at end of file
+}
From 4119dcdc15dbfa7e44ca7b8aea2027ab17a4133d Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 5 Dec 2024 18:02:01 +0530
Subject: [PATCH 03/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 16 ++++------------
2 files changed, 5 insertions(+), 13 deletions(-)
diff --git a/package.json b/package.json
index 249362b5a8..c60af5f11b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.1.6",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.2-beta-3",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index f8e583a36d..5cc6f8b565 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.1.6":
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.1.6.tgz#13390eaef23265dfaf2525506fc37c263df3416c"
- integrity sha512-t5o8rjIBxaUfL97yhXZHJUZDhiwhRQOgWGqaulD2PqzCei9I8rP+0wlwQbbRHh73HFSc3sxmesItYoHGeQhNJw==
+"@devtron-labs/devtron-fe-common-lib@1.2.2-beta-3":
+ version "1.2.2-beta-3"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.2-beta-3.tgz#b5fb175d47689f116ce675d7ba1a5114eb3dcf36"
+ integrity sha512-ZcQpgD1LNrMSfnizMs2l9AoW9Pz1iC8nzZJKHVnTKSKILII39wWL87Z8bU+IHbj72Z1diAGTEQQAtBCKIOtNBg==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
@@ -988,7 +988,6 @@
marked "^13.0.3"
react-dates "^21.8.0"
react-diff-viewer-continued "^3.4.0"
- react-monaco-editor "^0.54.0"
sass "^1.69.7"
tslib "2.7.0"
@@ -6616,13 +6615,6 @@ react-moment-proptypes@^1.6.0:
dependencies:
moment ">=1.6.0"
-react-monaco-editor@^0.54.0:
- version "0.54.0"
- resolved "https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.54.0.tgz"
- integrity sha512-9JwO69851mfpuhYLHlKbae7omQWJ/2ICE2lbL0VHyNyZR8rCOH7440u+zAtDgiOMpLwmYdY1sEZCdRefywX6GQ==
- dependencies:
- prop-types "^15.8.1"
-
react-monaco-editor@^0.55.0:
version "0.55.0"
resolved "https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.55.0.tgz"
From 584cc742b3d7b297c2cb8a187c1c4f0d26ec35f4 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 00:20:02 +0530
Subject: [PATCH 04/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index a0d659390e..2f15a947a3 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-1",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index 27a5b0d181..a252d284d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4":
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4.tgz#4af6cc9803efb2eba7fd4988daca92805b8e2853"
- integrity sha512-Azi5EofwNjuevapxExkERXREZqsnttM8A652EhSRd9xDOz2mcv/ebXvu/1YKMdIP/0mETUaaEb6pkYSZAsy6OQ==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-1":
+ version "1.2.4-beta-1"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-1.tgz#ee3cf0ba6f9af55f6d463d281fcb57e12a56a00e"
+ integrity sha512-GyCFZ+EP7J0i29I+f+ENR+ZUDY5+vUYh7+AprrU+Hptz3p/96Xc5riDBZJSk6pvHzvbXyhJCsOSE4HmaLedOZA==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From ef580e139aaecf6ec047e291131fb16883faf36a Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 03:20:59 +0530
Subject: [PATCH 05/36] feat: Build CI - Runtime Params Integration
---
.../Details/TriggerView/BulkCITrigger.tsx | 4 ++
.../Details/TriggerView/EnvTriggerView.tsx | 8 ++-
src/components/CIPipelineN/CIPipeline.tsx | 5 +-
.../VariableDataTable/ValueConfigOverlay.tsx | 9 ++-
.../VariableDataTable/VariableDataTable.tsx | 71 +++++++++----------
.../VariableDataTablePopupMenu.tsx | 7 +-
.../CIPipelineN/VariableDataTable/types.ts | 11 ++-
.../CIPipelineN/VariableDataTable/utils.tsx | 49 ++++++++-----
.../app/details/triggerView/TriggerView.tsx | 33 ++++++---
.../app/details/triggerView/ciMaterial.tsx | 3 +
.../app/details/triggerView/types.ts | 7 ++
.../ciPipeline/ciPipeline.service.ts | 34 +--------
src/components/ciPipeline/types.ts | 8 ---
.../GitInfoMaterialCard/GitInfoMaterial.tsx | 16 +++--
.../helpers/GitInfoMaterialCard/types.ts | 3 +
src/components/workflowEditor/types.ts | 5 +-
src/config/constants.ts | 1 -
17 files changed, 148 insertions(+), 126 deletions(-)
diff --git a/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx b/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
index 1053e8a23b..a6b0ac1055 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
@@ -40,6 +40,7 @@ import {
ButtonVariantType,
ComponentSizeType,
ButtonStyleType,
+ noop,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import { getCIPipelineURL, getParsedBranchValuesForPlugin, importComponentFromFELibrary } from '../../../common'
@@ -526,6 +527,9 @@ const BulkCITrigger = ({
isWebhookPayloadLoading={isWebhookPayloadLoading}
isBulk
appId={selectedApp.appId.toString()}
+ runtimeParamsV2={[]}
+ handleRuntimeParamChangeV2={noop}
+ uploadFile={noop}
/>
)
}
diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
index 3769e79f3b..52e39a77e8 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
@@ -47,6 +47,7 @@ import {
ToastManager,
ToastVariantType,
BlockedStateData,
+ noop,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -1982,9 +1983,7 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
if (selectedCINode?.id) {
return (
-
+
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index c702800832..7d290f27b5 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -46,6 +46,7 @@ import {
ToastManager,
ProcessPluginDataParamsType,
ResourceKindType,
+ uploadCIPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -62,7 +63,6 @@ import {
getInitData,
getInitDataWithCIPipeline,
saveCIPipeline,
- uploadCIPipelineFile,
} from '../ciPipeline/ciPipeline.service'
import { ValidationRules } from '../ciPipeline/validationRules'
import { CIBuildType, CIPipelineBuildType, CIPipelineDataType, CIPipelineType } from '../ciPipeline/types'
@@ -784,7 +784,8 @@ export default function CIPipeline({
}
}
- const uploadFile = (file: File[]) => uploadCIPipelineFile({ appId: +appId, ciPipelineId: +ciPipelineId, file })
+ const uploadFile: PipelineContext['uploadFile'] = ({ allowedExtensions, file, maxUploadSize }) =>
+ uploadCIPipelineFile({ appId: +appId, ciPipelineId: +ciPipelineId, file, allowedExtensions, maxUploadSize })
const contextValue = useMemo(
() => ({
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
index 2d94cd8667..09d1663ea5 100644
--- a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
@@ -84,10 +84,14 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
}
const handleFileMountChange = (e: ChangeEvent) => {
+ const fileMountValue = e.target.value
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FILE_MOUNT,
rowId,
- actionValue: e.target.value,
+ actionValue: {
+ error: !fileMountValue ? 'This field is required' : '',
+ value: fileMountValue,
+ },
})
}
@@ -139,11 +143,12 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
name="fileMount"
label="File mount path"
placeholder="Enter file mount path"
- value={fileInfo.mountDir}
+ value={fileInfo.mountDir.value}
onChange={handleFileMountChange}
dataTestid={`file-mount-${rowId}`}
inputWrapClassName="w-100"
isRequiredField
+ error={fileInfo.mountDir.error}
/>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
index 8cb15c0e43..a8bf1a8035 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -7,8 +7,6 @@ import {
PluginType,
RefVariableType,
SelectPickerOptionType,
- ToastManager,
- ToastVariantType,
VariableType,
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -33,10 +31,10 @@ import {
import {
convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
+ getUploadFileConstraints,
getValColumnRowProps,
getValColumnRowValue,
getVariableDataTableInitialRows,
- validateMaxFileSize,
} from './utils'
import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
@@ -429,7 +427,10 @@ export const VariableDataTable = ({
id: null,
allowedExtensions: '',
maxUploadSize: '',
- mountDir: '',
+ mountDir: {
+ value: '/devtroncd',
+ error: '',
+ },
unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
},
},
@@ -484,45 +485,37 @@ export const VariableDataTable = ({
updatedRow.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD &&
extraData.files.length
) {
- const isFileSizeValid = validateMaxFileSize(
- extraData.files[0],
- parseFloat(updatedRow.customState.fileInfo.maxUploadSize),
- updatedRow.customState.fileInfo.unit.label as string,
- )
-
- if (isFileSizeValid) {
- // TODO: check this merge with UPDATE_FILE_UPLOAD_INFO after loading state
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: value,
- headerKey,
- rowId: updatedRow.id,
- })
+ // TODO: check this merge with UPDATE_FILE_UPLOAD_INFO after loading state
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: value,
+ headerKey,
+ rowId: updatedRow.id,
+ })
- uploadFile(extraData.files)
- .then((res) => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
- actionValue: { fileReferenceId: res.id },
- rowId: updatedRow.id,
- })
+ uploadFile({
+ file: extraData.files,
+ ...getUploadFileConstraints({
+ unit: updatedRow.customState.fileInfo.unit.label as string,
+ allowedExtensions: updatedRow.customState.fileInfo.allowedExtensions,
+ maxUploadSize: updatedRow.customState.fileInfo.maxUploadSize,
+ }),
+ })
+ .then((res) => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
+ actionValue: { fileReferenceId: res.id },
+ rowId: updatedRow.id,
})
- .catch(() => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: '',
- headerKey,
- rowId: updatedRow.id,
- })
+ })
+ .catch(() => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: '',
+ headerKey,
+ rowId: updatedRow.id,
})
- } else {
- // TODO: get message from Utkarsh
- ToastManager.showToast({
- title: 'Large File Size',
- description: 'Large File Size',
- variant: ToastVariantType.error,
})
- }
} else if (headerKey === 'format' && updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FORMAT_COLUMN,
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
index 9169b6c3d9..e473b75110 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -18,14 +18,17 @@ export const VariableDataTablePopupMenu = ({
heading,
children,
onClose,
+ disableClose,
}: VariableDataTablePopupMenuProps) => {
// STATES
const [visible, setVisible] = useState(false)
// METHODS
const handleClose = () => {
- setVisible(false)
- onClose?.()
+ if (!disableClose) {
+ setVisible(false)
+ onClose?.()
+ }
}
const handleAction = (open: boolean) => {
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index a84fb9e9ad..a43b1ed796 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -14,7 +14,10 @@ export type VariableDataCustomState = {
selectedValue: Record
fileInfo: {
id: number
- mountDir: string
+ mountDir: {
+ value: string
+ error: string
+ }
allowedExtensions: string
maxUploadSize: string
unit: SelectPickerOptionType
@@ -93,7 +96,10 @@ type VariableDataTableActionPropsMap = {
}
[VariableDataTableActionType.UPDATE_FILE_MOUNT]: {
rowId: string | number
- actionValue: string
+ actionValue: {
+ value: string
+ error: string
+ }
}
[VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS]: {
rowId: string | number
@@ -120,6 +126,7 @@ export type HandleRowUpdateActionProps = VariableDataTableAction
export interface VariableDataTablePopupMenuProps {
heading: string
showIcon?: boolean
+ disableClose?: boolean
onClose?: () => void
children: JSX.Element
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index c2427e047c..8cc3dc6f95 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -288,7 +288,10 @@ export const getEmptyVariableDataTableRow = (params: GetValColumnRowPropsType):
selectedValue: null,
fileInfo: {
id: null,
- mountDir: '/devtroncd',
+ mountDir: {
+ value: '/devtroncd',
+ error: '',
+ },
allowedExtensions: '',
maxUploadSize: '',
unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
@@ -330,6 +333,8 @@ export const getVariableDataTableInitialRows = ({
value: name,
required: isInputVariableRequired,
disabled: !isCustomTask,
+ showTooltip: !isCustomTask && !!description,
+ tooltipText: description,
},
format: getFormatColumnRowProps({ format, isCustomTask }),
val: getValColumnRowProps({
@@ -356,7 +361,7 @@ export const getVariableDataTableInitialRows = ({
selectedValue: null,
fileInfo: {
id: fileReferenceId,
- mountDir: fileMountDir,
+ mountDir: { value: fileMountDir, error: '' },
allowedExtensions:
valueConstraint?.constraint?.fileProperty?.allowedExtensions.join(', ') || '',
maxUploadSize: (
@@ -370,6 +375,25 @@ export const getVariableDataTableInitialRows = ({
},
)
+export const getUploadFileConstraints = ({
+ unit,
+ allowedExtensions,
+ maxUploadSize,
+}: {
+ unit: string
+ allowedExtensions: string
+ maxUploadSize: string
+}) => {
+ const unitMultiplier = unit === 'MB' ? 1024 : 1
+ return {
+ allowedExtensions: allowedExtensions
+ .split(',')
+ .map((value) => value.trim())
+ .filter((value) => !!value),
+ maxUploadSize: maxUploadSize ? parseFloat(maxUploadSize) * unitMultiplier * 1024 : null,
+ }
+}
+
export const convertVariableDataTableToFormData = ({
rows,
type,
@@ -431,18 +455,15 @@ export const convertVariableDataTableToFormData = ({
}
if (fileInfo) {
- const unitMultiplier = fileInfo.unit.label === 'MB' ? 1024 : 1
variableDetail.value = data.val.value
variableDetail.fileReferenceId = fileInfo.id
- variableDetail.fileMountDir = fileInfo.mountDir
+ variableDetail.fileMountDir = fileInfo.mountDir.value
variableDetail.valueConstraint.constraint = {
- fileProperty: {
- allowedExtensions: fileInfo.allowedExtensions
- .split(',')
- .map((value) => value.trim())
- .filter((value) => !!value),
- maxUploadSize: parseFloat(fileInfo.maxUploadSize) * unitMultiplier * 1024,
- },
+ fileProperty: getUploadFileConstraints({
+ allowedExtensions: fileInfo.allowedExtensions,
+ maxUploadSize: fileInfo.maxUploadSize,
+ unit: fileInfo.unit.label as string,
+ }),
}
}
@@ -512,9 +533,3 @@ export const convertVariableDataTableToFormData = ({
return { updatedFormDataErrorObj, updatedFormData }
}
-
-// VALIDATIONS
-export const validateMaxFileSize = (file: File, maxFileSizeInMB: number, unit: string = 'KB') => {
- const unitMultiplier = unit === 'MB' ? 1024 : 1
- return typeof maxFileSizeInMB !== 'number' || file.size <= maxFileSizeInMB * unitMultiplier * 1024
-}
diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx
index 4429bf4ede..3ec8abea0d 100644
--- a/src/components/app/details/triggerView/TriggerView.tsx
+++ b/src/components/app/details/triggerView/TriggerView.tsx
@@ -34,6 +34,7 @@ import {
TOAST_ACCESS_DENIED,
BlockedStateData,
getEnvironmentListMinPublic,
+ uploadCIPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import ReactGA from 'react-ga4'
import { withRouter, NavLink, Route, Switch } from 'react-router-dom'
@@ -104,6 +105,7 @@ const getCIBlockState: (...props) => Promise = importComponent
const ImagePromotionRouter = importComponentFromFELibrary('ImagePromotionRouter', null, 'function')
const getRuntimeParams = importComponentFromFELibrary('getRuntimeParams', null, 'function')
const getRuntimeParamsPayload = importComponentFromFELibrary('getRuntimeParamsPayload', null, 'function')
+const getRuntimeParamsV2Payload = importComponentFromFELibrary('getRuntimeParamsV2Payload', null, 'function')
class TriggerView extends Component {
timerRef
@@ -145,6 +147,7 @@ class TriggerView extends Component {
searchImageTag: '',
resourceFilters: [],
runtimeParams: [],
+ runtimeParamsV2: [],
}
this.refreshMaterial = this.refreshMaterial.bind(this)
this.onClickCIMaterial = this.onClickCIMaterial.bind(this)
@@ -713,7 +716,7 @@ class TriggerView extends Component {
this.props.appContext.currentAppName,
)
: null,
- getRuntimeParams?.(ciNodeId) ?? null,
+ getRuntimeParams?.(ciNodeId, true) ?? null,
])
.then((resp) => {
// For updateCIMaterialList, it's already being set inside the same function so not setting that
@@ -739,7 +742,7 @@ class TriggerView extends Component {
if (resp[2]) {
// Not saving as null since page ViewType is set as Error in case of error
this.setState({
- runtimeParams: resp[2] || [],
+ runtimeParamsV2: resp[2] || [],
})
}
})
@@ -905,7 +908,7 @@ class TriggerView extends Component {
}
// No need to validate here since ciMaterial handles it for trigger view
- const runtimeParamsPayload = getRuntimeParamsPayload?.(this.state.runtimeParams ?? [])
+ const runtimeParamsPayload = getRuntimeParamsV2Payload?.(this.state.runtimeParamsV2 ?? [])
const payload = {
pipelineId: +this.state.ciNodeId,
@@ -1095,7 +1098,6 @@ class TriggerView extends Component {
this.getWorkflowStatus()
}
-
onClickWebhookTimeStamp = () => {
if (this.state.webhookTimeStampOrder === TIME_STAMP_ORDER.DESCENDING) {
this.setState({ webhookTimeStampOrder: TIME_STAMP_ORDER.ASCENDING })
@@ -1130,6 +1132,21 @@ class TriggerView extends Component {
})
}
+ handleRuntimeParamChangeV2: CIMaterialProps['handleRuntimeParamChangeV2'] = (updatedRuntimeParams) => {
+ this.setState({
+ runtimeParamsV2: updatedRuntimeParams,
+ })
+ }
+
+ uploadFile: CIMaterialProps['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) =>
+ uploadCIPipelineFile({
+ appId: +this.props.match.params.appId,
+ ciPipelineId: this.getCINode().parentCiPipeline,
+ file,
+ allowedExtensions,
+ maxUploadSize,
+ })
+
setLoader = (isLoader) => {
this.setState({
loader: isLoader,
@@ -1172,16 +1189,13 @@ class TriggerView extends Component {
this.abortCIBuild = new AbortController()
}
-
renderCIMaterial = () => {
if (this.state.ciNodeId) {
const nd: CommonNodeAttr = this.getCINode()
const material = nd?.[this.state.materialType] || []
return (
-
+
{
isJobCI={!!nd?.isJobCI}
runtimeParams={this.state.runtimeParams}
handleRuntimeParamChange={this.handleRuntimeParamChange}
+ runtimeParamsV2={this.state.runtimeParamsV2}
+ handleRuntimeParamChangeV2={this.handleRuntimeParamChangeV2}
closeCIModal={this.closeCIModal}
abortController={this.abortCIBuild}
resetAbortController={this.resetAbortController}
+ uploadFile={this.uploadFile}
/>
diff --git a/src/components/app/details/triggerView/ciMaterial.tsx b/src/components/app/details/triggerView/ciMaterial.tsx
index 2c235b859c..5fcbdef404 100644
--- a/src/components/app/details/triggerView/ciMaterial.tsx
+++ b/src/components/app/details/triggerView/ciMaterial.tsx
@@ -309,6 +309,9 @@ class CIMaterial extends Component {
handleRuntimeParamChange={this.props.handleRuntimeParamChange}
handleRuntimeParamError={this.handleRuntimeParamError}
appId={this.props.appId}
+ runtimeParamsV2={this.props.runtimeParamsV2}
+ handleRuntimeParamChangeV2={this.props.handleRuntimeParamChangeV2}
+ uploadFile={this.props.uploadFile}
/>
{this.props.isCITriggerBlocked ? null : this.renderMaterialStartBuild(canTrigger)}
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index 6f18d5839f..a53f015953 100644
--- a/src/components/app/details/triggerView/types.ts
+++ b/src/components/app/details/triggerView/types.ts
@@ -43,6 +43,9 @@ import {
CdPipeline,
ConsequenceType,
PolicyKindType,
+ RuntimePluginVariables,
+ UploadFileDTO,
+ UploadFileProps,
} from '@devtron-labs/devtron-fe-common-lib'
import React from 'react'
import { EnvironmentWithSelectPickerType } from '@Components/CIPipelineN/types'
@@ -389,6 +392,7 @@ export interface TriggerViewState {
searchImageTag?: string
resourceFilters?: FilterConditionsListType[]
runtimeParams?: RuntimeParamsListItemType[]
+ runtimeParamsV2: RuntimePluginVariables[]
}
export interface CIMaterialProps
@@ -424,7 +428,10 @@ export interface CIMaterialProps
setSelectedEnv?: React.Dispatch>
isJobCI?: boolean
handleRuntimeParamChange: HandleRuntimeParamChange
+ runtimeParamsV2: RuntimePluginVariables[]
+ handleRuntimeParamChangeV2: (runtimeParams: RuntimePluginVariables[]) => void
runtimeParams: KeyValueListType[]
+ uploadFile: (props: UploadFileProps) => Promise
}
// -- begining of response type objects for trigger view
diff --git a/src/components/ciPipeline/ciPipeline.service.ts b/src/components/ciPipeline/ciPipeline.service.ts
index 26750be32b..e16350fc51 100644
--- a/src/components/ciPipeline/ciPipeline.service.ts
+++ b/src/components/ciPipeline/ciPipeline.service.ts
@@ -30,7 +30,7 @@ import {
import { Routes, SourceTypeMap, TriggerType, ViewType } from '../../config'
import { getSourceConfig, getWebhookDataMetaConfig } from '../../services/service'
import { CiPipelineSourceTypeBaseOptions } from '../CIPipelineN/ciPipeline.utils'
-import { PatchAction, UploadCIPipelineFileDTO } from './types'
+import { PatchAction } from './types'
import { safeTrim } from '../../util/Util'
import { ChangeCIPayloadType } from '../workflowEditor/types'
import { MutableRefObject } from 'react'
@@ -607,35 +607,3 @@ export async function getGlobalVariable(appId: number, isCD?: boolean): Promise<
return { result: variableList }
}
-
-export const uploadCIPipelineFile = async ({
- file,
- appId,
- ciPipelineId,
- abortControllerRef,
-}: {
- file: File[]
- appId: number
- ciPipelineId: number
- abortControllerRef?: MutableRefObject
-}): Promise => {
- const formData = new FormData()
- formData.append('file', file[0])
-
- try {
- const { result } = await post(
- `${Routes.CI_CONFIG_GET}/${appId}/${ciPipelineId}/${Routes.FILE_UPLOAD}`,
- formData,
- { abortControllerRef },
- true,
- )
-
- return result
- } catch (err) {
- if (getIsRequestAborted(err)) {
- return
- }
- showError(err)
- throw err
- }
-}
diff --git a/src/components/ciPipeline/types.ts b/src/components/ciPipeline/types.ts
index b08001c232..588495567a 100644
--- a/src/components/ciPipeline/types.ts
+++ b/src/components/ciPipeline/types.ts
@@ -428,11 +428,3 @@ export interface WebhookConditionType {
deleteWebhookCondition: (index: number) => void
canEditSelectorCondition: boolean
}
-
-export interface UploadCIPipelineFileDTO {
- id: number
- name: string
- size: number
- mimeType: string
- extension: string
-}
diff --git a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
index 3729695f3d..60080f4af2 100644
--- a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
+++ b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
@@ -52,7 +52,7 @@ import { ReceivedWebhookRedirectButton } from './ReceivedWebhookRedirectButton'
const MissingPluginBlockState = importComponentFromFELibrary('MissingPluginBlockState', null, 'function')
const RuntimeParamTabs = importComponentFromFELibrary('RuntimeParamTabs', null, 'function')
-const RuntimeParameters = importComponentFromFELibrary('RuntimeParameters', null, 'function')
+const RuntimeParametersV2 = importComponentFromFELibrary('RuntimeParametersV2', null, 'function')
export const GitInfoMaterial = ({
dataTestId = '',
@@ -74,8 +74,6 @@ export const GitInfoMaterial = ({
// Not required for BulkCI
currentSidebarTab,
handleSidebarTabChange,
- runtimeParams,
- handleRuntimeParamChange,
handleRuntimeParamError,
isBulkCIWebhook,
webhookPayloads,
@@ -83,6 +81,9 @@ export const GitInfoMaterial = ({
setIsWebhookBulkCI,
isBulk = false,
appId,
+ runtimeParamsV2,
+ handleRuntimeParamChangeV2,
+ uploadFile,
}: GitInfoMaterialProps) => {
const { push } = useHistory()
const location = useLocation()
@@ -365,15 +366,16 @@ export const GitInfoMaterial = ({
)
}
- if (RuntimeParameters && currentSidebarTab === CIMaterialSidebarType.PARAMETERS) {
+ if (RuntimeParametersV2 && currentSidebarTab === CIMaterialSidebarType.PARAMETERS) {
return (
-
)
}
diff --git a/src/components/common/helpers/GitInfoMaterialCard/types.ts b/src/components/common/helpers/GitInfoMaterialCard/types.ts
index 1aa0b505e9..6fdfa18ba1 100644
--- a/src/components/common/helpers/GitInfoMaterialCard/types.ts
+++ b/src/components/common/helpers/GitInfoMaterialCard/types.ts
@@ -15,6 +15,9 @@ export interface GitInfoMaterialProps
| 'pipelineId'
| 'runtimeParams'
| 'appId'
+ | 'runtimeParamsV2'
+ | 'handleRuntimeParamChangeV2'
+ | 'uploadFile'
> {
dataTestId?: string
material: CIMaterialType[]
diff --git a/src/components/workflowEditor/types.ts b/src/components/workflowEditor/types.ts
index db5a64bdfd..25594cfb72 100644
--- a/src/components/workflowEditor/types.ts
+++ b/src/components/workflowEditor/types.ts
@@ -28,8 +28,9 @@ import {
PipelineFormType,
MandatoryPluginDataType,
CiPipeline,
+ UploadFileDTO,
+ UploadFileProps,
} from '@devtron-labs/devtron-fe-common-lib'
-import { UploadCIPipelineFileDTO } from '@Components/ciPipeline/types'
import { RouteComponentProps } from 'react-router-dom'
import { HostURLConfig } from '../../services/service.types'
import { CIPipelineNodeType, CdPipelineResult } from '../app/details/triggerView/types'
@@ -330,7 +331,7 @@ export interface PipelineContext {
handleDisableParentModalCloseUpdate?: (disableParentModalClose: boolean) => void
handleValidateMandatoryPlugins: (params: HandleValidateMandatoryPluginsParamsType) => void
mandatoryPluginData: MandatoryPluginDataType
- uploadFile: (file: File[]) => Promise
+ uploadFile: (file: UploadFileProps) => Promise
}
export interface SourceTypeCardProps {
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 99a91660e6..1844e17147 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -128,7 +128,6 @@ export const Routes = {
SSO_LOGIN_SERVICES: 'login-service',
API_TOKEN: 'api-token',
API_TOKEN_WEBHOOK: 'api-token/webhook',
- FILE_UPLOAD: 'file/upload',
DEPLOYMENT_METRICS: 'deployment-metrics',
APP_CONFIG_MAP_GET: 'configmap/applevel/get',
From a400ecd8ad7b7cb30f1c67116601850f8d1041bf Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 03:25:44 +0530
Subject: [PATCH 06/36] fix: VariableDataTable - showError bool -> true
---
.../CIPipelineN/VariableDataTable/VariableDataTable.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
index a8bf1a8035..41f3f33cd6 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -607,7 +607,7 @@ export const VariableDataTable = ({
onRowEdit={dataTableHandleChange}
onRowDelete={dataTableHandleDelete}
onRowAdd={dataTableHandleAddition}
- // showError
+ showError
validationSchema={validationSchema}
actionButtonConfig={getActionButtonConfig()}
/>
From ebfe434cf1bf058b6c9aba86a88261e02962b849 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 11:09:05 +0530
Subject: [PATCH 07/36] feat: CD Pipeline - Runtime Params Integration
---
.../ApplicationGroup/AppGroup.types.ts | 6 ++--
.../Details/TriggerView/BulkCDTrigger.tsx | 34 +++++++++++--------
.../Details/TriggerView/BulkCITrigger.tsx | 21 ++++++++----
.../Details/TriggerView/EnvTriggerView.tsx | 16 +++++----
.../app/details/triggerView/TriggerView.tsx | 14 ++------
.../app/details/triggerView/cdMaterial.tsx | 24 +++++++------
.../app/details/triggerView/ciMaterial.tsx | 2 --
.../app/details/triggerView/types.ts | 18 ++++------
src/components/app/types.ts | 4 +--
src/components/cdPipeline/CDPipeline.tsx | 7 ++--
.../GitInfoMaterialCard/GitInfoMaterial.tsx | 8 ++---
.../helpers/GitInfoMaterialCard/types.ts | 2 --
12 files changed, 79 insertions(+), 77 deletions(-)
diff --git a/src/components/ApplicationGroup/AppGroup.types.ts b/src/components/ApplicationGroup/AppGroup.types.ts
index b0fbf8fd6c..4677b96f46 100644
--- a/src/components/ApplicationGroup/AppGroup.types.ts
+++ b/src/components/ApplicationGroup/AppGroup.types.ts
@@ -26,9 +26,9 @@ import {
WorkflowType,
AppInfoListType,
GVKType,
- RuntimeParamsListItemType,
UseUrlFiltersReturnType,
CommonNodeAttr,
+ RuntimePluginVariables,
} from '@devtron-labs/devtron-fe-common-lib'
import { CDMaterialProps } from '../app/details/triggerView/types'
import { EditDescRequest, NodeType, Nodes, OptionType } from '../app/types'
@@ -103,8 +103,8 @@ export interface ResponseRowType {
}
interface BulkRuntimeParamsType {
- runtimeParams: Record
- setRuntimeParams: React.Dispatch>>
+ runtimeParams: Record
+ setRuntimeParams: React.Dispatch>>
runtimeParamsErrorState: Record
setRuntimeParamsErrorState: React.Dispatch>>
}
diff --git a/src/components/ApplicationGroup/Details/TriggerView/BulkCDTrigger.tsx b/src/components/ApplicationGroup/Details/TriggerView/BulkCDTrigger.tsx
index 5d1229a441..e9e825fcb0 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/BulkCDTrigger.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/BulkCDTrigger.tsx
@@ -38,10 +38,12 @@ import {
SelectPicker,
CDMaterialSidebarType,
CD_MATERIAL_SIDEBAR_TABS,
- RuntimeParamsListItemType,
ToastManager,
ToastVariantType,
CommonNodeAttr,
+ RuntimePluginVariables,
+ uploadCDPipelineFile,
+ UploadFileProps,
} from '@devtron-labs/devtron-fe-common-lib'
import { useHistory, useLocation } from 'react-router-dom'
import { ReactComponent as Close } from '../../../../assets/icons/ic-cross.svg'
@@ -166,12 +168,21 @@ export default function BulkCDTrigger({
}))
}
- const handleRuntimeParamChange = (currentAppRuntimeParams: RuntimeParamsListItemType[]) => {
+ const handleRuntimeParamChange = (currentAppRuntimeParams: RuntimePluginVariables[]) => {
const clonedRuntimeParams = structuredClone(runtimeParams)
clonedRuntimeParams[selectedApp.appId] = currentAppRuntimeParams
setRuntimeParams(clonedRuntimeParams)
}
+ const bulkUploadFile = ({ file, allowedExtensions, maxUploadSize }: UploadFileProps) =>
+ uploadCDPipelineFile({
+ file,
+ allowedExtensions,
+ maxUploadSize,
+ appId: selectedApp.appId,
+ envId: selectedApp.envId,
+ })
+
const getDeploymentWindowData = async (_cdMaterialResponse) => {
const currentEnv = appList[0].envId
const appEnvMap = []
@@ -429,13 +440,9 @@ export default function BulkCDTrigger({
if (tagNotFoundWarningsMap.has(app.appId)) {
return (
-
+
-
- {tagNotFoundWarningsMap.get(app.appId)}
-
+ {tagNotFoundWarningsMap.get(app.appId)}
)
}
@@ -454,13 +461,9 @@ export default function BulkCDTrigger({
if (!!warningMessage && !app.showPluginWarning) {
return (
-
+
-
- {warningMessage}
-
+ {warningMessage}
)
}
@@ -473,7 +476,7 @@ export default function BulkCDTrigger({
nodeType={commonNodeAttrType}
shouldRenderAdditionalInfo={isAppSelected}
/>
- )
+ )
}
return null
@@ -795,6 +798,7 @@ export default function BulkCDTrigger({
bulkRuntimeParams={runtimeParams[selectedApp.appId] || []}
handleBulkRuntimeParamChange={handleRuntimeParamChange}
handleBulkRuntimeParamError={handleRuntimeParamError}
+ bulkUploadFile={bulkUploadFile}
bulkSidebarTab={currentSidebarTab}
selectedAppName={selectedApp.name}
/>
diff --git a/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx b/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
index a6b0ac1055..87b23788e5 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx
@@ -27,7 +27,6 @@ import {
GenericEmptyState,
CIMaterialSidebarType,
ApiQueuingWithBatch,
- RuntimeParamsListItemType,
ModuleNameMap,
SourceTypeMap,
ToastManager,
@@ -41,6 +40,9 @@ import {
ComponentSizeType,
ButtonStyleType,
noop,
+ RuntimePluginVariables,
+ uploadCIPipelineFile,
+ UploadFileProps,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import { getCIPipelineURL, getParsedBranchValuesForPlugin, importComponentFromFELibrary } from '../../../common'
@@ -169,14 +171,14 @@ const BulkCITrigger = ({
[appDetails.ciPipelineId]: [],
})
}
- return () => getRuntimeParams(appDetails.ciPipelineId)
+ return () => getRuntimeParams(appDetails.ciPipelineId, true)
})
if (runtimeParamsServiceList.length) {
try {
// Appending any for legacy code, since we did not had generics in APIQueuingWithBatch
const responses: any[] = await ApiQueuingWithBatch(runtimeParamsServiceList, true)
- const _runtimeParams: Record = {}
+ const _runtimeParams: Record = {}
responses.forEach((res, index) => {
_runtimeParams[appList[index]?.ciPipelineId] = res.value || []
})
@@ -249,6 +251,15 @@ const BulkCITrigger = ({
setRuntimeParams(updatedRuntimeParams)
}
+ const uploadFile = ({ file, allowedExtensions, maxUploadSize }: UploadFileProps) =>
+ uploadCIPipelineFile({
+ file,
+ allowedExtensions,
+ maxUploadSize,
+ appId: selectedApp.appId,
+ ciPipelineId: +selectedApp.ciPipelineId,
+ })
+
const handleSidebarTabChange = (e: React.ChangeEvent) => {
if (runtimeParamsErrorState[selectedApp.ciPipelineId]) {
ToastManager.showToast({
@@ -520,6 +531,7 @@ const BulkCITrigger = ({
runtimeParams={runtimeParams[selectedApp.ciPipelineId] || []}
handleRuntimeParamChange={handleRuntimeParamChange}
handleRuntimeParamError={handleRuntimeParamError}
+ uploadFile={uploadFile}
appName={selectedApp?.name}
isBulkCIWebhook={isWebhookBulkCI}
setIsWebhookBulkCI={setIsWebhookBulkCI}
@@ -527,9 +539,6 @@ const BulkCITrigger = ({
isWebhookPayloadLoading={isWebhookPayloadLoading}
isBulk
appId={selectedApp.appId.toString()}
- runtimeParamsV2={[]}
- handleRuntimeParamChangeV2={noop}
- uploadFile={noop}
/>
)
}
diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
index 52e39a77e8..0941c75840 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
@@ -42,12 +42,12 @@ import {
ApiQueuingWithBatch,
usePrompt,
SourceTypeMap,
- RuntimeParamsListItemType,
preventBodyScroll,
ToastManager,
ToastVariantType,
BlockedStateData,
- noop,
+ RuntimePluginVariables,
+ uploadCIPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -186,7 +186,7 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
const [isConfigPresent, setConfigPresent] = useState(false)
const [isDefaultConfigPresent, setDefaultConfig] = useState(false)
// Mapping pipelineId (in case of CI) and appId (in case of CD) to runtime params
- const [runtimeParams, setRuntimeParams] = useState>({})
+ const [runtimeParams, setRuntimeParams] = useState>({})
const [runtimeParamsErrorState, setRuntimeParamsErrorState] = useState>({})
const [isBulkTriggerLoading, setIsBulkTriggerLoading] = useState(false)
@@ -873,7 +873,7 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
_appName,
)
: null,
- getRuntimeParams ? getRuntimeParams(ciNodeId) : null,
+ getRuntimeParams ? getRuntimeParams(ciNodeId, true) : null,
])
.then((resp) => {
// need to set result for getCIBlockState call only as for updateCIMaterialList
@@ -1874,6 +1874,10 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
}
}
+ const uploadFile: CIMaterialProps['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) => null
+ // TODO: rohit confirm with BE
+ // uploadCIPipelineFile({ file, allowedExtensions, maxUploadSize, appId: +appId, ciPipelineId: null })
+
const createBulkCITriggerData = (): BulkCIDetailType[] => {
const _selectedAppWorkflowList: BulkCIDetailType[] = []
filteredWorkflows.forEach((wf) => {
@@ -2026,12 +2030,10 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
isJobCI={!!nd?.isJobCI}
runtimeParams={runtimeParams[nd?.id] ?? []}
handleRuntimeParamChange={handleRuntimeParamChange}
+ uploadFile={uploadFile}
closeCIModal={closeCIModal}
abortController={abortCIBuildRef.current}
resetAbortController={resetAbortController}
- runtimeParamsV2={[]}
- handleRuntimeParamChangeV2={noop}
- uploadFile={noop}
/>
diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx
index 3ec8abea0d..4e3491bc3b 100644
--- a/src/components/app/details/triggerView/TriggerView.tsx
+++ b/src/components/app/details/triggerView/TriggerView.tsx
@@ -105,7 +105,6 @@ const getCIBlockState: (...props) => Promise = importComponent
const ImagePromotionRouter = importComponentFromFELibrary('ImagePromotionRouter', null, 'function')
const getRuntimeParams = importComponentFromFELibrary('getRuntimeParams', null, 'function')
const getRuntimeParamsPayload = importComponentFromFELibrary('getRuntimeParamsPayload', null, 'function')
-const getRuntimeParamsV2Payload = importComponentFromFELibrary('getRuntimeParamsV2Payload', null, 'function')
class TriggerView extends Component {
timerRef
@@ -147,7 +146,6 @@ class TriggerView extends Component {
searchImageTag: '',
resourceFilters: [],
runtimeParams: [],
- runtimeParamsV2: [],
}
this.refreshMaterial = this.refreshMaterial.bind(this)
this.onClickCIMaterial = this.onClickCIMaterial.bind(this)
@@ -742,7 +740,7 @@ class TriggerView extends Component {
if (resp[2]) {
// Not saving as null since page ViewType is set as Error in case of error
this.setState({
- runtimeParamsV2: resp[2] || [],
+ runtimeParams: resp[2] || [],
})
}
})
@@ -908,7 +906,7 @@ class TriggerView extends Component {
}
// No need to validate here since ciMaterial handles it for trigger view
- const runtimeParamsPayload = getRuntimeParamsV2Payload?.(this.state.runtimeParamsV2 ?? [])
+ const runtimeParamsPayload = getRuntimeParamsPayload?.(this.state.runtimeParams ?? [])
const payload = {
pipelineId: +this.state.ciNodeId,
@@ -1132,12 +1130,6 @@ class TriggerView extends Component {
})
}
- handleRuntimeParamChangeV2: CIMaterialProps['handleRuntimeParamChangeV2'] = (updatedRuntimeParams) => {
- this.setState({
- runtimeParamsV2: updatedRuntimeParams,
- })
- }
-
uploadFile: CIMaterialProps['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) =>
uploadCIPipelineFile({
appId: +this.props.match.params.appId,
@@ -1241,8 +1233,6 @@ class TriggerView extends Component {
isJobCI={!!nd?.isJobCI}
runtimeParams={this.state.runtimeParams}
handleRuntimeParamChange={this.handleRuntimeParamChange}
- runtimeParamsV2={this.state.runtimeParamsV2}
- handleRuntimeParamChangeV2={this.handleRuntimeParamChangeV2}
closeCIModal={this.closeCIModal}
abortController={this.abortCIBuild}
resetAbortController={this.resetAbortController}
diff --git a/src/components/app/details/triggerView/cdMaterial.tsx b/src/components/app/details/triggerView/cdMaterial.tsx
index b565600070..5ecb36deeb 100644
--- a/src/components/app/details/triggerView/cdMaterial.tsx
+++ b/src/components/app/details/triggerView/cdMaterial.tsx
@@ -65,7 +65,6 @@ import {
useDownload,
SearchBar,
CDMaterialSidebarType,
- RuntimeParamsListItemType,
CDMaterialResponseType,
CD_MATERIAL_SIDEBAR_TABS,
getIsManualApprovalConfigured,
@@ -78,6 +77,8 @@ import {
ResponseType,
ApiResponseResultType,
CommonNodeAttr,
+ RuntimePluginVariables,
+ uploadCDPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -134,7 +135,7 @@ const getDeploymentWindowProfileMetaData = importComponentFromFELibrary(
const MaintenanceWindowInfoBar = importComponentFromFELibrary('MaintenanceWindowInfoBar')
const DeploymentWindowConfirmationDialog = importComponentFromFELibrary('DeploymentWindowConfirmationDialog')
const RuntimeParamTabs = importComponentFromFELibrary('RuntimeParamTabs', null, 'function')
-const RuntimeParameters = importComponentFromFELibrary('RuntimeParameters', null, 'function')
+const RuntimeParametersV2 = importComponentFromFELibrary('RuntimeParametersV2', null, 'function')
const getIsImageApproverFromUserApprovalMetaData: (
email: string,
userApprovalMetadata: UserApprovalMetadataType,
@@ -186,6 +187,7 @@ const CDMaterial = ({
consequence,
configurePluginURL,
isTriggerBlockedDueToPlugin,
+ bulkUploadFile,
}: Readonly) => {
// stageType should handle approval node, compute CDMaterialServiceEnum, create queryParams state
// FIXME: the query params returned by useSearchString seems faulty
@@ -270,7 +272,7 @@ const CDMaterial = ({
const [appliedFilterList, setAppliedFilterList] = useState([])
// ----- RUNTIME PARAMS States (To be overridden by parent props in case of bulk) -------
const [currentSidebarTab, setCurrentSidebarTab] = useState(CDMaterialSidebarType.IMAGE)
- const [runtimeParamsList, setRuntimeParamsList] = useState([])
+ const [runtimeParamsList, setRuntimeParamsList] = useState([])
const [runtimeParamsErrorState, setRuntimeParamsErrorState] = useState(false)
const [value, setValue] = useState()
const [showDeploymentWindowConfirmation, setShowDeploymentWindowConfirmation] = useState(false)
@@ -561,9 +563,7 @@ const CDMaterial = ({
}))
}
- const handleRuntimeParamChange: typeof handleBulkRuntimeParamChange = (
- updatedRuntimeParams: RuntimeParamsListItemType[],
- ) => {
+ const handleRuntimeParamChange: typeof handleBulkRuntimeParamChange = (updatedRuntimeParams) => {
setRuntimeParamsList(updatedRuntimeParams)
}
@@ -571,6 +571,9 @@ const CDMaterial = ({
setRuntimeParamsErrorState(errorState)
}
+ const uploadFile: typeof bulkUploadFile = ({ file, allowedExtensions, maxUploadSize }) =>
+ uploadCDPipelineFile({ file, allowedExtensions, maxUploadSize, appId, envId })
+
const clearSearch = (e: React.MouseEvent): void => {
stopPropagation(e)
if (state.searchText) {
@@ -1496,7 +1499,7 @@ const CDMaterial = ({
{(bulkSidebarTab
? bulkSidebarTab === CDMaterialSidebarType.IMAGE
- : currentSidebarTab === CDMaterialSidebarType.IMAGE) || !RuntimeParameters ? (
+ : currentSidebarTab === CDMaterialSidebarType.IMAGE) || !RuntimeParametersV2 ? (
<>
{isApprovalConfigured && renderMaterial(consumedImage, true, isApprovalConfigured)}
@@ -1543,11 +1546,11 @@ const CDMaterial = ({
)}
>
) : (
-
)}
@@ -1713,8 +1716,7 @@ const CDMaterial = ({
)}
>
- {AllowedWithWarningTippy &&
- showPluginWarningBeforeTrigger ? (
+ {AllowedWithWarningTippy && showPluginWarningBeforeTrigger ? (
{
handleRuntimeParamChange={this.props.handleRuntimeParamChange}
handleRuntimeParamError={this.handleRuntimeParamError}
appId={this.props.appId}
- runtimeParamsV2={this.props.runtimeParamsV2}
- handleRuntimeParamChangeV2={this.props.handleRuntimeParamChangeV2}
uploadFile={this.props.uploadFile}
/>
{this.props.isCITriggerBlocked ? null : this.renderMaterialStartBuild(canTrigger)}
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index a53f015953..895dc922e2 100644
--- a/src/components/app/details/triggerView/types.ts
+++ b/src/components/app/details/triggerView/types.ts
@@ -31,13 +31,10 @@ import {
PipelineType,
WorkflowType,
Material,
- KeyValueListType,
CIMaterialSidebarType,
ArtifactPromotionMetadata,
DeploymentWithConfigType,
CIMaterialType,
- RuntimeParamsListItemType,
- KeyValueTableProps,
CDMaterialSidebarType,
CiPipeline,
CdPipeline,
@@ -55,15 +52,16 @@ import { DeploymentHistoryDetail } from '../cdDetails/cd.type'
import { TIME_STAMP_ORDER } from './Constants'
import { Offset, WorkflowDimensions } from './config'
-export type HandleRuntimeParamChange = (updatedRuntimeParams: RuntimeParamsListItemType[]) => void
+export type HandleRuntimeParamChange = (updatedRuntimeParams: RuntimePluginVariables[]) => void
type CDMaterialBulkRuntimeParams =
| {
isFromBulkCD: true
- bulkRuntimeParams: RuntimeParamsListItemType[]
+ bulkRuntimeParams: RuntimePluginVariables[]
handleBulkRuntimeParamChange: HandleRuntimeParamChange
- handleBulkRuntimeParamError: KeyValueTableProps['onError']
+ handleBulkRuntimeParamError: (errorState: boolean) => void
bulkSidebarTab: CDMaterialSidebarType
+ bulkUploadFile: (props: UploadFileProps) => Promise
}
| {
isFromBulkCD?: false
@@ -71,6 +69,7 @@ type CDMaterialBulkRuntimeParams =
handleBulkRuntimeParamChange?: never
handleBulkRuntimeParamError?: never
bulkSidebarTab?: never
+ bulkUploadFile?: never
}
type CDMaterialPluginWarningProps =
@@ -391,8 +390,7 @@ export interface TriggerViewState {
isDefaultConfigPresent?: boolean
searchImageTag?: string
resourceFilters?: FilterConditionsListType[]
- runtimeParams?: RuntimeParamsListItemType[]
- runtimeParamsV2: RuntimePluginVariables[]
+ runtimeParams?: RuntimePluginVariables[]
}
export interface CIMaterialProps
@@ -428,9 +426,7 @@ export interface CIMaterialProps
setSelectedEnv?: React.Dispatch>
isJobCI?: boolean
handleRuntimeParamChange: HandleRuntimeParamChange
- runtimeParamsV2: RuntimePluginVariables[]
- handleRuntimeParamChangeV2: (runtimeParams: RuntimePluginVariables[]) => void
- runtimeParams: KeyValueListType[]
+ runtimeParams: RuntimePluginVariables[]
uploadFile: (props: UploadFileProps) => Promise
}
diff --git a/src/components/app/types.ts b/src/components/app/types.ts
index 1719bce688..fe22a80f43 100644
--- a/src/components/app/types.ts
+++ b/src/components/app/types.ts
@@ -25,9 +25,9 @@ import {
ReleaseMode,
AppEnvironment,
DeploymentNodeType,
- RuntimeParamsListItemType,
RuntimeParamsTriggerPayloadType,
HelmReleaseStatus,
+ RuntimePluginVariables,
} from '@devtron-labs/devtron-fe-common-lib'
import { DeploymentStatusDetailsBreakdownDataType, ErrorItem } from './details/appDetails/appDetails.type'
import { GroupFilterType } from '../ApplicationGroup/AppGroup.types'
@@ -649,7 +649,7 @@ export interface TriggerCDNodeServiceProps {
/**
* Would be available only case of PRE/POST CD
*/
- runtimeParams?: RuntimeParamsListItemType[]
+ runtimeParams?: RuntimePluginVariables[]
}
export interface TriggerCDPipelinePayloadType {
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index 0e14761217..2007a6ab26 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -50,6 +50,7 @@ import {
ResourceKindType,
getEnvironmentListMinPublic,
noop,
+ uploadCDPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Redirect, Route, Switch, useParams, useRouteMatch } from 'react-router-dom'
@@ -1200,6 +1201,9 @@ export default function CDPipeline({
setFormData({ ...formData, releaseMode: ReleaseMode.NEW_DEPLOYMENT })
}
+ const uploadFile: PipelineContext['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) =>
+ uploadCDPipelineFile({ file, allowedExtensions, maxUploadSize, appId: +appId, envId: formData.environmentId })
+
const contextValue = useMemo(() => {
return {
formData,
@@ -1238,8 +1242,7 @@ export default function CDPipeline({
handleDisableParentModalCloseUpdate,
handleValidateMandatoryPlugins,
mandatoryPluginData,
- // TODO: Handle for CD File Upload (Rohit)
- uploadFile: noop
+ uploadFile,
}
}, [
formData,
diff --git a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
index 60080f4af2..e7706d960d 100644
--- a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
+++ b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
@@ -74,6 +74,8 @@ export const GitInfoMaterial = ({
// Not required for BulkCI
currentSidebarTab,
handleSidebarTabChange,
+ runtimeParams,
+ handleRuntimeParamChange,
handleRuntimeParamError,
isBulkCIWebhook,
webhookPayloads,
@@ -81,8 +83,6 @@ export const GitInfoMaterial = ({
setIsWebhookBulkCI,
isBulk = false,
appId,
- runtimeParamsV2,
- handleRuntimeParamChangeV2,
uploadFile,
}: GitInfoMaterialProps) => {
const { push } = useHistory()
@@ -372,8 +372,8 @@ export const GitInfoMaterial = ({
// Have to add key for appId since key value config would not be updated incase of app change
key={`runtime-parameters-${appId}`}
heading={getRuntimeParametersHeading()}
- parameters={runtimeParamsV2}
- handleChange={handleRuntimeParamChangeV2}
+ parameters={runtimeParams}
+ handleChange={handleRuntimeParamChange}
onError={handleRuntimeParamError}
uploadFile={uploadFile}
/>
diff --git a/src/components/common/helpers/GitInfoMaterialCard/types.ts b/src/components/common/helpers/GitInfoMaterialCard/types.ts
index 6fdfa18ba1..9cb1b1625c 100644
--- a/src/components/common/helpers/GitInfoMaterialCard/types.ts
+++ b/src/components/common/helpers/GitInfoMaterialCard/types.ts
@@ -15,8 +15,6 @@ export interface GitInfoMaterialProps
| 'pipelineId'
| 'runtimeParams'
| 'appId'
- | 'runtimeParamsV2'
- | 'handleRuntimeParamChangeV2'
| 'uploadFile'
> {
dataTestId?: string
From 80aaeaae69695e4ead0c5d9125f84a6f27259410 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 16:21:33 +0530
Subject: [PATCH 08/36] feat: VariableDataTable - validation schema update, CI
/ CD Pipeline file upload util update
---
.../Details/TriggerView/EnvTriggerView.tsx | 5 --
src/components/CIPipelineN/CIPipeline.tsx | 2 +-
.../VariableDataTable/VariableDataTable.tsx | 80 ++++++-------------
.../CIPipelineN/VariableDataTable/utils.tsx | 6 +-
.../VariableDataTable/validationSchema.ts | 43 ++++++++++
.../details/triggerView/CIMaterialModal.tsx | 18 ++++-
.../app/details/triggerView/TriggerView.tsx | 10 ---
.../app/details/triggerView/types.ts | 2 +-
src/components/cdPipeline/CDPipeline.tsx | 1 -
9 files changed, 86 insertions(+), 81 deletions(-)
create mode 100644 src/components/CIPipelineN/VariableDataTable/validationSchema.ts
diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
index 0941c75840..2bd9037d42 100644
--- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
+++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx
@@ -1874,10 +1874,6 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
}
}
- const uploadFile: CIMaterialProps['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) => null
- // TODO: rohit confirm with BE
- // uploadCIPipelineFile({ file, allowedExtensions, maxUploadSize, appId: +appId, ciPipelineId: null })
-
const createBulkCITriggerData = (): BulkCIDetailType[] => {
const _selectedAppWorkflowList: BulkCIDetailType[] = []
filteredWorkflows.forEach((wf) => {
@@ -2030,7 +2026,6 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
isJobCI={!!nd?.isJobCI}
runtimeParams={runtimeParams[nd?.id] ?? []}
handleRuntimeParamChange={handleRuntimeParamChange}
- uploadFile={uploadFile}
closeCIModal={closeCIModal}
abortController={abortCIBuildRef.current}
resetAbortController={resetAbortController}
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index 7d290f27b5..a279d61905 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -982,7 +982,7 @@ export default function CIPipeline({
<>
{renderFloatingVariablesWidget()}
-
+
{renderCIPipelineModal()}
>
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
index 41f3f33cd6..24ccd7ab64 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -36,6 +36,7 @@ import {
getValColumnRowValue,
getVariableDataTableInitialRows,
} from './utils'
+import { variableDataTableValidationSchema } from './validationSchema'
import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
import { VariableConfigOverlay } from './VariableConfigOverlay'
@@ -93,11 +94,6 @@ export const VariableDataTable = ({
type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
]
- const ioVariablesError: { isValid: boolean; message: string }[] =
- formDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
- type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
- ]
-
// STATES
const [rows, setRows] = useState([])
@@ -105,18 +101,10 @@ export const VariableDataTable = ({
const initialRowsSet = useRef('')
useEffect(() => {
- setRows(
- ioVariables?.length
- ? getVariableDataTableInitialRows({ emptyRowParams, ioVariables, isCustomTask, type })
- : [getEmptyVariableDataTableRow(emptyRowParams)],
- )
+ setRows(getVariableDataTableInitialRows({ emptyRowParams, ioVariables, isCustomTask, type }))
initialRowsSet.current = 'set'
}, [])
- // useEffect(() => {
- // console.log('meg', rows, ioVariables, formDataErrorObj)
- // }, [JSON.stringify(ioVariables)])
-
// METHODS
const handleRowUpdateAction = (rowAction: HandleRowUpdateActionProps) => {
const { actionType } = rowAction
@@ -310,9 +298,6 @@ export const VariableDataTable = ({
case VariableDataTableActionType.DELETE_ROW:
updatedRows = updatedRows.filter((row) => row.id !== rowAction.rowId)
- if (updatedRows.length === 0) {
- updatedRows = [getEmptyVariableDataTableRow(emptyRowParams)]
- }
break
case VariableDataTableActionType.UPDATE_ROW:
@@ -464,7 +449,7 @@ export const VariableDataTable = ({
const dataTableHandleAddition = () => {
handleRowUpdateAction({
actionType: VariableDataTableActionType.ADD_ROW,
- actionValue: rows.length,
+ actionValue: Math.floor(new Date().valueOf() * Math.random()),
})
}
@@ -548,22 +533,7 @@ export const VariableDataTable = ({
})
}
- const validationSchema: DynamicDataTableProps['validationSchema'] = (
- _,
- key,
- { id },
- ) => {
- if (key === 'val') {
- const index = rows.findIndex((row) => row.id === id)
- if (index > -1 && ioVariablesError[index]) {
- const { isValid, message } = ioVariablesError[index]
- return { isValid, errorMessages: [message] }
- }
- }
-
- return { isValid: true, errorMessages: [] }
- }
-
+ // RENDERERS
const actionButtonRenderer = (row: VariableDataRowType) => (
)
- const getActionButtonConfig = (): DynamicDataTableProps['actionButtonConfig'] => {
- if (type === PluginVariableType.INPUT) {
- return {
- renderer: actionButtonRenderer,
- key: 'val',
- position: 'end',
- }
- }
- return null
- }
+ const variableTrailingCellIcon = (row: VariableDataRowType) => (
+
+
+
+ )
- const getTrailingCellIcon = (): DynamicDataTableProps['trailingCellIcon'] => ({
- variable:
- isCustomTask && type === PluginVariableType.INPUT
- ? (row: VariableDataRowType) => (
-
-
-
- )
- : null,
- })
+ const trailingCellIcon: DynamicDataTableProps['trailingCellIcon'] = {
+ variable: isCustomTask && type === PluginVariableType.INPUT ? variableTrailingCellIcon : null,
+ }
return (
@@ -603,13 +561,21 @@ export const VariableDataTable = ({
readOnly={!isCustomTask && type === PluginVariableType.OUTPUT}
isAdditionNotAllowed={!isCustomTask}
isDeletionNotAllowed={!isCustomTask}
- trailingCellIcon={getTrailingCellIcon()}
+ trailingCellIcon={trailingCellIcon}
onRowEdit={dataTableHandleChange}
onRowDelete={dataTableHandleDelete}
onRowAdd={dataTableHandleAddition}
showError
- validationSchema={validationSchema}
- actionButtonConfig={getActionButtonConfig()}
+ validationSchema={variableDataTableValidationSchema}
+ {...(type === PluginVariableType.INPUT
+ ? {
+ actionButtonConfig: {
+ renderer: actionButtonRenderer,
+ key: 'val',
+ position: 'end',
+ },
+ }
+ : {})}
/>
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 8cc3dc6f95..58842b9488 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -308,7 +308,7 @@ export const getVariableDataTableInitialRows = ({
isCustomTask,
emptyRowParams,
}: GetVariableDataTableInitialRowsProps): VariableDataRowType[] =>
- ioVariables.map(
+ (ioVariables || []).map(
({
name,
allowEmptyValue,
@@ -504,10 +504,6 @@ export const convertVariableDataTableToFormData = ({
calculateLastStepDetail(false, updatedFormData, activeStageName, selectedTaskIndex)
}
- if (updatedIOVariables.length === 1 && !updatedIOVariables[0].name && !updatedIOVariables[0].value) {
- updatedIOVariables.pop()
- }
-
if (updatedIOVariables.length === 0) {
const { conditionDetails } = updatedFormData[activeStageName].steps[selectedTaskIndex].inlineStepDetail
for (let i = 0; i < conditionDetails?.length; i++) {
diff --git a/src/components/CIPipelineN/VariableDataTable/validationSchema.ts b/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
new file mode 100644
index 0000000000..4110e8bc44
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
@@ -0,0 +1,43 @@
+import { DynamicDataTableProps } from '@devtron-labs/devtron-fe-common-lib'
+
+import { PATTERNS } from '@Config/constants'
+
+import { VariableDataCustomState, VariableDataKeys } from './types'
+
+export const variableDataTableValidationSchema: DynamicDataTableProps<
+ VariableDataKeys,
+ VariableDataCustomState
+>['validationSchema'] = (value, key, { data, customState }) => {
+ const { variableDescription, isVariableRequired } = customState
+
+ const re = new RegExp(PATTERNS.VARIABLE)
+
+ if (key === 'variable') {
+ const variableValue = !isVariableRequired || data.val.value
+
+ if (!value && !variableValue && !variableDescription) {
+ return { errorMessages: ['Please complete or remove this variable'], isValid: false }
+ }
+
+ if (!value) {
+ return { errorMessages: ['Variable name is required'], isValid: false }
+ }
+
+ if (!re.test(value)) {
+ return { errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`], isValid: false }
+ }
+
+ // TODO: need to confirm this validation from product
+ // if (availableInputVariables.get(name)) {
+ // return { errorMessages: ['Variable name should be unique'], isValid: false }
+ // }
+ }
+
+ if (key === 'val') {
+ if (isVariableRequired && !value) {
+ return { errorMessages: ['Variable value is required'], isValid: false }
+ }
+ }
+
+ return { errorMessages: [], isValid: true }
+}
diff --git a/src/components/app/details/triggerView/CIMaterialModal.tsx b/src/components/app/details/triggerView/CIMaterialModal.tsx
index 7a51404aa4..5e1cfe7083 100644
--- a/src/components/app/details/triggerView/CIMaterialModal.tsx
+++ b/src/components/app/details/triggerView/CIMaterialModal.tsx
@@ -24,6 +24,7 @@ import {
Progressing,
VisibleModal,
stopPropagation,
+ uploadCIPipelineFile,
usePrompt,
} from '@devtron-labs/devtron-fe-common-lib'
import CIMaterial from './ciMaterial'
@@ -44,6 +45,15 @@ export const CIMaterialModal = ({
[props.filteredCIPipelines, props.pipelineId],
)
+ const uploadFile = ({ file, allowedExtensions, maxUploadSize }) =>
+ uploadCIPipelineFile({
+ file,
+ allowedExtensions,
+ maxUploadSize,
+ appId: +props.appId,
+ ciPipelineId: +props.pipelineId,
+ })
+
usePrompt({ shouldPrompt: isLoading })
useEffect(
@@ -84,7 +94,13 @@ export const CIMaterialModal = ({
>
) : (
-
+
)}
)
diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx
index 4e3491bc3b..d5d673d67b 100644
--- a/src/components/app/details/triggerView/TriggerView.tsx
+++ b/src/components/app/details/triggerView/TriggerView.tsx
@@ -1130,15 +1130,6 @@ class TriggerView extends Component {
})
}
- uploadFile: CIMaterialProps['uploadFile'] = ({ file, allowedExtensions, maxUploadSize }) =>
- uploadCIPipelineFile({
- appId: +this.props.match.params.appId,
- ciPipelineId: this.getCINode().parentCiPipeline,
- file,
- allowedExtensions,
- maxUploadSize,
- })
-
setLoader = (isLoader) => {
this.setState({
loader: isLoader,
@@ -1236,7 +1227,6 @@ class TriggerView extends Component {
closeCIModal={this.closeCIModal}
abortController={this.abortCIBuild}
resetAbortController={this.resetAbortController}
- uploadFile={this.uploadFile}
/>
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index 895dc922e2..81ef82de91 100644
--- a/src/components/app/details/triggerView/types.ts
+++ b/src/components/app/details/triggerView/types.ts
@@ -684,7 +684,7 @@ export interface RenderCTAType {
disableSelection: boolean
}
-export interface CIMaterialModalProps extends CIMaterialProps {
+export interface CIMaterialModalProps extends Omit {
closeCIModal: () => void
abortController: AbortController
resetAbortController: () => void
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index 2007a6ab26..e5ad88411b 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -49,7 +49,6 @@ import {
ProcessPluginDataReturnType,
ResourceKindType,
getEnvironmentListMinPublic,
- noop,
uploadCDPipelineFile,
} from '@devtron-labs/devtron-fe-common-lib'
import { useEffect, useMemo, useRef, useState } from 'react'
From df86d535bb476e203ff2b84ab18c7b5759e623e9 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 6 Dec 2024 16:27:04 +0530
Subject: [PATCH 09/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 2f15a947a3..bee7b9acc2 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-1",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-5",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index a252d284d5..a428842b7b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-1":
- version "1.2.4-beta-1"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-1.tgz#ee3cf0ba6f9af55f6d463d281fcb57e12a56a00e"
- integrity sha512-GyCFZ+EP7J0i29I+f+ENR+ZUDY5+vUYh7+AprrU+Hptz3p/96Xc5riDBZJSk6pvHzvbXyhJCsOSE4HmaLedOZA==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-5":
+ version "1.2.4-beta-5"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-5.tgz#61e02e770b9756efc904fefd9739fc3706959ea7"
+ integrity sha512-59SesiSFaAxP+tePpGTy505GRCbX7cVHO0cg5GM4QOJ8GGBBMdYG9e88pecsZtYpdLc4PYztBwZnCgOssB0Oiw==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From aead12bce926c0082d58bc74e02241baeae8babd Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Sun, 8 Dec 2024 22:14:04 +0530
Subject: [PATCH 10/36] refactor: VariableDataTable - code refactor
---
.../VariableDataTable/VariableDataTable.tsx | 44 +++++++++----------
1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
index 24ccd7ab64..c65154857e 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
@@ -453,7 +453,7 @@ export const VariableDataTable = ({
})
}
- const dataTableHandleChange: DynamicDataTableProps['onRowEdit'] = (
+ const dataTableHandleChange: DynamicDataTableProps['onRowEdit'] = async (
updatedRow,
headerKey,
value,
@@ -478,29 +478,29 @@ export const VariableDataTable = ({
rowId: updatedRow.id,
})
- uploadFile({
- file: extraData.files,
- ...getUploadFileConstraints({
- unit: updatedRow.customState.fileInfo.unit.label as string,
- allowedExtensions: updatedRow.customState.fileInfo.allowedExtensions,
- maxUploadSize: updatedRow.customState.fileInfo.maxUploadSize,
- }),
- })
- .then((res) => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
- actionValue: { fileReferenceId: res.id },
- rowId: updatedRow.id,
- })
+ try {
+ const { id } = await uploadFile({
+ file: extraData.files,
+ ...getUploadFileConstraints({
+ unit: updatedRow.customState.fileInfo.unit.label as string,
+ allowedExtensions: updatedRow.customState.fileInfo.allowedExtensions,
+ maxUploadSize: updatedRow.customState.fileInfo.maxUploadSize,
+ }),
})
- .catch(() => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: '',
- headerKey,
- rowId: updatedRow.id,
- })
+
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
+ actionValue: { fileReferenceId: id },
+ rowId: updatedRow.id,
+ })
+ } catch {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.UPDATE_ROW,
+ actionValue: '',
+ headerKey,
+ rowId: updatedRow.id,
})
+ }
} else if (headerKey === 'format' && updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FORMAT_COLUMN,
From 05aff25185e96e49ca24f51bf8dbf1d908255dfe Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Mon, 9 Dec 2024 15:18:19 +0530
Subject: [PATCH 11/36] refactor: VariableDataTable - update validations, date
type formats update, review fixes, code refactor
---
src/components/CIPipelineN/CIPipeline.tsx | 9 +-
.../CIPipelineN/CreatePluginModal/utils.tsx | 3 +-
.../CIPipelineN/TaskDetailComponent.tsx | 14 +--
...le.tsx => VariableDataTable.component.tsx} | 50 ++++++-----
.../VariableDataTablePopupMenu.tsx | 2 +-
.../VariableDataTable/constants.ts | 49 +++++++++--
.../CIPipelineN/VariableDataTable/helpers.tsx | 12 ---
.../CIPipelineN/VariableDataTable/index.ts | 2 +-
.../CIPipelineN/VariableDataTable/types.ts | 27 +++++-
.../CIPipelineN/VariableDataTable/utils.tsx | 83 ++++++++++++------
.../VariableDataTable/validationSchema.ts | 85 +++++++++++--------
src/components/ciPipeline/validationRules.ts | 1 +
src/css/base.scss | 4 -
13 files changed, 214 insertions(+), 127 deletions(-)
rename src/components/CIPipelineN/VariableDataTable/{VariableDataTable.tsx => VariableDataTable.component.tsx} (95%)
delete mode 100644 src/components/CIPipelineN/VariableDataTable/helpers.tsx
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index a279d61905..3e1c50fd73 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -785,7 +785,14 @@ export default function CIPipeline({
}
const uploadFile: PipelineContext['uploadFile'] = ({ allowedExtensions, file, maxUploadSize }) =>
- uploadCIPipelineFile({ appId: +appId, ciPipelineId: +ciPipelineId, file, allowedExtensions, maxUploadSize })
+ uploadCIPipelineFile({
+ appId: +appId,
+ envId: isJobView ? selectedEnv?.id : null,
+ ciPipelineId: +ciPipelineId,
+ file,
+ allowedExtensions,
+ maxUploadSize,
+ })
const contextValue = useMemo(
() => ({
diff --git a/src/components/CIPipelineN/CreatePluginModal/utils.tsx b/src/components/CIPipelineN/CreatePluginModal/utils.tsx
index 73b6a77846..ab4dd287c2 100644
--- a/src/components/CIPipelineN/CreatePluginModal/utils.tsx
+++ b/src/components/CIPipelineN/CreatePluginModal/utils.tsx
@@ -156,7 +156,8 @@ const parseInputVariablesIntoCreatePluginPayload = (
valueType: variable.variableType,
referenceVariableName: variable.refVariableName,
isExposed: true,
- // TODO: handle file type here
+ fileMountDir: variable.fileMountDir,
+ fileReferenceId: variable.fileReferenceId,
})) || []
export const getCreatePluginPayload = ({
diff --git a/src/components/CIPipelineN/TaskDetailComponent.tsx b/src/components/CIPipelineN/TaskDetailComponent.tsx
index bfda4e0bc2..2ec2787908 100644
--- a/src/components/CIPipelineN/TaskDetailComponent.tsx
+++ b/src/components/CIPipelineN/TaskDetailComponent.tsx
@@ -273,12 +273,10 @@ export const TaskDetailComponent = () => {
{selectedStep.stepType === PluginType.INLINE ? (
- <>
-
- >
+
) : (
- )}{' '}
+ )}
{selectedStep[currentStepTypeVariable]?.inputVariables?.length > 0 && (
<>
@@ -291,13 +289,7 @@ export const TaskDetailComponent = () => {
{formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].scriptType !==
ScriptType.CONTAINERIMAGE && (
- <>
-
- >
+
)}
>
) : (
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
similarity index 95%
rename from src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
rename to src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index c65154857e..6ee6aaee64 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -1,4 +1,4 @@
-import { useContext, useState, useEffect, useRef } from 'react'
+import { useContext, useState, useEffect, useRef, useMemo } from 'react'
import {
DynamicDataTable,
@@ -6,49 +6,43 @@ import {
DynamicDataTableRowDataType,
PluginType,
RefVariableType,
- SelectPickerOptionType,
VariableType,
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import { ExtendedOptionType } from '@Components/app/types'
import {
FILE_UPLOAD_SIZE_UNIT_OPTIONS,
getVariableDataTableHeaders,
VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
} from './constants'
-import { getSystemVariableIcon } from './helpers'
import {
HandleRowUpdateActionProps,
VariableDataCustomState,
VariableDataKeys,
VariableDataRowType,
VariableDataTableActionType,
+ VariableDataTableProps,
} from './types'
import {
+ checkForSystemVariable,
convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
+ getSystemVariableIcon,
getUploadFileConstraints,
getValColumnRowProps,
getValColumnRowValue,
getVariableDataTableInitialRows,
} from './utils'
-import { variableDataTableValidationSchema } from './validationSchema'
+import { getVariableDataTableValidationSchema } from './validationSchema'
import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
import { VariableConfigOverlay } from './VariableConfigOverlay'
import { ValueConfigOverlay } from './ValueConfigOverlay'
-export const VariableDataTable = ({
- type,
- isCustomTask = false,
-}: {
- type: PluginVariableType
- isCustomTask?: boolean
-}) => {
+export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTableProps) => {
// CONTEXTS
const {
inputVariablesListFromPrevStep,
@@ -97,6 +91,22 @@ export const VariableDataTable = ({
// STATES
const [rows, setRows] = useState([])
+ // KEYS FREQUENCY MAP
+ const keysFrequencyMap: Record = useMemo(
+ () =>
+ rows.reduce(
+ (acc, curr) => {
+ const currentKey = curr.data.variable.value
+ if (currentKey) {
+ acc[currentKey] = (acc[currentKey] || 0) + 1
+ }
+ return acc
+ },
+ {} as Record,
+ ),
+ [rows],
+ )
+
// REFS
const initialRowsSet = useRef('')
@@ -340,13 +350,8 @@ export const VariableDataTable = ({
row.id === rowAction.rowId &&
row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
) {
- const { selectedValue, value } = rowAction.actionValue as {
- selectedValue: SelectPickerOptionType & ExtendedOptionType
- value: string
- }
- const isSystemVariable =
- !!selectedValue.refVariableStage ||
- (selectedValue?.variableType && selectedValue.variableType !== RefVariableType.NEW)
+ const { selectedValue, value } = rowAction.actionValue
+ const isSystemVariable = checkForSystemVariable(selectedValue)
return {
...row,
@@ -355,11 +360,9 @@ export const VariableDataTable = ({
val: {
...row.data.val,
value: getValColumnRowValue(
- row.data.val.value,
row.data.format.value as VariableTypeFormat,
value,
selectedValue,
- isSystemVariable,
),
props: {
...row.data.val.props,
@@ -538,6 +541,9 @@ export const VariableDataTable = ({
@@ -566,7 +572,7 @@ export const VariableDataTable = ({
onRowDelete={dataTableHandleDelete}
onRowAdd={dataTableHandleAddition}
showError
- validationSchema={variableDataTableValidationSchema}
+ validationSchema={getVariableDataTableValidationSchema({ keysFrequencyMap, pluginVariableType: type })}
{...(type === PluginVariableType.INPUT
? {
actionButtonConfig: {
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
index e473b75110..a303760f53 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -42,7 +42,7 @@ export const VariableDataTablePopupMenu = ({
}
return (
-
+
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 1589e5e9c7..972a0547f9 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -67,13 +67,46 @@ export const VAL_COLUMN_BOOL_OPTIONS: SelectPickerOptionType[] = [
]
export const VAL_COLUMN_DATE_OPTIONS: SelectPickerOptionType[] = [
- { label: 'YYYY-MM-DD', value: 'YYYY-MM-DD', description: 'RFC 3339' },
- { label: 'YYYY-MM-DD HH:mm', value: 'YYYY-MM-DD HH:mm', description: 'RFC 3339 with mins' },
- { label: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss', description: 'RFC 3339 with secs' },
- { label: 'YYYY-MM-DD HH:mm:ssZ', value: 'YYYY-MM-DD HH:mm:ssZ', description: 'RFC 3339 with secs and TZ' },
- { label: 'YYYY-MM-DDT15Z0700', value: 'ISO', description: 'ISO8601 with hours' },
- { label: 'YYYY-MM-DDTHH:mm:ss[Z]', value: 'YYYY-MM-DDTHH:mm:ss[Z]', description: 'ISO8601 with secs' },
- { label: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]', value: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]', description: 'ISO8601 with nanosecs' },
+ {
+ label: 'YYYY-MM-DD',
+ value: 'YYYY-MM-DD',
+ description: 'RFC 3339',
+ },
+ {
+ label: 'YYYY-MM-DD HH:mm',
+ value: 'YYYY-MM-DD HH:mm',
+ description: 'RFC 3339 with minutes',
+ },
+ {
+ label: 'YYYY-MM-DD HH:mm:ss',
+ value: 'YYYY-MM-DD HH:mm:ss',
+ description: 'RFC 3339 with seconds',
+ },
+ {
+ label: 'YYYY-MM-DD HH:mm:ssZ',
+ value: 'YYYY-MM-DD HH:mm:ssZ',
+ description: 'RFC 3339 with seconds and timezone',
+ },
+ {
+ label: 'YYYY-MM-DDTHH[Z]',
+ value: 'YYYY-MM-DDTHH[Z]',
+ description: 'ISO8601 with hour',
+ },
+ {
+ label: 'YYYY-MM-DDTHH:mm[Z]',
+ value: 'YYYY-MM-DDTHH:mm[Z]',
+ description: 'ISO8601 with minutes',
+ },
+ {
+ label: 'YYYY-MM-DDTHH:mm:ss[Z]',
+ value: 'YYYY-MM-DDTHH:mm:ss[Z]',
+ description: 'ISO8601 with seconds',
+ },
+ {
+ label: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
+ value: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
+ description: 'ISO8601 with nanoseconds',
+ },
]
export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
@@ -87,4 +120,4 @@ export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
},
]
-export const DECIMAL_REGEX = /^\d*\.?\d*$/
+export const DECIMAL_WITH_SCOPE_VARIABLES_REGEX = /^(\d+(\.\d+)?|@{{[a-zA-Z0-9-]+}})$/
diff --git a/src/components/CIPipelineN/VariableDataTable/helpers.tsx b/src/components/CIPipelineN/VariableDataTable/helpers.tsx
deleted file mode 100644
index 763a3ca6fe..0000000000
--- a/src/components/CIPipelineN/VariableDataTable/helpers.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Tooltip } from '@devtron-labs/devtron-fe-common-lib'
-
-import { ReactComponent as Var } from '@Icons/ic-var-initial.svg'
-import { TIPPY_VAR_MSG } from '../Constants'
-
-export const getSystemVariableIcon = () => (
-
-
-
-
-
-)
diff --git a/src/components/CIPipelineN/VariableDataTable/index.ts b/src/components/CIPipelineN/VariableDataTable/index.ts
index ee1427a9a9..46eeb15f03 100644
--- a/src/components/CIPipelineN/VariableDataTable/index.ts
+++ b/src/components/CIPipelineN/VariableDataTable/index.ts
@@ -1 +1 @@
-export * from './VariableDataTable'
+export * from './VariableDataTable.component'
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index a43b1ed796..bc739d390e 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -1,6 +1,25 @@
import { PluginVariableType } from '@Components/ciPipeline/types'
import { PipelineContext } from '@Components/workflowEditor/types'
-import { DynamicDataTableRowType, SelectPickerOptionType, VariableType } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ DynamicDataTableRowType,
+ RefVariableStageType,
+ RefVariableType,
+ SelectPickerOptionType,
+ VariableType,
+ VariableTypeFormat,
+} from '@devtron-labs/devtron-fe-common-lib'
+
+export interface VariableDataTableProps {
+ type: PluginVariableType
+ isCustomTask?: boolean
+}
+
+export interface VariableDataTableSelectPickerOptionType extends SelectPickerOptionType {
+ format?: VariableTypeFormat
+ variableType?: RefVariableType
+ refVariableStage?: RefVariableStageType
+ refVariableName?: string
+}
export type VariableDataKeys = 'variable' | 'format' | 'val'
@@ -11,7 +30,7 @@ export type VariableDataCustomState = {
askValueAtRuntime: boolean
blockCustomValue: boolean
// Check for support in the TableRowTypes
- selectedValue: Record
+ selectedValue: VariableDataTableSelectPickerOptionType & Record
fileInfo: {
id: number
mountDir: {
@@ -62,7 +81,7 @@ type VariableDataTableActionPropsMap = {
[VariableDataTableActionType.UPDATE_VAL_COLUMN]: {
actionValue: {
value: string
- selectedValue: SelectPickerOptionType
+ selectedValue: VariableDataTableSelectPickerOptionType
files: File[]
}
rowId: string | number
@@ -70,7 +89,7 @@ type VariableDataTableActionPropsMap = {
[VariableDataTableActionType.UPDATE_FORMAT_COLUMN]: {
actionValue: {
value: string
- selectedValue: SelectPickerOptionType
+ selectedValue: VariableDataTableSelectPickerOptionType
}
rowId: string | number
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 58842b9488..872bd8a965 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -1,4 +1,4 @@
-import dayjs from 'dayjs'
+import moment from 'moment'
import {
ConditionType,
@@ -9,24 +9,29 @@ import {
SelectPickerOptionType,
VariableType,
VariableTypeFormat,
+ Tooltip,
} from '@devtron-labs/devtron-fe-common-lib'
+import { ReactComponent as Var } from '@Icons/ic-var-initial.svg'
import { BuildStageVariable } from '@Config/constants'
import { PipelineContext } from '@Components/workflowEditor/types'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import { ExtendedOptionType } from '@Components/app/types'
-import { excludeVariables } from '../Constants'
+import { excludeVariables, TIPPY_VAR_MSG } from '../Constants'
import {
- DECIMAL_REGEX,
+ DECIMAL_WITH_SCOPE_VARIABLES_REGEX,
FILE_UPLOAD_SIZE_UNIT_OPTIONS,
FORMAT_COLUMN_OPTIONS,
VAL_COLUMN_BOOL_OPTIONS,
VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
VAL_COLUMN_DATE_OPTIONS,
} from './constants'
-import { GetValColumnRowPropsType, GetVariableDataTableInitialRowsProps, VariableDataRowType } from './types'
-import { getSystemVariableIcon } from './helpers'
+import {
+ GetValColumnRowPropsType,
+ GetVariableDataTableInitialRowsProps,
+ VariableDataTableSelectPickerOptionType,
+ VariableDataRowType,
+} from './types'
export const getOptionsForValColumn = ({
inputVariablesListFromPrevStep,
@@ -152,6 +157,14 @@ export const getOptionsForValColumn = ({
]
}
+export const getSystemVariableIcon = () => (
+
+
+
+
+
+)
+
export const getVariableColumnRowProps = () => {
const data: VariableDataRowType['data']['variable'] = {
value: '',
@@ -248,24 +261,27 @@ export const getValColumnRowProps = ({
}
}
-export const testValueForNumber = (value: string) => !value || DECIMAL_REGEX.test(value)
+export const testValueForNumber = (value: string) => !value || DECIMAL_WITH_SCOPE_VARIABLES_REGEX.test(value)
+
+export const checkForSystemVariable = (option: VariableDataTableSelectPickerOptionType) => {
+ const isSystemVariable =
+ !!option?.refVariableStage || (option?.variableType && option.variableType !== RefVariableType.NEW)
+
+ return isSystemVariable
+}
export const getValColumnRowValue = (
- currentValue: string,
format: VariableTypeFormat,
value: string,
- selectedValue: SelectPickerOptionType & ExtendedOptionType,
- isSystemVariable: boolean,
+ selectedValue: VariableDataTableSelectPickerOptionType,
) => {
- const isNumberFormat = !isSystemVariable && format === VariableTypeFormat.NUMBER
- if (isNumberFormat && !testValueForNumber(value)) {
- return currentValue
- }
+ const isSystemVariable = checkForSystemVariable(selectedValue)
const isDateFormat = !isSystemVariable && value && format === VariableTypeFormat.DATE
if (isDateFormat && selectedValue.description) {
- const now = dayjs()
- return selectedValue.value !== 'ISO' ? now.format(selectedValue.value) : now.toISOString()
+ const now = moment.now()
+ const formattedDate = moment(now).format(selectedValue.value)
+ return formattedDate.replace('Z', moment().format('Z'))
}
return value
@@ -447,23 +463,29 @@ export const convertVariableDataTableToFormData = ({
description: variableDescription,
allowEmptyValue: !isVariableRequired,
isRuntimeArg: askValueAtRuntime,
- valueConstraint: {
+ }
+
+ if (choices.length) {
+ variableDetail.valueConstraint = {
+ ...variableDetail.valueConstraint,
choices: choices.map(({ value }) => value),
blockCustomValue,
- constraint: null,
- },
+ }
}
if (fileInfo) {
variableDetail.value = data.val.value
variableDetail.fileReferenceId = fileInfo.id
variableDetail.fileMountDir = fileInfo.mountDir.value
- variableDetail.valueConstraint.constraint = {
- fileProperty: getUploadFileConstraints({
- allowedExtensions: fileInfo.allowedExtensions,
- maxUploadSize: fileInfo.maxUploadSize,
- unit: fileInfo.unit.label as string,
- }),
+ variableDetail.valueConstraint = {
+ ...variableDetail.valueConstraint,
+ constraint: {
+ fileProperty: getUploadFileConstraints({
+ allowedExtensions: fileInfo.allowedExtensions,
+ maxUploadSize: fileInfo.maxUploadSize,
+ unit: fileInfo.unit.label as string,
+ }),
+ },
}
}
@@ -472,22 +494,27 @@ export const convertVariableDataTableToFormData = ({
variableDetail.value = ''
variableDetail.variableType = RefVariableType.FROM_PREVIOUS_STEP
variableDetail.refVariableStepIndex = selectedValue.refVariableStepIndex
- variableDetail.refVariableName = selectedValue.label
+ variableDetail.refVariableName = selectedValue.label as string
variableDetail.format = selectedValue.format
variableDetail.refVariableStage = selectedValue.refVariableStage
} else if (selectedValue.variableType === RefVariableType.GLOBAL) {
variableDetail.variableType = RefVariableType.GLOBAL
variableDetail.refVariableStepIndex = 0
- variableDetail.refVariableName = selectedValue.label
+ variableDetail.refVariableName = selectedValue.label as string
variableDetail.format = selectedValue.format
variableDetail.value = ''
variableDetail.refVariableStage = null
} else {
variableDetail.variableType = RefVariableType.NEW
- variableDetail.value = selectedValue.label
+ if (data.format.value === VariableTypeFormat.DATE) {
+ variableDetail.value = data.val.value
+ } else {
+ variableDetail.value = selectedValue.label as string
+ }
variableDetail.refVariableName = ''
variableDetail.refVariableStage = null
}
+
if (formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.PLUGIN_REF) {
variableDetail.format = selectedIOVariable.format
}
diff --git a/src/components/CIPipelineN/VariableDataTable/validationSchema.ts b/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
index 4110e8bc44..dee292e5ac 100644
--- a/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
+++ b/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
@@ -1,43 +1,60 @@
-import { DynamicDataTableProps } from '@devtron-labs/devtron-fe-common-lib'
+import { DynamicDataTableProps, VariableTypeFormat } from '@devtron-labs/devtron-fe-common-lib'
+import { PluginVariableType } from '@Components/ciPipeline/types'
import { PATTERNS } from '@Config/constants'
import { VariableDataCustomState, VariableDataKeys } from './types'
-
-export const variableDataTableValidationSchema: DynamicDataTableProps<
- VariableDataKeys,
- VariableDataCustomState
->['validationSchema'] = (value, key, { data, customState }) => {
- const { variableDescription, isVariableRequired } = customState
-
- const re = new RegExp(PATTERNS.VARIABLE)
-
- if (key === 'variable') {
- const variableValue = !isVariableRequired || data.val.value
-
- if (!value && !variableValue && !variableDescription) {
- return { errorMessages: ['Please complete or remove this variable'], isValid: false }
- }
-
- if (!value) {
- return { errorMessages: ['Variable name is required'], isValid: false }
+import { checkForSystemVariable, testValueForNumber } from './utils'
+
+export const getVariableDataTableValidationSchema =
+ ({
+ pluginVariableType,
+ keysFrequencyMap,
+ }: {
+ pluginVariableType: PluginVariableType
+ keysFrequencyMap: Record
+ }): DynamicDataTableProps['validationSchema'] =>
+ (value, key, { data, customState }) => {
+ const { variableDescription, isVariableRequired, selectedValue, askValueAtRuntime } = customState
+
+ const re = new RegExp(PATTERNS.VARIABLE)
+
+ if (key === 'variable') {
+ const variableValue = !isVariableRequired || data.val.value
+
+ if (!value && !variableValue && !variableDescription) {
+ return { errorMessages: ['Please complete or remove this variable'], isValid: false }
+ }
+
+ if (!value) {
+ return { errorMessages: ['Variable name is required'], isValid: false }
+ }
+
+ if (!re.test(value)) {
+ return {
+ errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`],
+ isValid: false,
+ }
+ }
+
+ if ((keysFrequencyMap[value] || 0) > 1) {
+ return { errorMessages: ['Variable name should be unique'], isValid: false }
+ }
}
- if (!re.test(value)) {
- return { errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`], isValid: false }
+ if (pluginVariableType === PluginVariableType.INPUT && key === 'val') {
+ const checkForVariable = isVariableRequired && !askValueAtRuntime
+ if (checkForVariable && !value) {
+ return { errorMessages: ['Variable value is required'], isValid: false }
+ }
+
+ if (data.format.value === VariableTypeFormat.NUMBER) {
+ return {
+ isValid: checkForSystemVariable(selectedValue) || testValueForNumber(value),
+ errorMessages: ['Variable value is not a number'],
+ }
+ }
}
- // TODO: need to confirm this validation from product
- // if (availableInputVariables.get(name)) {
- // return { errorMessages: ['Variable name should be unique'], isValid: false }
- // }
+ return { errorMessages: [], isValid: true }
}
-
- if (key === 'val') {
- if (isVariableRequired && !value) {
- return { errorMessages: ['Variable value is required'], isValid: false }
- }
- }
-
- return { errorMessages: [], isValid: true }
-}
diff --git a/src/components/ciPipeline/validationRules.ts b/src/components/ciPipeline/validationRules.ts
index d9cefe6a03..02ad278f5b 100644
--- a/src/components/ciPipeline/validationRules.ts
+++ b/src/components/ciPipeline/validationRules.ts
@@ -92,6 +92,7 @@ export class ValidationRules {
(value['variableType'] === RefVariableType.FROM_PREVIOUS_STEP &&
value['refVariableStepIndex'] &&
value['refVariableStage'])))
+
if (!value['name'] && !variableValue && !value['description']) {
return { message: 'Please complete or remove this variable', isValid: false }
}
diff --git a/src/css/base.scss b/src/css/base.scss
index 48964df975..b0337b5d3d 100644
--- a/src/css/base.scss
+++ b/src/css/base.scss
@@ -3392,10 +3392,6 @@ textarea,
}
//min width
-.min-w-0 {
- min-width: 0;
-}
-
.min-w-200 {
min-width: 200px;
}
From 870be7f382ec77eb8c10cfe91a164837f0d6fc21 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Mon, 9 Dec 2024 15:27:20 +0530
Subject: [PATCH 12/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index bee7b9acc2..5f5874e319 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-5",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-11",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index a428842b7b..81c6a72cba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-5":
- version "1.2.4-beta-5"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-5.tgz#61e02e770b9756efc904fefd9739fc3706959ea7"
- integrity sha512-59SesiSFaAxP+tePpGTy505GRCbX7cVHO0cg5GM4QOJ8GGBBMdYG9e88pecsZtYpdLc4PYztBwZnCgOssB0Oiw==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-11":
+ version "1.2.4-beta-11"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-11.tgz#2354b7bd6280fd1c0fbd846e68136d76cf41941c"
+ integrity sha512-STpXqlKFLqXwOcNxptRJuWEBclGSMyN/2kZlUNOSbpfVUBCAkVA9Q5/PttC9hAKYCxL1JvinBfbDkwiVujTotQ==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From b95146aa937b4273801d32690b11094f62a64b7c Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Mon, 9 Dec 2024 19:11:05 +0530
Subject: [PATCH 13/36] feat: CI/CD Custom Task - remove value validations for
SaveAsPlugin & update validations in case of NUMBER format, code refactor
---
.../PluginDetailHeader/CreatePluginButton.tsx | 1 +
.../CIPipelineN/VariableDataTable/constants.ts | 14 ++++++--------
.../CIPipelineN/VariableDataTable/utils.tsx | 14 +++++++-------
.../app/details/triggerView/CIMaterialModal.tsx | 1 +
src/components/cdPipeline/cdpipeline.util.tsx | 4 ++--
src/components/ciPipeline/validationRules.ts | 16 +++++++++++++---
src/components/workflowEditor/types.ts | 2 +-
src/config/constants.ts | 1 +
8 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
index 4a18c48f13..1bcc17160b 100644
--- a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
+++ b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
@@ -22,6 +22,7 @@ const CreatePluginButton = () => {
validateTask(
formData[activeStageName].steps[selectedTaskIndex],
clonedFormErrorObj[activeStageName].steps[selectedTaskIndex],
+ true,
)
setFormDataErrorObj(clonedFormErrorObj)
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 972a0547f9..0da03ee178 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -83,28 +83,28 @@ export const VAL_COLUMN_DATE_OPTIONS: SelectPickerOptionType[] = [
description: 'RFC 3339 with seconds',
},
{
- label: 'YYYY-MM-DD HH:mm:ssZ',
+ label: 'YYYY-MM-DD HH:mm:ss-TZ',
value: 'YYYY-MM-DD HH:mm:ssZ',
description: 'RFC 3339 with seconds and timezone',
},
{
- label: 'YYYY-MM-DDTHH[Z]',
+ label: "YYYY-MM-DDTHH'Z'ZZZZ",
value: 'YYYY-MM-DDTHH[Z]',
description: 'ISO8601 with hour',
},
{
- label: 'YYYY-MM-DDTHH:mm[Z]',
+ label: "YYYY-MM-DDTHH:mm'Z'ZZZZ",
value: 'YYYY-MM-DDTHH:mm[Z]',
description: 'ISO8601 with minutes',
},
{
- label: 'YYYY-MM-DDTHH:mm:ss[Z]',
+ label: "YYYY-MM-DDTHH:mm:ss'Z'ZZZZ",
value: 'YYYY-MM-DDTHH:mm:ss[Z]',
description: 'ISO8601 with seconds',
},
{
- label: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
- value: 'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
+ label: "YYYY-MM-DDTHH:mm:ss.SSSSSSSSS'Z'ZZZZ",
+ value: 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSS[Z]',
description: 'ISO8601 with nanoseconds',
},
]
@@ -119,5 +119,3 @@ export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
value: 1 / 1024,
},
]
-
-export const DECIMAL_WITH_SCOPE_VARIABLES_REGEX = /^(\d+(\.\d+)?|@{{[a-zA-Z0-9-]+}})$/
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 872bd8a965..a5873085cc 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -13,13 +13,12 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'
import { ReactComponent as Var } from '@Icons/ic-var-initial.svg'
-import { BuildStageVariable } from '@Config/constants'
+import { BuildStageVariable, PATTERNS } from '@Config/constants'
import { PipelineContext } from '@Components/workflowEditor/types'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { excludeVariables, TIPPY_VAR_MSG } from '../Constants'
import {
- DECIMAL_WITH_SCOPE_VARIABLES_REGEX,
FILE_UPLOAD_SIZE_UNIT_OPTIONS,
FORMAT_COLUMN_OPTIONS,
VAL_COLUMN_BOOL_OPTIONS,
@@ -261,7 +260,7 @@ export const getValColumnRowProps = ({
}
}
-export const testValueForNumber = (value: string) => !value || DECIMAL_WITH_SCOPE_VARIABLES_REGEX.test(value)
+export const testValueForNumber = (value: string) => !value || PATTERNS.NUMBERS_WITH_SCOPE_VARIABLES.test(value)
export const checkForSystemVariable = (option: VariableDataTableSelectPickerOptionType) => {
const isSystemVariable =
@@ -276,12 +275,13 @@ export const getValColumnRowValue = (
selectedValue: VariableDataTableSelectPickerOptionType,
) => {
const isSystemVariable = checkForSystemVariable(selectedValue)
-
const isDateFormat = !isSystemVariable && value && format === VariableTypeFormat.DATE
+
if (isDateFormat && selectedValue.description) {
- const now = moment.now()
- const formattedDate = moment(now).format(selectedValue.value)
- return formattedDate.replace('Z', moment().format('Z'))
+ const now = moment()
+ const formattedDate = now.format(selectedValue.value)
+ const timezone = now.format('Z').replace(/([+/-])(\d{2})[:.](\d{2})/, '$1$2$3')
+ return formattedDate.replace('Z', timezone)
}
return value
diff --git a/src/components/app/details/triggerView/CIMaterialModal.tsx b/src/components/app/details/triggerView/CIMaterialModal.tsx
index 5e1cfe7083..a855923004 100644
--- a/src/components/app/details/triggerView/CIMaterialModal.tsx
+++ b/src/components/app/details/triggerView/CIMaterialModal.tsx
@@ -52,6 +52,7 @@ export const CIMaterialModal = ({
maxUploadSize,
appId: +props.appId,
ciPipelineId: +props.pipelineId,
+ envId: props.isJobView && props.selectedEnv ? +props.selectedEnv : null,
})
usePrompt({ shouldPrompt: isLoading })
diff --git a/src/components/cdPipeline/cdpipeline.util.tsx b/src/components/cdPipeline/cdpipeline.util.tsx
index 2a7efa6e1f..d557386387 100644
--- a/src/components/cdPipeline/cdpipeline.util.tsx
+++ b/src/components/cdPipeline/cdpipeline.util.tsx
@@ -53,7 +53,7 @@ export const ValueContainer = (props) => {
)
}
-export const validateTask = (taskData: StepType, taskErrorObj: TaskErrorObj): void => {
+export const validateTask = (taskData: StepType, taskErrorObj: TaskErrorObj, isSaveAsPlugin = false): void => {
const validationRules = new ValidationRules()
if (taskData && taskErrorObj) {
taskErrorObj.name = validationRules.requiredField(taskData.name)
@@ -68,7 +68,7 @@ export const validateTask = (taskData: StepType, taskErrorObj: TaskErrorObj): vo
taskErrorObj[currentStepTypeVariable].inputVariables = []
taskData[currentStepTypeVariable].inputVariables?.forEach((element, index) => {
taskErrorObj[currentStepTypeVariable].inputVariables.push(
- validationRules.inputVariable(element, inputVarMap),
+ validationRules.inputVariable(element, inputVarMap, isSaveAsPlugin),
)
taskErrorObj.isValid =
taskErrorObj.isValid && taskErrorObj[currentStepTypeVariable].inputVariables[index].isValid
diff --git a/src/components/ciPipeline/validationRules.ts b/src/components/ciPipeline/validationRules.ts
index 02ad278f5b..3903675cca 100644
--- a/src/components/ciPipeline/validationRules.ts
+++ b/src/components/ciPipeline/validationRules.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { RefVariableType } from '@devtron-labs/devtron-fe-common-lib'
+import { RefVariableType, VariableTypeFormat } from '@devtron-labs/devtron-fe-common-lib'
import { PATTERNS } from '../../config'
import {
CHARACTER_ERROR_MIN,
@@ -81,10 +81,14 @@ export class ValidationRules {
inputVariable = (
value: object,
availableInputVariables: Map,
+ /** disable value check when save as plugin is true */
+ isSaveAsPlugin = false,
): { message: string | null; isValid: boolean } => {
const re = new RegExp(PATTERNS.VARIABLE)
+ const numberReg = new RegExp(PATTERNS.NUMBERS_WITH_SCOPE_VARIABLES)
const variableValue =
value['allowEmptyValue'] ||
+ (!value['allowEmptyValue'] && value['isRuntimeArg']) ||
(!value['allowEmptyValue'] && value['defaultValue'] && value['defaultValue'] !== '') ||
(value['variableType'] === RefVariableType.NEW && value['value']) ||
(value['refVariableName'] &&
@@ -108,8 +112,14 @@ export class ValidationRules {
if (!re.test(value['name'])) {
return { message: `Invalid name. Only alphanumeric chars and (_) is allowed`, isValid: false }
}
- if (!variableValue) {
- return { message: 'Variable value is required', isValid: false }
+ if (!isSaveAsPlugin) {
+ if (!variableValue) {
+ return { message: 'Variable value is required', isValid: false }
+ }
+ // test for numbers and scope variables when format is "NUMBER".
+ if (value['format'] === VariableTypeFormat.NUMBER && variableValue && !numberReg.test(value['value'])) {
+ return { message: 'Variable value is not a number', isValid: false }
+ }
}
return { message: null, isValid: true }
}
diff --git a/src/components/workflowEditor/types.ts b/src/components/workflowEditor/types.ts
index 25594cfb72..417d7627dc 100644
--- a/src/components/workflowEditor/types.ts
+++ b/src/components/workflowEditor/types.ts
@@ -278,7 +278,7 @@ export interface PipelineContext {
}
formDataErrorObj: PipelineFormDataErrorType
setFormDataErrorObj: React.Dispatch>
- validateTask: (taskData: StepType, taskErrorobj: TaskErrorObj) => void
+ validateTask: (taskData: StepType, taskErrorobj: TaskErrorObj, isSaveAsPlugin?: boolean) => void
setSelectedTaskIndex: React.Dispatch>
validateStage: (
stageName: string,
diff --git a/src/config/constants.ts b/src/config/constants.ts
index 1844e17147..000d62c112 100644
--- a/src/config/constants.ts
+++ b/src/config/constants.ts
@@ -305,6 +305,7 @@ export const PATTERNS = {
CUSTOM_TAG: /^(?![.-])([a-zA-Z0-9_.-]*\{[Xx]\}[a-zA-Z0-9_.-]*)(?
Date: Mon, 9 Dec 2024 19:25:18 +0530
Subject: [PATCH 14/36] fix: VariableDataTable - number validation fix, value
update fix
---
.../VariableDataTable/VariableDataTable.component.tsx | 8 ++++----
src/components/CIPipelineN/VariableDataTable/types.ts | 5 +----
src/components/CIPipelineN/VariableDataTable/utils.tsx | 4 ----
src/components/ciPipeline/validationRules.ts | 7 ++++++-
4 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 6ee6aaee64..b181a61637 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -393,21 +393,21 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...row.data,
format: {
...row.data.format,
- value: rowAction.actionValue.value,
+ value: rowAction.actionValue,
},
val: getValColumnRowProps({
...emptyRowParams,
activeStageName,
formData,
type,
- format: rowAction.actionValue.value as VariableTypeFormat,
+ format: rowAction.actionValue,
id: rowAction.rowId as number,
}),
},
customState: {
isVariableRequired: false,
variableDescription: '',
- selectedValue: rowAction.actionValue.selectedValue,
+ selectedValue: null,
choices: [],
blockCustomValue: false,
askValueAtRuntime: false,
@@ -507,7 +507,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
} else if (headerKey === 'format' && updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FORMAT_COLUMN,
- actionValue: { value, selectedValue: extraData.selectedValue },
+ actionValue: value as VariableTypeFormat,
rowId: updatedRow.id,
})
} else {
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index bc739d390e..726c082c42 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -87,10 +87,7 @@ type VariableDataTableActionPropsMap = {
rowId: string | number
}
[VariableDataTableActionType.UPDATE_FORMAT_COLUMN]: {
- actionValue: {
- value: string
- selectedValue: VariableDataTableSelectPickerOptionType
- }
+ actionValue: VariableTypeFormat
rowId: string | number
}
[VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO]: {
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index a5873085cc..8475d51b8a 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -514,10 +514,6 @@ export const convertVariableDataTableToFormData = ({
variableDetail.refVariableName = ''
variableDetail.refVariableStage = null
}
-
- if (formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.PLUGIN_REF) {
- variableDetail.format = selectedIOVariable.format
- }
}
return variableDetail
diff --git a/src/components/ciPipeline/validationRules.ts b/src/components/ciPipeline/validationRules.ts
index 3903675cca..5bcf815055 100644
--- a/src/components/ciPipeline/validationRules.ts
+++ b/src/components/ciPipeline/validationRules.ts
@@ -117,7 +117,12 @@ export class ValidationRules {
return { message: 'Variable value is required', isValid: false }
}
// test for numbers and scope variables when format is "NUMBER".
- if (value['format'] === VariableTypeFormat.NUMBER && variableValue && !numberReg.test(value['value'])) {
+ if (
+ value['format'] === VariableTypeFormat.NUMBER &&
+ variableValue &&
+ !!value['value'] &&
+ !numberReg.test(value['value'])
+ ) {
return { message: 'Variable value is not a number', isValid: false }
}
}
From a0c90acb350b173681b100b2e6c10bda8b1b43f1 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Tue, 10 Dec 2024 00:35:33 +0530
Subject: [PATCH 15/36] fix: VariableDataTable - payload conversion incorrect
data fix
---
.../CIPipelineN/VariableDataTable/utils.tsx | 99 ++++++++++---------
1 file changed, 54 insertions(+), 45 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 8475d51b8a..6374286d57 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -458,61 +458,70 @@ export const convertVariableDataTableToFormData = ({
const variableDetail: VariableType = {
...selectedIOVariable,
+ value: data.val.value,
format: data.format.value as VariableTypeFormat,
name: data.variable.value,
- description: variableDescription,
- allowEmptyValue: !isVariableRequired,
- isRuntimeArg: askValueAtRuntime,
+ description: type === PluginVariableType.INPUT ? variableDescription : data.val.value,
+ variableType: selectedIOVariable?.variableType ?? RefVariableType.NEW,
}
- if (choices.length) {
- variableDetail.valueConstraint = {
- ...variableDetail.valueConstraint,
- choices: choices.map(({ value }) => value),
- blockCustomValue,
- }
- }
+ if (type === PluginVariableType.INPUT) {
+ variableDetail.allowEmptyValue = !isVariableRequired
+ variableDetail.isRuntimeArg = askValueAtRuntime
- if (fileInfo) {
- variableDetail.value = data.val.value
- variableDetail.fileReferenceId = fileInfo.id
- variableDetail.fileMountDir = fileInfo.mountDir.value
- variableDetail.valueConstraint = {
- ...variableDetail.valueConstraint,
- constraint: {
- fileProperty: getUploadFileConstraints({
- allowedExtensions: fileInfo.allowedExtensions,
- maxUploadSize: fileInfo.maxUploadSize,
- unit: fileInfo.unit.label as string,
- }),
- },
+ if (
+ (variableDetail.format === VariableTypeFormat.STRING ||
+ variableDetail.format === VariableTypeFormat.NUMBER) &&
+ choices.length
+ ) {
+ variableDetail.valueConstraint = {
+ ...variableDetail.valueConstraint,
+ choices: choices.map(({ value }) => value),
+ blockCustomValue,
+ }
}
- }
- if (selectedValue) {
- if (selectedValue.refVariableStepIndex) {
- variableDetail.value = ''
- variableDetail.variableType = RefVariableType.FROM_PREVIOUS_STEP
- variableDetail.refVariableStepIndex = selectedValue.refVariableStepIndex
- variableDetail.refVariableName = selectedValue.label as string
- variableDetail.format = selectedValue.format
- variableDetail.refVariableStage = selectedValue.refVariableStage
- } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
- variableDetail.variableType = RefVariableType.GLOBAL
- variableDetail.refVariableStepIndex = 0
- variableDetail.refVariableName = selectedValue.label as string
- variableDetail.format = selectedValue.format
- variableDetail.value = ''
- variableDetail.refVariableStage = null
- } else {
+ if (variableDetail.format === VariableTypeFormat.FILE && fileInfo) {
variableDetail.variableType = RefVariableType.NEW
- if (data.format.value === VariableTypeFormat.DATE) {
- variableDetail.value = data.val.value
- } else {
- variableDetail.value = selectedValue.label as string
- }
variableDetail.refVariableName = ''
variableDetail.refVariableStage = null
+ variableDetail.fileReferenceId = fileInfo.id
+ variableDetail.fileMountDir = fileInfo.mountDir.value
+ variableDetail.valueConstraint = {
+ ...variableDetail.valueConstraint,
+ constraint: {
+ fileProperty: getUploadFileConstraints({
+ allowedExtensions: fileInfo.allowedExtensions,
+ maxUploadSize: fileInfo.maxUploadSize,
+ unit: fileInfo.unit.label as string,
+ }),
+ },
+ }
+ }
+
+ if (selectedValue) {
+ if (selectedValue.refVariableStepIndex) {
+ variableDetail.value = ''
+ variableDetail.variableType = RefVariableType.FROM_PREVIOUS_STEP
+ variableDetail.refVariableStepIndex = selectedValue.refVariableStepIndex
+ variableDetail.refVariableName = selectedValue.label as string
+ variableDetail.format = selectedValue.format
+ variableDetail.refVariableStage = selectedValue.refVariableStage
+ } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
+ variableDetail.value = ''
+ variableDetail.variableType = RefVariableType.GLOBAL
+ variableDetail.refVariableStepIndex = 0
+ variableDetail.refVariableName = selectedValue.label as string
+ variableDetail.format = selectedValue.format
+ variableDetail.refVariableStage = null
+ } else {
+ if (variableDetail.format !== VariableTypeFormat.DATE) {
+ variableDetail.value = selectedValue.label as string
+ }
+ variableDetail.variableType = RefVariableType.NEW
+ variableDetail.refVariableName = ''
+ variableDetail.refVariableStage = null
+ }
}
}
From 1a9df27e4f6c3a4d294d58b0c6137ab06870aa8c Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Tue, 10 Dec 2024 11:21:37 +0530
Subject: [PATCH 16/36] refactor: VariableDataTable code refactor, replace
PopupMenu with TippyCustomized for overlay
---
.../VariableDataTable.component.tsx | 10 ++-
.../VariableDataTablePopupMenu.tsx | 79 ++++++++-----------
.../CIPipelineN/VariableDataTable/utils.tsx | 34 +++++---
3 files changed, 64 insertions(+), 59 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index b181a61637..71644b5b6d 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -150,7 +150,12 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
),
},
}
- : data.val,
+ : getValColumnRowProps({
+ ...emptyRowParams,
+ valueConstraint: {
+ choices: choicesOptions.map(({ label }) => label),
+ },
+ }),
},
}
}
@@ -397,9 +402,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
},
val: getValColumnRowProps({
...emptyRowParams,
- activeStageName,
- formData,
- type,
format: rowAction.actionValue,
id: rowAction.rowId as number,
}),
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
index a303760f53..a7f118f204 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -1,14 +1,7 @@
import { useState } from 'react'
-import {
- Button,
- ButtonStyleType,
- ButtonVariantType,
- ComponentSizeType,
- PopupMenu,
-} from '@devtron-labs/devtron-fe-common-lib'
+import { TippyCustomized, TippyTheme } from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
import { ReactComponent as ICSlidersVertical } from '@Icons/ic-sliders-vertical.svg'
import { VariableDataTablePopupMenuProps } from './types'
@@ -18,7 +11,7 @@ export const VariableDataTablePopupMenu = ({
heading,
children,
onClose,
- disableClose,
+ disableClose = false,
}: VariableDataTablePopupMenuProps) => {
// STATES
const [visible, setVisible] = useState(false)
@@ -31,45 +24,41 @@ export const VariableDataTablePopupMenu = ({
}
}
- const handleAction = (open: boolean) => {
- if (visible !== open) {
- if (open) {
- setVisible(true)
- } else {
- handleClose()
- }
- }
+ const handleOpen = () => {
+ setVisible(true)
}
return (
-
-
-
-
-
- {visible && (
-
-
-
- {showIcon &&
}
-
{heading}
-
-
}
- dataTestId="popup-close-button"
- ariaLabel="Close Popup"
- showAriaLabelInTippy={false}
- onClick={handleClose}
- />
-
- {children}
-
- )}
-
+ <>
+ {heading}
}
+ Icon={showIcon ? ICSlidersVertical : null}
+ iconSize={16}
+ additionalContent={{children}
}
+ >
+
+
{visible && }
-
+ >
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 6374286d57..abdd956c7c 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -225,21 +225,35 @@ export const getValColumnRowProps = ({
}
}
+ const optionsForValColumn = getOptionsForValColumn({
+ activeStageName,
+ formData,
+ globalVariables,
+ isCdPipeline,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ format,
+ valueConstraint,
+ })
+
+ const isOptionsEmpty = optionsForValColumn.every(({ options }) => !options.length)
+
+ if (isOptionsEmpty) {
+ return {
+ type: DynamicDataTableRowDataType.TEXT,
+ value,
+ props: {
+ placeholder: 'Enter value or variable',
+ },
+ }
+ }
+
return {
type: DynamicDataTableRowDataType.SELECT_TEXT,
value: variableType === RefVariableType.NEW ? value : refVariableName || '',
props: {
placeholder: 'Enter value or variable',
- options: getOptionsForValColumn({
- activeStageName,
- formData,
- globalVariables,
- isCdPipeline,
- selectedTaskIndex,
- inputVariablesListFromPrevStep,
- format,
- valueConstraint,
- }),
+ options: optionsForValColumn,
selectPickerProps: {
isCreatable:
format !== VariableTypeFormat.BOOL &&
From d8d694608d356ae4321f1995bd4822febf7fec84 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Tue, 10 Dec 2024 13:04:32 +0530
Subject: [PATCH 17/36] refactor: VariableDataTable - common code moved to
common-lib, feat: add global variables to runtime params
---
src/components/CIPipelineN/CIPipeline.tsx | 27 ++--
.../VariableDataTable.component.tsx | 4 +-
.../VariableDataTable/constants.ts | 58 +--------
.../CIPipelineN/VariableDataTable/utils.tsx | 116 ++++++++----------
.../app/details/triggerView/cdMaterial.tsx | 3 +-
src/components/cdPipeline/CDPipeline.tsx | 17 +--
.../ciPipeline/ciPipeline.service.ts | 11 --
.../GitInfoMaterialCard/GitInfoMaterial.tsx | 1 +
src/config/constants.ts | 1 -
9 files changed, 74 insertions(+), 164 deletions(-)
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index 3e1c50fd73..7a3fa84066 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -47,6 +47,7 @@ import {
ProcessPluginDataParamsType,
ResourceKindType,
uploadCIPipelineFile,
+ getGlobalVariables,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
@@ -59,7 +60,6 @@ import {
import { BuildStageVariable, BuildTabText, JobPipelineTabText, TriggerType, URLS, ViewType } from '../../config'
import {
deleteCIPipeline,
- getGlobalVariable,
getInitData,
getInitDataWithCIPipeline,
saveCIPipeline,
@@ -121,7 +121,7 @@ export default function CIPipeline({
const [apiInProgress, setApiInProgress] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [selectedTaskIndex, setSelectedTaskIndex] = useState(0)
- const [globalVariables, setGlobalVariables] = useState<{ label: string; value: string; format: string }[]>([])
+ const [globalVariables, setGlobalVariables] = useState([])
const [inputVariablesListFromPrevStep, setInputVariablesListFromPrevStep] = useState<{
preBuildStage: Map[]
postBuildStage: Map[]
@@ -346,23 +346,12 @@ export default function CIPipeline({
}
}
- const getGlobalVariables = async (): Promise => {
+ const callGlobalVariables = async () => {
try {
- const globalVariablesResponse = await getGlobalVariable(Number(appId))
- const globalVariablesResult = globalVariablesResponse?.result ?? []
- const _globalVariableOptions = globalVariablesResult.map((variable) => {
- variable.label = variable.name
- variable.value = variable.name
- variable.description = variable.description || ''
- variable.variableType = RefVariableType.GLOBAL
- delete variable.name
- return variable
- })
- setGlobalVariables(_globalVariableOptions || [])
- } catch (error) {
- if (error instanceof ServerErrors && error.code !== 403) {
- showError(error)
- }
+ const globalVariableOptions = await getGlobalVariables({ appId: Number(appId) })
+ setGlobalVariables(globalVariableOptions)
+ } catch {
+ // HANDLED IN SERVICE
}
}
@@ -499,7 +488,7 @@ export default function CIPipeline({
await getEnvironments(0)
}
}
- await getGlobalVariables()
+ await callGlobalVariables()
setPageState(ViewType.FORM)
} catch (error) {
setPageState(ViewType.ERROR)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 71644b5b6d..acca5bc4aa 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -6,6 +6,7 @@ import {
DynamicDataTableRowDataType,
PluginType,
RefVariableType,
+ SystemVariableIcon,
VariableType,
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
@@ -30,7 +31,6 @@ import {
checkForSystemVariable,
convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
- getSystemVariableIcon,
getUploadFileConstraints,
getValColumnRowProps,
getValColumnRowValue,
@@ -371,7 +371,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
),
props: {
...row.data.val.props,
- Icon: value && isSystemVariable ? getSystemVariableIcon() : null,
+ Icon: value && isSystemVariable ? : null,
},
},
},
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 0da03ee178..4180b52ba3 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -40,75 +40,27 @@ export const FORMAT_OPTIONS_MAP = {
export const FORMAT_COLUMN_OPTIONS: SelectPickerOptionType[] = [
{
- label: 'String',
+ label: FORMAT_OPTIONS_MAP.STRING,
value: VariableTypeFormat.STRING,
},
{
- label: 'Number',
+ label: FORMAT_OPTIONS_MAP.NUMBER,
value: VariableTypeFormat.NUMBER,
},
{
- label: 'Boolean',
+ label: FORMAT_OPTIONS_MAP.BOOL,
value: VariableTypeFormat.BOOL,
},
{
- label: 'Date',
+ label: FORMAT_OPTIONS_MAP.DATE,
value: VariableTypeFormat.DATE,
},
{
- label: 'File',
+ label: FORMAT_OPTIONS_MAP.FILE,
value: VariableTypeFormat.FILE,
},
]
-export const VAL_COLUMN_BOOL_OPTIONS: SelectPickerOptionType[] = [
- { label: 'TRUE', value: 'TRUE' },
- { label: 'FALSE', value: 'FALSE' },
-]
-
-export const VAL_COLUMN_DATE_OPTIONS: SelectPickerOptionType[] = [
- {
- label: 'YYYY-MM-DD',
- value: 'YYYY-MM-DD',
- description: 'RFC 3339',
- },
- {
- label: 'YYYY-MM-DD HH:mm',
- value: 'YYYY-MM-DD HH:mm',
- description: 'RFC 3339 with minutes',
- },
- {
- label: 'YYYY-MM-DD HH:mm:ss',
- value: 'YYYY-MM-DD HH:mm:ss',
- description: 'RFC 3339 with seconds',
- },
- {
- label: 'YYYY-MM-DD HH:mm:ss-TZ',
- value: 'YYYY-MM-DD HH:mm:ssZ',
- description: 'RFC 3339 with seconds and timezone',
- },
- {
- label: "YYYY-MM-DDTHH'Z'ZZZZ",
- value: 'YYYY-MM-DDTHH[Z]',
- description: 'ISO8601 with hour',
- },
- {
- label: "YYYY-MM-DDTHH:mm'Z'ZZZZ",
- value: 'YYYY-MM-DDTHH:mm[Z]',
- description: 'ISO8601 with minutes',
- },
- {
- label: "YYYY-MM-DDTHH:mm:ss'Z'ZZZZ",
- value: 'YYYY-MM-DDTHH:mm:ss[Z]',
- description: 'ISO8601 with seconds',
- },
- {
- label: "YYYY-MM-DDTHH:mm:ss.SSSSSSSSS'Z'ZZZZ",
- value: 'YYYY-MM-DDTHH:mm:ss.SSSSSSSSS[Z]',
- description: 'ISO8601 with nanoseconds',
- },
-]
-
export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
{
label: 'KB',
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index abdd956c7c..fd3b50ad11 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -1,30 +1,24 @@
-import moment from 'moment'
-
import {
ConditionType,
DynamicDataTableRowDataType,
+ getGoLangFormattedDateWithTimezone,
+ IO_VARIABLES_VALUE_COLUMN_BOOL_OPTIONS,
+ IO_VARIABLES_VALUE_COLUMN_DATE_OPTIONS,
PluginType,
RefVariableStageType,
RefVariableType,
SelectPickerOptionType,
+ SystemVariableIcon,
VariableType,
VariableTypeFormat,
- Tooltip,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Var } from '@Icons/ic-var-initial.svg'
import { BuildStageVariable, PATTERNS } from '@Config/constants'
import { PipelineContext } from '@Components/workflowEditor/types'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import { excludeVariables, TIPPY_VAR_MSG } from '../Constants'
-import {
- FILE_UPLOAD_SIZE_UNIT_OPTIONS,
- FORMAT_COLUMN_OPTIONS,
- VAL_COLUMN_BOOL_OPTIONS,
- VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
- VAL_COLUMN_DATE_OPTIONS,
-} from './constants'
+import { excludeVariables } from '../Constants'
+import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_COLUMN_OPTIONS, VAL_COLUMN_CHOICES_DROPDOWN_LABEL } from './constants'
import {
GetValColumnRowPropsType,
GetVariableDataTableInitialRowsProps,
@@ -51,18 +45,22 @@ export const getOptionsForValColumn = ({
| 'isCdPipeline'
> &
Pick) => {
+ const isBuildStagePostBuild = activeStageName === BuildStageVariable.PostBuild
+
const previousStepVariables = []
+ const preBuildStageVariables = []
+
const defaultValues = (valueConstraint?.choices || []).map>((value) => ({
label: value,
value,
}))
if (format === VariableTypeFormat.BOOL) {
- defaultValues.push(...VAL_COLUMN_BOOL_OPTIONS)
+ defaultValues.push(...IO_VARIABLES_VALUE_COLUMN_BOOL_OPTIONS)
}
if (format === VariableTypeFormat.DATE) {
- defaultValues.push(...VAL_COLUMN_DATE_OPTIONS)
+ defaultValues.push(...IO_VARIABLES_VALUE_COLUMN_DATE_OPTIONS)
}
if (format)
@@ -77,8 +75,7 @@ export const getOptionsForValColumn = ({
})
}
- if (activeStageName === BuildStageVariable.PostBuild) {
- const preBuildStageVariables = []
+ if (isBuildStagePostBuild) {
const preBuildTaskLength = formData[BuildStageVariable.PreBuild]?.steps?.length
if (preBuildTaskLength >= 1 && !isCdPipeline) {
if (inputVariablesListFromPrevStep[BuildStageVariable.PreBuild].length > 0) {
@@ -116,25 +113,17 @@ export const getOptionsForValColumn = ({
}
}
}
+ }
- return [
- {
- label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
- options: defaultValues,
- },
- {
- label: 'From Pre-build Stage',
- options: preBuildStageVariables,
- },
- {
- label: 'From Post-build Stage',
- options: previousStepVariables,
- },
- {
- label: 'System variables',
- options: globalVariables,
- },
- ]
+ const isOptionsEmpty =
+ !defaultValues.length &&
+ (isBuildStagePostBuild
+ ? !preBuildStageVariables.length && !previousStepVariables.length
+ : !previousStepVariables.length) &&
+ !globalVariables.length
+
+ if (isOptionsEmpty) {
+ return []
}
return [
@@ -142,28 +131,36 @@ export const getOptionsForValColumn = ({
label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
options: defaultValues,
},
- {
- label: 'From Previous Steps',
- options: previousStepVariables,
- },
+ ...(isBuildStagePostBuild
+ ? [
+ {
+ label: 'From Pre-build Stage',
+ options: preBuildStageVariables,
+ },
+ {
+ label: 'From Post-build Stage',
+ options: previousStepVariables,
+ },
+ ]
+ : [
+ {
+ label: 'From Previous Steps',
+ options: previousStepVariables,
+ },
+ ]),
{
label: 'System variables',
- options: globalVariables.filter(
- (variable) =>
- (isCdPipeline && variable.stageType !== 'post-cd') || !excludeVariables.includes(variable.value),
- ),
+ options: isBuildStagePostBuild
+ ? globalVariables
+ : globalVariables.filter(
+ (variable) =>
+ (isCdPipeline && variable.stageType !== 'post-cd') ||
+ !excludeVariables.includes(variable.value),
+ ),
},
]
}
-export const getSystemVariableIcon = () => (
-
-
-
-
-
-)
-
export const getVariableColumnRowProps = () => {
const data: VariableDataRowType['data']['variable'] = {
value: '',
@@ -236,9 +233,7 @@ export const getValColumnRowProps = ({
valueConstraint,
})
- const isOptionsEmpty = optionsForValColumn.every(({ options }) => !options.length)
-
- if (isOptionsEmpty) {
+ if (!optionsForValColumn.length) {
return {
type: DynamicDataTableRowDataType.TEXT,
value,
@@ -260,9 +255,9 @@ export const getValColumnRowProps = ({
(!valueConstraint?.choices?.length || !valueConstraint.blockCustomValue),
},
Icon:
- refVariableStage || (variableType && variableType !== RefVariableType.NEW)
- ? getSystemVariableIcon()
- : null,
+ refVariableStage || (variableType && variableType !== RefVariableType.NEW) ? (
+
+ ) : null,
},
}
}
@@ -291,14 +286,7 @@ export const getValColumnRowValue = (
const isSystemVariable = checkForSystemVariable(selectedValue)
const isDateFormat = !isSystemVariable && value && format === VariableTypeFormat.DATE
- if (isDateFormat && selectedValue.description) {
- const now = moment()
- const formattedDate = now.format(selectedValue.value)
- const timezone = now.format('Z').replace(/([+/-])(\d{2})[:.](\d{2})/, '$1$2$3')
- return formattedDate.replace('Z', timezone)
- }
-
- return value
+ return isDateFormat ? getGoLangFormattedDateWithTimezone(selectedValue.value) : value
}
export const getEmptyVariableDataTableRow = (params: GetValColumnRowPropsType): VariableDataRowType => {
diff --git a/src/components/app/details/triggerView/cdMaterial.tsx b/src/components/app/details/triggerView/cdMaterial.tsx
index 5ecb36deeb..434c803bb0 100644
--- a/src/components/app/details/triggerView/cdMaterial.tsx
+++ b/src/components/app/details/triggerView/cdMaterial.tsx
@@ -1547,11 +1547,12 @@ const CDMaterial = ({
>
) : (
)}
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index e5ad88411b..5178b21cac 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -50,6 +50,7 @@ import {
ResourceKindType,
getEnvironmentListMinPublic,
uploadCDPipelineFile,
+ getGlobalVariables,
} from '@devtron-labs/devtron-fe-common-lib'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Redirect, Route, Switch, useParams, useRouteMatch } from 'react-router-dom'
@@ -76,7 +77,6 @@ import {
import { Sidebar } from '../CIPipelineN/Sidebar'
import DeleteCDNode from './DeleteCDNode'
import { PreBuild } from '../CIPipelineN/PreBuild'
-import { getGlobalVariable } from '../ciPipeline/ciPipeline.service'
import { ValidationRules } from '../ciPipeline/validationRules'
import './cdPipeline.scss'
import {
@@ -356,10 +356,10 @@ export default function CDPipeline({
const getInit = () => {
Promise.all([
getDeploymentStrategyList(appId),
- getGlobalVariable(Number(appId), true),
+ getGlobalVariables({ appId: Number(appId), isCD: true }),
getDockerRegistryMinAuth(appId, true),
])
- .then(([pipelineStrategyResponse, envResponse, dockerResponse]) => {
+ .then(([pipelineStrategyResponse, globalVariablesOptions, dockerResponse]) => {
const strategies = pipelineStrategyResponse.result.pipelineStrategy || []
const dockerRegistries = dockerResponse.result || []
const _allStrategies = {}
@@ -385,16 +385,7 @@ export default function CDPipeline({
}
}
- const _globalVariableOptions = envResponse.result?.map((variable) => {
- variable.label = variable.name
- variable.value = variable.name
- variable.description = variable.description || ''
- variable.variableType = RefVariableType.GLOBAL
- delete variable.name
- return variable
- })
-
- setGlobalVariables(_globalVariableOptions || [])
+ setGlobalVariables(globalVariablesOptions)
setDockerRegistries(dockerRegistries)
})
.catch((error: ServerErrors) => {
diff --git a/src/components/ciPipeline/ciPipeline.service.ts b/src/components/ciPipeline/ciPipeline.service.ts
index e16350fc51..6008a6f949 100644
--- a/src/components/ciPipeline/ciPipeline.service.ts
+++ b/src/components/ciPipeline/ciPipeline.service.ts
@@ -24,8 +24,6 @@ import {
RefVariableType,
PipelineBuildStageType,
VariableTypeFormat,
- getIsRequestAborted,
- showError,
} from '@devtron-labs/devtron-fe-common-lib'
import { Routes, SourceTypeMap, TriggerType, ViewType } from '../../config'
import { getSourceConfig, getWebhookDataMetaConfig } from '../../services/service'
@@ -598,12 +596,3 @@ function createCurlRequest(externalCiConfig): string {
const curl = `curl -X POST -H 'Content-type: application/json' --data '${json}' ${url}/${externalCiConfig.accessKey}`
return curl
}
-
-export async function getGlobalVariable(appId: number, isCD?: boolean): Promise {
- let variableList = []
- await get(`${Routes.GLOBAL_VARIABLES}?appId=${appId}`).then((response) => {
- variableList = response.result?.filter((item) => (isCD ? item.stageType !== 'ci' : item.stageType === 'ci'))
- })
-
- return { result: variableList }
-}
diff --git a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
index e7706d960d..d308bd632d 100644
--- a/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
+++ b/src/components/common/helpers/GitInfoMaterialCard/GitInfoMaterial.tsx
@@ -371,6 +371,7 @@ export const GitInfoMaterial = ({
Date: Tue, 10 Dec 2024 16:28:44 +0530
Subject: [PATCH 18/36] feat: VariableDataTable - choices validations update,
filter global variables based on format
---
.../VariableDataTable/ValueConfigOverlay.tsx | 40 ++++---
.../VariableConfigOverlay.tsx | 2 +-
.../VariableDataTable.component.tsx | 109 +++++++++++++-----
.../VariableDataTable/constants.ts | 8 +-
.../CIPipelineN/VariableDataTable/types.ts | 3 +-
.../CIPipelineN/VariableDataTable/utils.tsx | 64 +++++-----
src/css/base.scss | 6 +
7 files changed, 157 insertions(+), 75 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
index 09d1663ea5..68dc58b6cb 100644
--- a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
@@ -45,16 +45,19 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
const handleChoiceChange = (choiceId: number) => (e: ChangeEvent) => {
const choiceValue = e.target.value
- if (isFormatNumber && !testValueForNumber(choiceValue)) {
- return
- }
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_CHOICES,
rowId,
actionValue: (currentChoices) =>
currentChoices.map((choice) =>
- choice.id === choiceId ? { id: choiceId, value: choiceValue } : choice,
+ choice.id === choiceId
+ ? {
+ id: choiceId,
+ value: choiceValue,
+ error: isFormatNumber && !testValueForNumber(choiceValue) ? 'Choice is not a number' : '',
+ }
+ : choice,
),
})
}
@@ -215,7 +218,7 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
/>
- {choices.map(({ id, value }) => (
+ {choices.map(({ id, value, error }) => (
-
}
- variant={ButtonVariantType.borderLess}
- size={ComponentSizeType.medium}
- onClick={handleChoiceDelete(id)}
- style={ButtonStyleType.negativeGrey}
- />
+
+ }
+ variant={ButtonVariantType.borderLess}
+ size={ComponentSizeType.medium}
+ onClick={handleChoiceDelete(id)}
+ style={ButtonStyleType.negativeGrey}
+ />
+
))}
@@ -282,7 +288,7 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
className="w-200"
placement="bottom-start"
content={
-
+
Allow custom input
Allow entering any value other than provided choices
@@ -304,7 +310,7 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
className="w-200"
placement="bottom-start"
content={
-
+
Ask value at runtime
Value can be provided at runtime. Entered value will be pre-filled as default
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
index 8527ff22a3..62a8d9c5a8 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
@@ -69,7 +69,7 @@ export const VariableConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOver
className="w-200"
placement="bottom-start"
content={
-
+
Value is required
Get this tooltip from Utkarsh
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index acca5bc4aa..550d33ee05 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -1,4 +1,5 @@
import { useContext, useState, useEffect, useRef, useMemo } from 'react'
+import Tippy from '@tippyjs/react'
import {
DynamicDataTable,
@@ -11,14 +12,11 @@ import {
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
+import { ReactComponent as Info } from '@Icons/info-filled.svg'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import {
- FILE_UPLOAD_SIZE_UNIT_OPTIONS,
- getVariableDataTableHeaders,
- VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
-} from './constants'
+import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, getVariableDataTableHeaders } from './constants'
import {
HandleRowUpdateActionProps,
VariableDataCustomState,
@@ -31,6 +29,7 @@ import {
checkForSystemVariable,
convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
+ getOptionsForValColumn,
getUploadFileConstraints,
getValColumnRowProps,
getValColumnRowValue,
@@ -125,11 +124,13 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
updatedRows = updatedRows.map((row) => {
const { id, data, customState } = row
- const choicesOptions = customState.choices
- .filter(({ value }) => !!value)
- .map(({ value }) => ({ label: value, value }))
+ // FILTERING EMPTY CHOICE VALUES
+ const choicesOptions = customState.choices.filter(({ value }) => !!value)
+
+ // RESETTING TO DEFAULT STATE IF CHOICES ARE EMPTY
+ const blockCustomValue = choicesOptions.length ? row.customState.blockCustomValue : false
- if (id === rowAction.rowId && choicesOptions.length > 0) {
+ if (id === rowAction.rowId) {
return {
...row,
data: {
@@ -140,23 +141,36 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...data.val,
props: {
...data.val.props,
- options: data.val.props.options.map((option) =>
- option.label === VAL_COLUMN_CHOICES_DROPDOWN_LABEL
- ? {
- label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
- options: choicesOptions,
- }
- : option,
- ),
+ options: getOptionsForValColumn({
+ activeStageName,
+ format: row.data.format.value as VariableTypeFormat,
+ formData,
+ globalVariables,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ isCdPipeline,
+ valueConstraint: {
+ blockCustomValue,
+ choices: choicesOptions.map(({ value }) => value),
+ },
+ }),
},
}
: getValColumnRowProps({
...emptyRowParams,
+ value: data.val.value,
+ format: data.format.value as VariableTypeFormat,
valueConstraint: {
- choices: choicesOptions.map(({ label }) => label),
+ blockCustomValue,
+ choices: choicesOptions.map(({ value }) => value),
},
}),
},
+ customState: {
+ ...row.customState,
+ blockCustomValue,
+ choices: choicesOptions,
+ },
}
}
@@ -191,11 +205,26 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...row.data.val,
props: {
...row.data.val.props,
+ options: getOptionsForValColumn({
+ activeStageName,
+ format: row.data.format.value as VariableTypeFormat,
+ formData,
+ globalVariables,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ isCdPipeline,
+ valueConstraint: {
+ blockCustomValue: rowAction.actionValue,
+ choices: row.customState.choices.map(
+ ({ value }) => value,
+ ),
+ },
+ }),
selectPickerProps: {
isCreatable:
row.data.format.value !== VariableTypeFormat.BOOL &&
row.data.format.value !== VariableTypeFormat.DATE &&
- !row.customState?.blockCustomValue,
+ !rowAction.actionValue,
},
},
},
@@ -544,21 +573,49 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
heading={row.data.variable.value || 'Value configuration'}
onClose={onActionButtonPopupClose(row.id)}
disableClose={
- row.data.format.value === VariableTypeFormat.FILE && !!row.customState.fileInfo.mountDir.error
+ (row.data.format.value === VariableTypeFormat.FILE && !!row.customState.fileInfo.mountDir.error) ||
+ (row.data.format.value === VariableTypeFormat.NUMBER &&
+ row.customState.choices.some(({ error }) => !!error))
}
>
)
- const variableTrailingCellIcon = (row: VariableDataRowType) => (
-
-
-
- )
+ const variableTrailingCellIcon = (row: VariableDataRowType) =>
+ isCustomTask && type === PluginVariableType.INPUT ? (
+
+
+
+ ) : null
+
+ const valTrailingCellIcon = (row: VariableDataRowType) =>
+ row.data.format.value === VariableTypeFormat.FILE ? (
+
+ File mount path
+
+ {row.customState.fileInfo.mountDir.value}
+
+
+ Ensure the uploaded file name is unique to avoid conflicts or overrides.
+
+
+ }
+ >
+
+
+
+
+ ) : null
const trailingCellIcon: DynamicDataTableProps
['trailingCellIcon'] = {
- variable: isCustomTask && type === PluginVariableType.INPUT ? variableTrailingCellIcon : null,
+ variable: variableTrailingCellIcon,
+ val: valTrailingCellIcon,
}
return (
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 4180b52ba3..67e854b055 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -28,7 +28,13 @@ export const getVariableDataTableHeaders = (
},
]
-export const VAL_COLUMN_CHOICES_DROPDOWN_LABEL = 'Default values'
+export const VAL_COLUMN_DROPDOWN_LABEL = {
+ CHOICES: 'Default values',
+ SYSTEM_VARIABLES: 'System variables',
+ PRE_BUILD_STAGE: 'From Pre-build Stage',
+ POST_BUILD_STAGE: 'From Post-build Stage',
+ PREVIOUS_STEPS: 'From Previous Steps',
+}
export const FORMAT_OPTIONS_MAP = {
[VariableTypeFormat.STRING]: 'String',
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index 726c082c42..3588af0fd4 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -26,10 +26,9 @@ export type VariableDataKeys = 'variable' | 'format' | 'val'
export type VariableDataCustomState = {
variableDescription: string
isVariableRequired: boolean
- choices: { id: number; value: string }[]
+ choices: { id: number; value: string; error: string }[]
askValueAtRuntime: boolean
blockCustomValue: boolean
- // Check for support in the TableRowTypes
selectedValue: VariableDataTableSelectPickerOptionType & Record
fileInfo: {
id: number
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index fd3b50ad11..6189ec3097 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -18,7 +18,7 @@ import { PipelineContext } from '@Components/workflowEditor/types'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { excludeVariables } from '../Constants'
-import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_COLUMN_OPTIONS, VAL_COLUMN_CHOICES_DROPDOWN_LABEL } from './constants'
+import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_COLUMN_OPTIONS, VAL_COLUMN_DROPDOWN_LABEL } from './constants'
import {
GetValColumnRowPropsType,
GetVariableDataTableInitialRowsProps,
@@ -115,12 +115,14 @@ export const getOptionsForValColumn = ({
}
}
+ const filteredGlobalVariablesBasedOnFormat = globalVariables.filter((variable) => variable.format === format)
+
const isOptionsEmpty =
!defaultValues.length &&
(isBuildStagePostBuild
? !preBuildStageVariables.length && !previousStepVariables.length
: !previousStepVariables.length) &&
- !globalVariables.length
+ !filteredGlobalVariablesBasedOnFormat.length
if (isOptionsEmpty) {
return []
@@ -128,36 +130,40 @@ export const getOptionsForValColumn = ({
return [
{
- label: VAL_COLUMN_CHOICES_DROPDOWN_LABEL,
+ label: VAL_COLUMN_DROPDOWN_LABEL.CHOICES,
options: defaultValues,
},
- ...(isBuildStagePostBuild
+ ...(!valueConstraint?.blockCustomValue
? [
+ ...(isBuildStagePostBuild
+ ? [
+ {
+ label: VAL_COLUMN_DROPDOWN_LABEL.PRE_BUILD_STAGE,
+ options: preBuildStageVariables,
+ },
+ {
+ label: VAL_COLUMN_DROPDOWN_LABEL.POST_BUILD_STAGE,
+ options: previousStepVariables,
+ },
+ ]
+ : [
+ {
+ label: VAL_COLUMN_DROPDOWN_LABEL.PREVIOUS_STEPS,
+ options: previousStepVariables,
+ },
+ ]),
{
- label: 'From Pre-build Stage',
- options: preBuildStageVariables,
- },
- {
- label: 'From Post-build Stage',
- options: previousStepVariables,
+ label: VAL_COLUMN_DROPDOWN_LABEL.SYSTEM_VARIABLES,
+ options: isBuildStagePostBuild
+ ? filteredGlobalVariablesBasedOnFormat
+ : filteredGlobalVariablesBasedOnFormat.filter(
+ (variable) =>
+ (isCdPipeline && variable.stageType !== 'post-cd') ||
+ !excludeVariables.includes(variable.value),
+ ),
},
]
- : [
- {
- label: 'From Previous Steps',
- options: previousStepVariables,
- },
- ]),
- {
- label: 'System variables',
- options: isBuildStagePostBuild
- ? globalVariables
- : globalVariables.filter(
- (variable) =>
- (isCdPipeline && variable.stageType !== 'post-cd') ||
- !excludeVariables.includes(variable.value),
- ),
- },
+ : []),
]
}
@@ -351,8 +357,9 @@ export const getVariableDataTableInitialRows = ({
value: name,
required: isInputVariableRequired,
disabled: !isCustomTask,
- showTooltip: !isCustomTask && !!description,
- tooltipText: description,
+ tooltip: {
+ content: !isCustomTask && description,
+ },
},
format: getFormatColumnRowProps({ format, isCustomTask }),
val: getValColumnRowProps({
@@ -373,6 +380,7 @@ export const getVariableDataTableInitialRows = ({
choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
id: index,
value: choiceValue,
+ error: '',
})),
askValueAtRuntime: isRuntimeArg ?? false,
blockCustomValue: valueConstraint?.blockCustomValue ?? false,
diff --git a/src/css/base.scss b/src/css/base.scss
index b0337b5d3d..7650eb410b 100644
--- a/src/css/base.scss
+++ b/src/css/base.scss
@@ -1531,6 +1531,12 @@ button.anchor {
line-height: 1.6;
border: 1px solid rgba(255, 255, 255, 0.4);
background-color: var(--N900);
+
+ &.no-content-padding {
+ .tippy-content {
+ padding: 0;
+ }
+ }
}
.tippy-box.default-white {
From 4889c59fa649665ab69fe5a81b55ca97ac881851 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Tue, 10 Dec 2024 16:45:18 +0530
Subject: [PATCH 19/36] refactor: TaskDetailComponent - add heading for IO
Variables
---
src/components/CIPipelineN/TaskDetailComponent.tsx | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/components/CIPipelineN/TaskDetailComponent.tsx b/src/components/CIPipelineN/TaskDetailComponent.tsx
index 2ec2787908..c0221bf9e4 100644
--- a/src/components/CIPipelineN/TaskDetailComponent.tsx
+++ b/src/components/CIPipelineN/TaskDetailComponent.tsx
@@ -273,7 +273,10 @@ export const TaskDetailComponent = () => {
{selectedStep.stepType === PluginType.INLINE ? (
-
+
+
Input variables
+
+
) : (
)}
@@ -289,7 +292,14 @@ export const TaskDetailComponent = () => {
{formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].scriptType !==
ScriptType.CONTAINERIMAGE && (
-
+
+
Output variables
+
+
)}
>
) : (
From 3eebba5e080b78d12dc4070d1d505e1475f87858 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Tue, 10 Dec 2024 16:46:17 +0530
Subject: [PATCH 20/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 5f5874e319..ac3e24581e 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-11",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-13",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index 81c6a72cba..509e08b6de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-11":
- version "1.2.4-beta-11"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-11.tgz#2354b7bd6280fd1c0fbd846e68136d76cf41941c"
- integrity sha512-STpXqlKFLqXwOcNxptRJuWEBclGSMyN/2kZlUNOSbpfVUBCAkVA9Q5/PttC9hAKYCxL1JvinBfbDkwiVujTotQ==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-13":
+ version "1.2.4-beta-13"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-13.tgz#f6ed73388e8d0dc12b5757189cd3c211b9e69adb"
+ integrity sha512-WK76lSq3Cm3pW8f7vtTCXDgS4LQC+seFjkBHFEdqFmnnK56YHCizsztzhj6iCt6V+25wtlGmspDeh201ScleBw==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From 3f21fc302a771cf2a54e963e35c15c621bb66f76 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 12 Dec 2024 09:18:31 +0530
Subject: [PATCH 21/36] feat: VariableDataTable - CI/CD Pipeline - validations
functionality update
---
src/components/CIPipelineN/CIPipeline.tsx | 1 +
.../PluginDetailHeader/CreatePluginButton.tsx | 2 +-
.../ValueConfigFileTippy.tsx | 26 +
.../VariableDataTable.component.tsx | 828 ++++++++++--------
.../CIPipelineN/VariableDataTable/types.ts | 40 +-
.../CIPipelineN/VariableDataTable/utils.tsx | 90 +-
.../VariableDataTable/validationSchema.ts | 60 --
.../VariableDataTable/validations.ts | 84 ++
src/components/cdPipeline/CDPipeline.tsx | 1 +
src/components/cdPipeline/cdpipeline.util.tsx | 53 +-
src/components/workflowEditor/types.ts | 10 +-
11 files changed, 695 insertions(+), 500 deletions(-)
create mode 100644 src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
delete mode 100644 src/components/CIPipelineN/VariableDataTable/validationSchema.ts
create mode 100644 src/components/CIPipelineN/VariableDataTable/validations.ts
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index 7a3fa84066..2616fecf83 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -438,6 +438,7 @@ export default function CIPipeline({
if (!_formDataErrorObj[stageName].steps[i]) {
_formDataErrorObj[stageName].steps.push({ isValid: true })
}
+ _formDataErrorObj.triggerValidation = true
validateTask(_formData[stageName].steps[i], _formDataErrorObj[stageName].steps[i])
isStageValid = isStageValid && _formDataErrorObj[stageName].steps[i].isValid
}
diff --git a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
index 1bcc17160b..32942928bd 100644
--- a/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
+++ b/src/components/CIPipelineN/PluginDetailHeader/CreatePluginButton.tsx
@@ -22,7 +22,7 @@ const CreatePluginButton = () => {
validateTask(
formData[activeStageName].steps[selectedTaskIndex],
clonedFormErrorObj[activeStageName].steps[selectedTaskIndex],
- true,
+ { isSaveAsPlugin: true },
)
setFormDataErrorObj(clonedFormErrorObj)
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
new file mode 100644
index 0000000000..36cf8c0f9e
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
@@ -0,0 +1,26 @@
+import Tippy from '@tippyjs/react'
+
+import { ReactComponent as Info } from '@Icons/info-filled.svg'
+
+export const ValueConfigFileTippy = ({ mountDir }: { mountDir: string }) => (
+
+ File mount path
+
+ {mountDir}
+
+
+ Ensure the uploaded file name is unique to avoid conflicts or overrides.
+
+
+ }
+ >
+
+
+
+
+)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 550d33ee05..c59d75bc8b 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -1,23 +1,22 @@
import { useContext, useState, useEffect, useRef, useMemo } from 'react'
-import Tippy from '@tippyjs/react'
import {
DynamicDataTable,
+ DynamicDataTableCellErrorType,
DynamicDataTableProps,
DynamicDataTableRowDataType,
PluginType,
RefVariableType,
- SystemVariableIcon,
VariableType,
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
-import { ReactComponent as Info } from '@Icons/info-filled.svg'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, getVariableDataTableHeaders } from './constants'
import {
+ GetValColumnRowPropsType,
HandleRowUpdateActionProps,
VariableDataCustomState,
VariableDataKeys,
@@ -26,20 +25,20 @@ import {
VariableDataTableProps,
} from './types'
import {
- checkForSystemVariable,
convertVariableDataTableToFormData,
getEmptyVariableDataTableRow,
- getOptionsForValColumn,
getUploadFileConstraints,
getValColumnRowProps,
getValColumnRowValue,
+ getVariableDataTableInitialCellError,
getVariableDataTableInitialRows,
} from './utils'
-import { getVariableDataTableValidationSchema } from './validationSchema'
+import { getVariableDataTableCellValidateState, validateVariableDataTable } from './validations'
import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
import { VariableConfigOverlay } from './VariableConfigOverlay'
import { ValueConfigOverlay } from './ValueConfigOverlay'
+import { ValueConfigFileTippy } from './ValueConfigFileTippy'
export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTableProps) => {
// CONTEXTS
@@ -59,7 +58,8 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
} = useContext(pipelineContext)
// CONSTANTS
- const emptyRowParams = {
+ const headers = getVariableDataTableHeaders(type)
+ const defaultRowValColumnParams: GetValColumnRowPropsType = {
inputVariablesListFromPrevStep,
activeStageName,
selectedTaskIndex,
@@ -67,14 +67,13 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
globalVariables,
isCdPipeline,
type,
- description: null,
format: VariableTypeFormat.STRING,
variableType: RefVariableType.NEW,
value: '',
+ description: null,
refVariableName: null,
refVariableStage: null,
valueConstraint: null,
- id: 0,
}
const currentStepTypeVariable =
@@ -87,8 +86,12 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
]
+ const isTableValid =
+ formDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].isValid ?? true
+
// STATES
const [rows, setRows] = useState
([])
+ const [cellError, setCellError] = useState>({})
// KEYS FREQUENCY MAP
const keysFrequencyMap: Record = useMemo(
@@ -110,380 +113,454 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const initialRowsSet = useRef('')
useEffect(() => {
- setRows(getVariableDataTableInitialRows({ emptyRowParams, ioVariables, isCustomTask, type }))
+ // SETTING INITIAL ROWS & ERROR STATE
+ const initialRows = getVariableDataTableInitialRows({
+ ioVariables,
+ isCustomTask,
+ type,
+ activeStageName,
+ formData,
+ globalVariables,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ isCdPipeline,
+ })
+ const updatedCellError = getVariableDataTableInitialCellError(initialRows, headers)
+
+ setRows(initialRows)
+ setCellError(updatedCellError)
+
initialRowsSet.current = 'set'
}, [])
+ useEffect(() => {
+ // Validate the table when:
+ // 1. Rows have been initialized (`initialRowsSet.current` is 'set').
+ // 2. Validation is explicitly triggered (`formDataErrorObj.triggerValidation` is true)
+ // or the table is currently invalid (`!isTableValid` -> this is only triggered on mount)
+ if (initialRowsSet.current === 'set') {
+ if (formDataErrorObj.triggerValidation || !isTableValid) {
+ setCellError(
+ validateVariableDataTable({
+ headers,
+ rows,
+ keysFrequencyMap,
+ pluginVariableType: type,
+ }),
+ )
+ // Reset the triggerValidation flag after validation is complete.
+ setFormDataErrorObj((prevState) => ({
+ ...prevState,
+ triggerValidation: false,
+ }))
+ }
+ }
+ }, [initialRowsSet.current, formDataErrorObj.triggerValidation])
+
// METHODS
const handleRowUpdateAction = (rowAction: HandleRowUpdateActionProps) => {
const { actionType } = rowAction
+ let updatedRows = rows
+ const updatedCellError = structuredClone(cellError)
- setRows((prevRows) => {
- let updatedRows = [...prevRows]
- switch (actionType) {
- case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
- updatedRows = updatedRows.map((row) => {
- const { id, data, customState } = row
+ switch (actionType) {
+ case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
+ updatedRows = updatedRows.map((row) => {
+ const { id, data, customState } = row
+
+ if (id === rowAction.rowId) {
// FILTERING EMPTY CHOICE VALUES
const choicesOptions = customState.choices.filter(({ value }) => !!value)
-
// RESETTING TO DEFAULT STATE IF CHOICES ARE EMPTY
- const blockCustomValue = choicesOptions.length ? row.customState.blockCustomValue : false
-
- if (id === rowAction.rowId) {
- return {
- ...row,
- data: {
- ...data,
- val:
- data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
- ? {
- ...data.val,
- props: {
- ...data.val.props,
- options: getOptionsForValColumn({
- activeStageName,
- format: row.data.format.value as VariableTypeFormat,
- formData,
- globalVariables,
- selectedTaskIndex,
- inputVariablesListFromPrevStep,
- isCdPipeline,
- valueConstraint: {
- blockCustomValue,
- choices: choicesOptions.map(({ value }) => value),
- },
- }),
- },
- }
- : getValColumnRowProps({
- ...emptyRowParams,
- value: data.val.value,
- format: data.format.value as VariableTypeFormat,
- valueConstraint: {
- blockCustomValue,
- choices: choicesOptions.map(({ value }) => value),
- },
- }),
- },
- customState: {
- ...row.customState,
- blockCustomValue,
- choices: choicesOptions,
- },
- }
+ const blockCustomValue = !!choicesOptions.length && row.customState.blockCustomValue
+
+ const isCurrentValueValid =
+ !blockCustomValue ||
+ ((!customState.selectedValue ||
+ customState.selectedValue?.variableType === RefVariableType.NEW) &&
+ choicesOptions.some(({ value }) => value === data.val.value))
+
+ updatedCellError[row.id].val = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ key: 'val',
+ row,
+ })
+
+ return {
+ ...row,
+ data: {
+ ...data,
+ val: getValColumnRowProps({
+ ...defaultRowValColumnParams,
+ ...(!blockCustomValue && customState.selectedValue
+ ? {
+ variableType: customState.selectedValue.variableType,
+ refVariableName: customState.selectedValue.value,
+ refVariableStage: customState.selectedValue.refVariableStage,
+ }
+ : {}),
+ value: isCurrentValueValid ? data.val.value : '',
+ format: data.format.value as VariableTypeFormat,
+ valueConstraint: {
+ blockCustomValue,
+ choices: choicesOptions.map(({ value }) => value),
+ },
+ }),
+ },
+ customState: {
+ ...customState,
+ selectedValue: !blockCustomValue ? customState.selectedValue : null,
+ blockCustomValue,
+ choices: choicesOptions,
+ },
}
+ }
- return row
- })
- break
-
- case VariableDataTableActionType.UPDATE_CHOICES:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: {
- ...row.customState,
- choices: rowAction.actionValue(row.customState.choices),
- },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- data: {
- ...row.data,
- ...(row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
- ? {
- val: {
- ...row.data.val,
- props: {
- ...row.data.val.props,
- options: getOptionsForValColumn({
- activeStageName,
- format: row.data.format.value as VariableTypeFormat,
- formData,
- globalVariables,
- selectedTaskIndex,
- inputVariablesListFromPrevStep,
- isCdPipeline,
- valueConstraint: {
- blockCustomValue: rowAction.actionValue,
- choices: row.customState.choices.map(
- ({ value }) => value,
- ),
- },
- }),
- selectPickerProps: {
- isCreatable:
- row.data.format.value !== VariableTypeFormat.BOOL &&
- row.data.format.value !== VariableTypeFormat.DATE &&
- !rowAction.actionValue,
- },
- },
- },
- }
- : {}),
- },
- customState: { ...row.customState, blockCustomValue: rowAction.actionValue },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? { ...row, customState: { ...row.customState, askValueAtRuntime: rowAction.actionValue } }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_VARIABLE_DESCRIPTION:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: { ...row.customState, variableDescription: rowAction.actionValue },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_VARIABLE_REQUIRED:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- data: {
- ...row.data,
- variable: { ...row.data.variable, required: rowAction.actionValue },
- },
- customState: { ...row.customState, isVariableRequired: rowAction.actionValue },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_FILE_MOUNT:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: {
- ...row.customState,
- fileInfo: { ...row.customState.fileInfo, mountDir: rowAction.actionValue },
- },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- data:
- row.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD
- ? {
- ...row.data,
- val: {
- ...row.data.val,
- props: {
- ...row.data.val.props,
- fileTypes: rowAction.actionValue.split(','),
- },
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_CHOICES:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ choices: rowAction.actionValue(row.customState.choices),
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ blockCustomValue: rowAction.actionValue,
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_ASK_VALUE_AT_RUNTIME:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowAction.rowId) {
+ return { ...row, customState: { ...row.customState, askValueAtRuntime: rowAction.actionValue } }
+ }
+
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_VARIABLE_DESCRIPTION:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: { ...row.customState, variableDescription: rowAction.actionValue },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_VARIABLE_REQUIRED:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowAction.rowId) {
+ const updatedRow = {
+ ...row,
+ data: {
+ ...row.data,
+ variable: { ...row.data.variable, required: rowAction.actionValue },
+ },
+ customState: { ...row.customState, isVariableRequired: rowAction.actionValue },
+ }
+ updatedCellError[row.id].variable = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ key: 'variable',
+ row: updatedRow,
+ })
+ updatedCellError[row.id].val = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ key: 'val',
+ row: updatedRow,
+ })
+
+ return updatedRow
+ }
+
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_MOUNT:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ fileInfo: { ...row.customState.fileInfo, mountDir: rowAction.actionValue },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_ALLOWED_EXTENSIONS:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ data:
+ row.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD
+ ? {
+ ...row.data,
+ val: {
+ ...row.data.val,
+ props: {
+ ...row.data.val.props,
+ fileTypes: rowAction.actionValue.split(','),
},
- }
- : row.data,
- customState: {
- ...row.customState,
- fileInfo: {
- ...row.customState.fileInfo,
- allowedExtensions: rowAction.actionValue,
- },
- },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_FILE_MAX_SIZE:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: {
- ...row.customState,
- fileInfo: {
- ...row.customState.fileInfo,
- maxUploadSize: rowAction.actionValue.size,
- unit: rowAction.actionValue.unit,
- },
+ },
+ }
+ : row.data,
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ allowedExtensions: rowAction.actionValue,
},
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.ADD_ROW:
- updatedRows = [
- getEmptyVariableDataTableRow({ ...emptyRowParams, id: rowAction.actionValue }),
- ...updatedRows,
- ]
- break
-
- case VariableDataTableActionType.DELETE_ROW:
- updatedRows = updatedRows.filter((row) => row.id !== rowAction.rowId)
- break
-
- case VariableDataTableActionType.UPDATE_ROW:
- updatedRows = rows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- data: {
- ...row.data,
- [rowAction.headerKey]: {
- ...row.data[rowAction.headerKey],
- value: rowAction.actionValue,
- },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_MAX_SIZE:
+ updatedRows = updatedRows.map((row) =>
+ row.id === rowAction.rowId
+ ? {
+ ...row,
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ maxUploadSize: rowAction.actionValue.size,
+ unit: rowAction.actionValue.unit,
},
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: {
- ...row.customState,
- fileInfo: {
- ...row.customState.fileInfo,
- id: rowAction.actionValue.fileReferenceId,
- },
- },
- }
- : row,
- )
- break
-
- case VariableDataTableActionType.UPDATE_VAL_COLUMN:
- updatedRows = updatedRows.map((row) => {
- if (
- row.id === rowAction.rowId &&
- row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
- ) {
- const { selectedValue, value } = rowAction.actionValue
- const isSystemVariable = checkForSystemVariable(selectedValue)
-
- return {
- ...row,
- data: {
- ...row.data,
- val: {
- ...row.data.val,
- value: getValColumnRowValue(
- row.data.format.value as VariableTypeFormat,
- value,
- selectedValue,
- ),
- props: {
- ...row.data.val.props,
- Icon: value && isSystemVariable ? : null,
- },
+ },
+ }
+ : row,
+ )
+ break
+
+ case VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowAction.rowId && row.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD) {
+ updatedCellError[row.id].val = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ value: rowAction.actionValue.fileName,
+ key: 'val',
+ row,
+ })
+
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ val: {
+ ...row.data.val,
+ value: rowAction.actionValue.fileName,
+ props: {
+ ...row.data.val.props,
+ isLoading: rowAction.actionValue.isLoading,
},
},
- customState: {
- ...row.customState,
- selectedValue: rowAction.actionValue.selectedValue,
+ },
+ customState: {
+ ...row.customState,
+ fileInfo: {
+ ...row.customState.fileInfo,
+ id: rowAction.actionValue.fileReferenceId,
},
- }
+ },
}
+ }
- return row
- })
- break
-
- case VariableDataTableActionType.UPDATE_FORMAT_COLUMN:
- updatedRows = updatedRows.map((row) => {
- if (
- row.id === rowAction.rowId &&
- row.data.format.type === DynamicDataTableRowDataType.DROPDOWN
- ) {
- return {
- ...row,
- data: {
- ...row.data,
- format: {
- ...row.data.format,
- value: rowAction.actionValue,
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.ADD_ROW:
+ updatedRows = [
+ getEmptyVariableDataTableRow({ ...defaultRowValColumnParams, id: rowAction.rowId }),
+ ...updatedRows,
+ ]
+ updatedCellError[rowAction.rowId] = {}
+ break
+
+ case VariableDataTableActionType.DELETE_ROW:
+ updatedRows = updatedRows.filter((row) => row.id !== rowAction.rowId)
+ delete updatedCellError[rowAction.rowId]
+ break
+
+ case VariableDataTableActionType.UPDATE_ROW:
+ updatedRows = rows.map((row) => {
+ if (row.id === rowAction.rowId) {
+ updatedCellError[row.id][rowAction.headerKey] = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ value: rowAction.actionValue,
+ key: rowAction.headerKey,
+ row,
+ })
+
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ [rowAction.headerKey]: {
+ ...row.data[rowAction.headerKey],
+ value: rowAction.actionValue,
+ },
+ },
+ }
+ }
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_VAL_COLUMN:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowAction.rowId && row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
+ const { selectedValue, value } = rowAction.actionValue
+ const valColumnRowValue = getValColumnRowValue(
+ row.data.format.value as VariableTypeFormat,
+ value,
+ selectedValue,
+ )
+
+ updatedCellError[row.id].val = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ value: valColumnRowValue,
+ key: 'val',
+ row,
+ })
+
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ val: getValColumnRowProps({
+ ...defaultRowValColumnParams,
+ value: valColumnRowValue,
+ ...(!row.customState.blockCustomValue && rowAction.actionValue.selectedValue
+ ? {
+ variableType: rowAction.actionValue.selectedValue.variableType,
+ refVariableName: rowAction.actionValue.selectedValue.value,
+ refVariableStage: rowAction.actionValue.selectedValue.refVariableStage,
+ }
+ : {}),
+ format: row.data.format.value as VariableTypeFormat,
+ valueConstraint: {
+ blockCustomValue: row.customState.blockCustomValue,
+ choices: row.customState.choices.map((choice) => choice.value),
},
- val: getValColumnRowProps({
- ...emptyRowParams,
- format: rowAction.actionValue,
- id: rowAction.rowId as number,
- }),
+ }),
+ },
+ customState: {
+ ...row.customState,
+ selectedValue: rowAction.actionValue.selectedValue,
+ },
+ }
+ }
+
+ return row
+ })
+ break
+
+ case VariableDataTableActionType.UPDATE_FORMAT_COLUMN:
+ updatedRows = updatedRows.map((row) => {
+ if (row.id === rowAction.rowId && row.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
+ updatedCellError[row.id].val = getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType: type,
+ key: 'val',
+ row,
+ })
+
+ return {
+ ...row,
+ data: {
+ ...row.data,
+ format: {
+ ...row.data.format,
+ value: rowAction.actionValue,
},
- customState: {
- isVariableRequired: false,
- variableDescription: '',
- selectedValue: null,
- choices: [],
- blockCustomValue: false,
- askValueAtRuntime: false,
- fileInfo: {
- id: null,
- allowedExtensions: '',
- maxUploadSize: '',
- mountDir: {
- value: '/devtroncd',
- error: '',
- },
- unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
+ val: getValColumnRowProps({
+ ...defaultRowValColumnParams,
+ format: rowAction.actionValue,
+ }),
+ },
+ customState: {
+ ...row.customState,
+ selectedValue: null,
+ choices: [],
+ blockCustomValue: false,
+ fileInfo: {
+ id: null,
+ allowedExtensions: '',
+ maxUploadSize: '',
+ mountDir: {
+ value: '/devtroncd',
+ error: '',
},
+ unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
},
- }
+ },
}
- return row
- })
- break
-
- default:
- break
- }
+ }
+ return row
+ })
+ break
- const { updatedFormData, updatedFormDataErrorObj } = convertVariableDataTableToFormData({
- rows: updatedRows,
- activeStageName,
- formData,
- formDataErrorObj,
- selectedTaskIndex,
- type,
- validateTask,
- calculateLastStepDetail,
- })
- setFormDataErrorObj(updatedFormDataErrorObj)
- setFormData(updatedFormData)
+ default:
+ break
+ }
- return updatedRows
+ const { updatedFormData, updatedFormDataErrorObj } = convertVariableDataTableToFormData({
+ rows: updatedRows,
+ cellError: updatedCellError,
+ activeStageName,
+ formData,
+ formDataErrorObj,
+ selectedTaskIndex,
+ type,
+ validateTask,
+ calculateLastStepDetail,
})
+ setFormDataErrorObj(updatedFormDataErrorObj)
+ setFormData(updatedFormData)
+
+ setRows(updatedRows)
+ setCellError(updatedCellError)
}
const dataTableHandleAddition = () => {
handleRowUpdateAction({
actionType: VariableDataTableActionType.ADD_ROW,
- actionValue: Math.floor(new Date().valueOf() * Math.random()),
+ rowId: Math.floor(new Date().valueOf() * Math.random()),
})
}
@@ -496,7 +573,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
if (headerKey === 'val' && updatedRow.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
- actionValue: { value, selectedValue: extraData.selectedValue, files: extraData.files },
+ actionValue: { value, selectedValue: extraData.selectedValue },
rowId: updatedRow.id,
})
} else if (
@@ -504,16 +581,14 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
updatedRow.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD &&
extraData.files.length
) {
- // TODO: check this merge with UPDATE_FILE_UPLOAD_INFO after loading state
handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: value,
- headerKey,
+ actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
+ actionValue: { fileReferenceId: null, isLoading: true, fileName: value },
rowId: updatedRow.id,
})
try {
- const { id } = await uploadFile({
+ const { id, name } = await uploadFile({
file: extraData.files,
...getUploadFileConstraints({
unit: updatedRow.customState.fileInfo.unit.label as string,
@@ -524,14 +599,13 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
- actionValue: { fileReferenceId: id },
+ actionValue: { fileReferenceId: id, isLoading: false, fileName: name },
rowId: updatedRow.id,
})
} catch {
handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_ROW,
- actionValue: '',
- headerKey,
+ actionType: VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO,
+ actionValue: { fileReferenceId: null, isLoading: false, fileName: '' },
rowId: updatedRow.id,
})
}
@@ -582,47 +656,29 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
)
- const variableTrailingCellIcon = (row: VariableDataRowType) =>
+ const getTrailingCellIconForVariableColumn = (row: VariableDataRowType) =>
isCustomTask && type === PluginVariableType.INPUT ? (
) : null
- const valTrailingCellIcon = (row: VariableDataRowType) =>
+ const getTrailingCellIconForValueColumn = (row: VariableDataRowType) =>
row.data.format.value === VariableTypeFormat.FILE ? (
-
- File mount path
-
- {row.customState.fileInfo.mountDir.value}
-
-
- Ensure the uploaded file name is unique to avoid conflicts or overrides.
-
-
- }
- >
-
-
-
-
+
) : null
const trailingCellIcon: DynamicDataTableProps['trailingCellIcon'] = {
- variable: variableTrailingCellIcon,
- val: valTrailingCellIcon,
+ variable: getTrailingCellIconForVariableColumn,
+ val: getTrailingCellIconForValueColumn,
}
return (
key={initialRowsSet.current}
- headers={getVariableDataTableHeaders(type)}
+ headers={headers}
rows={rows}
+ cellError={cellError}
readOnly={!isCustomTask && type === PluginVariableType.OUTPUT}
isAdditionNotAllowed={!isCustomTask}
isDeletionNotAllowed={!isCustomTask}
@@ -630,8 +686,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
onRowEdit={dataTableHandleChange}
onRowDelete={dataTableHandleDelete}
onRowAdd={dataTableHandleAddition}
- showError
- validationSchema={getVariableDataTableValidationSchema({ keysFrequencyMap, pluginVariableType: type })}
{...(type === PluginVariableType.INPUT
? {
actionButtonConfig: {
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index 3588af0fd4..a386592df3 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -1,6 +1,7 @@
import { PluginVariableType } from '@Components/ciPipeline/types'
import { PipelineContext } from '@Components/workflowEditor/types'
import {
+ DynamicDataTableHeaderType,
DynamicDataTableRowType,
RefVariableStageType,
RefVariableType,
@@ -24,6 +25,7 @@ export interface VariableDataTableSelectPickerOptionType extends SelectPickerOpt
export type VariableDataKeys = 'variable' | 'format' | 'val'
export type VariableDataCustomState = {
+ defaultValue: string
variableDescription: string
isVariableRequired: boolean
choices: { id: number; value: string; error: string }[]
@@ -68,7 +70,7 @@ export enum VariableDataTableActionType {
}
type VariableDataTableActionPropsMap = {
- [VariableDataTableActionType.ADD_ROW]: { actionValue: number }
+ [VariableDataTableActionType.ADD_ROW]: { rowId: number }
[VariableDataTableActionType.UPDATE_ROW]: {
actionValue: string
headerKey: VariableDataKeys
@@ -81,7 +83,6 @@ type VariableDataTableActionPropsMap = {
actionValue: {
value: string
selectedValue: VariableDataTableSelectPickerOptionType
- files: File[]
}
rowId: string | number
}
@@ -90,7 +91,10 @@ type VariableDataTableActionPropsMap = {
rowId: string | number
}
[VariableDataTableActionType.UPDATE_FILE_UPLOAD_INFO]: {
- actionValue: Pick
+ actionValue: Pick & {
+ fileName: string
+ isLoading: boolean
+ }
rowId: string | number
}
@@ -162,19 +166,29 @@ export type GetValColumnRowPropsType = Pick<
> &
Pick<
VariableType,
- | 'format'
- | 'value'
- | 'refVariableName'
- | 'refVariableStage'
- | 'valueConstraint'
- | 'description'
- | 'variableType'
- | 'id'
+ 'format' | 'value' | 'refVariableName' | 'refVariableStage' | 'valueConstraint' | 'description' | 'variableType'
> & { type: PluginVariableType }
-export interface GetVariableDataTableInitialRowsProps {
+export interface GetVariableDataTableInitialRowsProps
+ extends Omit<
+ GetValColumnRowPropsType,
+ 'description' | 'format' | 'variableType' | 'value' | 'refVariableName' | 'refVariableStage' | 'valueConstraint'
+ > {
ioVariables: VariableType[]
type: PluginVariableType
isCustomTask: boolean
- emptyRowParams: GetValColumnRowPropsType
+}
+
+export interface GetValidateCellProps {
+ pluginVariableType: PluginVariableType
+ keysFrequencyMap: Record
+ key: VariableDataKeys
+ row: VariableDataRowType
+ value?: string
+}
+
+export interface ValidateVariableDataTableProps
+ extends Pick {
+ rows: VariableDataRowType[]
+ headers: DynamicDataTableHeaderType[]
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 6189ec3097..3d6df2fdde 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -1,5 +1,7 @@
import {
ConditionType,
+ DynamicDataTableCellErrorType,
+ DynamicDataTableHeaderType,
DynamicDataTableRowDataType,
getGoLangFormattedDateWithTimezone,
IO_VARIABLES_VALUE_COLUMN_BOOL_OPTIONS,
@@ -24,6 +26,7 @@ import {
GetVariableDataTableInitialRowsProps,
VariableDataTableSelectPickerOptionType,
VariableDataRowType,
+ VariableDataKeys,
} from './types'
export const getOptionsForValColumn = ({
@@ -115,13 +118,28 @@ export const getOptionsForValColumn = ({
}
}
- const filteredGlobalVariablesBasedOnFormat = globalVariables.filter((variable) => variable.format === format)
+ const filteredGlobalVariables = isBuildStagePostBuild
+ ? globalVariables
+ : globalVariables.filter(
+ (variable) =>
+ (isCdPipeline && variable.stageType !== 'post-cd') || !excludeVariables.includes(variable.value),
+ )
+
+ const filteredGlobalVariablesBasedOnFormat = filteredGlobalVariables.filter(
+ (variable) => variable.format === format,
+ )
+ const filteredPreBuildStageVariablesBasedOnFormat = preBuildStageVariables.filter(
+ (variable) => variable.format === format,
+ )
+ const filteredPreviousStepVariablesBasedOnFormat = previousStepVariables.filter(
+ (variable) => variable.format === format,
+ )
const isOptionsEmpty =
!defaultValues.length &&
(isBuildStagePostBuild
- ? !preBuildStageVariables.length && !previousStepVariables.length
- : !previousStepVariables.length) &&
+ ? !filteredPreBuildStageVariablesBasedOnFormat.length && !filteredPreviousStepVariablesBasedOnFormat.length
+ : !filteredPreviousStepVariablesBasedOnFormat.length) &&
!filteredGlobalVariablesBasedOnFormat.length
if (isOptionsEmpty) {
@@ -139,28 +157,22 @@ export const getOptionsForValColumn = ({
? [
{
label: VAL_COLUMN_DROPDOWN_LABEL.PRE_BUILD_STAGE,
- options: preBuildStageVariables,
+ options: filteredPreBuildStageVariablesBasedOnFormat,
},
{
label: VAL_COLUMN_DROPDOWN_LABEL.POST_BUILD_STAGE,
- options: previousStepVariables,
+ options: filteredPreviousStepVariablesBasedOnFormat,
},
]
: [
{
label: VAL_COLUMN_DROPDOWN_LABEL.PREVIOUS_STEPS,
- options: previousStepVariables,
+ options: filteredPreviousStepVariablesBasedOnFormat,
},
]),
{
label: VAL_COLUMN_DROPDOWN_LABEL.SYSTEM_VARIABLES,
- options: isBuildStagePostBuild
- ? filteredGlobalVariablesBasedOnFormat
- : filteredGlobalVariablesBasedOnFormat.filter(
- (variable) =>
- (isCdPipeline && variable.stageType !== 'post-cd') ||
- !excludeVariables.includes(variable.value),
- ),
+ options: filteredGlobalVariablesBasedOnFormat,
},
]
: []),
@@ -270,7 +282,7 @@ export const getValColumnRowProps = ({
return {
type: DynamicDataTableRowDataType.TEXT,
- value: description,
+ value: description || 'No description available',
props: {},
}
}
@@ -295,15 +307,19 @@ export const getValColumnRowValue = (
return isDateFormat ? getGoLangFormattedDateWithTimezone(selectedValue.value) : value
}
-export const getEmptyVariableDataTableRow = (params: GetValColumnRowPropsType): VariableDataRowType => {
+export const getEmptyVariableDataTableRow = ({
+ id,
+ ...params
+}: GetValColumnRowPropsType & { id: number }): VariableDataRowType => {
const data: VariableDataRowType = {
data: {
variable: getVariableColumnRowProps(),
format: getFormatColumnRowProps({ format: VariableTypeFormat.STRING, isCustomTask: true }),
val: getValColumnRowProps(params),
},
- id: params.id,
+ id,
customState: {
+ defaultValue: '',
variableDescription: '',
isVariableRequired: false,
choices: [],
@@ -330,7 +346,7 @@ export const getVariableDataTableInitialRows = ({
ioVariables,
type,
isCustomTask,
- emptyRowParams,
+ ...restProps
}: GetVariableDataTableInitialRowsProps): VariableDataRowType[] =>
(ioVariables || []).map(
({
@@ -346,6 +362,7 @@ export const getVariableDataTableInitialRows = ({
isRuntimeArg,
fileMountDir,
fileReferenceId,
+ defaultValue,
id,
}) => {
const isInputVariableRequired = type === PluginVariableType.INPUT && !allowEmptyValue
@@ -363,18 +380,19 @@ export const getVariableDataTableInitialRows = ({
},
format: getFormatColumnRowProps({ format, isCustomTask }),
val: getValColumnRowProps({
- ...emptyRowParams,
+ ...restProps,
+ type,
description,
format,
- variableType,
value,
+ variableType,
refVariableName,
refVariableStage,
valueConstraint,
- id,
}),
},
customState: {
+ defaultValue,
isVariableRequired: isInputVariableRequired,
variableDescription: description ?? '',
choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
@@ -384,7 +402,13 @@ export const getVariableDataTableInitialRows = ({
})),
askValueAtRuntime: isRuntimeArg ?? false,
blockCustomValue: valueConstraint?.blockCustomValue ?? false,
- selectedValue: null,
+ selectedValue: {
+ label: refVariableName || value,
+ value: refVariableName || value,
+ refVariableName,
+ refVariableStage,
+ variableType: refVariableName ? RefVariableType.GLOBAL : RefVariableType.NEW,
+ },
fileInfo: {
id: fileReferenceId,
mountDir: { value: fileMountDir, error: '' },
@@ -401,6 +425,21 @@ export const getVariableDataTableInitialRows = ({
},
)
+export const getVariableDataTableInitialCellError = (
+ rows: VariableDataRowType[],
+ headers: DynamicDataTableHeaderType[],
+) =>
+ rows.reduce((acc, curr) => {
+ if (!acc[curr.id]) {
+ acc[curr.id] = headers.reduce(
+ (headerAcc, { key }) => ({ ...headerAcc, [key]: { isValid: true, errorMessages: [] } }),
+ {},
+ )
+ }
+
+ return acc
+ }, {})
+
export const getUploadFileConstraints = ({
unit,
allowedExtensions,
@@ -422,6 +461,7 @@ export const getUploadFileConstraints = ({
export const convertVariableDataTableToFormData = ({
rows,
+ cellError,
type,
activeStageName,
selectedTaskIndex,
@@ -440,6 +480,7 @@ export const convertVariableDataTableToFormData = ({
> & {
type: PluginVariableType
rows: VariableDataRowType[]
+ cellError: DynamicDataTableCellErrorType
}) => {
const updatedFormData = structuredClone(formData)
const updatedFormDataErrorObj = structuredClone(formDataErrorObj)
@@ -564,9 +605,16 @@ export const convertVariableDataTableToFormData = ({
updatedFormData[activeStageName].steps[selectedTaskIndex].inlineStepDetail.conditionDetails = conditionDetails
}
+ const isValid = Object.values(cellError).reduce(
+ (acc, curr) => acc && !Object.values(curr).some((item) => !item.isValid),
+ true,
+ )
+ updatedFormDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].isValid = isValid
+
validateTask(
updatedFormData[activeStageName].steps[selectedTaskIndex],
updatedFormDataErrorObj[activeStageName].steps[selectedTaskIndex],
+ { validateIOVariables: false },
)
return { updatedFormDataErrorObj, updatedFormData }
diff --git a/src/components/CIPipelineN/VariableDataTable/validationSchema.ts b/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
deleted file mode 100644
index dee292e5ac..0000000000
--- a/src/components/CIPipelineN/VariableDataTable/validationSchema.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { DynamicDataTableProps, VariableTypeFormat } from '@devtron-labs/devtron-fe-common-lib'
-
-import { PluginVariableType } from '@Components/ciPipeline/types'
-import { PATTERNS } from '@Config/constants'
-
-import { VariableDataCustomState, VariableDataKeys } from './types'
-import { checkForSystemVariable, testValueForNumber } from './utils'
-
-export const getVariableDataTableValidationSchema =
- ({
- pluginVariableType,
- keysFrequencyMap,
- }: {
- pluginVariableType: PluginVariableType
- keysFrequencyMap: Record
- }): DynamicDataTableProps['validationSchema'] =>
- (value, key, { data, customState }) => {
- const { variableDescription, isVariableRequired, selectedValue, askValueAtRuntime } = customState
-
- const re = new RegExp(PATTERNS.VARIABLE)
-
- if (key === 'variable') {
- const variableValue = !isVariableRequired || data.val.value
-
- if (!value && !variableValue && !variableDescription) {
- return { errorMessages: ['Please complete or remove this variable'], isValid: false }
- }
-
- if (!value) {
- return { errorMessages: ['Variable name is required'], isValid: false }
- }
-
- if (!re.test(value)) {
- return {
- errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`],
- isValid: false,
- }
- }
-
- if ((keysFrequencyMap[value] || 0) > 1) {
- return { errorMessages: ['Variable name should be unique'], isValid: false }
- }
- }
-
- if (pluginVariableType === PluginVariableType.INPUT && key === 'val') {
- const checkForVariable = isVariableRequired && !askValueAtRuntime
- if (checkForVariable && !value) {
- return { errorMessages: ['Variable value is required'], isValid: false }
- }
-
- if (data.format.value === VariableTypeFormat.NUMBER) {
- return {
- isValid: checkForSystemVariable(selectedValue) || testValueForNumber(value),
- errorMessages: ['Variable value is not a number'],
- }
- }
- }
-
- return { errorMessages: [], isValid: true }
- }
diff --git a/src/components/CIPipelineN/VariableDataTable/validations.ts b/src/components/CIPipelineN/VariableDataTable/validations.ts
new file mode 100644
index 0000000000..c7f8fa6efa
--- /dev/null
+++ b/src/components/CIPipelineN/VariableDataTable/validations.ts
@@ -0,0 +1,84 @@
+import { DynamicDataTableCellValidationState, VariableTypeFormat } from '@devtron-labs/devtron-fe-common-lib'
+
+import { PluginVariableType } from '@Components/ciPipeline/types'
+import { PATTERNS } from '@Config/constants'
+
+import { GetValidateCellProps, ValidateVariableDataTableProps } from './types'
+import { checkForSystemVariable, testValueForNumber } from './utils'
+
+export const getVariableDataTableCellValidateState = ({
+ pluginVariableType,
+ keysFrequencyMap,
+ row: { data, customState },
+ key,
+ value: latestValue,
+}: GetValidateCellProps): DynamicDataTableCellValidationState => {
+ const value = latestValue ?? data[key].value
+ const { variableDescription, isVariableRequired, selectedValue, askValueAtRuntime, defaultValue } = customState
+ const re = new RegExp(PATTERNS.VARIABLE)
+
+ if (key === 'variable') {
+ const variableValue = !isVariableRequired || data.val.value
+
+ if (!value && !variableValue && !variableDescription) {
+ return { errorMessages: ['Please complete or remove this variable'], isValid: false }
+ }
+
+ if (!value) {
+ return { errorMessages: ['Variable name is required'], isValid: false }
+ }
+
+ if (!re.test(value)) {
+ return {
+ errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`],
+ isValid: false,
+ }
+ }
+
+ if ((keysFrequencyMap[value] || 0) > 1) {
+ return { errorMessages: ['Variable name should be unique'], isValid: false }
+ }
+ }
+
+ if (pluginVariableType === PluginVariableType.INPUT && key === 'val') {
+ const checkForVariable = isVariableRequired && !askValueAtRuntime && !defaultValue
+ if (checkForVariable && !value) {
+ return { errorMessages: ['Variable value is required'], isValid: false }
+ }
+
+ if (data.format.value === VariableTypeFormat.NUMBER) {
+ return {
+ isValid: checkForSystemVariable(selectedValue) || testValueForNumber(value),
+ errorMessages: ['Variable value is not a number'],
+ }
+ }
+ }
+
+ return { errorMessages: [], isValid: true }
+}
+
+export const validateVariableDataTable = ({
+ rows,
+ headers,
+ keysFrequencyMap,
+ pluginVariableType,
+}: ValidateVariableDataTableProps) => {
+ const cellError = rows.reduce((acc, row) => {
+ acc[row.id] = headers.reduce(
+ (headerAcc, { key }) => ({
+ ...headerAcc,
+ [key]: getVariableDataTableCellValidateState({
+ keysFrequencyMap,
+ pluginVariableType,
+ key,
+ row,
+ }),
+ }),
+ {},
+ )
+
+ return acc
+ }, {})
+
+ return cellError
+}
diff --git a/src/components/cdPipeline/CDPipeline.tsx b/src/components/cdPipeline/CDPipeline.tsx
index 5178b21cac..82dcc62f07 100644
--- a/src/components/cdPipeline/CDPipeline.tsx
+++ b/src/components/cdPipeline/CDPipeline.tsx
@@ -877,6 +877,7 @@ export default function CDPipeline({
if (!_formDataErrorObj[stageName].steps[i]) {
_formDataErrorObj[stageName].steps.push({ isValid: true })
}
+ _formDataErrorObj.triggerValidation = true
validateTask(_formData[stageName].steps[i], _formDataErrorObj[stageName].steps[i])
isStageValid = isStageValid && _formDataErrorObj[stageName].steps[i].isValid
}
diff --git a/src/components/cdPipeline/cdpipeline.util.tsx b/src/components/cdPipeline/cdpipeline.util.tsx
index d557386387..38f3053271 100644
--- a/src/components/cdPipeline/cdpipeline.util.tsx
+++ b/src/components/cdPipeline/cdpipeline.util.tsx
@@ -53,7 +53,15 @@ export const ValueContainer = (props) => {
)
}
-export const validateTask = (taskData: StepType, taskErrorObj: TaskErrorObj, isSaveAsPlugin = false): void => {
+export const validateTask = (
+ taskData: StepType,
+ taskErrorObj: TaskErrorObj,
+ options?: {
+ isSaveAsPlugin?: boolean
+ validateIOVariables?: boolean
+ },
+) => {
+ const { isSaveAsPlugin = false, validateIOVariables = true } = options ?? {}
const validationRules = new ValidationRules()
if (taskData && taskErrorObj) {
taskErrorObj.name = validationRules.requiredField(taskData.name)
@@ -65,26 +73,37 @@ export const validateTask = (taskData: StepType, taskErrorObj: TaskErrorObj, isS
const currentStepTypeVariable =
taskData.stepType === PluginType.INLINE ? 'inlineStepDetail' : 'pluginRefStepDetail'
+ taskErrorObj[currentStepTypeVariable].isValid = taskErrorObj[currentStepTypeVariable].isValid ?? true
+
taskErrorObj[currentStepTypeVariable].inputVariables = []
- taskData[currentStepTypeVariable].inputVariables?.forEach((element, index) => {
- taskErrorObj[currentStepTypeVariable].inputVariables.push(
- validationRules.inputVariable(element, inputVarMap, isSaveAsPlugin),
- )
- taskErrorObj.isValid =
- taskErrorObj.isValid && taskErrorObj[currentStepTypeVariable].inputVariables[index].isValid
- inputVarMap.set(element.name, true)
- })
+ if (validateIOVariables) {
+ taskData[currentStepTypeVariable].inputVariables?.forEach((element, index) => {
+ taskErrorObj[currentStepTypeVariable].inputVariables.push(
+ validationRules.inputVariable(element, inputVarMap, isSaveAsPlugin),
+ )
+ taskErrorObj[currentStepTypeVariable].isValid =
+ taskErrorObj[currentStepTypeVariable].isValid &&
+ taskErrorObj[currentStepTypeVariable].inputVariables[index].isValid
+ inputVarMap.set(element.name, true)
+ })
+ }
+ taskErrorObj.isValid = taskErrorObj.isValid && taskErrorObj[currentStepTypeVariable].isValid
if (taskData.stepType === PluginType.INLINE) {
taskErrorObj.inlineStepDetail.outputVariables = []
- taskData.inlineStepDetail.outputVariables?.forEach((element, index) => {
- taskErrorObj.inlineStepDetail.outputVariables.push(
- validationRules.outputVariable(element, outputVarMap),
- )
- taskErrorObj.isValid =
- taskErrorObj.isValid && taskErrorObj.inlineStepDetail.outputVariables[index].isValid
- outputVarMap.set(element.name, true)
- })
+ if (validateIOVariables) {
+ taskData.inlineStepDetail.outputVariables?.forEach((element, index) => {
+ taskErrorObj.inlineStepDetail.outputVariables.push(
+ validationRules.outputVariable(element, outputVarMap),
+ )
+ taskErrorObj[currentStepTypeVariable].isValid =
+ taskErrorObj[currentStepTypeVariable].isValid &&
+ taskErrorObj.inlineStepDetail.outputVariables[index].isValid
+ outputVarMap.set(element.name, true)
+ })
+ }
+ taskErrorObj.isValid = taskErrorObj.isValid && taskErrorObj[currentStepTypeVariable].isValid
+
if (taskData.inlineStepDetail['scriptType'] === ScriptType.SHELL) {
taskErrorObj.inlineStepDetail['script'] = validationRules.requiredField(
taskData.inlineStepDetail['script'],
diff --git a/src/components/workflowEditor/types.ts b/src/components/workflowEditor/types.ts
index 417d7627dc..1b27a6878a 100644
--- a/src/components/workflowEditor/types.ts
+++ b/src/components/workflowEditor/types.ts
@@ -254,6 +254,7 @@ export interface PipelineFormDataErrorType {
isValid: boolean
}
userApprovalConfig?: ValidationResponseType
+ triggerValidation?: boolean
}
interface HandleValidateMandatoryPluginsParamsType {
@@ -278,7 +279,14 @@ export interface PipelineContext {
}
formDataErrorObj: PipelineFormDataErrorType
setFormDataErrorObj: React.Dispatch>
- validateTask: (taskData: StepType, taskErrorobj: TaskErrorObj, isSaveAsPlugin?: boolean) => void
+ validateTask: (
+ taskData: StepType,
+ taskErrorobj: TaskErrorObj,
+ options?: {
+ isSaveAsPlugin?: boolean
+ validateIOVariables?: boolean
+ },
+ ) => void
setSelectedTaskIndex: React.Dispatch>
validateStage: (
stageName: string,
From 5333918bfd702c00ad1041ef2b9fc3a699f6cec4 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 12 Dec 2024 09:28:26 +0530
Subject: [PATCH 22/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index ac3e24581e..0425e3a8c9 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-13",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-15",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index 509e08b6de..76d43acacc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-13":
- version "1.2.4-beta-13"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-13.tgz#f6ed73388e8d0dc12b5757189cd3c211b9e69adb"
- integrity sha512-WK76lSq3Cm3pW8f7vtTCXDgS4LQC+seFjkBHFEdqFmnnK56YHCizsztzhj6iCt6V+25wtlGmspDeh201ScleBw==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-15":
+ version "1.2.4-beta-15"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-15.tgz#938bee0a468e2f799e5301c8a096a2f73efaf4c1"
+ integrity sha512-zGi4uY2Yc/onyuPq9B/SU881rS2TKLxxfxSpvzuTvQNWWj/Pb/FiYRd5HgT30kow+ujvZOzdbC5OLq+RP17F5A==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From 8baba09dc2b46c1495d7c876e5249dc5bdc24a3f Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 12 Dec 2024 12:22:14 +0530
Subject: [PATCH 23/36] fix: VariableDataTable - undefined error fix, refactor
---
.../VariableDataTable.component.tsx | 66 +++++-----
.../CIPipelineN/VariableDataTable/types.ts | 5 +-
.../CIPipelineN/VariableDataTable/utils.tsx | 114 ++++++++++--------
.../VariableDataTable/validations.ts | 5 +-
4 files changed, 100 insertions(+), 90 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index c59d75bc8b..4b727bddbe 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -135,25 +135,23 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
useEffect(() => {
// Validate the table when:
- // 1. Rows have been initialized (`initialRowsSet.current` is 'set').
+ // 1. Rows have been initialized (`initialRowsSet.current` is 'set' & rows is not empty).
// 2. Validation is explicitly triggered (`formDataErrorObj.triggerValidation` is true)
// or the table is currently invalid (`!isTableValid` -> this is only triggered on mount)
- if (initialRowsSet.current === 'set') {
- if (formDataErrorObj.triggerValidation || !isTableValid) {
- setCellError(
- validateVariableDataTable({
- headers,
- rows,
- keysFrequencyMap,
- pluginVariableType: type,
- }),
- )
- // Reset the triggerValidation flag after validation is complete.
- setFormDataErrorObj((prevState) => ({
- ...prevState,
- triggerValidation: false,
- }))
- }
+ if (initialRowsSet.current === 'set' && rows.length && (formDataErrorObj.triggerValidation || !isTableValid)) {
+ setCellError(
+ validateVariableDataTable({
+ headers,
+ rows,
+ keysFrequencyMap,
+ pluginVariableType: type,
+ }),
+ )
+ // Reset the triggerValidation flag after validation is complete.
+ setFormDataErrorObj((prevState) => ({
+ ...prevState,
+ triggerValidation: false,
+ }))
}
}, [initialRowsSet.current, formDataErrorObj.triggerValidation])
@@ -176,8 +174,8 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const isCurrentValueValid =
!blockCustomValue ||
- ((!customState.selectedValue ||
- customState.selectedValue?.variableType === RefVariableType.NEW) &&
+ ((!customState.valColumnSelectedValue ||
+ customState.valColumnSelectedValue?.variableType === RefVariableType.NEW) &&
choicesOptions.some(({ value }) => value === data.val.value))
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
@@ -193,11 +191,11 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...data,
val: getValColumnRowProps({
...defaultRowValColumnParams,
- ...(!blockCustomValue && customState.selectedValue
+ ...(!blockCustomValue && customState.valColumnSelectedValue
? {
- variableType: customState.selectedValue.variableType,
- refVariableName: customState.selectedValue.value,
- refVariableStage: customState.selectedValue.refVariableStage,
+ variableType: customState.valColumnSelectedValue.variableType,
+ refVariableName: customState.valColumnSelectedValue.value,
+ refVariableStage: customState.valColumnSelectedValue.refVariableStage,
}
: {}),
value: isCurrentValueValid ? data.val.value : '',
@@ -210,7 +208,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
},
customState: {
...customState,
- selectedValue: !blockCustomValue ? customState.selectedValue : null,
+ valColumnSelectedValue: !blockCustomValue ? customState.valColumnSelectedValue : null,
blockCustomValue,
choices: choicesOptions,
},
@@ -443,11 +441,11 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
case VariableDataTableActionType.UPDATE_VAL_COLUMN:
updatedRows = updatedRows.map((row) => {
if (row.id === rowAction.rowId && row.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
- const { selectedValue, value } = rowAction.actionValue
+ const { valColumnSelectedValue, value } = rowAction.actionValue
const valColumnRowValue = getValColumnRowValue(
row.data.format.value as VariableTypeFormat,
value,
- selectedValue,
+ valColumnSelectedValue,
)
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
@@ -465,11 +463,13 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
val: getValColumnRowProps({
...defaultRowValColumnParams,
value: valColumnRowValue,
- ...(!row.customState.blockCustomValue && rowAction.actionValue.selectedValue
+ ...(!row.customState.blockCustomValue &&
+ rowAction.actionValue.valColumnSelectedValue
? {
- variableType: rowAction.actionValue.selectedValue.variableType,
- refVariableName: rowAction.actionValue.selectedValue.value,
- refVariableStage: rowAction.actionValue.selectedValue.refVariableStage,
+ variableType: rowAction.actionValue.valColumnSelectedValue.variableType,
+ refVariableName: rowAction.actionValue.valColumnSelectedValue.value,
+ refVariableStage:
+ rowAction.actionValue.valColumnSelectedValue.refVariableStage,
}
: {}),
format: row.data.format.value as VariableTypeFormat,
@@ -481,7 +481,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
},
customState: {
...row.customState,
- selectedValue: rowAction.actionValue.selectedValue,
+ valColumnSelectedValue: rowAction.actionValue.valColumnSelectedValue,
},
}
}
@@ -515,7 +515,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
},
customState: {
...row.customState,
- selectedValue: null,
+ valColumnSelectedValue: null,
choices: [],
blockCustomValue: false,
fileInfo: {
@@ -573,7 +573,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
if (headerKey === 'val' && updatedRow.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
- actionValue: { value, selectedValue: extraData.selectedValue },
+ actionValue: { value, valColumnSelectedValue: extraData.selectedValue },
rowId: updatedRow.id,
})
} else if (
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index a386592df3..212164b2c1 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -20,6 +20,7 @@ export interface VariableDataTableSelectPickerOptionType extends SelectPickerOpt
variableType?: RefVariableType
refVariableStage?: RefVariableStageType
refVariableName?: string
+ refVariableStepIndex?: number
}
export type VariableDataKeys = 'variable' | 'format' | 'val'
@@ -31,7 +32,7 @@ export type VariableDataCustomState = {
choices: { id: number; value: string; error: string }[]
askValueAtRuntime: boolean
blockCustomValue: boolean
- selectedValue: VariableDataTableSelectPickerOptionType & Record
+ valColumnSelectedValue: VariableDataTableSelectPickerOptionType
fileInfo: {
id: number
mountDir: {
@@ -82,7 +83,7 @@ type VariableDataTableActionPropsMap = {
[VariableDataTableActionType.UPDATE_VAL_COLUMN]: {
actionValue: {
value: string
- selectedValue: VariableDataTableSelectPickerOptionType
+ valColumnSelectedValue: VariableDataTableSelectPickerOptionType
}
rowId: string | number
}
diff --git a/src/components/CIPipelineN/VariableDataTable/utils.tsx b/src/components/CIPipelineN/VariableDataTable/utils.tsx
index 3d6df2fdde..11588a935f 100644
--- a/src/components/CIPipelineN/VariableDataTable/utils.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/utils.tsx
@@ -216,7 +216,7 @@ export const getFormatColumnRowProps = ({
export const getValColumnRowProps = ({
format,
type,
- variableType,
+ variableType = RefVariableType.NEW,
value,
refVariableName,
refVariableStage,
@@ -299,12 +299,12 @@ export const checkForSystemVariable = (option: VariableDataTableSelectPickerOpti
export const getValColumnRowValue = (
format: VariableTypeFormat,
value: string,
- selectedValue: VariableDataTableSelectPickerOptionType,
+ valColumnSelectedValue: VariableDataTableSelectPickerOptionType,
) => {
- const isSystemVariable = checkForSystemVariable(selectedValue)
+ const isSystemVariable = checkForSystemVariable(valColumnSelectedValue)
const isDateFormat = !isSystemVariable && value && format === VariableTypeFormat.DATE
- return isDateFormat ? getGoLangFormattedDateWithTimezone(selectedValue.value) : value
+ return isDateFormat ? getGoLangFormattedDateWithTimezone(valColumnSelectedValue.value) : value
}
export const getEmptyVariableDataTableRow = ({
@@ -325,7 +325,7 @@ export const getEmptyVariableDataTableRow = ({
choices: [],
askValueAtRuntime: false,
blockCustomValue: false,
- selectedValue: null,
+ valColumnSelectedValue: null,
fileInfo: {
id: null,
mountDir: {
@@ -349,23 +349,37 @@ export const getVariableDataTableInitialRows = ({
...restProps
}: GetVariableDataTableInitialRowsProps): VariableDataRowType[] =>
(ioVariables || []).map(
- ({
- name,
- allowEmptyValue,
- description,
- format,
- variableType,
- value,
- refVariableName,
- refVariableStage,
- valueConstraint,
- isRuntimeArg,
- fileMountDir,
- fileReferenceId,
- defaultValue,
- id,
- }) => {
+ (
+ {
+ name,
+ allowEmptyValue,
+ description,
+ format,
+ variableType,
+ value,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ isRuntimeArg,
+ fileMountDir,
+ fileReferenceId,
+ defaultValue,
+ id,
+ },
+ index,
+ ) => {
const isInputVariableRequired = type === PluginVariableType.INPUT && !allowEmptyValue
+ const valColumnValue = getValColumnRowProps({
+ ...restProps,
+ type,
+ description,
+ format,
+ value,
+ variableType,
+ refVariableName,
+ refVariableStage,
+ valueConstraint,
+ })
return {
data: {
@@ -379,36 +393,30 @@ export const getVariableDataTableInitialRows = ({
},
},
format: getFormatColumnRowProps({ format, isCustomTask }),
- val: getValColumnRowProps({
- ...restProps,
- type,
- description,
- format,
- value,
- variableType,
- refVariableName,
- refVariableStage,
- valueConstraint,
- }),
+ val: valColumnValue,
},
customState: {
defaultValue,
isVariableRequired: isInputVariableRequired,
variableDescription: description ?? '',
- choices: (valueConstraint?.choices || []).map((choiceValue, index) => ({
- id: index,
+ choices: (valueConstraint?.choices || []).map((choiceValue, choiceIndex) => ({
+ id: choiceIndex,
value: choiceValue,
error: '',
})),
askValueAtRuntime: isRuntimeArg ?? false,
blockCustomValue: valueConstraint?.blockCustomValue ?? false,
- selectedValue: {
- label: refVariableName || value,
- value: refVariableName || value,
- refVariableName,
- refVariableStage,
- variableType: refVariableName ? RefVariableType.GLOBAL : RefVariableType.NEW,
- },
+ valColumnSelectedValue:
+ valColumnValue.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ? {
+ label: refVariableName || value,
+ value: refVariableName || value,
+ refVariableName,
+ refVariableStage,
+ variableType: refVariableName ? RefVariableType.GLOBAL : RefVariableType.NEW,
+ format,
+ }
+ : null,
fileInfo: {
id: fileReferenceId,
mountDir: { value: fileMountDir, error: '' },
@@ -420,7 +428,7 @@ export const getVariableDataTableInitialRows = ({
unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
},
},
- id,
+ id: id || index,
}
},
)
@@ -501,7 +509,7 @@ export const convertVariableDataTableToFormData = ({
askValueAtRuntime,
blockCustomValue,
choices,
- selectedValue,
+ valColumnSelectedValue,
isVariableRequired,
variableDescription,
fileInfo,
@@ -550,24 +558,24 @@ export const convertVariableDataTableToFormData = ({
}
}
- if (selectedValue) {
- if (selectedValue.refVariableStepIndex) {
+ if (valColumnSelectedValue) {
+ if (valColumnSelectedValue.refVariableStepIndex) {
variableDetail.value = ''
variableDetail.variableType = RefVariableType.FROM_PREVIOUS_STEP
- variableDetail.refVariableStepIndex = selectedValue.refVariableStepIndex
- variableDetail.refVariableName = selectedValue.label as string
- variableDetail.format = selectedValue.format
- variableDetail.refVariableStage = selectedValue.refVariableStage
- } else if (selectedValue.variableType === RefVariableType.GLOBAL) {
+ variableDetail.refVariableStepIndex = valColumnSelectedValue.refVariableStepIndex
+ variableDetail.refVariableName = valColumnSelectedValue.label as string
+ variableDetail.format = valColumnSelectedValue.format
+ variableDetail.refVariableStage = valColumnSelectedValue.refVariableStage
+ } else if (valColumnSelectedValue.variableType === RefVariableType.GLOBAL) {
variableDetail.value = ''
variableDetail.variableType = RefVariableType.GLOBAL
variableDetail.refVariableStepIndex = 0
- variableDetail.refVariableName = selectedValue.label as string
- variableDetail.format = selectedValue.format
+ variableDetail.refVariableName = valColumnSelectedValue.label as string
+ variableDetail.format = valColumnSelectedValue.format
variableDetail.refVariableStage = null
} else {
if (variableDetail.format !== VariableTypeFormat.DATE) {
- variableDetail.value = selectedValue.label as string
+ variableDetail.value = valColumnSelectedValue.label as string
}
variableDetail.variableType = RefVariableType.NEW
variableDetail.refVariableName = ''
diff --git a/src/components/CIPipelineN/VariableDataTable/validations.ts b/src/components/CIPipelineN/VariableDataTable/validations.ts
index c7f8fa6efa..ca0904f81d 100644
--- a/src/components/CIPipelineN/VariableDataTable/validations.ts
+++ b/src/components/CIPipelineN/VariableDataTable/validations.ts
@@ -14,7 +14,8 @@ export const getVariableDataTableCellValidateState = ({
value: latestValue,
}: GetValidateCellProps): DynamicDataTableCellValidationState => {
const value = latestValue ?? data[key].value
- const { variableDescription, isVariableRequired, selectedValue, askValueAtRuntime, defaultValue } = customState
+ const { variableDescription, isVariableRequired, valColumnSelectedValue, askValueAtRuntime, defaultValue } =
+ customState
const re = new RegExp(PATTERNS.VARIABLE)
if (key === 'variable') {
@@ -48,7 +49,7 @@ export const getVariableDataTableCellValidateState = ({
if (data.format.value === VariableTypeFormat.NUMBER) {
return {
- isValid: checkForSystemVariable(selectedValue) || testValueForNumber(value),
+ isValid: checkForSystemVariable(valColumnSelectedValue) || testValueForNumber(value),
errorMessages: ['Variable value is not a number'],
}
}
From f01a4f6e866ea0deae9a0dbf23381df78c8f853a Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Thu, 12 Dec 2024 18:00:45 +0530
Subject: [PATCH 24/36] chore: common-lib version bump
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 0425e3a8c9..800452ef5b 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
- "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-15",
+ "@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-16",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
diff --git a/yarn.lock b/yarn.lock
index 76d43acacc..4b9a3345b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -974,10 +974,10 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
-"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-15":
- version "1.2.4-beta-15"
- resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-15.tgz#938bee0a468e2f799e5301c8a096a2f73efaf4c1"
- integrity sha512-zGi4uY2Yc/onyuPq9B/SU881rS2TKLxxfxSpvzuTvQNWWj/Pb/FiYRd5HgT30kow+ujvZOzdbC5OLq+RP17F5A==
+"@devtron-labs/devtron-fe-common-lib@1.2.4-beta-16":
+ version "1.2.4-beta-16"
+ resolved "https://registry.yarnpkg.com/@devtron-labs/devtron-fe-common-lib/-/devtron-fe-common-lib-1.2.4-beta-16.tgz#b381bb2d07cc4e912ad08735626c04646d74bd8e"
+ integrity sha512-imTvdBZJzo4/G4kc170qd9KYXvT0JfP6uW96pY3DiUDs7SIRWOFOgArRtjTmTvq/40e8fKqcB9gCaPIRD2qAVQ==
dependencies:
"@types/react-dates" "^21.8.6"
ansi_up "^5.2.1"
From d817ac70e6e2da839e0f9c9d65f797a9cf4ae93b Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 13 Dec 2024 02:02:43 +0530
Subject: [PATCH 25/36] fix: VariableDataTable - duplicate variable name
validation fix, restrict usage of file type for oss
---
.../VariableDataTable.component.tsx | 59 +++++++-------
.../VariableDataTable/constants.ts | 24 +++++-
.../CIPipelineN/VariableDataTable/types.ts | 9 +--
.../VariableDataTable/validations.ts | 81 +++++++++++++++----
4 files changed, 120 insertions(+), 53 deletions(-)
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 4b727bddbe..1e2b0a597c 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -1,4 +1,4 @@
-import { useContext, useState, useEffect, useRef, useMemo } from 'react'
+import { useContext, useState, useEffect, useRef } from 'react'
import {
DynamicDataTable,
@@ -33,7 +33,11 @@ import {
getVariableDataTableInitialCellError,
getVariableDataTableInitialRows,
} from './utils'
-import { getVariableDataTableCellValidateState, validateVariableDataTable } from './validations'
+import {
+ getVariableDataTableCellValidateState,
+ validateVariableDataTable,
+ validateVariableDataTableVariableKeys,
+} from './validations'
import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
import { VariableConfigOverlay } from './VariableConfigOverlay'
@@ -93,22 +97,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const [rows, setRows] = useState([])
const [cellError, setCellError] = useState>({})
- // KEYS FREQUENCY MAP
- const keysFrequencyMap: Record = useMemo(
- () =>
- rows.reduce(
- (acc, curr) => {
- const currentKey = curr.data.variable.value
- if (currentKey) {
- acc[currentKey] = (acc[currentKey] || 0) + 1
- }
- return acc
- },
- {} as Record,
- ),
- [rows],
- )
-
// REFS
const initialRowsSet = useRef('')
@@ -143,7 +131,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
validateVariableDataTable({
headers,
rows,
- keysFrequencyMap,
pluginVariableType: type,
}),
)
@@ -175,11 +162,12 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const isCurrentValueValid =
!blockCustomValue ||
((!customState.valColumnSelectedValue ||
- customState.valColumnSelectedValue?.variableType === RefVariableType.NEW) &&
+ (customState.valColumnSelectedValue?.variableType !== RefVariableType.GLOBAL &&
+ customState.valColumnSelectedValue?.variableType !==
+ RefVariableType.FROM_PREVIOUS_STEP)) &&
choicesOptions.some(({ value }) => value === data.val.value))
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
key: 'val',
row,
@@ -201,6 +189,13 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
value: isCurrentValueValid ? data.val.value : '',
format: data.format.value as VariableTypeFormat,
valueConstraint: {
+ constraint: {
+ fileProperty: getUploadFileConstraints({
+ allowedExtensions: customState.fileInfo.allowedExtensions,
+ maxUploadSize: customState.fileInfo.maxUploadSize,
+ unit: customState.fileInfo.unit.label as string,
+ }),
+ },
blockCustomValue,
choices: choicesOptions.map(({ value }) => value),
},
@@ -280,13 +275,11 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
customState: { ...row.customState, isVariableRequired: rowAction.actionValue },
}
updatedCellError[row.id].variable = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
key: 'variable',
row: updatedRow,
})
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
key: 'val',
row: updatedRow,
@@ -365,7 +358,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
updatedRows = updatedRows.map((row) => {
if (row.id === rowAction.rowId && row.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD) {
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
value: rowAction.actionValue.fileName,
key: 'val',
@@ -410,18 +402,29 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
case VariableDataTableActionType.DELETE_ROW:
updatedRows = updatedRows.filter((row) => row.id !== rowAction.rowId)
delete updatedCellError[rowAction.rowId]
+ validateVariableDataTableVariableKeys({
+ rows: updatedRows,
+ cellError: updatedCellError,
+ })
break
case VariableDataTableActionType.UPDATE_ROW:
updatedRows = rows.map((row) => {
if (row.id === rowAction.rowId) {
- updatedCellError[row.id][rowAction.headerKey] = getVariableDataTableCellValidateState({
- keysFrequencyMap,
+ updatedCellError[rowAction.rowId][rowAction.headerKey] = getVariableDataTableCellValidateState({
pluginVariableType: type,
value: rowAction.actionValue,
- key: rowAction.headerKey,
row,
+ key: rowAction.headerKey,
})
+ if (rowAction.headerKey === 'variable') {
+ validateVariableDataTableVariableKeys({
+ rows,
+ cellError: updatedCellError,
+ rowId: rowAction.rowId,
+ value: rowAction.actionValue,
+ })
+ }
return {
...row,
@@ -449,7 +452,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
)
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
value: valColumnRowValue,
key: 'val',
@@ -494,7 +496,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
updatedRows = updatedRows.map((row) => {
if (row.id === rowAction.rowId && row.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
- keysFrequencyMap,
pluginVariableType: type,
key: 'val',
row,
diff --git a/src/components/CIPipelineN/VariableDataTable/constants.ts b/src/components/CIPipelineN/VariableDataTable/constants.ts
index 67e854b055..2f4ff88448 100644
--- a/src/components/CIPipelineN/VariableDataTable/constants.ts
+++ b/src/components/CIPipelineN/VariableDataTable/constants.ts
@@ -4,10 +4,13 @@ import {
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
+import { importComponentFromFELibrary } from '@Components/common'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { VariableDataKeys } from './types'
+const isFELibAvailable = importComponentFromFELibrary('isFELibAvailable', null, 'function')
+
export const getVariableDataTableHeaders = (
type: PluginVariableType,
): DynamicDataTableHeaderType[] => [
@@ -61,10 +64,14 @@ export const FORMAT_COLUMN_OPTIONS: SelectPickerOptionType[] = [
label: FORMAT_OPTIONS_MAP.DATE,
value: VariableTypeFormat.DATE,
},
- {
- label: FORMAT_OPTIONS_MAP.FILE,
- value: VariableTypeFormat.FILE,
- },
+ ...(isFELibAvailable
+ ? [
+ {
+ label: FORMAT_OPTIONS_MAP.FILE,
+ value: VariableTypeFormat.FILE,
+ },
+ ]
+ : []),
]
export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
@@ -77,3 +84,12 @@ export const FILE_UPLOAD_SIZE_UNIT_OPTIONS: SelectPickerOptionType[] = [
value: 1 / 1024,
},
]
+
+export const VARIABLE_DATA_TABLE_CELL_ERROR_MSGS = {
+ EMPTY_ROW: 'Please complete or remove this variable',
+ VARIABLE_NAME_REQUIRED: 'Variable name is required',
+ INVALID_VARIABLE_NAME: 'Invalid name. Only alphanumeric chars and (_) is allowed',
+ UNIQUE_VARIABLE_NAME: 'Variable name should be unique',
+ VARIABLE_VALUE_REQUIRED: 'Variable value is required',
+ VARIABLE_VALUE_NOT_A_NUMBER: 'Variable value is not a number',
+}
diff --git a/src/components/CIPipelineN/VariableDataTable/types.ts b/src/components/CIPipelineN/VariableDataTable/types.ts
index 212164b2c1..c4710b68e0 100644
--- a/src/components/CIPipelineN/VariableDataTable/types.ts
+++ b/src/components/CIPipelineN/VariableDataTable/types.ts
@@ -180,16 +180,15 @@ export interface GetVariableDataTableInitialRowsProps
isCustomTask: boolean
}
-export interface GetValidateCellProps {
+export type GetValidateCellProps = {
pluginVariableType: PluginVariableType
- keysFrequencyMap: Record
- key: VariableDataKeys
row: VariableDataRowType
value?: string
+ key: VariableDataKeys
+ keysFrequencyMap?: Record
}
-export interface ValidateVariableDataTableProps
- extends Pick {
+export interface ValidateVariableDataTableProps extends Pick {
rows: VariableDataRowType[]
headers: DynamicDataTableHeaderType[]
}
diff --git a/src/components/CIPipelineN/VariableDataTable/validations.ts b/src/components/CIPipelineN/VariableDataTable/validations.ts
index ca0904f81d..9bb28531f6 100644
--- a/src/components/CIPipelineN/VariableDataTable/validations.ts
+++ b/src/components/CIPipelineN/VariableDataTable/validations.ts
@@ -1,17 +1,38 @@
-import { DynamicDataTableCellValidationState, VariableTypeFormat } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ DynamicDataTableCellErrorType,
+ DynamicDataTableCellValidationState,
+ VariableTypeFormat,
+} from '@devtron-labs/devtron-fe-common-lib'
import { PluginVariableType } from '@Components/ciPipeline/types'
import { PATTERNS } from '@Config/constants'
-import { GetValidateCellProps, ValidateVariableDataTableProps } from './types'
+import { GetValidateCellProps, ValidateVariableDataTableProps, VariableDataKeys, VariableDataRowType } from './types'
import { checkForSystemVariable, testValueForNumber } from './utils'
+import { VARIABLE_DATA_TABLE_CELL_ERROR_MSGS } from './constants'
+
+export const getVariableDataTableVariableKeysFrequency = (
+ rows: VariableDataRowType[],
+ rowId?: string | number,
+ value?: string,
+) => {
+ const keysFrequencyMap: Record = rows.reduce((acc, curr) => {
+ const currentKey = curr.id === rowId ? value : curr.data.variable.value
+ if (currentKey) {
+ acc[currentKey] = (acc[currentKey] ?? 0) + 1
+ }
+ return acc
+ }, {})
+
+ return keysFrequencyMap
+}
export const getVariableDataTableCellValidateState = ({
- pluginVariableType,
- keysFrequencyMap,
row: { data, customState },
key,
value: latestValue,
+ pluginVariableType,
+ keysFrequencyMap = {},
}: GetValidateCellProps): DynamicDataTableCellValidationState => {
const value = latestValue ?? data[key].value
const { variableDescription, isVariableRequired, valColumnSelectedValue, askValueAtRuntime, defaultValue } =
@@ -22,35 +43,35 @@ export const getVariableDataTableCellValidateState = ({
const variableValue = !isVariableRequired || data.val.value
if (!value && !variableValue && !variableDescription) {
- return { errorMessages: ['Please complete or remove this variable'], isValid: false }
+ return { errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.EMPTY_ROW], isValid: false }
}
if (!value) {
- return { errorMessages: ['Variable name is required'], isValid: false }
+ return { errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.VARIABLE_NAME_REQUIRED], isValid: false }
}
if (!re.test(value)) {
return {
- errorMessages: [`Invalid name. Only alphanumeric chars and (_) is allowed`],
+ errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.INVALID_VARIABLE_NAME],
isValid: false,
}
}
if ((keysFrequencyMap[value] || 0) > 1) {
- return { errorMessages: ['Variable name should be unique'], isValid: false }
+ return { errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.UNIQUE_VARIABLE_NAME], isValid: false }
}
}
if (pluginVariableType === PluginVariableType.INPUT && key === 'val') {
const checkForVariable = isVariableRequired && !askValueAtRuntime && !defaultValue
if (checkForVariable && !value) {
- return { errorMessages: ['Variable value is required'], isValid: false }
+ return { errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.VARIABLE_VALUE_REQUIRED], isValid: false }
}
if (data.format.value === VariableTypeFormat.NUMBER) {
return {
isValid: checkForSystemVariable(valColumnSelectedValue) || testValueForNumber(value),
- errorMessages: ['Variable value is not a number'],
+ errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.VARIABLE_VALUE_NOT_A_NUMBER],
}
}
}
@@ -58,12 +79,42 @@ export const getVariableDataTableCellValidateState = ({
return { errorMessages: [], isValid: true }
}
-export const validateVariableDataTable = ({
+export const validateVariableDataTableVariableKeys = ({
rows,
- headers,
- keysFrequencyMap,
- pluginVariableType,
-}: ValidateVariableDataTableProps) => {
+ rowId,
+ value,
+ cellError,
+}: Pick & {
+ cellError: DynamicDataTableCellErrorType
+ rowId?: string | number
+ value?: string
+}) => {
+ const updatedCellError = cellError
+ const keysFrequencyMap = getVariableDataTableVariableKeysFrequency(rows, rowId, value)
+
+ rows.forEach(({ data, id }) => {
+ const cellValue = rowId === id ? value : data.variable.value
+ const variableErrorState = updatedCellError[id].variable
+ if (variableErrorState.isValid && keysFrequencyMap[cellValue] > 1) {
+ updatedCellError[id].variable = {
+ isValid: false,
+ errorMessages: [VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.UNIQUE_VARIABLE_NAME],
+ }
+ } else if (
+ keysFrequencyMap[cellValue] < 2 &&
+ variableErrorState.errorMessages[0] === VARIABLE_DATA_TABLE_CELL_ERROR_MSGS.UNIQUE_VARIABLE_NAME
+ ) {
+ updatedCellError[id].variable = {
+ isValid: true,
+ errorMessages: [],
+ }
+ }
+ })
+}
+
+export const validateVariableDataTable = ({ rows, headers, pluginVariableType }: ValidateVariableDataTableProps) => {
+ const keysFrequencyMap = getVariableDataTableVariableKeysFrequency(rows)
+
const cellError = rows.reduce((acc, row) => {
acc[row.id] = headers.reduce(
(headerAcc, { key }) => ({
From a5927fddee34a41f93864f037236ef10b17673a3 Mon Sep 17 00:00:00 2001
From: Rohit Raj
Date: Fri, 13 Dec 2024 23:50:17 +0530
Subject: [PATCH 26/36] refactor: VariableDataTable - removed component level
state - using parent state, optimised validations, various improvements &
fixes
---
src/components/CIPipelineN/CIPipeline.tsx | 1 -
.../CIPipelineN/TaskDetailComponent.tsx | 14 +-
.../CIPipelineN/VariableContainer.tsx | 4 +-
.../ValueConfigFileTippy.tsx | 6 +-
.../VariableDataTable/ValueConfigOverlay.tsx | 162 +++++-----
.../VariableConfigOverlay.tsx | 99 +++---
.../VariableDataTable.component.tsx | 289 ++++++++----------
.../VariableDataTablePopupMenu.tsx | 2 +-
.../VariableDataTable/constants.ts | 20 +-
.../CIPipelineN/VariableDataTable/types.ts | 58 ++--
.../CIPipelineN/VariableDataTable/utils.tsx | 118 +++----
.../VariableDataTable/validations.ts | 149 +++++----
src/components/cdPipeline/CDPipeline.tsx | 1 -
src/components/cdPipeline/cdpipeline.util.tsx | 100 ++++--
.../ciPipeline/ciPipeline.service.ts | 2 +-
src/components/ciPipeline/types.ts | 7 +-
src/components/ciPipeline/validationRules.ts | 70 +----
src/components/workflowEditor/types.ts | 7 +-
18 files changed, 557 insertions(+), 552 deletions(-)
diff --git a/src/components/CIPipelineN/CIPipeline.tsx b/src/components/CIPipelineN/CIPipeline.tsx
index 2616fecf83..7a3fa84066 100644
--- a/src/components/CIPipelineN/CIPipeline.tsx
+++ b/src/components/CIPipelineN/CIPipeline.tsx
@@ -438,7 +438,6 @@ export default function CIPipeline({
if (!_formDataErrorObj[stageName].steps[i]) {
_formDataErrorObj[stageName].steps.push({ isValid: true })
}
- _formDataErrorObj.triggerValidation = true
validateTask(_formData[stageName].steps[i], _formDataErrorObj[stageName].steps[i])
isStageValid = isStageValid && _formDataErrorObj[stageName].steps[i].isValid
}
diff --git a/src/components/CIPipelineN/TaskDetailComponent.tsx b/src/components/CIPipelineN/TaskDetailComponent.tsx
index c0221bf9e4..2ec2787908 100644
--- a/src/components/CIPipelineN/TaskDetailComponent.tsx
+++ b/src/components/CIPipelineN/TaskDetailComponent.tsx
@@ -273,10 +273,7 @@ export const TaskDetailComponent = () => {
{selectedStep.stepType === PluginType.INLINE ? (
-
-
Input variables
-
-
+
) : (
)}
@@ -292,14 +289,7 @@ export const TaskDetailComponent = () => {
{formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].scriptType !==
ScriptType.CONTAINERIMAGE && (
-
-
Output variables
-
-
+
)}
>
) : (
diff --git a/src/components/CIPipelineN/VariableContainer.tsx b/src/components/CIPipelineN/VariableContainer.tsx
index 6abd456c1a..5d7ce96957 100644
--- a/src/components/CIPipelineN/VariableContainer.tsx
+++ b/src/components/CIPipelineN/VariableContainer.tsx
@@ -33,9 +33,9 @@ export const VariableContainer = ({ type }: { type: PluginVariableType }) => {
]?.length || 0
useEffect(() => {
if (collapsedSection) {
- const invalidInputVariables = formDataErrorObj[activeStageName].steps[
+ const invalidInputVariables = !formDataErrorObj[activeStageName].steps[
selectedTaskIndex
- ].pluginRefStepDetail.inputVariables?.some((inputVariable) => !inputVariable.isValid)
+ ].pluginRefStepDetail.isValid
if (invalidInputVariables) {
setCollapsedSection(false) // expand input variables in case of error
}
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
index 36cf8c0f9e..da9032912a 100644
--- a/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigFileTippy.tsx
@@ -1,8 +1,10 @@
import Tippy from '@tippyjs/react'
+import { VariableType } from '@devtron-labs/devtron-fe-common-lib'
+
import { ReactComponent as Info } from '@Icons/info-filled.svg'
-export const ValueConfigFileTippy = ({ mountDir }: { mountDir: string }) => (
+export const ValueConfigFileTippy = ({ fileMountDir }: Pick) => (
(
File mount path
- {mountDir}
+ {fileMountDir}
Ensure the uploaded file name is unique to avoid conflicts or overrides.
diff --git a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
index 68dc58b6cb..f90709188f 100644
--- a/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/ValueConfigOverlay.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent } from 'react'
+import { ChangeEvent, useState } from 'react'
import {
Button,
@@ -23,51 +23,45 @@ import { ReactComponent as ICInfoOutlineGrey } from '@Icons/ic-info-outline-grey
import { ConfigOverlayProps, VariableDataTableActionType } from './types'
import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, FORMAT_OPTIONS_MAP } from './constants'
import { testValueForNumber } from './utils'
+import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlayProps) => {
const { id: rowId, data, customState } = row
- const { choices, askValueAtRuntime, blockCustomValue, fileInfo } = customState
+ const { choices: initialChoices, askValueAtRuntime, blockCustomValue, fileInfo } = customState
+
+ // STATES
+ const [choices, setChoices] = useState(initialChoices.map((choice, id) => ({ id, value: choice, error: '' })))
// CONSTANTS
const isFormatNumber = data.format.value === VariableTypeFormat.NUMBER
const isFormatBoolOrDate =
data.format.value === VariableTypeFormat.BOOL || data.format.value === VariableTypeFormat.DATE
const isFormatFile = data.format.value === VariableTypeFormat.FILE
+ const hasChoicesError = choices.some(({ error }) => !!error)
+ const hasFileMountError = !fileInfo.fileMountDir
// METHODS
const handleAddChoices = () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- actionValue: (currentChoices) => [{ value: '', id: currentChoices.length, error: '' }, ...currentChoices],
- })
+ setChoices([{ value: '', id: choices.length + 1, error: '' }, ...choices])
}
const handleChoiceChange = (choiceId: number) => (e: ChangeEvent) => {
const choiceValue = e.target.value
-
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- actionValue: (currentChoices) =>
- currentChoices.map((choice) =>
- choice.id === choiceId
- ? {
- id: choiceId,
- value: choiceValue,
- error: isFormatNumber && !testValueForNumber(choiceValue) ? 'Choice is not a number' : '',
- }
- : choice,
- ),
- })
+ setChoices(
+ choices.map((choice) =>
+ choice.id === choiceId
+ ? {
+ id: choiceId,
+ value: choiceValue,
+ error: isFormatNumber && !testValueForNumber(choiceValue) ? 'Choice is not a number' : '',
+ }
+ : choice,
+ ),
+ )
}
const handleChoiceDelete = (choiceId: number) => () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.UPDATE_CHOICES,
- rowId,
- actionValue: (currentChoices) => currentChoices.filter(({ id }) => id !== choiceId),
- })
+ setChoices(choices.filter(({ id }) => id !== choiceId))
}
const handleAllowCustomInput = () => {
@@ -87,14 +81,11 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
}
const handleFileMountChange = (e: ChangeEvent) => {
- const fileMountValue = e.target.value
+ const fileMountDir = e.target.value
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FILE_MOUNT,
rowId,
- actionValue: {
- error: !fileMountValue ? 'This field is required' : '',
- value: fileMountValue,
- },
+ actionValue: fileMountDir,
})
}
@@ -137,6 +128,14 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
}
}
+ const handlePopupClose = () => {
+ handleRowUpdateAction({
+ actionType: VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS,
+ rowId,
+ actionValue: choices.filter(({ value }) => !!value).map(({ value }) => value),
+ })
+ }
+
// RENDERERS
const renderContent = () => {
if (isFormatFile) {
@@ -146,12 +145,13 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
name="fileMount"
label="File mount path"
placeholder="Enter file mount path"
- value={fileInfo.mountDir.value}
+ value={fileInfo.fileMountDir}
onChange={handleFileMountChange}
dataTestid={`file-mount-${rowId}`}
inputWrapClassName="w-100"
isRequiredField
- error={fileInfo.mountDir.error}
+ error={hasFileMountError ? 'This field is required' : ''}
+ autoFocus
/>
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
@@ -218,21 +218,22 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
/>
- {choices.map(({ id, value, error }) => (
+ {choices.map(({ id, value, error }, index) => (
}
variant={ButtonVariantType.borderLess}
@@ -272,16 +273,48 @@ export const ValueConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlay
}
return (
- <>
- {renderContent()}
-
- {!!choices.length && (
+
+ <>
+ {renderContent()}
+
+ {!!choices.length && (
+
+
+ Allow custom input
+
+ Allow entering any value other than provided choices
+
+
+ }
+ >
+ Allow Custom input
+
+
+ )}
- Allow custom input
- Allow entering any value other than provided choices
+ Ask value at runtime
+
+ Value can be provided at runtime. Entered value will be pre-filled as default
+
}
>
-
Allow Custom input
+
Ask value at runtime
- )}
-
-
- Ask value at runtime
-
- Value can be provided at runtime. Entered value will be pre-filled as default
-
-
- }
- >
-
Ask value at runtime
-
-
-
- >
+
+ >
+
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
index 62a8d9c5a8..589abb4196 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableConfigOverlay.tsx
@@ -1,8 +1,16 @@
import { ChangeEvent } from 'react'
-import { Checkbox, CHECKBOX_VALUE, CustomInput, ResizableTextarea, Tooltip } from '@devtron-labs/devtron-fe-common-lib'
+import {
+ Checkbox,
+ CHECKBOX_VALUE,
+ CustomInput,
+ InputOutputVariablesHeaderKeys,
+ ResizableTextarea,
+ Tooltip,
+} from '@devtron-labs/devtron-fe-common-lib'
import { ConfigOverlayProps, VariableDataTableActionType } from './types'
+import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
export const VariableConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOverlayProps) => {
const { id: rowId, data, customState } = row
@@ -13,7 +21,7 @@ export const VariableConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOver
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_ROW,
rowId,
- headerKey: 'variable',
+ headerKey: InputOutputVariablesHeaderKeys.VARIABLE,
actionValue: e.target.value,
})
}
@@ -35,50 +43,53 @@ export const VariableConfigOverlay = ({ row, handleRowUpdateAction }: ConfigOver
}
return (
- <>
-
-
-
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
-
-
+ <>
+
+
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
+
+
+
-
-
-
-
- Value is required
- Get this tooltip from Utkarsh
-
- }
+
+
- Value is required
-
-
-
- >
+
+ Value is required
+ Get this tooltip from Utkarsh
+
+ }
+ >
+ Value is required
+
+
+
+ >
+
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
index 1e2b0a597c..4e22ad4eff 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTable.component.tsx
@@ -1,25 +1,32 @@
-import { useContext, useState, useEffect, useRef } from 'react'
+import { useContext, useMemo } from 'react'
import {
+ Button,
+ ButtonVariantType,
DynamicDataTable,
DynamicDataTableCellErrorType,
DynamicDataTableProps,
DynamicDataTableRowDataType,
+ InputOutputVariablesHeaderKeys,
PluginType,
RefVariableType,
VariableType,
VariableTypeFormat,
} from '@devtron-labs/devtron-fe-common-lib'
+import { ReactComponent as ICAdd } from '@Icons/ic-add.svg'
import { pipelineContext } from '@Components/workflowEditor/workflowEditor'
import { PluginVariableType } from '@Components/ciPipeline/types'
-import { FILE_UPLOAD_SIZE_UNIT_OPTIONS, getVariableDataTableHeaders } from './constants'
+import {
+ FILE_UPLOAD_SIZE_UNIT_OPTIONS,
+ getVariableDataTableHeaders,
+ VARIABLE_DATA_TABLE_EMPTY_ROW_MESSAGE,
+} from './constants'
import {
GetValColumnRowPropsType,
HandleRowUpdateActionProps,
VariableDataCustomState,
- VariableDataKeys,
VariableDataRowType,
VariableDataTableActionType,
VariableDataTableProps,
@@ -31,15 +38,10 @@ import {
getValColumnRowProps,
getValColumnRowValue,
getVariableDataTableInitialCellError,
- getVariableDataTableInitialRows,
+ getVariableDataTableRows,
} from './utils'
-import {
- getVariableDataTableCellValidateState,
- validateVariableDataTable,
- validateVariableDataTableVariableKeys,
-} from './validations'
+import { getVariableDataTableCellValidateState, validateVariableDataTableVariableKeys } from './validations'
-import { VariableDataTablePopupMenu } from './VariableDataTablePopupMenu'
import { VariableConfigOverlay } from './VariableConfigOverlay'
import { ValueConfigOverlay } from './ValueConfigOverlay'
import { ValueConfigFileTippy } from './ValueConfigFileTippy'
@@ -80,6 +82,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
valueConstraint: null,
}
+ const isInputPluginVariable = type === PluginVariableType.INPUT
const currentStepTypeVariable =
formData[activeStageName].steps[selectedTaskIndex].stepType === PluginType.INLINE
? 'inlineStepDetail'
@@ -87,66 +90,45 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const ioVariables: VariableType[] =
formData[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
- type === PluginVariableType.INPUT ? 'inputVariables' : 'outputVariables'
+ isInputPluginVariable ? 'inputVariables' : 'outputVariables'
]
- const isTableValid =
- formDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable].isValid ?? true
-
- // STATES
- const [rows, setRows] = useState([])
- const [cellError, setCellError] = useState>({})
+ const ioVariablesError =
+ formDataErrorObj[activeStageName].steps[selectedTaskIndex][currentStepTypeVariable][
+ isInputPluginVariable ? 'inputVariables' : 'outputVariables'
+ ]
- // REFS
- const initialRowsSet = useRef('')
+ // TABLE ROWS
+ const rows = useMemo(
+ () =>
+ getVariableDataTableRows({
+ ioVariables,
+ isCustomTask,
+ type,
+ activeStageName,
+ formData,
+ globalVariables,
+ selectedTaskIndex,
+ inputVariablesListFromPrevStep,
+ isCdPipeline,
+ }),
+ [ioVariables],
+ )
- useEffect(() => {
- // SETTING INITIAL ROWS & ERROR STATE
- const initialRows = getVariableDataTableInitialRows({
- ioVariables,
- isCustomTask,
- type,
- activeStageName,
- formData,
- globalVariables,
- selectedTaskIndex,
- inputVariablesListFromPrevStep,
- isCdPipeline,
- })
- const updatedCellError = getVariableDataTableInitialCellError(initialRows, headers)
-
- setRows(initialRows)
- setCellError(updatedCellError)
-
- initialRowsSet.current = 'set'
- }, [])
-
- useEffect(() => {
- // Validate the table when:
- // 1. Rows have been initialized (`initialRowsSet.current` is 'set' & rows is not empty).
- // 2. Validation is explicitly triggered (`formDataErrorObj.triggerValidation` is true)
- // or the table is currently invalid (`!isTableValid` -> this is only triggered on mount)
- if (initialRowsSet.current === 'set' && rows.length && (formDataErrorObj.triggerValidation || !isTableValid)) {
- setCellError(
- validateVariableDataTable({
- headers,
- rows,
- pluginVariableType: type,
- }),
- )
- // Reset the triggerValidation flag after validation is complete.
- setFormDataErrorObj((prevState) => ({
- ...prevState,
- triggerValidation: false,
- }))
- }
- }, [initialRowsSet.current, formDataErrorObj.triggerValidation])
+ // TABLE CELL ERROR
+ const cellError = useMemo>(
+ () =>
+ Object.keys(ioVariablesError).length
+ ? ioVariablesError
+ : getVariableDataTableInitialCellError(rows, headers),
+ [ioVariablesError, rows],
+ )
// METHODS
const handleRowUpdateAction = (rowAction: HandleRowUpdateActionProps) => {
const { actionType } = rowAction
let updatedRows = rows
- const updatedCellError = structuredClone(cellError)
+ const updatedCellError = cellError
switch (actionType) {
case VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS:
@@ -154,22 +136,20 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
const { id, data, customState } = row
if (id === rowAction.rowId) {
- // FILTERING EMPTY CHOICE VALUES
- const choicesOptions = customState.choices.filter(({ value }) => !!value)
+ const choicesOptions = rowAction.actionValue
// RESETTING TO DEFAULT STATE IF CHOICES ARE EMPTY
const blockCustomValue = !!choicesOptions.length && row.customState.blockCustomValue
const isCurrentValueValid =
!blockCustomValue ||
((!customState.valColumnSelectedValue ||
- (customState.valColumnSelectedValue?.variableType !== RefVariableType.GLOBAL &&
- customState.valColumnSelectedValue?.variableType !==
- RefVariableType.FROM_PREVIOUS_STEP)) &&
- choicesOptions.some(({ value }) => value === data.val.value))
+ !customState.valColumnSelectedValue?.variableType ||
+ customState.valColumnSelectedValue.variableType === RefVariableType.NEW) &&
+ choicesOptions.some((value) => value === data.val.value))
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
pluginVariableType: type,
- key: 'val',
+ key: InputOutputVariablesHeaderKeys.VALUE,
row,
})
@@ -197,7 +177,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
}),
},
blockCustomValue,
- choices: choicesOptions.map(({ value }) => value),
+ choices: choicesOptions,
},
}),
},
@@ -214,20 +194,6 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
})
break
- case VariableDataTableActionType.UPDATE_CHOICES:
- updatedRows = updatedRows.map((row) =>
- row.id === rowAction.rowId
- ? {
- ...row,
- customState: {
- ...row.customState,
- choices: rowAction.actionValue(row.customState.choices),
- },
- }
- : row,
- )
- break
-
case VariableDataTableActionType.UPDATE_ALLOW_CUSTOM_INPUT:
updatedRows = updatedRows.map((row) =>
row.id === rowAction.rowId
@@ -276,12 +242,12 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
}
updatedCellError[row.id].variable = getVariableDataTableCellValidateState({
pluginVariableType: type,
- key: 'variable',
+ key: InputOutputVariablesHeaderKeys.VARIABLE,
row: updatedRow,
})
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
pluginVariableType: type,
- key: 'val',
+ key: InputOutputVariablesHeaderKeys.VALUE,
row: updatedRow,
})
@@ -299,7 +265,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...row,
customState: {
...row.customState,
- fileInfo: { ...row.customState.fileInfo, mountDir: rowAction.actionValue },
+ fileInfo: { ...row.customState.fileInfo, fileMountDir: rowAction.actionValue },
},
}
: row,
@@ -360,7 +326,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
pluginVariableType: type,
value: rowAction.actionValue.fileName,
- key: 'val',
+ key: InputOutputVariablesHeaderKeys.VALUE,
row,
})
@@ -381,7 +347,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
...row.customState,
fileInfo: {
...row.customState.fileInfo,
- id: rowAction.actionValue.fileReferenceId,
+ fileReferenceId: rowAction.actionValue.fileReferenceId,
},
},
}
@@ -417,7 +383,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
row,
key: rowAction.headerKey,
})
- if (rowAction.headerKey === 'variable') {
+ if (rowAction.headerKey === InputOutputVariablesHeaderKeys.VARIABLE) {
validateVariableDataTableVariableKeys({
rows,
cellError: updatedCellError,
@@ -454,7 +420,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
pluginVariableType: type,
value: valColumnRowValue,
- key: 'val',
+ key: InputOutputVariablesHeaderKeys.VALUE,
row,
})
@@ -477,7 +443,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
format: row.data.format.value as VariableTypeFormat,
valueConstraint: {
blockCustomValue: row.customState.blockCustomValue,
- choices: row.customState.choices.map((choice) => choice.value),
+ choices: row.customState.choices,
},
}),
},
@@ -497,7 +463,7 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
if (row.id === rowAction.rowId && row.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
updatedCellError[row.id].val = getVariableDataTableCellValidateState({
pluginVariableType: type,
- key: 'val',
+ key: InputOutputVariablesHeaderKeys.VALUE,
row,
})
@@ -520,13 +486,10 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
choices: [],
blockCustomValue: false,
fileInfo: {
- id: null,
+ fileReferenceId: null,
allowedExtensions: '',
maxUploadSize: '',
- mountDir: {
- value: '/devtroncd',
- error: '',
- },
+ fileMountDir: '/devtroncd',
unit: FILE_UPLOAD_SIZE_UNIT_OPTIONS[0],
},
},
@@ -553,32 +516,30 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
})
setFormDataErrorObj(updatedFormDataErrorObj)
setFormData(updatedFormData)
-
- setRows(updatedRows)
- setCellError(updatedCellError)
}
- const dataTableHandleAddition = () => {
+ const handleRowAdd = () => {
handleRowUpdateAction({
actionType: VariableDataTableActionType.ADD_ROW,
rowId: Math.floor(new Date().valueOf() * Math.random()),
})
}
- const dataTableHandleChange: DynamicDataTableProps['onRowEdit'] = async (
- updatedRow,
- headerKey,
- value,
- extraData,
- ) => {
- if (headerKey === 'val' && updatedRow.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT) {
+ const handleRowEdit: DynamicDataTableProps<
+ InputOutputVariablesHeaderKeys,
+ VariableDataCustomState
+ >['onRowEdit'] = async (updatedRow, headerKey, value, extraData) => {
+ if (
+ headerKey === InputOutputVariablesHeaderKeys.VALUE &&
+ updatedRow.data.val.type === DynamicDataTableRowDataType.SELECT_TEXT
+ ) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_VAL_COLUMN,
actionValue: { value, valColumnSelectedValue: extraData.selectedValue },
rowId: updatedRow.id,
})
} else if (
- headerKey === 'val' &&
+ headerKey === InputOutputVariablesHeaderKeys.VALUE &&
updatedRow.data.val.type === DynamicDataTableRowDataType.FILE_UPLOAD &&
extraData.files.length
) {
@@ -610,7 +571,10 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
rowId: updatedRow.id,
})
}
- } else if (headerKey === 'format' && updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN) {
+ } else if (
+ headerKey === InputOutputVariablesHeaderKeys.FORMAT &&
+ updatedRow.data.format.type === DynamicDataTableRowDataType.DROPDOWN
+ ) {
handleRowUpdateAction({
actionType: VariableDataTableActionType.UPDATE_FORMAT_COLUMN,
actionValue: value as VariableTypeFormat,
@@ -626,76 +590,81 @@ export const VariableDataTable = ({ type, isCustomTask = false }: VariableDataTa
}
}
- const dataTableHandleDelete: DynamicDataTableProps['onRowDelete'] = (
- row,
- ) => {
+ const handleRowDelete: DynamicDataTableProps<
+ InputOutputVariablesHeaderKeys,
+ VariableDataCustomState
+ >['onRowDelete'] = (row) => {
handleRowUpdateAction({
actionType: VariableDataTableActionType.DELETE_ROW,
rowId: row.id,
})
}
- const onActionButtonPopupClose = (rowId: string | number) => () => {
- handleRowUpdateAction({
- actionType: VariableDataTableActionType.ADD_CHOICES_TO_VALUE_COLUMN_OPTIONS,
- rowId,
- })
- }
-
// RENDERERS
const actionButtonRenderer = (row: VariableDataRowType) => (
- !!error))
- }
- >
-
-
+
)
const getTrailingCellIconForVariableColumn = (row: VariableDataRowType) =>
- isCustomTask && type === PluginVariableType.INPUT ? (
-
-
-
+ isCustomTask && isInputPluginVariable ? (
+
) : null
const getTrailingCellIconForValueColumn = (row: VariableDataRowType) =>
- row.data.format.value === VariableTypeFormat.FILE ? (
-
+ isInputPluginVariable && row.data.format.value === VariableTypeFormat.FILE ? (
+
) : null
- const trailingCellIcon: DynamicDataTableProps['trailingCellIcon'] = {
+ const trailingCellIcon: DynamicDataTableProps['trailingCellIcon'] = {
variable: getTrailingCellIconForVariableColumn,
val: getTrailingCellIconForValueColumn,
}
return (
-
- key={initialRowsSet.current}
- headers={headers}
- rows={rows}
- cellError={cellError}
- readOnly={!isCustomTask && type === PluginVariableType.OUTPUT}
- isAdditionNotAllowed={!isCustomTask}
- isDeletionNotAllowed={!isCustomTask}
- trailingCellIcon={trailingCellIcon}
- onRowEdit={dataTableHandleChange}
- onRowDelete={dataTableHandleDelete}
- onRowAdd={dataTableHandleAddition}
- {...(type === PluginVariableType.INPUT
- ? {
- actionButtonConfig: {
- renderer: actionButtonRenderer,
- key: 'val',
- position: 'end',
- },
- }
- : {})}
- />
+
+ {isCustomTask && (
+
+
+ {isInputPluginVariable ? 'Input variables' : 'Output variables'}
+
+ {!rows.length && (
+ }
+ onClick={handleRowAdd}
+ />
+ )}
+
+ )}
+ {rows.length ? (
+
+ headers={headers}
+ rows={rows}
+ cellError={cellError}
+ readOnly={!isCustomTask && !isInputPluginVariable}
+ isAdditionNotAllowed={!isCustomTask}
+ isDeletionNotAllowed={!isCustomTask}
+ trailingCellIcon={trailingCellIcon}
+ onRowEdit={handleRowEdit}
+ onRowDelete={handleRowDelete}
+ onRowAdd={handleRowAdd}
+ {...(isInputPluginVariable
+ ? {
+ actionButtonConfig: {
+ renderer: actionButtonRenderer,
+ key: InputOutputVariablesHeaderKeys.VALUE,
+ position: 'end',
+ },
+ }
+ : {})}
+ />
+ ) : (
+
+
{VARIABLE_DATA_TABLE_EMPTY_ROW_MESSAGE[type]}
+
+ )}
+
)
}
diff --git a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
index a7f118f204..d447f12e2a 100644
--- a/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
+++ b/src/components/CIPipelineN/VariableDataTable/VariableDataTablePopupMenu.tsx
@@ -47,7 +47,7 @@ export const VariableDataTablePopupMenu = ({
heading={{heading}
}
Icon={showIcon ? ICSlidersVertical : null}
iconSize={16}
- additionalContent={{children}
}
+ additionalContent={visible && {children}
}
>