Skip to content

Commit

Permalink
Merge pull request #291 from nhuyvan/form-validation-improvement
Browse files Browse the repository at this point in the history
- Add a new validator to check for range membership
  • Loading branch information
babybeet authored and Nhuy committed Feb 20, 2020
2 parents 68e5e0b + 658a2a3 commit c74866f
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,23 @@ export class PowerGridModels extends React.Component<Props, State> {

readonly formGroupModel = new FormGroupModel({
requestType: new FormControlModel<QueryPowerGridModelsRequestType>(null),
queryString: new FormControlModel(`SELECT ?feeder ?fid WHERE {?s r:type c:Feeder.?s c:IdentifiedObject.name ?feeder.?s c:IdentifiedObject.mRID ?fid.?s c:Feeder.NormalEnergizingSubstation ?sub.?sub c:IdentifiedObject.name ?station.?sub c:IdentifiedObject.mRID ?sid.?sub c:Substation.Region ?sgr.?sgr c:IdentifiedObject.name ?subregion.?sgr c:IdentifiedObject.mRID ?sgrid.?sgr c:SubGeographicalRegion.Region ?rgn.?rgn c:IdentifiedObject.name ?region.?rgn c:IdentifiedObject.mRID ?rgnid.} ORDER by ?station ?feeder`),
queryString: new FormControlModel(`
|SELECT ?feeder ?fid
|WHERE {
| ?s r:type c:Feeder .
| ?s c:IdentifiedObject.name ?feeder .
| ?s c:IdentifiedObject.mRID ?fid .
| ?s c:Feeder.NormalEnergizingSubstation ?sub .
| ?sub c:IdentifiedObject.name ?station .
| ?sub c:IdentifiedObject.mRID ?sid .
| ?sub c:Substation.Region ?sgr .
| ?sgr c:IdentifiedObject.name ?subregion .
| ?sgr c:IdentifiedObject.mRID ?sgrid .
| ?sgr c:SubGeographicalRegion.Region ?rgn .
| ?rgn c:IdentifiedObject.name ?region .
| ?rgn c:IdentifiedObject.mRID ?rgnid .
|}
|ORDER by ?station ?feeder`.replace(/(?:\s+\|)/g, '\n').trim()),
objectId: new FormControlModel(this.props.feederModelLines.find(line => line.name === 'ieee8500')),
modelId: new FormControlModel(this.props.feederModelLines.find(line => line.name === 'ieee8500')),
filter: new FormControlModel(`?s cim:IdentifiedObject.name \u0027q14733\u0027","objectType":"http://iec.ch/TC57/2012/CIM-schema-cim17#ConnectivityNode`),
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/shared/form/textarea/TextArea.common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
.CodeMirror-gutters {
z-index: 0;
}

.errored-line {
display: inline-block;
text-decoration-style: wavy;
text-decoration-line: underline;
}
}

.textarea__input-box-container {
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/app/shared/form/textarea/TextArea.dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
.CodeMirror-linenumber {
color: #fff;
}

.errored-line {
text-decoration-color: #f00;
}
}

.textarea__input-box {
Expand Down Expand Up @@ -95,4 +99,8 @@
.CodeMirror-linenumber {
color: #6c6c6c;
}

.errored-line {
text-decoration-color: #6c6c6c;
}
}
8 changes: 8 additions & 0 deletions frontend/src/app/shared/form/textarea/TextArea.light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
}
}
}

.errored-line {
text-decoration-color: #f00;
}
}

.textarea__input-box,
Expand Down Expand Up @@ -67,4 +71,8 @@
color: #dfdfdf;
}

.errored-line {
text-decoration-color: #dfdfdf;
}

}
36 changes: 33 additions & 3 deletions frontend/src/app/shared/form/textarea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ export class TextArea extends React.Component<Props, State> {
}
});

this.props.formControlModel.validityChanges()
.subscribe({
next: isValid => {
if (this._codeMirror) {
if (!isValid) {
const currentCursor = this._codeMirror.getCursor();
const startCursor = {
line: currentCursor.line,
ch: 0
};
this._codeMirror.markText(startCursor, currentCursor).clear();
this._codeMirror.markText(startCursor, currentCursor, { className: 'errored-line' });
} else {
for (const mark of this._codeMirror.getAllMarks()) {
mark.clear();
}
}
}
}
});

this._valueChanges.pipe(debounceTime(250))
.subscribe({
next: value => {
Expand Down Expand Up @@ -105,6 +126,13 @@ export class TextArea extends React.Component<Props, State> {
mode: {
name: 'javascript',
json: true
},
indentUnit: 4
});
this._codeMirror.setOption('extraKeys', {
Tab: codeMirror => {
const spaces = ' '.repeat(codeMirror.getOption('indentUnit'));
codeMirror.replaceSelection(spaces);
}
});
this._codeMirror.on('change', this._onValueChange);
Expand Down Expand Up @@ -150,9 +178,11 @@ export class TextArea extends React.Component<Props, State> {
this.props.formControlModel.cleanup();
window.removeEventListener('mousemove', this.resize, false);
window.removeEventListener('mouseup', this.stopResize, false);
this._codeMirror.off('change', this._onValueChange);
this._codeMirror.off('focus', this._onCodeMirrorEditorFocused);
this._codeMirror.off('blur', this._onCodeMirrorEditorFocusLost);
if (this._codeMirror) {
this._codeMirror.off('change', this._onValueChange);
this._codeMirror.off('focus', this._onCodeMirrorEditorFocused);
this._codeMirror.off('blur', this._onCodeMirrorEditorFocusLost);
}
}

render() {
Expand Down
29 changes: 20 additions & 9 deletions frontend/src/app/shared/form/validation/Validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ export class Validators {
};
}

static checkValidJSON(): Validator {
return control => {
static checkValidJSON(subjectDisplayName: string): Validator {
return (control: FormControlModel<string>) => {
const value = control.getValue();
try {
if (control.getValue() !== '') {
JSON.parse(control.getValue());
if (value !== '') {
JSON.parse(value);
}
return {
isValid: true,
errorMessage: ''
};
} catch (e) {
} catch {
return {
errorMessage: `Invalid JSON: ${e.message.replace('JSON.parse: ', '')}`,
errorMessage: `${subjectDisplayName} is not a valid JSON`,
isValid: false
};
}
Expand All @@ -50,7 +51,7 @@ export class Validators {
}

private static _checkPattern(errorMessage: string, pattern: RegExp): Validator {
return control => ({
return (control: FormControlModel<number> | FormControlModel<string>) => ({
isValid: pattern.test(String(control.getValue())),
errorMessage
});
Expand All @@ -63,15 +64,25 @@ export class Validators {
});
}

static checkBetween(subjectDisplayName: string, min: number, max: number): Validator {
return (control: FormControlModel<number>) => {
const value = control.getValue();
return {
isValid: min <= value && value <= max,
errorMessage: `${subjectDisplayName} must be between ${min} and ${max}`
};
};
}

static checkMin(subjectDisplayName: string, min: number): Validator {
return control => ({
return (control: FormControlModel<number>) => ({
isValid: control.getValue() >= min,
errorMessage: `${subjectDisplayName} must be greater than or equal to ${min}`
});
}

static checkMax(subjectDisplayName: string, max: number): Validator {
return control => ({
return (control: FormControlModel<number>) => ({
isValid: control.getValue() <= max,
errorMessage: `${subjectDisplayName} must be less than or equal to ${max}`
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class ApplicationConfigurationTab extends React.Component<Props, State> {
readonly nameFormControlModel = new FormControlModel('');
readonly configStringFormControlModel = new FormControlModel(
'',
[Validators.checkValidJSON()]
[Validators.checkValidJSON('Application configuration')]
);

constructor(props: Props) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,25 @@ export class ServiceConfiguration extends React.Component<Props, State> {
}

private _resolveValidatorsForUserInputSpec(label: string, userInputSpec: ServiceConfigUserInputSpec) {
const validators = [] as Validator[];
const validators = [Validators.checkNotEmpty(label)] as Validator[];
switch (userInputSpec.type) {
case 'float':
case 'int':
validators.push(
Validators.checkNotEmpty(label),
Validators.checkValidNumber(label)
);
validators.push(Validators.checkValidNumber(label));
const min = userInputSpec.min_value;
const max = userInputSpec.max_value;
if (typeof min === 'number') {
validators.push(Validators.checkMin(label, min));
}
if (typeof max === 'number') {
if (typeof max === 'number') {
validators.push(Validators.checkBetween(label, min, max));
} else {
validators.push(Validators.checkMin(label, min));
}
} else if (typeof max === 'number') {
validators.push(Validators.checkMax(label, max));
}
break;
case 'object':
validators.push(
Validators.checkNotEmpty(label),
Validators.checkValidJSON()
);
validators.push(Validators.checkValidJSON(label));
break;
}
return validators;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class SimulationConfigurationTab extends React.Component<Props, State> {
JSON.stringify(this.props.simulationConfig.model_creation_config, null, 4),
[
Validators.checkNotEmpty('Model creation config'),
Validators.checkValidJSON()
Validators.checkValidJSON('Model creation configuration')
]
);
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/stomp-client/StompClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class StompClient extends React.Component<Props, State> {
'{}',
[
Validators.checkNotEmpty('Request Body'),
Validators.checkValidJSON()
Validators.checkValidJSON('Request body')
]
)
});
Expand Down

0 comments on commit c74866f

Please sign in to comment.