From 8ca615060cee791d68713f0e621509aa0e658d07 Mon Sep 17 00:00:00 2001 From: avelichk Date: Wed, 21 Oct 2020 04:25:07 +0100 Subject: [PATCH 1/4] Extend submit Experiment using parameters in UI. Add all parameters from Trial template. Add YAML template submission. --- .../frontend/src/actions/generalActions.js | 100 +++++-- .../Common/Create/Params/MetricsCollector.jsx | 2 +- .../Create/Params/Trial/TrialParameters.jsx | 10 +- .../Create/Params/Trial/TrialTemplate.jsx | 244 +++++++++++++++++- .../src/components/HP/Create/HPParameters.jsx | 63 ++++- .../frontend/src/constants/constants.js | 3 + .../v1beta1/frontend/src/reducers/general.js | 157 ++++++++--- .../v1beta1/frontend/src/reducers/hpCreate.js | 1 + .../frontend/src/reducers/nasCreate.js | 1 + 9 files changed, 495 insertions(+), 86 deletions(-) diff --git a/pkg/ui/v1beta1/frontend/src/actions/generalActions.js b/pkg/ui/v1beta1/frontend/src/actions/generalActions.js index d087f475820..8407fe238b9 100644 --- a/pkg/ui/v1beta1/frontend/src/actions/generalActions.js +++ b/pkg/ui/v1beta1/frontend/src/actions/generalActions.js @@ -91,6 +91,67 @@ export const closeDialogSuggestion = () => ({ type: CLOSE_DIALOG_SUGGESTION, }); +export const FETCH_EXPERIMENTS_REQUEST = 'FETCH_EXPERIMENTS_REQUEST'; +export const FETCH_EXPERIMENTS_SUCCESS = 'FETCH_EXPERIMENTS_SUCCESS'; +export const FETCH_EXPERIMENTS_FAILURE = 'FETCH_EXPERIMENTS_FAILURE'; + +export const fetchExperiments = () => ({ + type: FETCH_EXPERIMENTS_REQUEST, +}); + +export const FILTER_EXPERIMENTS = 'FILTER_EXPERIMENTS'; + +export const filterExperiments = (experimentName, experimentNamespace) => ({ + type: FILTER_EXPERIMENTS, + experimentName, + experimentNamespace, +}); + +export const CHANGE_STATUS = 'CHANGE_STATUS'; + +export const changeStatus = (filter, checked) => ({ + type: CHANGE_STATUS, + filter, + checked, +}); + +export const CHANGE_TRIAL_TEMPLATE_SOURCE = 'CHANGE_TRIAL_TEMPLATE_SOURCE'; + +export const changeTrialTemplateSource = source => ({ + type: CHANGE_TRIAL_TEMPLATE_SOURCE, + source, +}); + +export const ADD_PRIMARY_POD_LABEL = 'ADD_PRIMARY_POD_LABEL'; + +export const addPrimaryPodLabel = () => ({ + type: ADD_PRIMARY_POD_LABEL, +}); + +export const CHANGE_PRIMARY_POD_LABEL = 'CHANGE_PRIMARY_POD_LABEL'; + +export const changePrimaryPodLabel = (fieldName, index, value) => ({ + type: CHANGE_PRIMARY_POD_LABEL, + fieldName, + index, + value, +}); + +export const DELETE_PRIMARY_POD_LABEL = 'DELETE_PRIMARY_POD_LABEL'; + +export const deletePrimaryPodLabel = index => ({ + type: DELETE_PRIMARY_POD_LABEL, + index, +}); + +export const CHANGE_TRIAL_TEMPLATE_SPEC = 'CHANGE_TRIAL_TEMPLATE_SPEC'; + +export const changeTrialTemplateSpec = (name, value) => ({ + type: CHANGE_TRIAL_TEMPLATE_SPEC, + name, + value, +}); + export const FILTER_TEMPLATES_EXPERIMENT = 'FILTER_TEMPLATES_EXPERIMENT'; export const filterTemplatesExperiment = ( @@ -104,43 +165,26 @@ export const filterTemplatesExperiment = ( configMapPathIndex, }); -export const VALIDATION_ERROR = 'VALIDATION_ERROR'; +export const CHANGE_TRIAL_TEMPLATE_YAML = 'CHANGE_TRIAL_TEMPLATE_YAML'; -export const validationError = message => ({ - type: VALIDATION_ERROR, - message, +export const changeTrialTemplateYAML = templateYAML => ({ + type: CHANGE_TRIAL_TEMPLATE_YAML, + templateYAML, }); -export const EDIT_TRIAL_PARAMETERS = 'EDIT_TRIAL_PARAMETERS'; +export const CHANGE_TRIAL_PARAMETERS = 'CHANGE_TRIAL_PARAMETERS'; -export const editTrialParameters = (index, name, reference, description) => ({ - type: EDIT_TRIAL_PARAMETERS, +export const changeTrialParameters = (index, name, reference, description) => ({ + type: CHANGE_TRIAL_PARAMETERS, index, name, reference, description, }); -export const FETCH_EXPERIMENTS_REQUEST = 'FETCH_EXPERIMENTS_REQUEST'; -export const FETCH_EXPERIMENTS_SUCCESS = 'FETCH_EXPERIMENTS_SUCCESS'; -export const FETCH_EXPERIMENTS_FAILURE = 'FETCH_EXPERIMENTS_FAILURE'; - -export const fetchExperiments = () => ({ - type: FETCH_EXPERIMENTS_REQUEST, -}); - -export const FILTER_EXPERIMENTS = 'FILTER_EXPERIMENTS'; - -export const filterExperiments = (experimentName, experimentNamespace) => ({ - type: FILTER_EXPERIMENTS, - experimentName, - experimentNamespace, -}); - -export const CHANGE_STATUS = 'CHANGE_STATUS'; +export const VALIDATION_ERROR = 'VALIDATION_ERROR'; -export const changeStatus = (filter, checked) => ({ - type: CHANGE_STATUS, - filter, - checked, +export const validationError = message => ({ + type: VALIDATION_ERROR, + message, }); diff --git a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/MetricsCollector.jsx b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/MetricsCollector.jsx index 31c06de1d53..06b18b7011b 100644 --- a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/MetricsCollector.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/MetricsCollector.jsx @@ -525,7 +525,7 @@ class MetricsCollectorSpec extends React.Component { - {'Yaml for the Custom Container'} + {'YAML for the Custom Container'} diff --git a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialParameters.jsx index 251f3c4fb7c..9ff55c934a5 100644 --- a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialParameters.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialParameters.jsx @@ -8,7 +8,7 @@ import Typography from '@material-ui/core/Typography'; import TextField from '@material-ui/core/TextField'; import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; -import { editTrialParameters } from '../../../../../actions/generalActions'; +import { changeTrialParameters } from '../../../../../actions/generalActions'; import { GENERAL_MODULE } from '../../../../../constants/constants'; const useStyles = makeStyles({ @@ -36,21 +36,21 @@ const TrialParameters = props => { let param = props.trialParameters[index]; let reference = param.reference; let description = param.description; - props.editTrialParameters(index, event.target.value, reference, description); + props.changeTrialParameters(index, event.target.value, reference, description); }; const onReferenceChange = index => event => { let param = props.trialParameters[index]; let name = param.name; let description = param.description; - props.editTrialParameters(index, name, event.target.value, description); + props.changeTrialParameters(index, name, event.target.value, description); }; const onDescriptionChange = index => event => { let param = props.trialParameters[index]; let name = param.name; let reference = param.reference; - props.editTrialParameters(index, name, reference, event.target.value); + props.changeTrialParameters(index, name, reference, event.target.value); }; return ( @@ -121,4 +121,4 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps, { editTrialParameters })(TrialParameters); +export default connect(mapStateToProps, { changeTrialParameters })(TrialParameters); diff --git a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialTemplate.jsx b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialTemplate.jsx index db34fe838bc..40b9cdba9b1 100644 --- a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialTemplate.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/Trial/TrialTemplate.jsx @@ -1,15 +1,235 @@ import React from 'react'; +import { connect } from 'react-redux'; -import Divider from '@material-ui/core/Divider'; +import AceEditor from 'react-ace'; +import 'ace-builds/src-noconflict/theme-sqlserver'; +import 'ace-builds/src-noconflict/mode-yaml'; +import { withStyles } from '@material-ui/core/styles'; +import Divider from '@material-ui/core/Divider'; +import FormControl from '@material-ui/core/FormControl'; +import InputLabel from '@material-ui/core/InputLabel'; +import Select from '@material-ui/core/Select'; +import Grid from '@material-ui/core/Grid'; +import Tooltip from '@material-ui/core/Tooltip'; +import HelpOutlineIcon from '@material-ui/icons/HelpOutline'; +import Typography from '@material-ui/core/Typography'; +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import DeleteIcon from '@material-ui/icons/Delete'; +import MenuItem from '@material-ui/core/MenuItem'; import TrialConfigMap from './TrialConfigMap'; import TrialParameters from './TrialParameters'; +import { GENERAL_MODULE, TEMPLATE_SOURCE_CONFIG_MAP } from '../../../../../constants/constants'; +import { + changeTrialTemplateSource, + addPrimaryPodLabel, + changePrimaryPodLabel, + deletePrimaryPodLabel, + changeTrialTemplateSpec, + changeTrialTemplateYAML, +} from '../../../../../actions/generalActions'; +import { fetchTrialTemplates } from '../../../../../actions/templateActions'; + +const styles = () => ({ + parameter: { + padding: 2, + marginBottom: 10, + }, + help: { + padding: 4 / 2, + verticalAlign: 'middle', + marginRight: 5, + }, + textField: { + marginLeft: 4, + marginRight: 4, + width: '90%', + }, + button: { + marginTop: 10, + marginBottom: 10, + }, + formSelect: { + width: '70%', + }, +}); + class TrialTemplate extends React.Component { + onTrialTemplateSourceChange = event => { + // Change source only if value is changed. + if (event.target.value !== this.props.trialTemplateSource) { + this.props.changeTrialTemplateSource(event.target.value); + // Fetch templates if source is ConfigMap. + if (event.target.value === TEMPLATE_SOURCE_CONFIG_MAP) { + this.props.fetchTrialTemplates(); + } + } + }; + + onPrimaryPodLabelAdd = () => { + this.props.addPrimaryPodLabel(); + }; + + onPrimaryPodLabelChange = (fieldName, index) => event => { + this.props.changePrimaryPodLabel(fieldName, index, event.target.value); + }; + + onPrimaryPodLabelDelete = index => () => { + this.props.deletePrimaryPodLabel(index); + }; + + onTrialTemplateSpecChange = name => event => { + this.props.changeTrialTemplateSpec(name, event.target.value); + }; + + onTrialTemplateYAMLChange = templateYAML => { + this.props.changeTrialTemplateYAML(templateYAML); + }; + render() { + const { classes } = this.props; return (
- + + + + + + + {'Source type'} + + + + + Source type + + + + + + + + + + + {'PrimaryPodLabels (optional)'} + + + + + {this.props.primaryPodLabels.map((label, index) => { + return ( +
+ + + + + + + + + + + + + + +
+ ); + })} + {this.props.trialTemplateSpec.map((param, i) => { + return ( +
+ + + + + + + {param.name} + + + + + + +
+ ); + })} + {this.props.trialTemplateSource === TEMPLATE_SOURCE_CONFIG_MAP ? ( + + ) : ( + + + + + + + {'Trial template YAML'} + + + + + + + )}
@@ -17,4 +237,22 @@ class TrialTemplate extends React.Component { } } -export default TrialTemplate; +const mapStateToProps = state => { + return { + trialTemplateSourceList: state[GENERAL_MODULE].trialTemplateSourceList, + trialTemplateSource: state[GENERAL_MODULE].trialTemplateSource, + primaryPodLabels: state[GENERAL_MODULE].primaryPodLabels, + trialTemplateSpec: state[GENERAL_MODULE].trialTemplateSpec, + trialTemplateYAML: state[GENERAL_MODULE].trialTemplateYAML, + }; +}; + +export default connect(mapStateToProps, { + changeTrialTemplateSource, + addPrimaryPodLabel, + changePrimaryPodLabel, + deletePrimaryPodLabel, + changeTrialTemplateSpec, + changeTrialTemplateYAML, + fetchTrialTemplates, +})(withStyles(styles)(TrialTemplate)); diff --git a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx index c4901fe32c7..b4c20b3bca5 100644 --- a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx @@ -192,14 +192,57 @@ const HPParameters = props => { data.spec.metricsCollectorSpec = newMCSpec; - data.spec.trialTemplate = { - configMap: { - configMapNamespace: props.templateConfigMapNamespace, - configMapName: props.templateConfigMapName, - templatePath: props.templateConfigMapPath, - }, - trialParameters: props.trialParameters, - }; + // Add Trial template. + // Add Trial specification. + data.spec.trialTemplate = {}; + deCapitalizeFirstLetterAndAppend(props.trialTemplateSpec, data.spec.trialTemplate); + console.log(data.spec.trialTemplate.retain); + if (data.spec.trialTemplate.retain === 'true') { + data.spec.trialTemplate.retain = true; + } else if (data.spec.trialTemplate.retain === 'false') { + data.spec.trialTemplate.retain = false; + } else { + props.validationError('Trial template retain parameter must be true or false!'); + return; + } + + // Remove empty items from PrimaryPodLabels array. + let filteredPrimaryLabels = props.primaryPodLabels.filter(function(label) { + return label.key.trim() !== '' && label.value.trim() !== ''; + }); + + // If array is not empty add PrimaryPodLabels. + if (filteredPrimaryLabels.length > 0) { + data.spec.trialTemplate.primaryPodLabels = {}; + filteredPrimaryLabels.forEach( + label => (data.spec.trialTemplate.primaryPodLabels[label.key] = label.value), + ); + } + + // Add Trial Source. + if ( + props.trialTemplateSource === constants.TEMPLATE_SOURCE_YAML && + props.trialTemplateYAML !== '' + ) { + // Try to parse template YAML to JSON. + try { + let trialTemplateJSON = jsyaml.load(props.trialTemplateYAML); + data.spec.trialTemplate.trialSource = trialTemplateJSON; + } catch { + props.validationError('Trial Template is not valid YAML!'); + return; + } + // Otherwise assign ConfigMap. + } else { + data.spec.trialTemplate = { + configMap: { + configMapNamespace: props.templateConfigMapNamespace, + configMapName: props.templateConfigMapName, + templatePath: props.templateConfigMapPath, + }, + }; + } + data.spec.trialTemplate.trialParameters = props.trialParameters; props.submitHPJob(data); }; @@ -264,9 +307,13 @@ const mapStateToProps = state => { algorithmName: state[constants.HP_CREATE_MODULE].algorithmName, algorithmSettings: state[constants.HP_CREATE_MODULE].algorithmSettings, parameters: state[constants.HP_CREATE_MODULE].parameters, + primaryPodLabels: state[constants.GENERAL_MODULE].primaryPodLabels, + trialTemplateSpec: state[constants.GENERAL_MODULE].trialTemplateSpec, + trialTemplateSource: state[constants.GENERAL_MODULE].trialTemplateSource, templateConfigMapNamespace: templateCMNamespace, templateConfigMapName: templateCMName, templateConfigMapPath: templateCMPath, + trialTemplateYAML: state[constants.GENERAL_MODULE].trialTemplateYAML, trialParameters: state[constants.GENERAL_MODULE].trialParameters, mcSpec: state[constants.HP_CREATE_MODULE].mcSpec, mcCustomContainerYaml: state[constants.HP_CREATE_MODULE].mcCustomContainerYaml, diff --git a/pkg/ui/v1beta1/frontend/src/constants/constants.js b/pkg/ui/v1beta1/frontend/src/constants/constants.js index 14041fa6189..35f3350ac4c 100644 --- a/pkg/ui/v1beta1/frontend/src/constants/constants.js +++ b/pkg/ui/v1beta1/frontend/src/constants/constants.js @@ -31,3 +31,6 @@ export const HP_MONITOR_MODULE = 'hpMonitor'; export const NAS_CREATE_MODULE = 'nasCreate'; export const NAS_MONITOR_MODULE = 'nasMonitor'; export const TEMPLATE_MODULE = 'template'; + +export const TEMPLATE_SOURCE_CONFIG_MAP = 'ConfigMap'; +export const TEMPLATE_SOURCE_YAML = 'YAML'; diff --git a/pkg/ui/v1beta1/frontend/src/reducers/general.js b/pkg/ui/v1beta1/frontend/src/reducers/general.js index c7695b89796..5a317544f47 100644 --- a/pkg/ui/v1beta1/frontend/src/reducers/general.js +++ b/pkg/ui/v1beta1/frontend/src/reducers/general.js @@ -5,6 +5,8 @@ import * as hpMonitorActions from '../actions/hpMonitorActions'; import * as nasMonitorActions from '../actions/nasMonitorActions'; import * as templateActions from '../actions/templateActions'; +import { TEMPLATE_SOURCE_CONFIG_MAP, TEMPLATE_SOURCE_YAML } from '../constants/constants'; + const initialState = { menuOpen: false, snackOpen: false, @@ -30,6 +32,37 @@ const initialState = { suggestion: {}, dialogSuggestionOpen: false, + trialTemplateSourceList: [TEMPLATE_SOURCE_CONFIG_MAP, TEMPLATE_SOURCE_YAML], + trialTemplateSource: 'ConfigMap', + primaryPodLabels: [], + trialTemplateSpec: [ + { + name: 'PrimaryContainerName', + value: 'training-container', + description: 'Name of training container where actual model training is running', + }, + { + name: 'SuccessCondition', + value: 'status.conditions.#(type=="Complete")#|#(status=="True")#', + description: `Condition when Trial custom resource is succeeded. + Default value for k8s BatchJob: status.conditions.#(type=="Complete")#|#(status=="True")#. + Default value for Kubeflow Job (TFJob, PyTorchJob): status.conditions.#(type=="Succeeded")#|#(status=="True")#.`, + }, + { + name: 'FailureCondition', + value: 'status.conditions.#(type=="Failed")#|#(status=="True")#', + description: `Condition when Trial custom resource is failed. + Default value for k8s BatchJob: status.conditions.#(type=="Failed")#|#(status=="True")#. + Default value for Kubeflow Job (TFJob, PyTorchJob): status.conditions.#(type=="Failed")#|#(status=="True")#.`, + }, + { + name: 'Retain', + value: 'false', + description: + 'Retain indicates that Trial resources must be not cleanup. Default value: false', + }, + ], + trialTemplateYAML: '', trialTemplatesData: [], configMapNamespaceIndex: -1, @@ -43,7 +76,33 @@ const initialState = { mcURISchemesList: ['HTTP', 'HTTPS'], }; -const templateParameterRegex = '\\{trialParameters\\..+?\\}'; +// filterValue finds index from array where name == key. +const filterValue = (obj, key) => { + return obj.findIndex(p => p.name === key); +}; + +// getTrialParameters returns Trial parameters for the given YAML. +// It parses only Trial parameters names from the YAML. +const getTrialParameters = YAML => { + const templateParameterRegex = '\\{trialParameters\\..+?\\}'; + + let trialParameters = []; + let trialParameterNames = []; + let matchStr = [...YAML.matchAll(templateParameterRegex)]; + + matchStr.forEach(param => { + let newParameter = param[0].slice(param[0].indexOf('.') + 1, param[0].indexOf('}')); + if (!trialParameterNames.includes(newParameter)) { + trialParameterNames.push(newParameter); + trialParameters.push({ + name: newParameter, + reference: '', + description: '', + }); + } + }); + return trialParameters; +}; const generalReducer = (state = initialState, action) => { switch (action.type) { @@ -178,6 +237,7 @@ const generalReducer = (state = initialState, action) => { dialogExperimentOpen: false, dialogSuggestionOpen: false, }; + // Experiment Trial Template actions. case templateActions.FETCH_TRIAL_TEMPLATES_SUCCESS: var trialTemplatesData = action.trialTemplatesData; @@ -193,24 +253,10 @@ const generalReducer = (state = initialState, action) => { configMapNamespaceIndex = 0; configMapNameIndex = 0; configMapPathIndex = 0; - // Get Parameter names from ConfigMap for Trial parameters - var yaml = trialTemplatesData[0].ConfigMaps[0].Templates[0].Yaml; - var trialParameterNames = []; - var matchStr = [...yaml.matchAll(templateParameterRegex)]; - matchStr.forEach(param => { - let newParameter = param[0].slice(param[0].indexOf('.') + 1, param[0].indexOf('}')); - if (!trialParameterNames.includes(newParameter)) { - trialParameterNames.push(newParameter); - trialParameters.push({ - name: newParameter, - reference: '', - description: '', - }); - } - }); + // Get Trial parameters names from the ConfigMap template YAML + trialParameters = getTrialParameters(trialTemplatesData[0].ConfigMaps[0].Templates[0].Yaml); } - return { ...state, trialTemplatesData: trialTemplatesData, @@ -219,6 +265,42 @@ const generalReducer = (state = initialState, action) => { configMapPathIndex: configMapPathIndex, trialParameters: trialParameters, }; + case actions.CHANGE_TRIAL_TEMPLATE_SOURCE: + return { + ...state, + trialTemplateSource: action.source, + trialTemplateYAML: '', + trialParameters: [], + }; + case actions.ADD_PRIMARY_POD_LABEL: + var newLabels = state.primaryPodLabels.slice(); + newLabels.push({ key: '', value: '' }); + return { + ...state, + primaryPodLabels: newLabels, + }; + case actions.CHANGE_PRIMARY_POD_LABEL: + newLabels = state.primaryPodLabels.slice(); + newLabels[action.index][action.fieldName] = action.value; + return { + ...state, + primaryPodLabels: newLabels, + }; + case actions.DELETE_PRIMARY_POD_LABEL: + newLabels = state.primaryPodLabels.slice(); + newLabels.splice(action.index, 1); + return { + ...state, + primaryPodLabels: newLabels, + }; + case actions.CHANGE_TRIAL_TEMPLATE_SPEC: + let newTrialTemplateSpec = state.trialTemplateSpec.slice(); + let index = filterValue(newTrialTemplateSpec, action.name); + newTrialTemplateSpec[index].value = action.value; + return { + ...state, + trialTemplateSpec: newTrialTemplateSpec, + }; case actions.FILTER_TEMPLATES_EXPERIMENT: let newNamespaceIndex = 0; let newNameIndex = 0; @@ -235,32 +317,17 @@ const generalReducer = (state = initialState, action) => { newPathIndex = action.configMapPathIndex; } - // Get Parameter names from ConfigMap for Trial parameters - // Change only if any ConfigMap information has been changed + // Get Parameter names from ConfigMap for Trial parameters. + // Change only if any ConfigMap information has been changed. trialParameters = state.trialParameters.slice(); if ( newNamespaceIndex !== state.configMapNamespaceIndex || newNameIndex !== state.configMapNameIndex || newPathIndex !== state.configMapPathIndex ) { - trialTemplatesData = state.trialTemplatesData; - yaml = - trialTemplatesData[newNamespaceIndex].ConfigMaps[newNameIndex].Templates[newPathIndex] - .Yaml; - trialParameterNames = []; - trialParameters = []; - matchStr = [...yaml.matchAll(templateParameterRegex)]; - matchStr.forEach(param => { - let newParameter = param[0].slice(param[0].indexOf('.') + 1, param[0].indexOf('}')); - if (!trialParameterNames.includes(newParameter)) { - trialParameterNames.push(newParameter); - trialParameters.push({ - name: newParameter, - reference: '', - description: '', - }); - } - }); + // Get Trial parameters from the YAML. + let configMap = state.trialTemplatesData[newNamespaceIndex].ConfigMaps[newNameIndex]; + trialParameters = getTrialParameters(configMap.Templates[newPathIndex].Yaml); } return { ...state, @@ -269,13 +336,15 @@ const generalReducer = (state = initialState, action) => { configMapPathIndex: newPathIndex, trialParameters: trialParameters, }; - case actions.VALIDATION_ERROR: + case actions.CHANGE_TRIAL_TEMPLATE_YAML: + // Get Trial parameters from the YAML. + trialParameters = getTrialParameters(action.templateYAML); return { ...state, - snackOpen: true, - snackText: action.message, + trialTemplateYAML: action.templateYAML, + trialParameters: trialParameters, }; - case actions.EDIT_TRIAL_PARAMETERS: + case actions.CHANGE_TRIAL_PARAMETERS: let newParams = state.trialParameters.slice(); newParams[action.index].name = action.name; newParams[action.index].reference = action.reference; @@ -385,6 +454,12 @@ const generalReducer = (state = initialState, action) => { snackOpen: true, snackText: 'Delete Template failed: ' + action.error, }; + case actions.VALIDATION_ERROR: + return { + ...state, + snackOpen: true, + snackText: action.message, + }; default: return state; } diff --git a/pkg/ui/v1beta1/frontend/src/reducers/hpCreate.js b/pkg/ui/v1beta1/frontend/src/reducers/hpCreate.js index 6a3b287a1ea..e45d8dbad44 100644 --- a/pkg/ui/v1beta1/frontend/src/reducers/hpCreate.js +++ b/pkg/ui/v1beta1/frontend/src/reducers/hpCreate.js @@ -109,6 +109,7 @@ const initialState = { mcCustomContainerYaml: '', }; +// filterValue finds index from array where name == key. const filterValue = (obj, key) => { return obj.findIndex(p => p.name === key); }; diff --git a/pkg/ui/v1beta1/frontend/src/reducers/nasCreate.js b/pkg/ui/v1beta1/frontend/src/reducers/nasCreate.js index ef1f0db7658..1de1e5bf51c 100644 --- a/pkg/ui/v1beta1/frontend/src/reducers/nasCreate.js +++ b/pkg/ui/v1beta1/frontend/src/reducers/nasCreate.js @@ -349,6 +349,7 @@ const initialState = { mcCustomContainerYaml: '', }; +// filterValue finds index from array where name == key. const filterValue = (obj, key) => { return obj.findIndex(p => p.name === key); }; From b0eac10fd36149306bd14cfb2cdc72594e381a8a Mon Sep 17 00:00:00 2001 From: avelichk Date: Wed, 21 Oct 2020 17:37:53 +0100 Subject: [PATCH 2/4] Fix Trial spec --- .../src/components/HP/Create/HPParameters.jsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx index b4c20b3bca5..5f70db04d25 100644 --- a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx @@ -196,7 +196,6 @@ const HPParameters = props => { // Add Trial specification. data.spec.trialTemplate = {}; deCapitalizeFirstLetterAndAppend(props.trialTemplateSpec, data.spec.trialTemplate); - console.log(data.spec.trialTemplate.retain); if (data.spec.trialTemplate.retain === 'true') { data.spec.trialTemplate.retain = true; } else if (data.spec.trialTemplate.retain === 'false') { @@ -227,22 +226,24 @@ const HPParameters = props => { // Try to parse template YAML to JSON. try { let trialTemplateJSON = jsyaml.load(props.trialTemplateYAML); - data.spec.trialTemplate.trialSource = trialTemplateJSON; + data.spec.trialTemplate.trialSpec = trialTemplateJSON; } catch { props.validationError('Trial Template is not valid YAML!'); return; } // Otherwise assign ConfigMap. } else { - data.spec.trialTemplate = { - configMap: { - configMapNamespace: props.templateConfigMapNamespace, - configMapName: props.templateConfigMapName, - templatePath: props.templateConfigMapPath, - }, + data.spec.trialTemplate.configMap = { + configMapNamespace: props.templateConfigMapNamespace, + configMapName: props.templateConfigMapName, + templatePath: props.templateConfigMapPath, }; } - data.spec.trialTemplate.trialParameters = props.trialParameters; + + // Add Trial parameters if it is not empty. + if (props.trialParameters.length > 0) { + data.spec.trialTemplate.trialParameters = props.trialParameters; + } props.submitHPJob(data); }; From f64ca9746d3fc0976b07ca067afa0ceb8c2c30ec Mon Sep 17 00:00:00 2001 From: avelichk Date: Wed, 21 Oct 2020 18:15:33 +0100 Subject: [PATCH 3/4] Add changes for NAS submit --- .../components/NAS/Create/NASParameters.jsx | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx index e58be290063..994bc44a62a 100644 --- a/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx @@ -206,14 +206,58 @@ const NASParameters = props => { data.spec.metricsCollectorSpec = newMCSpec; - data.spec.trialTemplate = { - configMap: { + // Add Trial template. + // Add Trial specification. + data.spec.trialTemplate = {}; + deCapitalizeFirstLetterAndAppend(props.trialTemplateSpec, data.spec.trialTemplate); + if (data.spec.trialTemplate.retain === 'true') { + data.spec.trialTemplate.retain = true; + } else if (data.spec.trialTemplate.retain === 'false') { + data.spec.trialTemplate.retain = false; + } else { + props.validationError('Trial template retain parameter must be true or false!'); + return; + } + + // Remove empty items from PrimaryPodLabels array. + let filteredPrimaryLabels = props.primaryPodLabels.filter(function(label) { + return label.key.trim() !== '' && label.value.trim() !== ''; + }); + + // If array is not empty add PrimaryPodLabels. + if (filteredPrimaryLabels.length > 0) { + data.spec.trialTemplate.primaryPodLabels = {}; + filteredPrimaryLabels.forEach( + label => (data.spec.trialTemplate.primaryPodLabels[label.key] = label.value), + ); + } + + // Add Trial Source. + if ( + props.trialTemplateSource === constants.TEMPLATE_SOURCE_YAML && + props.trialTemplateYAML !== '' + ) { + // Try to parse template YAML to JSON. + try { + let trialTemplateJSON = jsyaml.load(props.trialTemplateYAML); + data.spec.trialTemplate.trialSpec = trialTemplateJSON; + } catch { + props.validationError('Trial Template is not valid YAML!'); + return; + } + // Otherwise assign ConfigMap. + } else { + data.spec.trialTemplate.configMap = { configMapNamespace: props.templateConfigMapNamespace, configMapName: props.templateConfigMapName, templatePath: props.templateConfigMapPath, - }, - trialParameters: props.trialParameters, - }; + }; + } + + // Add Trial parameters if it is not empty. + if (props.trialParameters.length > 0) { + data.spec.trialTemplate.trialParameters = props.trialParameters; + } props.submitNASJob(data); }; @@ -278,9 +322,12 @@ const mapStateToProps = state => { inputSize: state[constants.NAS_CREATE_MODULE].inputSize, outputSize: state[constants.NAS_CREATE_MODULE].outputSize, operations: state[constants.NAS_CREATE_MODULE].operations, + trialTemplateSpec: state[constants.GENERAL_MODULE].trialTemplateSpec, + trialTemplateSource: state[constants.GENERAL_MODULE].trialTemplateSource, templateConfigMapNamespace: templateCMNamespace, templateConfigMapName: templateCMName, templateConfigMapPath: templateCMPath, + trialTemplateYAML: state[constants.GENERAL_MODULE].trialTemplateYAML, trialParameters: state[constants.GENERAL_MODULE].trialParameters, mcSpec: state[constants.NAS_CREATE_MODULE].mcSpec, mcCustomContainerYaml: state[constants.NAS_CREATE_MODULE].mcCustomContainerYaml, From 67a4edb6daa3f02ae630b96c0820734b6738635c Mon Sep 17 00:00:00 2001 From: avelichk Date: Wed, 21 Oct 2020 18:18:41 +0100 Subject: [PATCH 4/4] Add primary pod labels to NAS --- .../v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx index 994bc44a62a..60b51caf82c 100644 --- a/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx +++ b/pkg/ui/v1beta1/frontend/src/components/NAS/Create/NASParameters.jsx @@ -322,6 +322,7 @@ const mapStateToProps = state => { inputSize: state[constants.NAS_CREATE_MODULE].inputSize, outputSize: state[constants.NAS_CREATE_MODULE].outputSize, operations: state[constants.NAS_CREATE_MODULE].operations, + primaryPodLabels: state[constants.GENERAL_MODULE].primaryPodLabels, trialTemplateSpec: state[constants.GENERAL_MODULE].trialTemplateSpec, trialTemplateSource: state[constants.GENERAL_MODULE].trialTemplateSource, templateConfigMapNamespace: templateCMNamespace,