TSK-1750: configuration schema now respects order of individual fields

This commit is contained in:
Mustapha Zorgati 2021-10-08 13:00:53 +02:00
parent 3398e81a7c
commit 331bf2998b
6 changed files with 115 additions and 107 deletions

View File

@ -18,65 +18,75 @@
"colorLowPriority": "#5FAD00", "colorLowPriority": "#5FAD00",
"colorMediumPriority": "#FFD700", "colorMediumPriority": "#FFD700",
"filter": "{ \"Tasks with state READY\": { \"state\": [\"READY\"]}, \"Tasks with state CLAIMED\": {\"state\": [\"CLAIMED\"] }}", "filter": "{ \"Tasks with state READY\": { \"state\": [\"READY\"]}, \"Tasks with state CLAIMED\": {\"state\": [\"CLAIMED\"] }}",
"schema": { "schema": [
"Monitor Workbasket-Priority-Report": { {
"displayName": "Priority Report", "displayName": "Priority Report",
"members": { "members": [
"nameHighPriority": { {
"key": "nameHighPriority",
"displayName": "High Priority Name", "displayName": "High Priority Name",
"type": "text", "type": "text",
"max": 32 "max": 32
}, },
"nameMediumPriority": { {
"key": "nameMediumPriority",
"displayName": "Medium Priority Name", "displayName": "Medium Priority Name",
"type": "text", "type": "text",
"min": 0, "min": 0,
"max": 32 "max": 32
}, },
"nameLowPriority": { {
"key": "nameLowPriority",
"displayName": "Low Priority Name", "displayName": "Low Priority Name",
"type": "text", "type": "text",
"min": 0, "min": 0,
"max": 32 "max": 32
}, },
"intervalHighPriority": { {
"key": "intervalHighPriority",
"displayName": "High Priority Interval", "displayName": "High Priority Interval",
"type": "interval", "type": "interval",
"min": 0 "min": 0
}, },
"intervalMediumPriority": { {
"key": "intervalMediumPriority",
"displayName": "Medium Priority Interval", "displayName": "Medium Priority Interval",
"type": "interval", "type": "interval",
"min": 0 "min": 0
}, },
"intervalLowPriority": { {
"key": "intervalLowPriority",
"displayName": "Low Priority Interval", "displayName": "Low Priority Interval",
"type": "interval", "type": "interval",
"min": 0 "min": 0
}, },
"colorHighPriority": { {
"key": "colorHighPriority",
"displayName": "High Priority Color", "displayName": "High Priority Color",
"type": "color" "type": "color"
}, },
"colorMediumPriority": { {
"key": "colorMediumPriority",
"displayName": "Medium Priority Color", "displayName": "Medium Priority Color",
"type": "color" "type": "color"
}, },
"colorLowPriority": { {
"key": "colorLowPriority",
"displayName": "Low Priority Color", "displayName": "Low Priority Color",
"type": "color" "type": "color"
} }
} ]
}, },
"Monitor Workbasket-Priority-Report-Filter": { {
"displayName": "Filter for Task-Priority-Report", "displayName": "Filter for Task-Priority-Report",
"members": { "members": [
"filter": { {
"key": "filter",
"displayName": "Filter values", "displayName": "Filter values",
"type": "json", "type": "json",
"min": 1 "min": 1
} }
} ]
} }
} ]
} }

View File

@ -2,12 +2,14 @@
<!-- BUTTONS --> <!-- BUTTONS -->
<div class="settings__buttons"> <div class="settings__buttons">
<button mat-button class="settings__button--primary" matTooltip="Save settings" (click)="onSave()"> <button (click)="onSave()" class="settings__button--primary" mat-button
matTooltip="Save settings">
Save Save
<mat-icon class="md-20">save</mat-icon> <mat-icon class="md-20">save</mat-icon>
</button> </button>
<button mat-stroked-button class="settings__button--secondary" matTooltip="Revert changes" (click)="onReset()"> <button (click)="onReset()" class="settings__button--secondary" mat-stroked-button
matTooltip="Revert changes">
Undo changes Undo changes
<mat-icon class="settings__icon md-20">restore</mat-icon> <mat-icon class="settings__icon md-20">restore</mat-icon>
</button> </button>
@ -15,54 +17,63 @@
<div class="settings__content"> <div class="settings__content">
<div *ngFor="let group of groups; let outerIndex = index"> <div *ngFor="let group of settings.schema; let outerIndex = index">
<h4 class="settings__domain-name"> {{settings.schema[group].displayName}} </h4> <h4 class="settings__domain-name"> {{group.displayName}} </h4>
<div *ngFor="let member of members[outerIndex]; let innerIndex = index"> <div *ngFor="let member of group.members">
<!-- STRING --> <!-- STRING -->
<div *ngIf="getMember(group,member).type == settingTypes.TEXT" class="settings__grid"> <div *ngIf="member.type == settingTypes.TEXT" class="settings__grid">
<span> {{getMember(group,member).displayName}} </span> <span> {{member.displayName}} </span>
<mat-form-field appearance="outline" class="settings__grid--two-columns"> <mat-form-field appearance="outline" class="settings__grid--two-columns">
<mat-label class="{{member}}">{{getMember(group,member).displayName}}</mat-label> <mat-label class="{{member.key}}">{{member.displayName}}</mat-label>
<input matInput type="text" placeholder="{{getMember(group,member).displayName}}" [(ngModel)]="settings[member]" <input [(ngModel)]="settings[member.key]" matInput maxlength="{{member.max}}"
minlength="{{settings.schema[group].members[member].min}}" maxlength="{{settings.schema[group].members[member].max}}"> minlength="{{member.min}}"
placeholder="{{member.displayName}}"
type="text">
</mat-form-field> </mat-form-field>
</div> </div>
<!-- INTERVAL --> <!-- INTERVAL -->
<div *ngIf="getMember(group,member).type == settingTypes.INTERVAL" class="settings__grid"> <div *ngIf="member.type == settingTypes.INTERVAL" class="settings__grid">
<span>{{getMember(group,member).displayName}}</span> <span>{{member.displayName}}</span>
<mat-form-field appearance="outline" > <mat-form-field appearance="outline">
<mat-label class="{{member}}">Lower boundary</mat-label> <mat-label class="{{member.key}}">Lower boundary</mat-label>
<input matInput type="number" placeholder="Lower boundary" [(ngModel)]="settings[member][0]" <input [(ngModel)]="settings[member.key][0]" matInput max="{{member.max}}"
min="{{getMember(group,member).min}}" max="{{getMember(group,member).max}}"> min="{{member.min}}"
placeholder="Lower boundary" type="number">
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label class="{{member}}">Upper boundary</mat-label> <mat-label class="{{member}}">Upper boundary</mat-label>
<input matInput type="number" placeholder="Upper boundary" [(ngModel)]="settings[member][1]" <input [(ngModel)]="settings[member.key][1]" matInput max="{{member.max}}"
min="{{getMember(group,member).min}}" max="{{getMember(group,member).max}}"> min="{{member.min}}"
placeholder="Upper boundary" type="number">
</mat-form-field> </mat-form-field>
</div> </div>
<!-- COLOR --> <!-- COLOR -->
<div *ngIf="getMember(group,member).type == settingTypes.COLOR" class="settings__grid settings__color"> <div *ngIf="member.type == settingTypes.COLOR"
<span>{{getMember(group,member).displayName}}</span> class="settings__grid settings__color">
<input matInput value="{{settings[member]}}" type="color" id="{{member}}" <span>{{member.displayName}}</span>
(change)="onColorChange(member)" class="settings__colors--input"> <input (change)="onColorChange(member.key)" class="settings__colors--input"
id="{{member.key}}" matInput
type="color" value="{{settings[member.key]}}">
</div> </div>
<!-- JSON --> <!-- JSON -->
<div *ngIf="getMember(group,member).type == settingTypes.JSON" class="settings__grid"> <div *ngIf="member.type == settingTypes.JSON" class="settings__grid">
<span>{{getMember(group,member).displayName}}</span> <span>{{member.displayName}}</span>
<mat-form-field appearance="outline" class="settings__grid--two-columns"> <mat-form-field appearance="outline" class="settings__grid--two-columns">
<mat-label class="{{member}}"> <mat-label class="{{member.key}}">
{{getMember(group,member).displayName}} {{member.displayName}}
</mat-label> </mat-label>
<textarea matInput cdkTextareaAutosize cdkAutosizeMinRows="1" cdkAutosizeMaxRows="10" <textarea [(ngModel)]="settings[member.key]" cdkAutosizeMaxRows="10"
placeholder="{{getMember(group,member).displayName}}" [(ngModel)]="settings[member]"></textarea> cdkAutosizeMinRows="1"
cdkTextareaAutosize
matInput
placeholder="{{member.displayName}}"></textarea>
</mat-form-field> </mat-form-field>
</div> </div>

View File

@ -1,12 +1,12 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { Settings, SettingsMember, SettingTypes } from '../../models/settings'; import { Settings, SettingTypes } from '../../models/settings';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import { NotificationService } from '../../../shared/services/notifications/notification.service'; import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { SetSettings } from '../../../shared/store/settings-store/settings.actions'; import { SetSettings } from '../../../shared/store/settings-store/settings.actions';
import { SettingsSelectors } from '../../../shared/store/settings-store/settings.selectors'; import { SettingsSelectors } from '../../../shared/store/settings-store/settings.selectors';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { validateForm } from './settings.validators'; import { validateSettings } from './settings.validators';
@Component({ @Component({
selector: 'taskana-administration-settings', selector: 'taskana-administration-settings',
@ -17,8 +17,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
settingTypes = SettingTypes; settingTypes = SettingTypes;
settings: Settings; settings: Settings;
oldSettings: Settings; oldSettings: Settings;
groups: string[];
members: string[][] = [];
invalidMembers: string[] = []; invalidMembers: string[] = [];
destroy$ = new Subject<void>(); destroy$ = new Subject<void>();
@ -30,7 +28,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
this.settings$.pipe(takeUntil(this.destroy$)).subscribe((settings) => { this.settings$.pipe(takeUntil(this.destroy$)).subscribe((settings) => {
this.settings = this.deepCopy(settings); this.settings = this.deepCopy(settings);
this.oldSettings = this.deepCopy(settings); this.oldSettings = this.deepCopy(settings);
this.getKeysOfSettings();
}); });
} }
@ -38,22 +35,9 @@ export class SettingsComponent implements OnInit, OnDestroy {
return JSON.parse(JSON.stringify(settings)); return JSON.parse(JSON.stringify(settings));
} }
getKeysOfSettings() {
this.groups = Object.keys(this.settings.schema);
this.groups.forEach((group) => {
let groupMembers = Object.keys(this.settings.schema[group].members);
this.members.push(groupMembers);
groupMembers.forEach((member) => {
if (!(member in this.settings)) {
this.notificationService.showWarning('SETTINGS_INVALID_DATA', { setting: member });
}
});
});
}
onSave() { onSave() {
this.changeLabelColor('grey'); this.changeLabelColor('grey');
this.invalidMembers = validateForm(this.members, this.settings, this.groups); this.invalidMembers = validateSettings(this.settings);
if (this.invalidMembers.length === 0) { if (this.invalidMembers.length === 0) {
this.store.dispatch(new SetSettings(this.settings)).subscribe(() => { this.store.dispatch(new SetSettings(this.settings)).subscribe(() => {
this.notificationService.showSuccess('SETTINGS_SAVE'); this.notificationService.showSuccess('SETTINGS_SAVE');
@ -78,12 +62,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
this.settings = this.deepCopy(this.oldSettings); this.settings = this.deepCopy(this.oldSettings);
} }
getMember(group: string, member: string): SettingsMember { onColorChange(key: string) {
return this.settings.schema[group].members[member]; this.settings[key] = (document.getElementById(key) as HTMLInputElement).value;
}
onColorChange(member: string) {
this.settings[member] = (document.getElementById(member) as HTMLInputElement).value;
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -1,13 +1,11 @@
import { Settings, SettingTypes } from '../../models/settings'; import { Settings, SettingTypes } from '../../models/settings';
export const validateForm = (members: string[][], settings: Settings, groups: string[]): string[] => { export const validateSettings = (settings: Settings): string[] => {
const invalidMembers = []; const invalidMembers = [];
for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) { for (let group of settings.schema) {
for (let memberIndex = 0; memberIndex < members[groupIndex].length; memberIndex++) { for (let member of group.members) {
const memberKey = members[groupIndex][memberIndex]; const value = settings[member.key];
const member = settings.schema[groups[groupIndex]].members[memberKey];
const value = settings[memberKey];
if (member.type == SettingTypes.TEXT || member.type == SettingTypes.INTERVAL) { if (member.type == SettingTypes.TEXT || member.type == SettingTypes.INTERVAL) {
let compareWithMin; let compareWithMin;
@ -33,11 +31,11 @@ export const validateForm = (members: string[][], settings: Settings, groups: st
} }
if (!isValid) { if (!isValid) {
invalidMembers.push(memberKey); invalidMembers.push(member.key);
} }
if (member.type == SettingTypes.INTERVAL && compareWithMin > compareWithMax) { if (member.type == SettingTypes.INTERVAL && compareWithMin > compareWithMax) {
invalidMembers.push(memberKey); invalidMembers.push(member.key);
} }
} }
@ -45,7 +43,7 @@ export const validateForm = (members: string[][], settings: Settings, groups: st
try { try {
JSON.parse(value); JSON.parse(value);
} catch { } catch {
invalidMembers.push(memberKey); invalidMembers.push(member.key);
} }
} }
} }

View File

@ -1,20 +1,20 @@
export interface SettingsMember { export interface SettingsMember {
displayName: string; displayName: string;
key: string;
type: string; type: string;
min?: number; min?: number;
max?: number; max?: number;
} }
export interface GroupSetting {
displayName: string;
members: SettingsMember[];
}
export interface Settings { export interface Settings {
schema: GroupSetting[];
[setting: string]: any; [setting: string]: any;
schema: {
[parameterGroup: string]: {
displayName: string;
members: {
[memberName: string]: SettingsMember;
};
};
};
} }
export enum SettingTypes { export enum SettingTypes {

View File

@ -2,7 +2,6 @@ import { Workbasket } from '../../models/workbasket';
import { WorkbasketType } from '../../models/workbasket-type'; import { WorkbasketType } from '../../models/workbasket-type';
import { ACTION } from '../../models/action'; import { ACTION } from '../../models/action';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation'; import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
import { Settings } from '../../../settings/models/settings';
export const classificationStateMock = { export const classificationStateMock = {
classifications: [], classifications: [],
@ -529,66 +528,76 @@ export const settingsStateMock = {
colorLowPriority: '#5FAD00', colorLowPriority: '#5FAD00',
colorMediumPriority: '#FFD700', colorMediumPriority: '#FFD700',
filter: '{ "Tasks with state READY": { "state": ["READY"]}, "Tasks with state CLAIMED": {"state": ["CLAIMED"] }}', filter: '{ "Tasks with state READY": { "state": ["READY"]}, "Tasks with state CLAIMED": {"state": ["CLAIMED"] }}',
schema: { schema: [
'Monitor Workbasket-Priority-Report': { {
displayName: 'Priority Report', displayName: 'Priority Report',
members: { members: [
nameHighPriority: { {
key: 'nameHighPriority',
displayName: 'High Priority Name', displayName: 'High Priority Name',
type: 'text', type: 'text',
max: 32 max: 32
}, },
nameMediumPriority: { {
key: 'nameMediumPriority',
displayName: 'Medium Priority Name', displayName: 'Medium Priority Name',
type: 'text', type: 'text',
min: 0, min: 0,
max: 32 max: 32
}, },
nameLowPriority: { {
key: 'nameLowPriority',
displayName: 'Low Priority Name', displayName: 'Low Priority Name',
type: 'text', type: 'text',
min: 0, min: 0,
max: 32 max: 32
}, },
intervalHighPriority: { {
key: 'intervalHighPriority',
displayName: 'High Priority Interval', displayName: 'High Priority Interval',
type: 'interval', type: 'interval',
min: 0 min: 0
}, },
intervalMediumPriority: { {
key: 'intervalMediumPriority',
displayName: 'Medium Priority Interval', displayName: 'Medium Priority Interval',
type: 'interval', type: 'interval',
min: 0 min: 0
}, },
intervalLowPriority: { {
key: 'intervalLowPriority',
displayName: 'Low Priority Interval', displayName: 'Low Priority Interval',
type: 'interval', type: 'interval',
min: 0 min: 0
}, },
colorHighPriority: { {
key: 'colorHighPriority',
displayName: 'High Priority Color', displayName: 'High Priority Color',
type: 'color' type: 'color'
}, },
colorMediumPriority: { {
key: 'colorMediumPriority',
displayName: 'Medium Priority Color', displayName: 'Medium Priority Color',
type: 'color' type: 'color'
}, },
colorLowPriority: { {
key: 'colorLowPriority',
displayName: 'Low Priority Color', displayName: 'Low Priority Color',
type: 'color' type: 'color'
} }
} ]
}, },
'Monitor Workbasket-Priority-Report-Filter': { {
displayName: 'Filter for Task-Priority-Report', displayName: 'Filter for Task-Priority-Report',
members: { members: [
filter: { {
key: 'filter',
displayName: 'Filter values', displayName: 'Filter values',
type: 'json', type: 'json',
min: 1 min: 1
} }
} ]
} }
} ]
} }
}; };