Browse Source

Added string autocomplete for telemetry device tab (#14507)

* added string autocomplete for telemetry device tab

* fixed validation

* fixed button alignment

* fixed styles
pull/14525/head
Maksym Tsymbarov 9 months ago
committed by GitHub
parent
commit
aeb9958bf5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 22
      ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html
  2. 33
      ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts
  3. 14
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  4. 8
      ui-ngx/src/app/shared/components/string-autocomplete.component.html
  5. 20
      ui-ngx/src/app/shared/components/string-autocomplete.component.scss
  6. 50
      ui-ngx/src/app/shared/components/string-autocomplete.component.ts

22
ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html

@ -30,16 +30,18 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col gap-2"> <fieldset [disabled]="isLoading$ | async" class="flex flex-col gap-2">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="mat-block tb-inline-field"> <tb-string-autocomplete
<mat-label translate>attribute.key</mat-label> panelWidth=""
<input matInput formControlName="key" required> additionalClass="mat-block tb-inline-field tb-suffix-absolute tb-suffix-show-on-hover"
<mat-error *ngIf="attributeFormGroup.get('key').hasError('required')"> required
{{ (isTelemetry ? 'attribute.telemetry-key-required' : 'attribute.key-required') | translate }} [label]="'attribute.key' | translate"
</mat-error> subscriptSizing="dynamic"
<mat-error *ngIf="attributeFormGroup.get('key').hasError('maxlength')"> appearance="outline"
{{ 'attribute.key-max-length' | translate }} [fetchOptionsFn]="isTelemetry && fetchOptions.bind(this)"
</mat-error> [errorMessages]="attributeErrorMessages"
</mat-form-field> [controlValidators]="keyValidators"
formControlName="key">
</tb-string-autocomplete>
<tb-value-input <tb-value-input
formControlName="value" formControlName="value"
[requiredText]="isTelemetry ? 'attribute.telemetry-value-required' : 'attribute.value-required'"> [requiredText]="isTelemetry ? 'attribute.telemetry-value-required' : 'attribute.value-required'">

33
ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts

@ -23,13 +23,23 @@ import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Valida
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component'; import { DialogComponent } from '@app/shared/components/dialog.component';
import { AttributeData, AttributeScope, LatestTelemetry, TelemetryType } from '@shared/models/telemetry/telemetry.models'; import {
AttributeData,
AttributeScope,
LatestTelemetry,
TelemetryType
} from '@shared/models/telemetry/telemetry.models';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AttributeDatasource } from '@home/models/datasource/attribute-datasource';
import { map } from 'rxjs/operators';
import { ErrorMessageConfig } from '@shared/components/string-autocomplete.component';
import { TranslateService } from '@ngx-translate/core';
export interface AddAttributeDialogData { export interface AddAttributeDialogData {
entityId: EntityId; entityId: EntityId;
attributeScope: TelemetryType; attributeScope: TelemetryType;
datasource?: AttributeDatasource;
} }
@Component({ @Component({
@ -47,19 +57,22 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
isTelemetry = false; isTelemetry = false;
keyValidators = [Validators.required, Validators.maxLength(255)];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router, protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddAttributeDialogData, @Inject(MAT_DIALOG_DATA) public data: AddAttributeDialogData,
private attributeService: AttributeService, private attributeService: AttributeService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AddAttributeDialogComponent, boolean>, public dialogRef: MatDialogRef<AddAttributeDialogComponent, boolean>,
public fb: FormBuilder) { public fb: FormBuilder,
private translate: TranslateService) {
super(store, router, dialogRef); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {
this.attributeFormGroup = this.fb.group({ this.attributeFormGroup = this.fb.group({
key: ['', [Validators.required, Validators.maxLength(255)]], key: ['', this.keyValidators],
value: [null, [Validators.required]] value: [null, [Validators.required]]
}); });
this.isTelemetry = this.data.attributeScope === LatestTelemetry.LATEST_TELEMETRY; this.isTelemetry = this.data.attributeScope === LatestTelemetry.LATEST_TELEMETRY;
@ -97,4 +110,18 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
} }
task.subscribe(() => this.dialogRef.close(true)); task.subscribe(() => this.dialogRef.close(true));
} }
fetchOptions(searchText: string): Observable<Array<string>> {
const search = searchText ? searchText?.toLowerCase() : '';
return this.data.datasource?.getAllAttributes(this.data.entityId,this.data.attributeScope).pipe(
map(attributes => attributes?.filter(attribute => attribute.key.toLowerCase().includes(search)).map(a => a.key)),
)
}
get attributeErrorMessages(): ErrorMessageConfig {
return {
required: this.translate.instant(this.isTelemetry ? 'attribute.telemetry-key-required' : 'attribute.key-required'),
maxlength: this.translate.instant('attribute.key-max-length')
}
}
} }

14
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

@ -317,13 +317,19 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const data: AddAttributeDialogData = {
entityId: this.entityIdValue,
attributeScope: this.attributeScope,
};
if(this.attributeScope === LatestTelemetry.LATEST_TELEMETRY) {
data.datasource = this.dataSource;
}
this.dialog.open<AddAttributeDialogComponent, AddAttributeDialogData, boolean>(AddAttributeDialogComponent, { this.dialog.open<AddAttributeDialogComponent, AddAttributeDialogData, boolean>(AddAttributeDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data
entityId: this.entityIdValue,
attributeScope: this.attributeScope
}
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { if (res) {

8
ui-ngx/src/app/shared/components/string-autocomplete.component.html

@ -31,21 +31,21 @@
</button> </button>
<mat-icon *ngIf="selectionFormControl.errors && selectionFormControl.touched && !showInlineError" <mat-icon *ngIf="selectionFormControl.errors && selectionFormControl.touched && !showInlineError"
matIconSuffix matIconSuffix
[matTooltip]="errorText" [matTooltip]="getErrorMessage"
matTooltipPosition="above" matTooltipPosition="above"
[matTooltipClass]="tooltipClass" [matTooltipClass]="tooltipClass"
class="material-icons tb-suffix-show-always tb-error"> class="material-icons tb-suffix-show-always tb-error">
warning warning
</mat-icon> </mat-icon>
<mat-error *ngIf="selectionFormControl.errors && showInlineError"> <mat-error *ngIf="selectionFormControl.errors && showInlineError">
{{errorText}} {{ getErrorMessage }}
</mat-error> </mat-error>
<mat-autocomplete <mat-autocomplete
#optionsAutocomplete="matAutocomplete" #optionsAutocomplete="matAutocomplete"
class="tb-autocomplete tb-options-input-autocomplete" class="tb-autocomplete"
[panelWidth]="panelWidth"> [panelWidth]="panelWidth">
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option"> <mat-option *ngFor="let option of filteredOptions$ | async" [value]="option">
<span class="tb-option-name flex-1" [innerHTML]="option | highlight:searchText:true:'ig'"></span> <span [innerHTML]="option | highlight:searchText:true:'ig'"></span>
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</mat-form-field> </mat-form-field>

20
ui-ngx/src/app/shared/components/string-autocomplete.component.scss

@ -25,25 +25,5 @@
margin-right: 8px; margin-right: 8px;
} }
} }
.tb-autocomplete.tb-option-input-autocomplete {
.mat-mdc-option {
border-bottom: none;
.mdc-list-item__primary-text {
flex: 1;
display: flex;
flex-direction: row;
gap: 8px;
.tb-option {
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0.2px;
}
}
}
}
} }
} }

50
ui-ngx/src/app/shared/components/string-autocomplete.component.ts

@ -14,27 +14,25 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
Component,
Input,
forwardRef,
OnInit,
ViewChild,
ElementRef
} from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
NG_VALUE_ACCESSOR, FormBuilder,
FormControl, FormControl,
Validators, NG_VALUE_ACCESSOR,
FormBuilder ValidatorFn,
Validators
} from '@angular/forms'; } from '@angular/forms';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { tap, map, switchMap, take } from 'rxjs/operators'; import { map, switchMap, take, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
export interface ErrorMessageConfig {
[errorKey: string]: string;
}
@Component({ @Component({
selector: 'tb-string-autocomplete', selector: 'tb-string-autocomplete',
templateUrl: './string-autocomplete.component.html', templateUrl: './string-autocomplete.component.html',
@ -85,6 +83,12 @@ export class StringAutocompleteComponent implements ControlValueAccessor, OnInit
@Input() @Input()
errorText: string; errorText: string;
@Input()
controlValidators: ValidatorFn[] = [];
@Input()
errorMessages: ErrorMessageConfig;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
showInlineError = false; showInlineError = false;
@ -107,7 +111,13 @@ export class StringAutocompleteComponent implements ControlValueAccessor, OnInit
ngOnInit() { ngOnInit() {
const validators = [Validators.pattern(/.*\S.*/)]; const validators = [Validators.pattern(/.*\S.*/)];
if (this.required) { if (this.controlValidators?.length) {
validators.push(...this.controlValidators);
const parentHasRequired = this.controlValidators.some(v => v === Validators.required);
if (this.required && !parentHasRequired) {
validators.push(Validators.required);
}
} else if (this.required) {
validators.push(Validators.required); validators.push(Validators.required);
} }
this.selectionFormControl = this.fb.control('', validators); this.selectionFormControl = this.fb.control('', validators);
@ -184,4 +194,18 @@ export class StringAutocompleteComponent implements ControlValueAccessor, OnInit
this.nameInput.nativeElement.focus(); this.nameInput.nativeElement.focus();
}, 0); }, 0);
} }
get getErrorMessage(): string {
if (!this.selectionFormControl.errors) {
return '';
}
if (this.errorMessages) {
for (const errorKey in this.selectionFormControl.errors) {
if (this.errorMessages[errorKey]) {
return this.errorMessages[errorKey];
}
}
}
return this.errorText;
}
} }

Loading…
Cancel
Save