Browse Source

Fix Alarm repository. Add queue type list select.

pull/2677/head
Andrii Shvaika 7 years ago
parent
commit
df3614f9e4
  1. 7
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java
  2. 40
      application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java
  3. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java
  4. 18
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  5. 1
      ui-ngx/src/app/core/http/public-api.ts
  6. 36
      ui-ngx/src/app/core/http/queue.service.ts
  7. 10
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html
  8. 22
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss
  9. 7
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
  10. 43
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.html
  11. 177
      ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts
  12. 1
      ui-ngx/src/app/shared/models/public-api.ts
  13. 22
      ui-ngx/src/app/shared/models/queue.models.ts
  14. 2
      ui-ngx/src/app/shared/models/tenant.model.ts
  15. 3
      ui-ngx/src/app/shared/shared.module.ts
  16. 11
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  17. 5
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  18. 5
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json

7
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraEntitiesToSqlMigrateService.java

@ -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"),

40
application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlEventTsColumn.java

@ -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;
}
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlDao.java

@ -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 {
}

18
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -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,

1
ui-ngx/src/app/core/http/public-api.ts

@ -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';

36
ui-ngx/src/app/core/http/queue.service.ts

@ -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));
}
}

10
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html

@ -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>

22
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss

@ -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;
}
}
}

7
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts

@ -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 : ''}});
}

43
ui-ngx/src/app/shared/components/queue/queue-type-list.component.html

@ -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>

177
ui-ngx/src/app/shared/components/queue/queue-type-list.component.ts

@ -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);
}
}

1
ui-ngx/src/app/shared/models/public-api.ts

@ -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';

22
ui-ngx/src/app/shared/models/queue.models.ts

@ -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'
}

2
ui-ngx/src/app/shared/models/tenant.model.ts

@ -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;
}

3
ui-ngx/src/app/shared/shared.module.ts

@ -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,

11
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -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} }",

5
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

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

5
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

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

Loading…
Cancel
Save