Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

Commit

Permalink
[Web Portal] fix port list bug (#3240)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunqinzheng authored and debuggy committed Jul 23, 2019
1 parent 8afb1af commit 2472b37
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) Microsoft Corporation
* All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import React, {useEffect, useState, useCallback, useMemo} from 'react';
import {TextField} from 'office-ui-fabric-react';

import PropTypes from 'prop-types';
import {debounce} from 'lodash';


export const DebouncedTextField = (props) => {
const {onChange, value} = props;
const [cachedValue, setCachedValue] = useState('');
useEffect(() => setCachedValue(value), [value]);
const debouncedOnChange = useMemo(() => debounce(onChange, 200), [onChange]);

const onChangeWrapper = useCallback(
(e, val) => {
setCachedValue(val);
debouncedOnChange(e, val);
},
[setCachedValue, debouncedOnChange],
);

return (
<TextField
{...props}
value={cachedValue}
onChange={onChangeWrapper}
/>
);
};

DebouncedTextField.propTypes = {
onChange: PropTypes.func,
value: PropTypes.string,
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@
* SOFTWARE.
*/

import {camelCase, isEmpty} from 'lodash';
import {TextField, IconButton, Stack, DetailsList, CheckboxVisibility, DetailsListLayoutMode, CommandBarButton, getTheme, SelectionMode} from 'office-ui-fabric-react';
import {camelCase, isEmpty, isNil} from 'lodash';
import {IconButton, Stack, DetailsList, CheckboxVisibility, DetailsListLayoutMode, CommandBarButton, getTheme, SelectionMode} from 'office-ui-fabric-react';
import PropTypes from 'prop-types';
import React, {useCallback, useLayoutEffect, useMemo, useState, useContext} from 'react';
import {DebouncedTextField} from './debounced-text-field';
import {dispatchResizeEvent} from '../../utils/utils';
import context from '../context';

export const KeyValueList = ({name, value, onChange, onError, columnWidth, keyName, keyField, valueName, valueField, secret}) => {
export const KeyValueList = ({name, value, onChange, onError, columnWidth, keyName, keyField, valueName, valueField, secret, onValidateKey, onValidateValue}) => {
columnWidth = columnWidth || 180;
keyName = keyName || 'Key';
keyField = keyField || camelCase(keyName);
Expand Down Expand Up @@ -58,6 +59,24 @@ export const KeyValueList = ({name, value, onChange, onError, columnWidth, keyNa
if (value.some((x) => isEmpty(x[keyField]) && !isEmpty(x[valueField]))) {
errorMessage = `${name || 'KeyValueList'} has value with empty key.`;
}
if (!isNil(onValidateKey) || !isNil(onValidateValue)) {
for (const item of value) {
if (!isNil(onValidateKey)) {
const key = item[keyField];
const res = onValidateKey(key);
if (!isEmpty(res)) {
errorMessage = res;
}
}
if (!isNil(onValidateValue)) {
const value = item[valueField];
const res = onValidateValue(value);
if (!isEmpty(res)) {
errorMessage = res;
}
}
}
}
if (onError) {
onError(errorMessage);
}
Expand Down Expand Up @@ -104,8 +123,14 @@ export const KeyValueList = ({name, value, onChange, onError, columnWidth, keyNa
if (isEmpty(item[keyField]) && !isEmpty(item[valueField])) {
errorMessage = 'empty key';
}
if (!isNil(onValidateKey)) {
const res = onValidateKey(item[keyField]);
if (!isEmpty(res)) {
errorMessage = res;
}
}
return (
<TextField
<DebouncedTextField
errorMessage={errorMessage}
value={item[keyField]}
onChange={(e, val) => onKeyChange(idx, val)}
Expand All @@ -118,8 +143,16 @@ export const KeyValueList = ({name, value, onChange, onError, columnWidth, keyNa
name: valueName,
minWidth: columnWidth,
onRender: (item, idx) => {
let errorMessage = null;
if (!isNil(onValidateValue)) {
const res = onValidateValue(item[valueField]);
if (!isEmpty(res)) {
errorMessage = res;
}
}
return (
<TextField
<DebouncedTextField
errorMessage={errorMessage}
value={item[valueField]}
type={secret && 'password'}
onChange={(e, val) => onValueChange(idx, val)}
Expand Down Expand Up @@ -189,4 +222,7 @@ KeyValueList.propTypes = {
keyField: PropTypes.string,
valueName: PropTypes.string,
valueField: PropTypes.string,
// validation
onValidateKey: PropTypes.func,
onValidateValue: PropTypes.func,
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,17 @@
* SOFTWARE.
*/

import React, {useEffect, useState, useCallback, useMemo} from 'react';
import {TextField} from 'office-ui-fabric-react';
import {isEmpty} from 'lodash';
import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import {BasicSection} from './basic-section';
import {FormShortSection} from './form-page';

import PropTypes from 'prop-types';
import {debounce, isEmpty} from 'lodash';
import {DebouncedTextField} from './controls/debounced-text-field';

const TEXT_FILED_REGX = /^[A-Za-z0-9\-._~]+$/;

export const FormTextField = React.memo((props) => {
const {sectionLabel, onChange, sectionOptional, sectionTooltip, shortStyle, value} = props;
const [cachedValue, setCachedValue] = useState('');
useEffect(() => setCachedValue(value), [value]);
const _onGetErrorMessage = (value) => {
const match = TEXT_FILED_REGX.exec(value);
if (isEmpty(match)) {
Expand All @@ -45,20 +42,17 @@ export const FormTextField = React.memo((props) => {
return '';
};

const debouncedOnChange = useMemo(() => debounce(onChange, 200), [onChange]);

const onChangeWrapper = useCallback(
(_, val) => {
setCachedValue(val);
debouncedOnChange(val);
onChange(val);
},
[setCachedValue, debouncedOnChange],
[onChange],
);

const textField = (
<TextField
<DebouncedTextField
{...props}
value={cachedValue}
value={value}
onChange={onChangeWrapper}
onGetErrorMessage={_onGetErrorMessage}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
* SOFTWARE.
*/

import {isNaN} from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import {BasicSection} from '../basic-section';
import {FormShortSection} from '../form-page';
import {KeyValueList} from '../controls/key-value-list';

const PORT_LABEL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

export const PortsList = React.memo(({onChange, ports}) => (
<BasicSection sectionLabel='Ports' sectionOptional>
<FormShortSection>
Expand All @@ -41,6 +44,20 @@ export const PortsList = React.memo(({onChange, ports}) => (
keyField='key'
valueName='Port Field'
valueField='value'
onValidateKey={(val) => {
if (!PORT_LABEL_REGEX.test(val)) {
return 'Should be string in ^[a-zA-Z_][a-zA-Z0-9_]*$ format';
}
}}
onValidateValue={(val) => {
let int = val;
if (typeof val === 'string') {
int = parseInt(val, 10);
}
if (int <= 0 || isNaN(int)) {
return 'Should be integer and no less than 1';
}
}}
/>
</FormShortSection>
</BasicSection>
Expand Down
8 changes: 6 additions & 2 deletions src/webportal/src/app/job-submission/models/job-task-role.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class JobTaskRole {
const taskDeployment = get(deployments[0], `taskRoles.${name}`, {});
const dockerInfo = prerequisites.find((prerequisite) => prerequisite.name === dockerImage) || {};
const ports = isNil(resourcePerInstance.ports) ? [] :
Object.entries(resourcePerInstance.ports).map(([key, value]) => ({key, value}));
Object.entries(resourcePerInstance.ports).map(([key, value]) => ({key, value: value.toString()}));
const taskRetryCount = get(taskRoleProtocol, 'taskRetryCount', 0);

const jobTaskRole = new JobTaskRole({
Expand Down Expand Up @@ -89,7 +89,11 @@ export class JobTaskRole {
convertToProtocolFormat() {
const taskRole = {};
const ports = this.ports.reduce((val, x) => {
val[x.key] = x.value;
if (typeof x.value === 'string') {
val[x.key] = parseInt(x.value);
} else {
val[x.key] = x.value;
}
return val;
}, {});
const resourcePerInstance = removeEmptyProperties({...this.containerSize, ports: ports});
Expand Down

0 comments on commit 2472b37

Please sign in to comment.