Skip to content

Commit 32eb310

Browse files
committed
fix: Fix cross-validation issues, error messages
* Perform cross-validation at the resource form level so that errors are properly displayed to users when GPU values change * Improve error messages, mostly by adding contextual information * Clean up .prettierrc
1 parent 1e66633 commit 32eb310

File tree

3 files changed

+108
-86
lines changed

3 files changed

+108
-86
lines changed

frontend/.prettierrc

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
{
2-
printWidth: 80,
3-
useTabs: false,
4-
tabWidth: 2,
5-
semi: true,
6-
singleQuote: true,
7-
trailingComma: 'all',
8-
bracketSpacing: true,
9-
arrowParens: 'avoid',
10-
proseWrap: 'preserve',
2+
"arrowParens": "avoid",
3+
"bracketSpacing": false,
4+
"trailingComma": "none"
115
}

frontend/src/app/resource-form/form-specs/form-specs.component.html

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,24 @@ <h3>
1212
<div class="inputs-wrapper">
1313
<mat-form-field appearance="outline">
1414
<mat-label>CPU</mat-label>
15-
<input matInput placeholder="# of CPU Cores" formControlName="cpu" />
16-
<mat-error>
17-
{{ showCPUError() }}
18-
</mat-error>
15+
<input
16+
matInput
17+
placeholder="# of CPU Cores"
18+
formControlName="cpu"
19+
[errorStateMatcher]="parentErrorKeysErrorStateMatcher('maxCpu')"
20+
/>
21+
<mat-error>{{ cpuErrorMessage() }}</mat-error>
1922
</mat-form-field>
2023

2124
<mat-form-field appearance="outline">
2225
<mat-label>Memory</mat-label>
23-
<input matInput placeholder="Amount of Memory" formControlName="memory" />
24-
<mat-error>
25-
{{ showMemoryError() }}
26-
</mat-error>
26+
<input
27+
matInput
28+
placeholder="Amount of Memory"
29+
formControlName="memory"
30+
[errorStateMatcher]="parentErrorKeysErrorStateMatcher('maxRam')"
31+
/>
32+
<mat-error>{{ memoryErrorMessage() }}</mat-error>
2733
</mat-form-field>
2834
</div>
2935
</div>

frontend/src/app/resource-form/form-specs/form-specs.component.ts

Lines changed: 91 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
1-
import { Component, OnInit, Input } from "@angular/core";
2-
import { FormGroup, AbstractControl, Validators, ValidatorFn, ControlContainer } from "@angular/forms";
1+
import {Component, OnInit, Input} from "@angular/core";
2+
import {
3+
FormGroup,
4+
AbstractControl,
5+
Validators,
6+
ValidatorFn,
7+
ValidationErrors,
8+
FormControl,
9+
FormGroupDirective,
10+
NgForm
11+
} from "@angular/forms";
12+
13+
const MAX_FOR_GPU: ReadonlyMap<number, MaxResourceSpec> = new Map([
14+
[0, {cpu: 15, ram: 96}],
15+
[1, {cpu: 5, ram: 48}]
16+
]);
17+
18+
type MaxResourceSpec = {cpu: number; ram: number};
19+
20+
function resourcesValidator(): ValidatorFn {
21+
return function (control: AbstractControl): ValidationErrors | null {
22+
const gpuNumValue = control.get("gpus").get("num").value;
23+
const gpu = gpuNumValue === "none" ? 0 : parseInt(gpuNumValue, 10) || 0;
24+
const cpu = parseFloat(control.get("cpu").value);
25+
const ram = parseFloat(control.get("memory").value);
26+
const errors = {};
27+
28+
const max = MAX_FOR_GPU.get(gpu);
29+
if (cpu > max.cpu) {
30+
errors["maxCpu"] = {max: max.cpu, gpu};
31+
}
32+
if (ram > max.ram) {
33+
errors["maxRam"] = {max: max.ram, gpu};
34+
}
35+
36+
return Object.entries(errors).length > 0 ? errors : null;
37+
};
38+
}
339

440
@Component({
541
selector: "app-form-specs",
@@ -11,84 +47,70 @@ export class FormSpecsComponent implements OnInit {
1147
@Input() readonlyCPU: boolean;
1248
@Input() readonlyMemory: boolean;
1349

14-
constructor() {}
15-
16-
ngOnInit()
17-
{
50+
ngOnInit() {
1851
this.parentForm
19-
.get('cpu')
20-
.setValidators([Validators.required, Validators.pattern(/^[0-9]+([.][0-9]+)?$/), Validators.min(0.5), this.maxCPUValidator()]);
52+
.get("cpu")
53+
.setValidators([
54+
Validators.required,
55+
Validators.pattern(/^[0-9]+([.][0-9]+)?$/),
56+
Validators.min(0.5)
57+
]);
2158
this.parentForm
22-
.get('memory')
23-
.setValidators([Validators.required, Validators.pattern(/^[0-9]+([.][0-9]+)?(Gi)?$/), Validators.min(1), this.maxMemoryValidator()]);
59+
.get("memory")
60+
.setValidators([
61+
Validators.required,
62+
Validators.pattern(/^[0-9]+([.][0-9]+)?(Gi)?$/),
63+
Validators.min(1)
64+
]);
65+
this.parentForm.setValidators(resourcesValidator());
2466
}
2567

26-
showCPUError()
27-
{
28-
const cpu = this.parentForm.get('cpu');
29-
const gpus = this.parentForm.get('gpus').get('num').value;
30-
31-
if (cpu.hasError('required')) {
32-
return `Please provide the CPU requirements`;
33-
}
34-
if (cpu.hasError('pattern')) {
35-
return `Invalid character`;
36-
}
37-
if (cpu.hasError('min')) {
38-
return `Can't be less than 0.5 CPU`;
39-
}
40-
if (cpu.hasError('maxCPU')) {
41-
if (gpus=='none') {
42-
return `Can't exceed 15 CPU`;
43-
} else {
44-
return `Can't exceed 5 CPU`;
68+
parentErrorKeysErrorStateMatcher(keys: string | string[]) {
69+
const arrKeys = ([] as string[]).concat(keys);
70+
return {
71+
isErrorState(
72+
control: FormControl,
73+
form: FormGroupDirective | NgForm
74+
): boolean {
75+
return (
76+
(control.dirty && control.invalid) ||
77+
(form.dirty && arrKeys.some(key => form.hasError(key)))
78+
);
4579
}
46-
}
47-
80+
};
4881
}
4982

50-
showMemoryError()
51-
{
52-
const memory = this.parentForm.get('memory');
53-
const gpus = this.parentForm.get('gpus').get('num').value;
83+
cpuErrorMessage(): string {
84+
let e: any;
85+
const errs = this.parentForm.get("cpu").errors || {};
5486

55-
if (memory.hasError('required')) {
56-
return "Please provide the RAM requirements";
57-
}
58-
if (memory.hasError('pattern')) {
59-
return `Invalid character`
60-
}
61-
if (memory.hasError('min')) {
62-
return `Can't be less than 1Gi of RAM`
63-
}
64-
if (memory.hasError('maxMemory')) {
65-
if (gpus=='none') {
66-
return `Can't exceed 48Gi of RAM`;
67-
} else {
68-
return `Can't exceed 96Gi of RAM`;
69-
}
70-
}
71-
}
72-
73-
private maxCPUValidator(): ValidatorFn
74-
{
75-
return (control: AbstractControl): { [key: string]: any} | null => {
76-
var max: number;
77-
const gpus = this.parentForm.get('gpus').get('num').value;
78-
gpus == 'none' ? max = 15 : max = 5;
79-
return control.value>max ? { maxCPU: true } : null
87+
if (errs.required) return "Specify number of CPUs";
88+
if (errs.pattern) return "Must be a number";
89+
if ((e = errs.min)) return `Specify at least ${e.min} CPUs`;
90+
91+
if (this.parentForm.hasError("maxCpu")) {
92+
e = this.parentForm.errors.maxCpu;
93+
return (
94+
`Can't exceed ${e.max} CPUs` +
95+
(e.gpu > 0 ? ` with ${e.gpu} GPU(s) selected` : "")
96+
);
8097
}
8198
}
8299

83-
private maxMemoryValidator(): ValidatorFn
84-
{
85-
return (control: AbstractControl): { [key: string]: any} | null => {
86-
var max: number;
87-
const gpus = this.parentForm.get('gpus').get('num').value;
88-
gpus == 'none' ? max = 48 : max = 96;
89-
return control.value>max ? { maxMemory: true } : null
100+
memoryErrorMessage(): string {
101+
let e: any;
102+
const errs = this.parentForm.get("memory").errors || {};
103+
104+
if (errs.required || errs.pattern)
105+
return "Specify amount of memory (e.g. 2Gi)";
106+
if ((e = errs.min)) return `Specify at least ${e.min}Gi of memory`;
107+
108+
if (this.parentForm.hasError("maxRam")) {
109+
e = this.parentForm.errors.maxRam;
110+
return (
111+
`Can't exceed ${e.max}Gi of memory` +
112+
(e.gpu > 0 ? ` with ${e.gpu} GPU(s) selected` : "")
113+
);
90114
}
91115
}
92116
}
93-
94-

0 commit comments

Comments
 (0)