UI add rule-chain autocompleted

This commit is contained in:
Vladyslav_Prykhodko 2020-09-15 19:51:57 +03:00
parent 4a3b28d331
commit 70ca190f59
6 changed files with 277 additions and 6 deletions

View File

@ -85,7 +85,7 @@ public class InstallScripts {
}
public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
return Paths.get(getDataDir(), JSON_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
}
public String getDataDir() {

View File

@ -68,6 +68,12 @@ export class RuleChainService {
return this.http.get<RuleChain>(`/api/ruleChain/${ruleChainId}`, defaultHttpOptionsFromConfig(config));
}
public createDefaultRuleChain(ruleChainName: string, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>('/api/ruleChain/device/default', {
name: ruleChainName
}, defaultHttpOptionsFromConfig(config));
}
public saveRuleChain(ruleChain: RuleChain, config?: RequestConfig): Observable<RuleChain> {
return this.http.post<RuleChain>('/api/ruleChain', ruleChain, defaultHttpOptionsFromConfig(config));
}

View File

@ -106,6 +106,7 @@ import { AlarmRuleConditionComponent } from './profile/alarm/alarm-rule-conditio
import { AlarmRuleKeyFiltersDialogComponent } from './profile/alarm/alarm-rule-key-filters-dialog.component';
import { FilterTextComponent } from './filter/filter-text.component';
import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-dialog.component';
import { RuleChainAutocompleteComponent } from './rule-chain/rule-chain-autocomplete.component';
@NgModule({
declarations:
@ -194,7 +195,8 @@ import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-di
DeviceProfileDataComponent,
DeviceProfileComponent,
DeviceProfileDialogComponent,
AddDeviceProfileDialogComponent
AddDeviceProfileDialogComponent,
RuleChainAutocompleteComponent
],
imports: [
CommonModule,
@ -272,7 +274,8 @@ import { AddDeviceProfileDialogComponent } from './profile/add-device-profile-di
DeviceProfileDataComponent,
DeviceProfileComponent,
DeviceProfileDialogComponent,
AddDeviceProfileDialogComponent
AddDeviceProfileDialogComponent,
RuleChainAutocompleteComponent
],
providers: [
WidgetComponentService,

View File

@ -41,11 +41,10 @@
{{ 'device-profile.name-required' | translate }}
</mat-error>
</mat-form-field>
<tb-entity-autocomplete
<tb-rule-chain-autocomplete
labelText="device-profile.default-rule-chain"
[entityType]="entityType.RULE_CHAIN"
formControlName="defaultRuleChainId">
</tb-entity-autocomplete>
</tb-rule-chain-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>device-profile.type</mat-label>
<mat-select formControlName="type" required>

View File

@ -0,0 +1,57 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-form-field [formGroup]="selectRuleChainFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ ruleChainLabel | translate }}"
#entityInput
formControlName="ruleChainId"
(focusin)="onFocus()"
[required]="required"
[matAutocomplete]="entityAutocomplete">
<button *ngIf="selectRuleChainFormGroup.get('ruleChainId').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete class="tb-autocomplete"
#entityAutocomplete="matAutocomplete"
[displayWith]="displayEntityFn">
<mat-option *ngFor="let entity of filteredRuleChains | async" [value]="entity">
<span [innerHTML]="entity.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredRuleChains | async)?.length" [value]="null" class="tb-not-found">
<div class="tb-not-found-content" (click)="$event.stopPropagation()">
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty">
<span translate>device-profile.no-device-profiles-found</span>
</div>
<ng-template #searchNotEmpty>
<span>
{{ translate.get('rulechain.no-rulechains-matching',
{entity: truncate.transform(searchText, true, 6, &apos;...&apos;)}) | async }}
</span>
</ng-template>
<span>
<a translate (click)="createDefaultRuleChain($event, searchText)">rulechain.create-new-rulechain</a>
</span>
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="selectRuleChainFormGroup.get('ruleChainId').hasError('required')">
{{ 'rulechain.rulechain-required' | translate }}
</mat-error>
</mat-form-field>

View File

@ -0,0 +1,206 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
import { EntityService } from '@core/http/entity.service';
import { TruncatePipe } from '@shared/pipe/truncate.pipe';
import { RuleChainService } from '@core/http/rule-chain.service';
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
@Component({
selector: 'tb-rule-chain-autocomplete',
templateUrl: './rule-chain-autocomplete.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RuleChainAutocompleteComponent),
multi: true
}]
})
export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnInit {
selectRuleChainFormGroup: FormGroup;
ruleChainLabel = 'rulechain.rulechain';
modelValue: string | null;
@Input()
labelText: string;
@Input()
requiredText: string;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@ViewChild('entityInput', {static: true}) entityInput: ElementRef;
filteredRuleChains: Observable<Array<BaseData<EntityId>>>;
searchText = '';
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
public truncate: TruncatePipe,
private entityService: EntityService,
private ruleChainService: RuleChainService,
private fb: FormBuilder) {
this.selectRuleChainFormGroup = this.fb.group({
ruleChainId: [null]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.filteredRuleChains = this.selectRuleChainFormGroup.get('ruleChainId').valueChanges
.pipe(
tap(value => {
let modelValue;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = value.id.id;
}
this.updateView(modelValue);
if (value === null) {
this.clear();
}
}), map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
mergeMap(name => this.fetchRuleChain(name) ),
share()
);
}
ngAfterViewInit(): void {}
getCurrentEntity(): BaseData<EntityId> | null {
const currentEntity = this.selectRuleChainFormGroup.get('ruleChainId').value;
if (currentEntity && typeof currentEntity !== 'string') {
return currentEntity as BaseData<EntityId>;
} else {
return null;
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.selectRuleChainFormGroup.disable({emitEvent: false});
} else {
this.selectRuleChainFormGroup.enable({emitEvent: false});
}
}
textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
writeValue(value: string | null): void {
this.searchText = '';
if (value != null) {
const targetEntityType = EntityType.RULE_CHAIN;
this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => {
this.modelValue = entity.id.id;
this.selectRuleChainFormGroup.get('ruleChainId').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;
this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: false});
if (value !== null) {
this.propagateChange(this.modelValue);
}
}
);
} else {
this.modelValue = null;
this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: false});
}
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.selectRuleChainFormGroup.get('ruleChainId').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
reset() {
this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: false});
}
updateView(value: string | null) {
if (this.modelValue !== value) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
}
fetchRuleChain(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText;
return this.entityService.getEntitiesByNameFilter(EntityType.RULE_CHAIN, searchText,
50, null, {ignoreLoading: true});
}
clear() {
this.selectRuleChainFormGroup.get('ruleChainId').patchValue('', {emitEvent: true});
setTimeout(() => {
this.entityInput.nativeElement.blur();
this.entityInput.nativeElement.focus();
}, 0);
}
createDefaultRuleChain($event: Event, ruleChainName: string) {
this.ruleChainService.createDefaultRuleChain(ruleChainName).subscribe((ruleChain) => {
this.updateView(ruleChain.id.id);
});
}
}