From 9d709bb58ac26f9f21c0ec89ee236f42386e9df0 Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Tue, 7 Feb 2023 17:45:03 +0200 Subject: [PATCH 1/6] UI: Refactoring timewindow component --- .../time/timewindow-panel.component.ts | 168 +++++++++--------- .../components/time/timewindow.component.html | 18 +- .../components/time/timewindow.component.ts | 145 ++++----------- 3 files changed, 128 insertions(+), 203 deletions(-) diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts index d0c7f88b51..0b6334200f 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core'; +import { Component, Input, OnInit, ViewContainerRef } from '@angular/core'; import { aggregationTranslations, AggregationType, @@ -25,14 +25,12 @@ import { Timewindow, TimewindowType } from '@shared/models/time/time.models'; -import { OverlayRef } from '@angular/cdk/overlay'; import { PageComponent } from '@shared/components/page.component'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { TimeService } from '@core/services/time.service'; - -export const TIMEWINDOW_PANEL_DATA = new InjectionToken('TimewindowPanelData'); +import { isDefined } from '@core/utils'; export interface TimewindowPanelData { historyOnly: boolean; @@ -50,6 +48,12 @@ export interface TimewindowPanelData { }) export class TimewindowPanelComponent extends PageComponent implements OnInit { + @Input() + data: any; + + @Input() + onClose: (result: Timewindow | null) => void; + historyOnly = false; quickIntervalOnly = false; @@ -62,8 +66,6 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { timewindow: Timewindow; - result: Timewindow; - timewindowForm: FormGroup; historyTypes = HistoryWindowType; @@ -78,22 +80,23 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { aggregationTypesTranslations = aggregationTranslations; - constructor(@Inject(TIMEWINDOW_PANEL_DATA) public data: TimewindowPanelData, - public overlayRef: OverlayRef, - protected store: Store, + private result: Timewindow; + + constructor(protected store: Store, public fb: FormBuilder, private timeService: TimeService, public viewContainerRef: ViewContainerRef) { super(store); - this.historyOnly = data.historyOnly; - this.quickIntervalOnly = data.quickIntervalOnly; - this.timewindow = data.timewindow; - this.aggregation = data.aggregation; - this.timezone = data.timezone; - this.isEdit = data.isEdit; } ngOnInit(): void { + this.historyOnly = this.data.historyOnly; + this.quickIntervalOnly = this.data.quickIntervalOnly; + this.timewindow = this.data.timewindow; + this.aggregation = this.data.aggregation; + this.timezone = this.data.timezone; + this.isEdit = this.data.isEdit; + const hideInterval = this.timewindow.hideInterval || false; const hideLastInterval = this.timewindow.hideLastInterval || false; const hideQuickInterval = this.timewindow.hideQuickInterval || false; @@ -101,77 +104,60 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { const hideAggInterval = this.timewindow.hideAggInterval || false; const hideTimezone = this.timewindow.hideTimezone || false; + const realtime = this.timewindow.realtime; + const history = this.timewindow.history; + const aggregation = this.timewindow.aggregation; + this.timewindowForm = this.fb.group({ - realtime: this.fb.group( - { - realtimeType: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.realtimeType !== 'undefined' - ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, - disabled: hideInterval - }), - timewindowMs: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.timewindowMs !== 'undefined' - ? this.timewindow.realtime.timewindowMs : null, - disabled: hideInterval || hideLastInterval - }), - interval: [ - this.timewindow.realtime && typeof this.timewindow.realtime.interval !== 'undefined' - ? this.timewindow.realtime.interval : null - ], - quickInterval: this.fb.control({ - value: this.timewindow.realtime && typeof this.timewindow.realtime.quickInterval !== 'undefined' - ? this.timewindow.realtime.quickInterval : null, - disabled: hideInterval || hideQuickInterval - }) - } - ), - history: this.fb.group( - { - historyType: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined' - ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL, - disabled: hideInterval - }), - timewindowMs: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined' - ? this.timewindow.history.timewindowMs : null, - disabled: hideInterval - }), - interval: [ - this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined' - ? this.timewindow.history.interval : null - ], - fixedTimewindow: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined' - ? this.timewindow.history.fixedTimewindow : null, - disabled: hideInterval - }), - quickInterval: this.fb.control({ - value: this.timewindow.history && typeof this.timewindow.history.quickInterval !== 'undefined' - ? this.timewindow.history.quickInterval : null, - disabled: hideInterval - }) - } - ), - aggregation: this.fb.group( - { - type: this.fb.control({ - value: this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined' - ? this.timewindow.aggregation.type : null, - disabled: hideAggregation - }), - limit: this.fb.control({ - value: this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined' - ? this.checkLimit(this.timewindow.aggregation.limit) : null, - disabled: hideAggInterval - }, []) - } - ), - timezone: this.fb.control({ - value: this.timewindow.timezone !== 'undefined' - ? this.timewindow.timezone : null, - disabled: hideTimezone - }) + realtime: this.fb.group({ + realtimeType: [{ + value: this.defined(realtime, realtime.realtimeType) ? this.timewindow.realtime.realtimeType : RealtimeWindowType.LAST_INTERVAL, + disabled: hideInterval + }], + timewindowMs: [{ + value: this.defined(realtime, realtime.timewindowMs) ? this.timewindow.realtime.timewindowMs : null, + disabled: hideInterval || hideLastInterval + }], + interval: [this.defined(realtime, realtime.interval) ? this.timewindow.realtime.interval : null], + quickInterval: [{ + value: this.defined(realtime, realtime.quickInterval) ? this.timewindow.realtime.quickInterval : null, + disabled: hideInterval || hideQuickInterval + }] + }), + history: this.fb.group({ + historyType: [{ + value: this.defined(history, history.historyType) ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL, + disabled: hideInterval + }], + timewindowMs: [{ + value: this.defined(history, history.timewindowMs) ? this.timewindow.history.timewindowMs : null, + disabled: hideInterval + }], + interval: [ this.defined(history, history.interval) ? this.timewindow.history.interval : null + ], + fixedTimewindow: [{ + value: this.defined(history, history.fixedTimewindow) ? this.timewindow.history.fixedTimewindow : null, + disabled: hideInterval + }], + quickInterval: [{ + value: this.defined(history, history.quickInterval) ? this.timewindow.history.quickInterval : null, + disabled: hideInterval + }] + }), + aggregation: this.fb.group({ + type: [{ + value: this.defined(aggregation, aggregation.type) ? this.timewindow.aggregation.type : null, + disabled: hideAggregation + }], + limit: [{ + value: this.defined(aggregation, aggregation.limit) ? this.checkLimit(this.timewindow.aggregation.limit) : null, + disabled: hideAggInterval + }, []] + }), + timezone: [{ + value: isDefined(this.timewindow.timezone) ? this.timewindow.timezone : null, + disabled: hideTimezone + }] }); this.updateValidators(); this.timewindowForm.get('aggregation.type').valueChanges.subscribe(() => { @@ -179,6 +165,10 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { }); } + private defined(arg1, arg2) { + return arg1 && isDefined(arg2); + } + private checkLimit(limit?: number): number { if (!limit || limit < this.minDatapointsLimit()) { return this.minDatapointsLimit(); @@ -224,11 +214,13 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit { this.timewindow.timezone = timewindowFormValue.timezone; } this.result = this.timewindow; - this.overlayRef.dispose(); + this.cancel(this.result); } - cancel() { - this.overlayRef.dispose(); + cancel(result: Timewindow | null = null) { + if (this.onClose) { + this.onClose(result); + } } minDatapointsLimit() { diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.html b/ui-ngx/src/app/shared/components/time/timewindow.component.html index 4ec59dc74a..2b689831a9 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.html +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.html @@ -15,30 +15,34 @@ limitations under the License. --> - -
+
{{innerValue?.displayValue}} | {{innerValue.displayTimezoneAbbr}}
+ + + + +
+
+ + +
+
+
+ + +
+ timewindow.last + +
+
+ +
+ timewindow.time-period + +
+
+ +
+ timewindow.interval + +
+
+
+
+
+
+ + +
+ + +
+
- +
- - -
-
- - -
+
+ + aggregation.function + + + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} + + + +
+
+
+
+ + +
+
+
+ +
+ + + + + +
+
+
+
- +
+ + +
+
+ + +
+
+
+ + +
+ + +
+ +
+ + +
diff --git a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss index 9029c48815..70f43f410c 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss +++ b/ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss @@ -16,16 +16,14 @@ @import "../../../../scss/constants"; :host { - width: 100%; - height: 100%; - form, - fieldset { - height: 100%; - } + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 100%; + background-color: #fff; .mat-content { overflow: hidden; - background-color: #fff; } .mat-padding { @@ -70,7 +68,6 @@ :host ::ng-deep { .mat-mdc-radio-button { display: block; - margin-bottom: 16px; .mdc-form-field { align-items: start; > label { @@ -78,4 +75,7 @@ } } } + .mat-mdc-tab-group:not(.tb-headless) { + height: 100%; + } } diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index 0c87020d5b..b525ff0c24 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -17,6 +17,7 @@ import { ChangeDetectorRef, Component, + ElementRef, forwardRef, Injector, Input, @@ -179,6 +180,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe, private datePipe: DatePipe, private cd: ChangeDetectorRef, + private nativeElement: ElementRef, public viewContainerRef: ViewContainerRef, public breakpointObserver: BreakpointObserver) { } @@ -193,20 +195,22 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces if ($event) { $event.stopPropagation(); } - const target = $event.target || $event.srcElement || $event.currentTarget; - const config = new OverlayConfig(); - config.backdropClass = 'cdk-overlay-transparent-backdrop'; + const config = new OverlayConfig({ + panelClass: 'tb-timewindow-panel', + backdropClass: 'cdk-overlay-transparent-backdrop', + hasBackdrop: true, + maxHeight: '80vh', + height: 'min-content' + }); config.hasBackdrop = true; const connectedPosition: ConnectedPosition = { - originX: 'end', + originX: 'start', originY: 'bottom', - overlayX: 'end', + overlayX: 'start', overlayY: 'top' }; - config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) + config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement) .withPositions([connectedPosition]); - config.maxHeight = '70vh'; - config.height = 'min-content'; const overlayRef = this.overlay.create(config); overlayRef.backdropClick().subscribe(() => { From 0788a89f5479d31699f4698844c16ec240862a8e Mon Sep 17 00:00:00 2001 From: AndriiD Date: Thu, 23 Mar 2023 14:07:35 +0200 Subject: [PATCH 4/6] added assignee check to controller --- .../server/controller/AlarmController.java | 3 ++ .../entitiy/alarm/DefaultTbAlarmService.java | 2 +- .../server/controller/AbstractWebTest.java | 3 +- .../controller/BaseAlarmControllerTest.java | 34 +++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 78ee6620fe..54368af0db 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -139,6 +139,9 @@ public class AlarmController extends BaseController { checkNotNull(alarm.getOriginator()); checkEntity(alarm.getId(), alarm, Resource.ALARM); checkEntityId(alarm.getOriginator(), Operation.READ); + if (alarm.getAssigneeId() != null) { + checkUserId(alarm.getAssigneeId(), Operation.READ); + } return tbAlarmService.save(alarm, getCurrentUser()); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index e452a766a4..b4ac7117c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -70,7 +70,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb UserId newAssignee = alarm.getAssigneeId(); UserId curAssignee = resultAlarm.getAssigneeId(); if (newAssignee != null && !newAssignee.equals(curAssignee)) { - resultAlarm = assign(alarm, newAssignee, alarm.getAssignTs(), user); + resultAlarm = assign(resultAlarm, newAssignee, alarm.getAssignTs(), user); } else if (newAssignee == null && curAssignee != null) { resultAlarm = unassign(alarm, alarm.getAssignTs(), user); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index c09f259aaa..a5c96d693e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -352,6 +352,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected Tenant savedDifferentTenant; protected User savedDifferentTenantUser; private Customer savedDifferentCustomer; + protected User differentCustomerUser; protected void loginDifferentTenant() throws Exception { if (savedDifferentTenant != null) { @@ -379,7 +380,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { createDifferentCustomer(); loginTenantAdmin(); - User differentCustomerUser = new User(); + differentCustomerUser = new User(); differentCustomerUser.setAuthority(Authority.CUSTOMER_USER); differentCustomerUser.setTenantId(tenantId); differentCustomerUser.setCustomerId(savedDifferentCustomer.getId()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 6a94cc5a1e..7cf6d7109a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -656,4 +656,38 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { return foundAlarm; } + + + @Test + public void testCreateAlarmWithOtherTenantsAssignee() throws Exception { + loginDifferentTenant(); + loginTenantAdmin(); + + Alarm alarm = Alarm.builder() + .tenantId(tenantId) + .customerId(customerId) + .originator(customerDevice.getId()) + .severity(AlarmSeverity.CRITICAL) + .assigneeId(savedDifferentTenantUser.getId()) + .build(); + + doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + } + + @Test + public void testCreateAlarmWithOtherCustomerAsAssignee() throws Exception { + loginDifferentCustomer(); + loginCustomerUser(); + + Alarm alarm = Alarm.builder() + .tenantId(tenantId) + .customerId(customerId) + .originator(customerDevice.getId()) + .severity(AlarmSeverity.CRITICAL) + .assigneeId(differentCustomerUser.getId()) + .build(); + + doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + } + } From 07de3ef7fd88f59630e0b59062f85021bf134181 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 24 Mar 2023 12:26:25 +0200 Subject: [PATCH 5/6] fix logic in procedure for drop ts_kv partitions by ttl --- .../main/data/upgrade/3.4.4/schema_update.sql | 187 ++++++++++++++++++ .../dao/sqlts/sql/JpaSqlTimeseriesDao.java | 6 +- dao/src/main/resources/sql/schema-ts-psql.sql | 24 +-- 3 files changed, 196 insertions(+), 21 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 17e5632a28..983176c225 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -345,3 +345,190 @@ END $$; -- ALARM FUNCTIONS END + +-- TTL DROP PARTITIONS FUNCTIONS UPDATE START + +DROP PROCEDURE IF EXISTS drop_partitions_by_max_ttl(character varying, bigint, bigint); +DROP FUNCTION IF EXISTS get_partition_by_max_ttl_date; + +CREATE OR REPLACE FUNCTION get_partition_by_system_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS +$$ +BEGIN + CASE + WHEN partition_type = 'DAYS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM') || '_' || to_char(date, 'dd'); + WHEN partition_type = 'MONTHS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); + WHEN partition_type = 'YEARS' THEN + partition := 'ts_kv_' || to_char(date, 'yyyy'); + ELSE + partition := NULL; + END CASE; + IF partition IS NOT NULL THEN + IF NOT EXISTS(SELECT + FROM pg_tables + WHERE schemaname = 'public' + AND tablename = partition) THEN + partition := NULL; + RAISE NOTICE 'Failed to found partition by ttl'; + END IF; + END IF; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; + +BEGIN + if system_ttl IS NOT NULL AND system_ttl > 0 THEN + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - system_ttl); + partition_by_max_ttl_date := get_partition_by_system_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; + RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; + IF partition_by_max_ttl_date IS NOT NULL THEN + CASE + WHEN partition_type = 'DAYS' THEN + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + WHEN partition_type = 'MONTHS' THEN + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + ELSE + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + END CASE; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; +END +$$; + +-- TTL DROP PARTITIONS FUNCTIONS UPDATE END \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java index 9a50483283..15a7e99129 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/sql/JpaSqlTimeseriesDao.java @@ -99,14 +99,16 @@ public class JpaSqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao @Override public void cleanup(long systemTtl) { - cleanupPartitions(systemTtl); + if (systemTtl > 0) { + cleanupPartitions(systemTtl); + } super.cleanup(systemTtl); } private void cleanupPartitions(long systemTtl) { log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); try (Connection connection = dataSource.getConnection(); - PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_max_ttl(?,?,?)")) { + PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_system_ttl(?,?,?)")) { stmt.setString(1, partitioning); stmt.setLong(2, systemTtl); stmt.setLong(3, 0); diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 567d743823..8b2b80203e 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -34,13 +34,10 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); -CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) +CREATE OR REPLACE PROCEDURE drop_partitions_by_system_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; date timestamp; partition_by_max_ttl_date varchar; partition_by_max_ttl_month varchar; @@ -52,20 +49,9 @@ DECLARE partition_day integer; BEGIN - SELECT max(attribute_kv.long_v) - FROM tenant - INNER JOIN attribute_kv ON tenant.id = attribute_kv.entity_id - WHERE attribute_kv.attribute_key = 'TTL' - into max_tenant_ttl; - SELECT max(attribute_kv.long_v) - FROM customer - INNER JOIN attribute_kv ON customer.id = attribute_kv.entity_id - WHERE attribute_kv.attribute_key = 'TTL' - into max_customer_ttl; - max_ttl := GREATEST(system_ttl, max_customer_ttl, max_tenant_ttl); - if max_ttl IS NOT NULL AND max_ttl > 0 THEN - date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); - partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); + if system_ttl IS NOT NULL AND system_ttl > 0 THEN + date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - system_ttl); + partition_by_max_ttl_date := get_partition_by_system_ttl_date(partition_type, date); RAISE NOTICE 'Date by max ttl: %', date; RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN @@ -203,7 +189,7 @@ BEGIN END $$; -CREATE OR REPLACE FUNCTION get_partition_by_max_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS +CREATE OR REPLACE FUNCTION get_partition_by_system_ttl_date(IN partition_type varchar, IN date timestamp, OUT partition varchar) AS $$ BEGIN CASE From 4ae7cd81c495fb4e930ce2997c4216bf9073ea4a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 24 Mar 2023 12:41:06 +0200 Subject: [PATCH 6/6] UI: Move rule chains menu link in top level --- ui-ngx/src/app/core/services/menu.service.ts | 14 ++++++------- .../pages/features/features-routing.module.ts | 4 +--- .../rulechain/rulechain-routing.module.ts | 20 +------------------ .../app/shared/models/entity-type.models.ts | 2 +- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 4d8455b8d6..c2c17c0b07 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -374,6 +374,13 @@ export class MenuService { type: 'link', path: '/customers', icon: 'supervisor_account' + }, + { + id: guid(), + name: 'rulechain.rulechains', + type: 'link', + path: '/ruleChains', + icon: 'settings_ethernet' } ); if (authState.edgesSupportEnabled) { @@ -411,13 +418,6 @@ export class MenuService { path: '/features', icon: 'construction', pages: [ - { - id: guid(), - name: 'rulechain.rulechains', - type: 'link', - path: '/features/ruleChains', - icon: 'settings_ethernet' - }, { id: guid(), name: 'ota-update.ota-updates', diff --git a/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts b/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts index 4448f8a559..56a0b79318 100644 --- a/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/features/features-routing.module.ts @@ -17,7 +17,6 @@ import { RouterModule, Routes } from '@angular/router'; import { Authority } from '@shared/models/authority.enum'; import { NgModule } from '@angular/core'; -import { ruleChainsRoutes } from '@home/pages/rulechain/rulechain-routing.module'; import { otaUpdatesRoutes } from '@home/pages/ota-update/ota-update-routing.module'; import { vcRoutes } from '@home/pages/vc/vc-routing.module'; @@ -37,10 +36,9 @@ const routes: Routes = [ children: [], data: { auth: [Authority.TENANT_ADMIN], - redirectTo: '/features/ruleChains' + redirectTo: '/features/otaUpdates' } }, - ...ruleChainsRoutes, ...otaUpdatesRoutes, ...vcRoutes ] diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts index ec2148b576..d8a22c67db 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -122,7 +122,7 @@ export const importRuleChainBreadcumbLabelFunction: BreadCrumbLabelFunction([ [EntityType.DEVICE, '/entities/devices'], [EntityType.DEVICE_PROFILE, '/profiles/deviceProfiles'], [EntityType.ASSET_PROFILE, '/profiles/assetProfiles'], - [EntityType.RULE_CHAIN, '/features/ruleChains'], + [EntityType.RULE_CHAIN, '/ruleChains'], [EntityType.EDGE, '/edgeManagement/instances'], [EntityType.ENTITY_VIEW, '/entities/entityViews'], [EntityType.TB_RESOURCE, '/resources/resources-library'],