Fix Alarm repository. Add queue type list select.

This commit is contained in:
Andrii Shvaika 2020-04-27 01:12:23 +03:00
parent 91aa7a2700
commit df3614f9e4
18 changed files with 404 additions and 9 deletions

View File

@ -203,7 +203,8 @@ public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateServ
stringColumn("entity_type"),
stringColumn("event_type"),
stringColumn("event_uid"),
stringColumn("body")),
stringColumn("body"),
new CassandraToSqlEventTsColumn()),
new CassandraToSqlTable("relation",
idColumn("from_id"),
stringColumn("from_type"),
@ -245,7 +246,9 @@ public class CassandraEntitiesToSqlMigrateService implements EntitiesMigrateServ
stringColumn("zip"),
stringColumn("phone"),
stringColumn("email"),
stringColumn("additional_info")),
stringColumn("additional_info"),
booleanColumn("isolated_tb_core"),
booleanColumn("isolated_tb_rule_engine")),
new CassandraToSqlTable("user_credentials",
idColumn("id"),
idColumn("user_id"),

View File

@ -0,0 +1,40 @@
/**
* 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.
*/
package org.thingsboard.server.service.install.migrate;
import com.datastax.driver.core.Row;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF;
public class CassandraToSqlEventTsColumn extends CassandraToSqlColumn {
CassandraToSqlEventTsColumn() {
super("id", "ts", CassandraToSqlColumnType.BIGINT, null);
}
@Override
public String getColumnValue(Row row) {
UUID id = row.getUUID(getIndex());
long ts = getTs(id);
return ts + "";
}
private long getTs(UUID uuid) {
return (uuid.timestamp() - EPOCH_DIFF) / 10000;
}
}

View File

@ -15,12 +15,9 @@
*/
package org.thingsboard.server.dao.util;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(prefix = "database.entities", value = "type", havingValue = "sql")
public @interface SqlDao {
}

View File

@ -38,7 +38,7 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
@Param("alarmType") String alarmType,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " +
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " +
"RelationEntity re " +
"WHERE a.tenantId = :tenantId " +
"AND a.id = re.toId AND re.toType = 'ALARM' " +
@ -51,7 +51,21 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
"AND (:idOffset IS NULL OR a.id < :idOffset) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))")
"OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))",
countQuery = "SELECT count(a) FROM AlarmEntity a, " +
"RelationEntity re " +
"WHERE a.tenantId = :tenantId " +
"AND a.id = re.toId AND re.toType = 'ALARM' " +
"AND re.relationTypeGroup = 'ALARM' " +
"AND re.relationType = :relationType " +
"AND re.fromId = :affectedEntityId " +
"AND re.fromType = :affectedEntityType " +
"AND (:startId IS NULL OR a.id >= :startId) " +
"AND (:endId IS NULL OR a.id <= :endId) " +
"AND (:idOffset IS NULL OR a.id < :idOffset) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))")
Page<AlarmInfoEntity> findAlarms(@Param("tenantId") String tenantId,
@Param("affectedEntityId") String affectedEntityId,
@Param("affectedEntityType") String affectedEntityType,

View File

@ -28,6 +28,7 @@ export * from './entity-relation.service';
export * from './entity-view.service';
export * from './event.service';
export * from './http-utils';
export * from './queue.service';
export * from './rule-chain.service';
export * from './tenant.service';
export * from './user.service';

View File

@ -0,0 +1,36 @@
///
/// 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 { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs';
import { ServiceType } from '@shared/models/queue.models';
@Injectable({
providedIn: 'root'
})
export class QueueService {
constructor(
private http: HttpClient
) { }
public getTenantQueuesByServiceType(serviceType: ServiceType, config?: RequestConfig): Observable<Array<string>> {
return this.http.get<Array<string>>(`/api/tenant/queues?serviceType=${serviceType}`,
defaultHttpOptionsFromConfig(config));
}
}

View File

@ -56,6 +56,16 @@
</mat-form-field>
</div>
<tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact>
<div fxLayout="column">
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
<div>{{ 'tenant.isolated-tb-core' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-core-details' | translate}}</div>
</mat-checkbox>
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbRuleEngine">
<div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>
</mat-checkbox>
</div>
</fieldset>
</form>
</div>

View File

@ -0,0 +1,22 @@
/**
* 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.
*/
:host ::ng-deep {
.mat-checkbox.hinted-checkbox {
.mat-checkbox-inner-container {
margin-top: 4px;
}
}
}

View File

@ -26,7 +26,8 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
@Component({
selector: 'tb-tenant',
templateUrl: './tenant.component.html'
templateUrl: './tenant.component.html',
styleUrls: ['./tenant.component.scss']
})
export class TenantComponent extends ContactBasedComponent<Tenant> {
@ -50,6 +51,8 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
return this.fb.group(
{
title: [entity ? entity.title : '', [Validators.required]],
isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
additionalInfo: this.fb.group(
{
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : '']
@ -61,6 +64,8 @@ export class TenantComponent extends ContactBasedComponent<Tenant> {
updateEntityForm(entity: Tenant) {
this.entityForm.patchValue({title: entity.title});
this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
}

View File

@ -0,0 +1,43 @@
<!--
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]="queueFormGroup" class="mat-block">
<mat-label>{{ 'queue.name' | translate }}</mat-label>
<input matInput type="text" placeholder="{{ 'queue.select_name' | translate }}"
#queueInput
formControlName="queue"
(focusin)="onFocus()"
[required]="required"
[matAutocomplete]="queueAutocomplete">
<button *ngIf="queueFormGroup.get('queue').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"
#queueAutocomplete="matAutocomplete"
[displayWith]="displayQueueFn">
<mat-option *ngFor="let queue of filteredQueues | async" [value]="queue">
<span [innerHTML]="queue | highlight:searchText"></span>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="queueFormGroup.get('queue').hasError('required')">
{{ 'queue.name_required' | translate }}
</mat-error>
</mat-form-field>

View File

@ -0,0 +1,177 @@
///
/// 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 { AfterViewInit, Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { QueueService } from '@core/http/queue.service';
import { ServiceType } from '@shared/models/queue.models';
@Component({
selector: 'tb-queue-type-list',
templateUrl: './queue-type-list.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => QueueTypeListComponent),
multi: true
}]
})
export class QueueTypeListComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy {
queueFormGroup: FormGroup;
modelValue: string | null;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@Input()
queueType: ServiceType;
@ViewChild('queueInput', {static: true}) queueInput: ElementRef<HTMLInputElement>;
filteredQueues: Observable<Array<string>>;
queues: Observable<Array<string>>;
searchText = '';
private dirty = false;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
private queueService: QueueService,
private fb: FormBuilder) {
this.queueFormGroup = this.fb.group({
queue: [null]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.filteredQueues = this.queueFormGroup.get('queue').valueChanges
.pipe(
tap(value => {
this.updateView(value);
}),
map(value => value ? value : ''),
mergeMap(queue => this.fetchQueues(queue) )
);
}
ngAfterViewInit(): void {
}
ngOnDestroy(): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.queueFormGroup.disable({emitEvent: false});
} else {
this.queueFormGroup.enable({emitEvent: false});
}
}
writeValue(value: string | null): void {
this.searchText = '';
this.modelValue = value;
this.queueFormGroup.get('queue').patchValue(value, {emitEvent: false});
this.dirty = true;
}
onFocus() {
if (this.dirty) {
this.queueFormGroup.get('queue').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
updateView(value: string | null) {
if (this.modelValue !== value) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayQueueFn(queue?: string): string | undefined {
return queue ? queue : undefined;
}
fetchQueues(searchText?: string): Observable<Array<string>> {
this.searchText = searchText;
return this.getQueues().pipe(
map(queues => {
const result = queues.filter( queue => {
return searchText ? queue.toUpperCase().startsWith(searchText.toUpperCase()) : true;
});
if (result.length) {
if (searchText && searchText.length && result.indexOf(searchText) === -1) {
result.push(searchText);
}
result.sort();
} else if (searchText && searchText.length) {
result.push(searchText);
}
return result;
})
);
}
getQueues(): Observable<Array<string>> {
if (!this.queues) {
this.queues = this.queueService.
getTenantQueuesByServiceType(this.queueType, {ignoreLoading: true}).pipe(
publishReplay(1),
refCount()
);
}
return this.queues;
}
clear() {
this.queueFormGroup.get('queue').patchValue(null, {emitEvent: true});
setTimeout(() => {
this.queueInput.nativeElement.blur();
this.queueInput.nativeElement.focus();
}, 0);
}
}

View File

@ -36,6 +36,7 @@ export * from './error.models';
export * from './event.models';
export * from './login.models';
export * from './material.models';
export * from './queue.models';
export * from './relation.models';
export * from './rule-chain.models';
export * from './rule-node.models';

View File

@ -0,0 +1,22 @@
///
/// 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.
///
export enum ServiceType {
TB_CORE = 'TB_CORE',
TB_RULE_ENGINE = 'TB_RULE_ENGINE',
TB_TRANSPORT = 'TB_TRANSPORT',
JS_EXECUTOR = 'JS_EXECUTOR'
}

View File

@ -21,5 +21,7 @@ import {TenantId} from './id/tenant-id';
export interface Tenant extends ContactBased<TenantId> {
title: string;
region: string;
isolatedTbCore: boolean;
isolatedTbRuleEngine: boolean;
additionalInfo?: any;
}

View File

@ -129,6 +129,7 @@ import { JsonObjectEditDialogComponent } from '@shared/components/dialog/json-ob
import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
import { TbTemplatePipe } from '@shared/pipe/template.pipe';
import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-gateway-select.component';
import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component';
@NgModule({
providers: [
@ -179,6 +180,7 @@ import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-g
EntityKeysListComponent,
EntityListSelectComponent,
EntityTypeListComponent,
QueueTypeListComponent,
RelationTypeAutocompleteComponent,
SocialSharePanelComponent,
JsonObjectEditComponent,
@ -297,6 +299,7 @@ import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-g
EntityKeysListComponent,
EntityListSelectComponent,
EntityTypeListComponent,
QueueTypeListComponent,
RelationTypeAutocompleteComponent,
SocialSharePanelComponent,
JsonObjectEditComponent,

View File

@ -1514,6 +1514,11 @@
"help": "Help",
"reset-debug-mode": "Reset debug mode in all nodes"
},
"queue": {
"select_name": "Select queue name",
"name": "Queue Name",
"name_required": "Queue name is required!"
},
"tenant": {
"tenant": "Tenant",
"tenants": "Tenants",
@ -1541,7 +1546,11 @@
"no-tenants-matching": "No tenants matching '{{entity}}' were found.",
"tenant-required": "Tenant is required",
"search": "Search tenants",
"selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected"
"selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selected",
"isolated-tb-core": "Processing in isolated ThingsBoard Core container",
"isolated-tb-rule-engine": "Processing in isolated ThingsBoard Rule Engine container",
"isolated-tb-core-details": "Requires separate microservice(s) per isolated Tenant",
"isolated-tb-rule-engine-details": "Requires separate microservice(s) per isolated Tenant"
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",

View File

@ -1404,6 +1404,11 @@
"help": "Помощь",
"reset-debug-mode": "Сбросить режим отладки во всех правилах"
},
"queue": {
"select_name": "Выберите имя для Queue",
"name": "Имя для Queue",
"name_required": "Поле 'Имя для Queue' обязательно к заполнению!"
},
"tenant": {
"tenant": "Владелец",
"tenants": "Владельцы",

View File

@ -1970,6 +1970,11 @@
"no-timezones-matching": "Не знайдено жодних часових поясів, які відповідають '{{timezone}}'.",
"timezone-required": "Необхідно вказати часовий пояс."
},
"queue": {
"select_name": "Виберіть ім'я для Queue",
"name": "Iм'я для Queue",
"name_required": "Поле 'Имя для Queue' обязательно к заполнению!"
},
"tenant": {
"tenant": "Власник",
"tenants": "Власники",