diff --git a/application/src/main/data/upgrade/basic/schema_update.sql b/application/src/main/data/upgrade/basic/schema_update.sql index a2dfeac358..66243461e2 100644 --- a/application/src/main/data/upgrade/basic/schema_update.sql +++ b/application/src/main/data/upgrade/basic/schema_update.sql @@ -14,3 +14,69 @@ -- limitations under the License. -- +-- UPDATE TENANT PROFILE CONFIGURATION START + +UPDATE tenant_profile +SET profile_data = jsonb_set( + profile_data, + '{configuration}', + jsonb_build_object( + 'minAllowedScheduledUpdateIntervalInSecForCF', 10, + 'maxRelationLevelPerCfArgument', 2, + 'maxRelatedEntitiesToReturnPerCfArgument', 100, + 'minAllowedDeduplicationIntervalInSecForCF', 10, + 'minAllowedAggregationIntervalInSecForCF', 60, + 'intermediateAggregationIntervalInSecForCF', 300, + 'cfReevaluationCheckInterval', 60, + 'alarmsReevaluationInterval', 60 + ) + || + jsonb_strip_nulls(profile_data -> 'configuration') +) +WHERE NOT ( + jsonb_strip_nulls(profile_data -> 'configuration') ?& ARRAY[ + 'minAllowedScheduledUpdateIntervalInSecForCF', + 'maxRelationLevelPerCfArgument', + 'maxRelatedEntitiesToReturnPerCfArgument', + 'minAllowedDeduplicationIntervalInSecForCF', + 'minAllowedAggregationIntervalInSecForCF', + 'intermediateAggregationIntervalInSecForCF', + 'cfReevaluationCheckInterval', + 'alarmsReevaluationInterval' + ] +); + +-- UPDATE TENANT PROFILE CONFIGURATION END + +-- CALCULATED FIELD UNIQUE CONSTRAINT UPDATE START + +ALTER TABLE calculated_field DROP CONSTRAINT IF EXISTS calculated_field_unq_key; +ALTER TABLE calculated_field ADD CONSTRAINT calculated_field_unq_key UNIQUE (entity_id, type, name); + +-- CALCULATED FIELD UNIQUE CONSTRAINT UPDATE END + +-- CALCULATED FIELD OUTPUT STRATEGY UPDATE START + +UPDATE calculated_field +SET configuration = jsonb_set( + configuration::jsonb, + '{output}', + (configuration::jsonb -> 'output') + || jsonb_build_object( + 'strategy', + jsonb_build_object( + 'type', 'RULE_CHAIN' + ) + ), + false + ) +WHERE (configuration::jsonb -> 'output' -> 'strategy') IS NULL; + +-- CALCULATED FIELD OUTPUT STRATEGY UPDATE END + +-- REMOVAL OF CALCULATED FIELD LINKS PERSISTENCE START + +DROP TABLE IF EXISTS calculated_field_link; +ANALYZE calculated_field; + +-- REMOVAL OF CALCULATED FIELD LINKS PERSISTENCE END diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 66695c396f..8ea989fc01 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -116,6 +116,7 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createDatabaseIndexes(); // TODO: cleanup update code after each release + systemDataLoaderService.updateDefaultNotificationConfigs(false); // Runs upgrade scripts that are not possible in plain SQL. dataUpdateService.updateData(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java index 468aa80e6c..77c943bfe8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDatabaseSchemaSettingsService.java @@ -21,17 +21,18 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.thingsboard.server.service.install.update.DefaultDataUpdateService; -import java.util.List; +import java.util.Map; @Service @Slf4j @RequiredArgsConstructor public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSettingsService { - // This list should include all versions that are compatible for the upgrade in 4 digits format (like 4.2.0.0, etc.). - // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after a new release. - // TODO: don't check the "patch" number, since upgrade is not required for patch releases - private static final List SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.3.0.0"); + // map of versions from which the upgrade to the current version is possible + // key - supported version prefix, value - display name + private static final Map SUPPORTED_VERSIONS_FOR_UPGRADE = Map.of( + "4.2.1", "4.2.1.x" + ); private final ProjectInfo projectInfo; private final JdbcTemplate jdbcTemplate; @@ -56,9 +57,9 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti onSchemaSettingsError("Upgrade failed: database already upgraded to current version. You can set SKIP_SCHEMA_VERSION_CHECK to 'true' if force re-upgrade needed."); } - if (!SUPPORTED_VERSIONS_FOR_UPGRADE.contains(dbSchemaVersion)) { + if (SUPPORTED_VERSIONS_FOR_UPGRADE.keySet().stream().noneMatch(dbSchemaVersion::startsWith)) { onSchemaSettingsError(String.format("Upgrade failed: database version '%s' is not supported for upgrade. Supported versions are: %s.", - dbSchemaVersion, SUPPORTED_VERSIONS_FOR_UPGRADE + dbSchemaVersion, SUPPORTED_VERSIONS_FOR_UPGRADE.values() )); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java index 668400423d..ca54775ea2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/ConstraintValidatorTest.java @@ -16,16 +16,12 @@ package org.thingsboard.server.dao.service; import org.junit.Assert; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.exception.DataValidationException; class ConstraintValidatorTest { - private static final int MIN_IN_MS = 60000; - private static final int _1M = 1_000_000; - @Test void validateFields() { StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value"); @@ -35,16 +31,4 @@ class ConstraintValidatorTest { ConstraintValidator.validateFields(stringDataEntryValid); } - @Test - void validatePerMinute() { - StringDataEntry stringDataEntryValid = new StringDataEntry("key", "value"); - - long start = System.currentTimeMillis(); - for (int i = 0; i < _1M; i++) { - ConstraintValidator.validateFields(stringDataEntryValid); - } - long end = System.currentTimeMillis(); - - Assertions.assertTrue(MIN_IN_MS > end - start); - } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java deleted file mode 100644 index 100ef1c11e..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright © 2016-2026 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.dao.service.timeseries.sql; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.service.AbstractServiceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.dao.timeseries.TimeseriesLatestDao; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -@DaoSqlTest -@Slf4j -public class LatestTimeseriesPerformanceTest extends AbstractServiceTest { - - private static final String STRING_KEY = "stringKey"; - private static final String LONG_KEY = "longKey"; - private static final String DOUBLE_KEY = "doubleKey"; - private static final String BOOLEAN_KEY = "booleanKey"; - private static final int AMOUNT_OF_UNIQ_KEY = 10000; - private static final int TIMEOUT = 100; - - private final Random random = new Random(); - - @Autowired - private TimeseriesLatestDao timeseriesLatestDao; - - private ListeningExecutorService testExecutor; - - private EntityId entityId; - - private AtomicLong saveCounter; - - @Before - public void before() { - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - Tenant savedTenant = tenantService.saveTenant(tenant); - Assert.assertNotNull(savedTenant); - tenantId = savedTenant.getId(); - entityId = new DeviceId(UUID.randomUUID()); - saveCounter = new AtomicLong(0); - testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(200, ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"))); - } - - @After - public void after() { - tenantService.deleteTenant(tenantId); - if (testExecutor != null) { - testExecutor.shutdownNow(); - } - } - - @Test - public void test_save_latest_timeseries() throws Exception { - warmup(); - saveCounter.set(0); - - long startTime = System.currentTimeMillis(); - List> futures = new ArrayList<>(); - for (int i = 0; i < 25_000; i++) { - futures.add(save(generateStrEntry(getRandomKey()))); - futures.add(save(generateLngEntry(getRandomKey()))); - futures.add(save(generateDblEntry(getRandomKey()))); - futures.add(save(generateBoolEntry(getRandomKey()))); - } - Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - long endTime = System.currentTimeMillis(); - - long totalTime = endTime - startTime; - - log.info("Total time: {}", totalTime); - log.info("Saved count: {}", saveCounter.get()); - log.warn("Saved per 1 sec: {}", saveCounter.get() * 1000 / totalTime); - } - - private void warmup() throws Exception { - List> futures = new ArrayList<>(); - for (int i = 0; i < AMOUNT_OF_UNIQ_KEY; i++) { - futures.add(save(generateStrEntry(i))); - futures.add(save(generateLngEntry(i))); - futures.add(save(generateDblEntry(i))); - futures.add(save(generateBoolEntry(i))); - } - Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - } - - private ListenableFuture save(TsKvEntry tsKvEntry) { - return Futures.transformAsync(testExecutor.submit(() -> timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)), result -> { - saveCounter.incrementAndGet(); - return result; - }, testExecutor); - } - - private TsKvEntry generateStrEntry(int keyIndex) { - return new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(STRING_KEY + keyIndex, RandomStringUtils.random(10))); - } - - private TsKvEntry generateLngEntry(int keyIndex) { - return new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(LONG_KEY + keyIndex, random.nextLong())); - } - - private TsKvEntry generateDblEntry(int keyIndex) { - return new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry(DOUBLE_KEY + keyIndex, random.nextDouble())); - } - - private TsKvEntry generateBoolEntry(int keyIndex) { - return new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(BOOLEAN_KEY + keyIndex, random.nextBoolean())); - } - - private int getRandomKey() { - return random.nextInt(AMOUNT_OF_UNIQ_KEY); - } - -} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 3438da8904..84d9061b26 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -653,7 +653,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC private hideToolbarSetting(): boolean { if (isDefined(this.dashboard.configuration?.settings?.hideToolbar)) { - const canApplyHideSetting = !this.forceFullscreen || this.isMobileApp; + const canApplyHideSetting = !this.forceFullscreen || this.isMobileApp || this.isPublicUser(); return this.dashboard.configuration.settings.hideToolbar && canApplyHideSetting; } else { return false;