diff --git a/src/components/JobWizard/JobWizard.js b/src/components/JobWizard/JobWizard.js index 52e991b54..043629bf1 100644 --- a/src/components/JobWizard/JobWizard.js +++ b/src/components/JobWizard/JobWizard.js @@ -411,7 +411,7 @@ const JobWizard = ({ ref: scheduleButtonRef }, { - id:'run-btn', + id: 'run-btn', label: mode === PANEL_EDIT_MODE ? 'Save' @@ -483,11 +483,14 @@ const JobWizard = ({ /> diff --git a/src/components/JobWizard/JobWizard.util.js b/src/components/JobWizard/JobWizard.util.js index 69f2f1f44..f575c80fa 100644 --- a/src/components/JobWizard/JobWizard.util.js +++ b/src/components/JobWizard/JobWizard.util.js @@ -29,7 +29,7 @@ import { merge, omit, set, - unionBy + some } from 'lodash' import { ADVANCED_STEP, @@ -83,7 +83,6 @@ import { generateObjectFromKeyValue, parseObjectToKeyValue } from 'igz-controls/ import { getDefaultSchedule, scheduleDataInitialState } from '../SheduleWizard/scheduleWizard.util' import { getErrorDetail } from 'igz-controls/utils/common.util' import { getPreemptionMode } from '../../utils/getPreemptionMode' -import { isEveryObjectValueEmpty } from '../../utils/isEveryObjectValueEmpty' import { trimSplit } from '../../utils' const volumeTypesMap = { @@ -105,21 +104,21 @@ export const generateJobWizardData = ( defaultData, currentProjectName, isEditMode, - prePopulatedData + prePopulatedData, + selectedVersion ) => { - const functions = selectedFunctionData.functions - const functionInfo = getFunctionInfo(selectedFunctionData) + const functionInfo = getFunctionInfo(selectedFunctionData, selectedVersion) const defaultResources = frontendSpec?.default_function_pod_resources ?? {} - const functionParameters = getFunctionParameters(functions, functionInfo.handler) - const [functionPriorityClassName] = getFunctionPriorityClass(functions) - const [limits] = getLimits(functions) - const [requests] = getRequests(functions) - const environmentVariables = getEnvironmentVariables(functions) - const [preemptionMode] = getFunctionPreemptionMode(functions) + const functionParameters = getFunctionParameters(functionInfo.function, functionInfo.handler) + const functionPriorityClassName = getFunctionPriorityClass(functionInfo.function) + const limits = getLimits(functionInfo.function) + const requests = getRequests(functionInfo.function) + const environmentVariables = getEnvironmentVariables(functionInfo.function) + const preemptionMode = getFunctionPreemptionMode(functionInfo.function) const jobPriorityClassName = functionPriorityClassName || frontendSpec.default_function_priority_class_name || '' - const nodeSelectorTable = getNodeSelectors(functions) - const volumesTable = getVolumesData(functions) + const nodeSelectorTable = getNodeSelectors(functionInfo.function) + const volumesTable = getVolumes(functionInfo.function) const gpuType = getLimitsGpuType(limits) const scheduleData = defaultData?.schedule ? getDefaultSchedule(defaultData.schedule) @@ -203,15 +202,15 @@ export const generateJobWizardData = ( export const generateJobWizardDefaultData = ( frontendSpec, - selectedFunctionData, + selectedFunction, defaultData, currentProjectName, isEditMode ) => { if (isEmpty(defaultData)) return [{}, {}] - const runInfo = getRunDefaultInfo(defaultData, selectedFunctionData) - const functionParameters = getFunctionDefaultParameters(selectedFunctionData, runInfo.handler) + const runInfo = getRunDefaultInfo(defaultData, selectedFunction) + const functionParameters = getFunctionDefaultParameters(selectedFunction, runInfo.handler) const [predefinedParameters, customParameters] = parseDefaultParameters( functionParameters, defaultData.task.spec.parameters, @@ -241,7 +240,7 @@ export const generateJobWizardDefaultData = ( handler: runInfo.handler, handlerData: runInfo.handlerData, labels: runInfo.labels, - image: parseImageData(selectedFunctionData, frontendSpec, currentProjectName) + image: parseImageData(selectedFunction, frontendSpec, currentProjectName) }, [DATA_INPUTS_STEP]: { dataInputsTable: parseDefaultDataInputs(functionParameters, defaultData.task.spec.inputs) @@ -299,12 +298,9 @@ export const generateJobWizardDefaultData = ( return [jobFormData, jobAdditionalData] } -export const getHandlerData = (selectedFunctionData, handler) => { - const currentFunction = selectedFunctionData?.functions - ? chain(selectedFunctionData.functions).orderBy('metadata.updated', 'desc').get(0).value() - : selectedFunctionData - const handlerData = get(currentFunction, ['spec', 'entry_points', handler], {}) - const outputs = (handlerData?.outputs ?? []).filter(output => !isEveryObjectValueEmpty(output)) +export const getHandlerData = (selectedFunction, handler) => { + const handlerData = get(selectedFunction, ['spec', 'entry_points', handler], {}) + const outputs = (handlerData?.outputs ?? []).filter(output => output.type) return { doc: handlerData?.doc, @@ -313,109 +309,96 @@ export const getHandlerData = (selectedFunctionData, handler) => { } } -const getFunctionInfo = selectedFunctionData => { +const getFunctionInfo = (selectedFunctionData, preSelectedVersion) => { const functions = selectedFunctionData?.functions if (!isEmpty(functions)) { const versionOptions = getVersionOptions(functions) - const handlerOptions = getHandlerOptions(functions) - const { defaultVersion, defaultHandler } = getDefaultHandlerAndVersion( - versionOptions, - handlerOptions, - functions - ) - const currentFunctionVersion = selectedFunctionData.tag || defaultVersion - const currentFunction = - functions.find(func => func.metadata.tag === currentFunctionVersion) ?? functions[0] + const selectedVersion = some(versionOptions, { id: preSelectedVersion }) + ? preSelectedVersion + : getDefaultVersion(versionOptions) + const handlerOptions = getHandlerOptions(functions, selectedVersion) + const defaultHandler = getDefaultHandler(handlerOptions, functions, selectedVersion) + const selectedFunction = getSelectedFunction(functions, selectedVersion) return { name: selectedFunctionData.name, handler: defaultHandler, - version: currentFunctionVersion, - handlerData: getHandlerData(currentFunction, defaultHandler), + version: selectedVersion, + handlerData: getHandlerData(selectedFunction, defaultHandler), handlerOptions, versionOptions, - function: currentFunction || {} + function: selectedFunction || {} } } } -const getRunDefaultInfo = (defaultData, selectedFunctionData) => { +const getRunDefaultInfo = (defaultData, selectedFunction) => { return { labels: parseChipsData(defaultData.task?.metadata?.labels), name: defaultData.task?.metadata?.name || '', handler: defaultData.task?.spec?.handler, - handlerData: getHandlerData(selectedFunctionData, defaultData.task?.spec?.handler), + handlerData: getHandlerData(selectedFunction, defaultData.task?.spec?.handler), handlerOptions: [], version: '', versionOptions: [] } } -const getHandlerOptions = selectedFunctions => { +const getHandlerOptions = (selectedFunctions, selectedVersion) => { return chain(selectedFunctions) - .map(func => Object.values(func.spec?.entry_points ?? {})) + .find(func => func.metadata.tag === selectedVersion) + .get('spec.entry_points', {}) + .values() .flatten() .map(entry_point => ({ label: entry_point.name, id: entry_point.name, subLabel: entry_point.doc })) - .uniqBy('label') .value() } const getVersionOptions = selectedFunctions => { - const versionOptions = unionBy( - selectedFunctions.map(func => { + const versionOptions = chain(selectedFunctions) + .map(func => { + if (!func.metadata.tag) return null + return { label: func.metadata.tag || 'N/A', id: func.metadata.tag || TAG_NA } - }), - 'id' - ) + }) + .compact() + .unionBy('id') + .value() return versionOptions.length ? versionOptions : [{ label: 'N/A', id: TAG_NA }] } -const getDefaultHandler = (handlerOptions, selectedFunctions) => { +const getDefaultHandler = (handlerOptions, selectedFunctions, selectedVersion) => { let handler = '' - - const latestFunction = selectedFunctions.find(item => item.metadata.tag === TAG_LATEST) + const selectedFunction = selectedFunctions.find(item => item.metadata.tag === selectedVersion) if (handlerOptions.length) { handler = handlerOptions[0]?.id - } else if (latestFunction) { - handler = latestFunction.spec.default_handler || FUNCTION_DEFAULT_HANDLER } else { - handler = selectedFunctions[0]?.spec.default_handler || FUNCTION_DEFAULT_HANDLER + handler = selectedFunction?.spec?.default_handler || FUNCTION_DEFAULT_HANDLER } return handler } -const getDefaultHandlerAndVersion = (versionOptions, handlerOptions, selectedFunctions) => { - const defaultVersion = - versionOptions.find(version => version.id === TAG_LATEST)?.id || versionOptions[0].id || '' - - const defaultHandler = getDefaultHandler(handlerOptions, selectedFunctions) +const getDefaultVersion = versionOptions => { + return versionOptions.find(version => version.id === TAG_LATEST)?.id || versionOptions[0].id || '' +} - return { - defaultVersion, - defaultHandler - } +const getSelectedFunction = (functions, selectedVersion) => { + return functions.find(func => func.metadata.tag === selectedVersion) ?? functions[0] } export const getFunctionParameters = (selectedFunction, handler) => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.entry_points ? func.spec.entry_points[handler]?.parameters ?? [] : [] - }) - .flatten() - .unionBy('name') - .value() + return get(selectedFunction, ['spec', 'entry_points', handler, 'parameters'], []) } export const getFunctionDefaultParameters = (selectedFunction, handler) => { @@ -425,72 +408,27 @@ export const getFunctionDefaultParameters = (selectedFunction, handler) => { } const getFunctionPriorityClass = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.priority_class_name - }) - .flatten() - .unionBy('name') - .value() + return get(selectedFunction, 'spec.priority_class_name', '') } const getLimits = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.resources?.limits ? func.spec.resources?.limits : {} - }) - .filter(limits => !isEveryObjectValueEmpty(limits)) - .flatten() - .unionBy('name') - .value() + return get(selectedFunction, 'spec.resources.limits', {}) } const getRequests = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.resources?.requests ? func.spec.resources.requests : {} - }) - .filter(request => !isEveryObjectValueEmpty(request)) - .flatten() - .unionBy('name') - .value() + return get(selectedFunction, 'spec.resources.requests', {}) } const getEnvironmentVariables = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.env ?? [] - }) - .flatten() - .unionBy('name') - .value() + return get(selectedFunction, 'spec.env', []) } const getFunctionPreemptionMode = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.preemption_mode ?? '' - }) - .flatten() - .unionBy('key') - .value() + return get(selectedFunction, 'spec.preemption_mode', '') } const getNodeSelectors = selectedFunction => { - return chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => { - return func.spec.node_selector ?? {} - }) - .map(parseObjectToKeyValue) - .flatten() - .unionBy('data.key') - .value() + return parseObjectToKeyValue(get(selectedFunction, 'spec.node_selector', {})) } const getVolumeType = volume => { @@ -505,20 +443,9 @@ const getVolumeType = volume => { } } -const getVolumesData = selectedFunction => { - const volumes = chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => func.spec.volumes ?? []) - .flatten() - .unionBy('name') - .value() - - const volumeMounts = chain(selectedFunction) - .orderBy('metadata.updated', 'desc') - .map(func => func.spec.volume_mounts ?? []) - .flatten() - .unionBy('name') - .value() +const getVolumes = selectedFunction => { + const volumes = get(selectedFunction, 'spec.volumes', []) + const volumeMounts = get(selectedFunction, 'spec.volume_mounts', []) return parseVolumes(volumes, volumeMounts) } @@ -922,13 +849,19 @@ const generateParameters = parametersTableData => { parametersTableData?.predefined ?.filter(parameter => !parameter.data.isHyper && parameter.data.isChecked) .forEach(parameter => { - parameters[parameter.data.name] = convertParameterValue(parameter.data.value, parameter.data.type) + parameters[parameter.data.name] = convertParameterValue( + parameter.data.value, + parameter.data.type + ) }) parametersTableData?.custom ?.filter(parameter => !parameter.data.isHyper && parameter.data.isChecked) .forEach(parameter => { - parameters[parameter.data.name] = convertParameterValue(parameter.data.value, parameter.data.type) + parameters[parameter.data.name] = convertParameterValue( + parameter.data.value, + parameter.data.type + ) }) return parameters @@ -1061,10 +994,10 @@ export const generateJobRequestData = ( mode, isSchedule ) => { - let selectedFunction = selectedFunctionData?.functions?.find( - func => func.metadata.tag === formData[RUN_DETAILS_STEP].version + const selectedFunction = getSelectedFunction( + selectedFunctionData?.functions, + formData[RUN_DETAILS_STEP].version ) - selectedFunction ??= selectedFunctionData?.functions?.[0] const [volume_mounts, volumes] = generateVolumes(formData[RESOURCES_STEP].volumesTable) const postData = { diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js b/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js index 0713a81f9..fba489bff 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardRunDetails/JobWizardRunDetails.js @@ -19,7 +19,7 @@ such restriction. */ import React, { useMemo, useState } from 'react' import PropTypes from 'prop-types' -import { get, isEmpty, set } from 'lodash' +import { get, isEmpty, omit, set } from 'lodash' import { OnChange } from 'react-final-form-listeners' import { FieldArray } from 'react-final-form-arrays' @@ -47,6 +47,7 @@ import { getChipOptions } from '../../../../utils/getChipOptions' import { getValidationRules } from 'igz-controls/utils/validation.util' import { openPopUp } from 'igz-controls/utils/common.util' import { + generateJobWizardData, getFunctionParameters, getHandlerData, parseDataInputs, @@ -57,17 +58,22 @@ import './jobWizardRunDetails.scss' const JobWizardRunDetails = ({ formState, + frontendSpec, isBatchInference, isEditMode, jobAdditionalData, + params, prePopulatedData, selectedFunctionData, + setJobData, stepIsActive }) => { const handlerPath = `${RUN_DETAILS_STEP}.handler` + const versionPath = `${RUN_DETAILS_STEP}.version` const imageSourcePath = `${RUN_DETAILS_STEP}.image.imageSource` const outputsPath = `${RUN_DETAILS_STEP}.handlerData.outputs` - const [spyOnHandlerChange, setspyOnHandlerChange] = useState(true) + const [spyOnHandlerChange, setSpyOnHandlerChange] = useState(true) + const [spyOnVersionChange, setSpyOnVersionChange] = useState(true) const commonImageWarningMsg = 'The image must include all the software packages that are required to run the function. ' + 'For example, for an XGBoost model, ensure that the image includes the correct XGboost package and version' @@ -80,13 +86,60 @@ const JobWizardRunDetails = ({ [formState.values, imageSourcePath] ) + const handleVersionChange = version => { + const [jobFormData, jobAdditionalData] = generateJobWizardData( + frontendSpec, + selectedFunctionData, + null, + params.projectName, + isEditMode, + null, + version + ) + setJobData(formState, jobFormData, jobAdditionalData) + + setSpyOnVersionChange(true) + } + + const onVersionChange = (currentVersion, prevVersion) => { + setSpyOnVersionChange(false) + const values = omit(formState.values, [versionPath]) + const initialValues = omit(formState.initialValues, [versionPath]) + + if (areFormValuesChanged(values, initialValues)) { + openPopUp(ConfirmDialog, { + cancelButton: { + label: 'Cancel', + variant: TERTIARY_BUTTON, + handler: () => { + formState.form.change(versionPath, prevVersion) + setSpyOnVersionChange(true) + } + }, + confirmButton: { + label: 'OK', + variant: SECONDARY_BUTTON, + handler: () => { + handleVersionChange(currentVersion) + } + }, + header: 'Are you sure?', + message: 'You have unsaved changes. All changes will be lost' + }) + } else { + handleVersionChange(currentVersion) + } + } + const handleHandlerChange = handler => { - setspyOnHandlerChange(true) + const selectedVersionedFunction = selectedFunctionData.functions?.find( + func => func.metadata.tag === formState.values[RUN_DETAILS_STEP].version + ) - const functionParameters = getFunctionParameters(selectedFunctionData.functions, handler) + const functionParameters = getFunctionParameters(selectedVersionedFunction, handler) const dataInputs = parseDataInputs(functionParameters, prePopulatedData?.dataInputs) const predefinedParameters = parsePredefinedParameters(functionParameters) - const handlerData = getHandlerData(selectedFunctionData, handler) + const handlerData = getHandlerData(selectedVersionedFunction, handler) set(formState.initialValues, `${DATA_INPUTS_STEP}.dataInputsTable`, dataInputs) set( @@ -102,10 +155,12 @@ const JobWizardRunDetails = ({ `${PARAMETERS_STEP}.parametersTable.custom`, get(formState.initialValues, `${PARAMETERS_STEP}.parametersTable.custom`, []) ) + + setSpyOnHandlerChange(true) } - const onHandlerChange = (value, prevValue) => { - setspyOnHandlerChange(false) + const onHandlerChange = (currentHandler, prevHandler) => { + setSpyOnHandlerChange(false) const dataInputsAreChanged = areFormValuesChanged( formState.initialValues[DATA_INPUTS_STEP].dataInputsTable, @@ -126,22 +181,22 @@ const JobWizardRunDetails = ({ label: 'Cancel', variant: TERTIARY_BUTTON, handler: () => { - formState.form.change(handlerPath, prevValue) - setspyOnHandlerChange(true) + formState.form.change(handlerPath, prevHandler) + setSpyOnHandlerChange(true) } }, confirmButton: { label: 'OK', variant: SECONDARY_BUTTON, handler: () => { - handleHandlerChange(value) + handleHandlerChange(currentHandler) } }, header: 'Are you sure?', message: 'Changes made to the Data Inputs and Parameters sections will be lost' }) } else { - handleHandlerChange(value) + handleHandlerChange(currentHandler) } } @@ -153,10 +208,7 @@ const JobWizardRunDetails = ({ {!isBatchInference && (
- +
)}
@@ -172,7 +224,7 @@ const JobWizardRunDetails = ({ {jobAdditionalData.versionOptions?.length !== 0 && (
) : (
- +
) ) : null} @@ -320,6 +368,9 @@ const JobWizardRunDetails = ({ {stepIsActive && spyOnHandlerChange && ( {onHandlerChange} )} + {stepIsActive && spyOnVersionChange && ( + {onVersionChange} + )}
) ) @@ -327,11 +378,14 @@ const JobWizardRunDetails = ({ JobWizardRunDetails.propTypes = { formState: PropTypes.shape({}).isRequired, + frontendSpec: PropTypes.shape({}).isRequired, isBatchInference: PropTypes.bool.isRequired, isEditMode: PropTypes.bool.isRequired, jobAdditionalData: PropTypes.shape({}).isRequired, + params: PropTypes.shape({}).isRequired, prePopulatedData: PropTypes.shape({}).isRequired, - selectedFunctionData: PropTypes.shape({}).isRequired + selectedFunctionData: PropTypes.shape({}).isRequired, + setJobData: PropTypes.func.isRequired } export default JobWizardRunDetails