Merge pull request #13493 from vvlladd28/improvement/rate-limits/validation-text

Improved rate limits labels and added validation for duplicate time values
This commit is contained in:
Viacheslav Klimov 2025-06-02 12:36:44 +03:00 committed by GitHub
commit b0b682f6a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 81 additions and 74 deletions

View File

@ -17,31 +17,33 @@
-->
<section class="tb-rate-limits-form" [formGroup]="rateLimitsListFormGroup">
<div class="flex-1" [formGroup]="rateLimit" *ngFor="let rateLimit of rateLimitsFormArray.controls; let $index = index">
<div class="tb-rate-limits-operation" *ngIf="$index > 0 && rateLimitsFormArray.controls.length > 1" translate>
tenant-profile.rate-limits.but-less-than
</div>
@if($index > 0) {
<div class="tb-rate-limits-operation" translate>tenant-profile.rate-limits.and-also-less-than</div>
}
<div class="flex flex-1 flex-row gap-2">
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1">
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rate-limits.number-of-messages</mat-label>
<input matInput placeholder="{{ 'tenant-profile.rate-limits.number-of-messages' | translate }}"
type="number" min="1" step="1" formControlName="value" required>
<mat-error *ngIf="rateLimit.get('value').hasError('required')">
{{ 'tenant-profile.rate-limits.number-of-messages-required' | translate }}
</mat-error>
<mat-error *ngIf="rateLimit.get('value').hasError('min')">
{{ 'tenant-profile.rate-limits.number-of-messages-min' | translate }}
</mat-error>
<mat-hint></mat-hint>
@if (rateLimit.get('value').hasError('required')) {
<mat-error translate>tenant-profile.rate-limits.number-of-messages-required</mat-error>
} @else if(rateLimit.get('value').hasError('min')) {
<mat-error translate>tenant-profile.rate-limits.number-of-messages-min</mat-error>
}
</mat-form-field>
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1">
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rate-limits.per-seconds</mat-label>
<input matInput placeholder="{{ 'tenant-profile.rate-limits.per-seconds' | translate }}"
type="number" min="1" step="1" formControlName="time" required>
<mat-error *ngIf="rateLimit.get('time').hasError('required')">
{{ 'tenant-profile.rate-limits.per-seconds-required' | translate }}
</mat-error>
<mat-error *ngIf="rateLimit.get('time').hasError('min')">
{{ 'tenant-profile.rate-limits.per-seconds-min' | translate }}
</mat-error>
<mat-hint></mat-hint>
@if (rateLimit.get('time').hasError('required')) {
<mat-error translate>tenant-profile.rate-limits.per-seconds-required</mat-error>
} @else if (rateLimit.get('time').hasError('min')) {
<mat-error translate>tenant-profile.rate-limits.per-seconds-min</mat-error>
} @else if (rateLimit.get('time').hasError('duplicateTime')) {
<mat-error translate>tenant-profile.rate-limits.per-seconds-duplicate</mat-error>
}
</mat-form-field>
<button mat-icon-button type="button" color="primary"
class="tb-rate-limits-button"

View File

@ -18,7 +18,7 @@
:host {
.tb-rate-limits-form {
@media #{$mat-gt-sm} {
min-width: 600px;
min-width: 620px;
}
}

View File

@ -14,22 +14,23 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, DestroyRef, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup,
FormArray,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import { Subject, Subscription } from 'rxjs';
import { RateLimits, rateLimitsArrayToString, stringToRateLimitsArray } from './rate-limits.models';
import { isDefinedAndNotNull } from '@core/utils';
import { takeUntil } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-rate-limits-list',
@ -48,51 +49,51 @@ import { takeUntil } from 'rxjs/operators';
}
]
})
export class RateLimitsListComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy {
export class RateLimitsListComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean;
rateLimitsListFormGroup: UntypedFormGroup;
rateLimitsListFormGroup: FormGroup;
rateLimitsArray: Array<RateLimits>;
private destroy$ = new Subject<void>();
private propagateChange = (v: any) => { };
private propagateChange = (_v: any) => { };
constructor(private fb: UntypedFormBuilder) {}
constructor(private fb: FormBuilder,
private destroyRef: DestroyRef) {}
ngOnInit(): void {
this.rateLimitsListFormGroup = this.fb.group({
rateLimits: this.fb.array([])
});
this.rateLimitsListFormGroup.valueChanges
.pipe(takeUntil(this.destroy$))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
this.updateView(value?.rateLimits);
this.updateView(value?.rateLimits ?? []);
}
);
}
public removeRateLimits(index: number) {
(this.rateLimitsListFormGroup.get('rateLimits') as UntypedFormArray).removeAt(index);
this.rateLimitsFormArray.removeAt(index);
}
public addRateLimits() {
this.rateLimitsFormArray.push(this.fb.group({
value: [null, [Validators.required]],
time: [null, [Validators.required]]
time: [null, [Validators.required, this.uniqTimeRequired()]]
}));
}
get rateLimitsFormArray(): UntypedFormArray {
return this.rateLimitsListFormGroup.get('rateLimits') as UntypedFormArray;
get rateLimitsFormArray(): FormArray<FormGroup> {
return this.rateLimitsListFormGroup.get('rateLimits') as FormArray<FormGroup>;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
registerOnTouched(_fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
@ -111,26 +112,26 @@ export class RateLimitsListComponent implements ControlValueAccessor, Validator,
}
writeValue(rateLimits: string) {
const rateLimitsControls: Array<UntypedFormGroup> = [];
const rateLimitsControls: Array<FormGroup> = [];
if (rateLimits) {
const rateLimitsArray = rateLimits.split(',');
for (let i = 0; i < rateLimitsArray.length; i++) {
const [value, time] = rateLimitsArray[i].split(':');
this.rateLimitsArray = stringToRateLimitsArray(rateLimits);
this.rateLimitsArray.forEach((rateLimit) => {
const rateLimitsControl = this.fb.group({
value: [value, [Validators.required]],
time: [time, [Validators.required]]
value: [rateLimit.value, [Validators.required]],
time: [rateLimit.time, [Validators.required, this.uniqTimeRequired()]]
});
if (this.disabled) {
rateLimitsControl.disable();
}
rateLimitsControls.push(rateLimitsControl);
}
})
} else {
this.rateLimitsArray = null;
}
this.rateLimitsListFormGroup.setControl('rateLimits', this.fb.array(rateLimitsControls), {emitEvent: false});
this.rateLimitsArray = stringToRateLimitsArray(rateLimits);
}
updateView(rateLimitsArray: Array<RateLimits>) {
private updateView(rateLimitsArray: Array<RateLimits>) {
if (rateLimitsArray.length > 0) {
const notNullRateLimits = rateLimitsArray.filter(rateLimits =>
isDefinedAndNotNull(rateLimits.value) && isDefinedAndNotNull(rateLimits.time)
@ -144,8 +145,22 @@ export class RateLimitsListComponent implements ControlValueAccessor, Validator,
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
private uniqTimeRequired(): ValidatorFn {
return (control: FormControl) => {
const formGroup = control.parent as FormGroup;
if (!formGroup) return null;
const formArray = formGroup.parent as FormArray;
if (!formArray) return null;
const newTime = control.value;
const index = formArray.controls.indexOf(formGroup);
const isDuplicate = formArray.controls
.filter((_, i) => i !== index)
.some(group => group.get('time')?.value === newTime && newTime !== '');
return isDuplicate ? { duplicateTime: true } : null;
};
}
}

View File

@ -17,8 +17,8 @@
import { TranslateService } from '@ngx-translate/core';
export interface RateLimits {
value: string;
time: string;
value: number;
time: number;
}
export enum RateLimitsType {
@ -113,11 +113,11 @@ export function stringToRateLimitsArray(rateLimits: string): Array<RateLimits> {
const result: Array<RateLimits> = [];
if (rateLimits?.length > 0) {
const rateLimitsArrays = rateLimits.split(',');
for (let i = 0; i < rateLimitsArrays.length; i++) {
const [value, time] = rateLimitsArrays[i].split(':');
for (const limit of rateLimitsArrays) {
const [value, time] = limit.split(':');
const rateLimitControl = {
value,
time
value: Number(value),
time: Number(time)
};
result.push(rateLimitControl);
}
@ -128,7 +128,7 @@ export function stringToRateLimitsArray(rateLimits: string): Array<RateLimits> {
export function rateLimitsArrayToString(rateLimits: Array<RateLimits>): string {
let result = '';
for (let i = 0; i < rateLimits.length; i++) {
result = result.concat(rateLimits[i].value, ':', rateLimits[i].time);
result = result.concat(rateLimits[i].value.toString(), ':', rateLimits[i].time.toString());
if ((rateLimits.length > 1) && (i !== rateLimits.length - 1)) {
result = result.concat(',');
}
@ -143,8 +143,8 @@ export function rateLimitsArrayToHtml(translate: TranslateService, rateLimitsArr
});
let result: string;
if (rateLimitsHtml.length > 1) {
const butLessThanText = translate.instant('tenant-profile.rate-limits.but-less-than');
result = rateLimitsHtml.join(' <span class="disabled">' + butLessThanText + '</span> ');
const andAlsoText = translate.instant('tenant-profile.rate-limits.and-also-less-than');
result = rateLimitsHtml.join(` <span class="disabled">${andAlsoText}</span> `);
} else {
result = rateLimitsHtml[0];
}

View File

@ -5247,7 +5247,6 @@
"add-limit": "إضافة حد",
"advanced-settings": "الإعدادات المتقدمة",
"edit-limit": "تعديل الحد",
"but-less-than": "ولكن أقل من",
"edit-transport-tenant-msg-title": "تعديل حدود معدل رسائل نقل المستأجر",
"edit-transport-tenant-telemetry-msg-title": "تعديل حدود معدل رسائل تليمتري نقل المستأجر",
"edit-transport-tenant-telemetry-data-points-title": "تعديل حدود معدل نقاط بيانات تليمتري نقل المستأجر",

View File

@ -5640,7 +5640,6 @@
"add-limit": "Tilføj begrænsning",
"advanced-settings": "Avancerede indstillinger",
"edit-limit": "Rediger begrænsning",
"but-less-than": "men mindre end",
"calculated-field-debug-event-rate-limit": "Fejlfindingshændelser for beregnede felter",
"edit-calculated-field-debug-event-rate-limit": "Rediger hastighedsgrænser for fejlfindingshændelser for beregnede felter",
"edit-transport-tenant-msg-title": "Rediger grænse for transportlejers beskeder",
@ -9221,4 +9220,4 @@
"zh_TW": "中文 (台灣)"
}
}
}
}

View File

@ -5640,7 +5640,6 @@
"add-limit": "Limit hinzufügen",
"advanced-settings": "Erweiterte Einstellungen",
"edit-limit": "Limit bearbeiten",
"but-less-than": "aber weniger als",
"calculated-field-debug-event-rate-limit": "Berechnete Feld-Debug-Ereignisse",
"edit-calculated-field-debug-event-rate-limit": "Limit für berechnete Feld-Debug-Ereignisse bearbeiten",
"edit-transport-tenant-msg-title": "Limit für Transport-Mieter-Nachrichten bearbeiten",
@ -9221,4 +9220,4 @@
"zh_TW": "chinesisch (Taiwan)"
}
}
}
}

View File

@ -5681,9 +5681,9 @@
"ws-limit-updates-per-session": "WS updates per session",
"rate-limits": {
"add-limit": "Add limit",
"and-also-less-than": "and also less than",
"advanced-settings": "Advanced settings",
"edit-limit": "Edit limit",
"but-less-than": "but less than",
"calculated-field-debug-event-rate-limit": "Calculated field debug events",
"edit-calculated-field-debug-event-rate-limit": "Edit calculated field debug events rate limits",
"edit-transport-tenant-msg-title": "Edit transport tenant messages rate limits",
@ -5723,6 +5723,7 @@
"per-seconds": "Per seconds",
"per-seconds-required": "Time rate is required.",
"per-seconds-min": "Minimum value is 1.",
"per-seconds-duplicate": "Duplicate time rate. Each time interval must be unique.",
"rate-limits": "Rate limits",
"remove-limit": "Remove limit",
"transport-tenant-msg": "Transport tenant messages",

View File

@ -5640,7 +5640,6 @@
"add-limit": "Agregar límite",
"advanced-settings": "Configuraciones avanzadas",
"edit-limit": "Editar límite",
"but-less-than": "pero menor que",
"calculated-field-debug-event-rate-limit": "Eventos de depuración de campo calculado",
"edit-calculated-field-debug-event-rate-limit": "Editar límites de eventos de depuración de campo calculado",
"edit-transport-tenant-msg-title": "Editar límites de velocidad de mensajes de transporte del inquilino",
@ -9221,4 +9220,4 @@
"zh_TW": "中文 (台灣)"
}
}
}
}

View File

@ -5640,7 +5640,6 @@
"add-limit": "Ajouter une limite",
"advanced-settings": "Paramètres avancés",
"edit-limit": "Modifier la limite",
"but-less-than": "mais inférieur à",
"calculated-field-debug-event-rate-limit": "Événements de débogage des champs calculés",
"edit-calculated-field-debug-event-rate-limit": "Modifier la limite des événements de débogage de champ calculé",
"edit-transport-tenant-msg-title": "Modifier les limites de messages de transport du locataire",
@ -9221,4 +9220,4 @@
"zh_TW": "中文 (台灣)"
}
}
}
}

View File

@ -5640,7 +5640,6 @@
"add-limit": "Aggiungi limite",
"advanced-settings": "Impostazioni avanzate",
"edit-limit": "Modifica limite",
"but-less-than": "ma inferiore a",
"calculated-field-debug-event-rate-limit": "Eventi di debug del campo calcolato",
"edit-calculated-field-debug-event-rate-limit": "Modifica i limiti di eventi di debug del campo calcolato",
"edit-transport-tenant-msg-title": "Modifica i limiti di messaggi di trasporto del tenant",
@ -9221,4 +9220,4 @@
"zh_TW": "中文 (台灣)"
}
}
}
}

View File

@ -5131,7 +5131,6 @@
"add-limit": "Add limit",
"advanced-settings": "Advanced settings",
"edit-limit": "Edit limit",
"but-less-than": "but less than",
"edit-transport-tenant-msg-title": "Edit transport tenant messages rate limits",
"edit-transport-tenant-telemetry-msg-title": "Edit transport tenant telemetry messages rate limits",
"edit-transport-tenant-telemetry-data-points-title": "Edit transport tenant telemetry data points rate limits",

View File

@ -5164,7 +5164,6 @@
"add-limit": "Limiet toevoegen",
"advanced-settings": "Geavanceerde instellingen",
"edit-limit": "Limiet bewerken",
"but-less-than": "maar minder dan",
"edit-transport-tenant-msg-title": "Snelheidslimieten voor berichten van transporttenants bewerken",
"edit-transport-tenant-telemetry-msg-title": "Snelheidslimieten voor telemetrie berichten van transporttenants bewerken",
"edit-transport-tenant-telemetry-data-points-title": "Snelheidslimieten voor telemetrie gegevenspunten van transporttenants bewerken",

View File

@ -5148,7 +5148,6 @@
"add-limit": "Dodaj limit",
"advanced-settings": "Zaawansowane ustawienia",
"edit-limit": "Edytuj limit",
"but-less-than": "ale mniej niż",
"edit-transport-tenant-msg-title": "Edytuj limity szybkości wiadomości tenanta transportu",
"edit-transport-tenant-telemetry-msg-title": "Edytuj limity szybkości komunikatów telemetrycznych Tenanta transportu",
"edit-transport-tenant-telemetry-data-points-title": "Edytuj limity szybkości punktów danych telemetrycznych Tenanta transportu",

View File

@ -4675,7 +4675,6 @@
"add-limit": "添加限制",
"advanced-settings": "高级设置",
"edit-limit": "编辑限制",
"but-less-than": "但小于",
"edit-transport-tenant-msg-title": "编辑传输租户消息速率限制",
"edit-transport-tenant-telemetry-msg-title": "编辑传输租户遥测消息速率限制",
"edit-transport-tenant-telemetry-data-points-title": "编辑传输租户遥测数据点速率限制",

View File

@ -3048,7 +3048,6 @@
"add-limit": "增加限制",
"advanced-settings": "進階設定",
"edit-limit": "編輯限制",
"but-less-than": "但小於",
"edit-transport-tenant-msg-title": "編輯傳輸租戶訊息速率限制",
"edit-transport-tenant-telemetry-msg-title": "編輯傳輸租戶遙測訊息速率限制",
"edit-transport-tenant-telemetry-data-points-title": "編輯傳輸租戶遙測資料端速率限制",