5 changed files with 256 additions and 0 deletions
@ -0,0 +1,21 @@ |
|||||
|
-- |
||||
|
-- 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. |
||||
|
-- |
||||
|
|
||||
|
-- CALCULATED FIELD ADDITIONAL INFO ADDITION START |
||||
|
|
||||
|
ALTER TABLE calculated_field ADD COLUMN IF NOT EXISTS additional_info varchar; |
||||
|
|
||||
|
-- CALCULATED FIELD ADDITIONAL INFO ADDITION END |
||||
@ -0,0 +1,37 @@ |
|||||
|
-- |
||||
|
-- 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. |
||||
|
-- |
||||
|
|
||||
|
-- IOT HUB INSTALLED ITEM START |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS iot_hub_installed_item ( |
||||
|
id UUID NOT NULL PRIMARY KEY, |
||||
|
created_time BIGINT NOT NULL, |
||||
|
tenant_id UUID NOT NULL, |
||||
|
item_id UUID NOT NULL, |
||||
|
item_version_id UUID NOT NULL, |
||||
|
item_name VARCHAR NOT NULL, |
||||
|
item_type VARCHAR NOT NULL, |
||||
|
version VARCHAR NOT NULL, |
||||
|
descriptor JSONB NOT NULL |
||||
|
); |
||||
|
|
||||
|
CREATE INDEX IF NOT EXISTS idx_iot_hub_installed_item_tenant_id ON iot_hub_installed_item(tenant_id); |
||||
|
|
||||
|
CREATE INDEX IF NOT EXISTS idx_iot_hub_installed_item_item_type ON iot_hub_installed_item(tenant_id, item_type); |
||||
|
|
||||
|
CREATE INDEX IF NOT EXISTS idx_iot_hub_installed_item_item_id ON iot_hub_installed_item(tenant_id, item_id); |
||||
|
|
||||
|
-- IOT HUB INSTALLED ITEM END |
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* 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.service.install.lts; |
||||
|
|
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
@Component |
||||
|
@TbCoreComponent |
||||
|
public class V4_3_1_2Migration implements LtsMigration { |
||||
|
|
||||
|
@Override |
||||
|
public String getVersion() { |
||||
|
return "4.3.1.2"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* 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.service.install.lts; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle; |
||||
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
||||
|
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@TbCoreComponent |
||||
|
@RequiredArgsConstructor |
||||
|
public class V4_3_1_3Migration implements LtsMigration { |
||||
|
|
||||
|
// Match on the bundle ALIAS, not the JSON filename stem.
|
||||
|
// The "industrial_widgets" bundle shipped in a misspelled file (industial_widgets.json),
|
||||
|
// but the alias inside it is correctly spelled.
|
||||
|
private static final List<String> OBSOLETE_BUNDLE_ALIASES = List.of( |
||||
|
"air_quality", "indoor_environment", "industrial_widgets", "outdoor_environment"); |
||||
|
|
||||
|
private final WidgetsBundleService widgetsBundleService; |
||||
|
private final WidgetTypeService widgetTypeService; |
||||
|
|
||||
|
@Override |
||||
|
public String getVersion() { |
||||
|
return "4.3.1.3"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void apply() { |
||||
|
for (String alias : OBSOLETE_BUNDLE_ALIASES) { |
||||
|
deprecateTypesAndDeleteBundle(alias); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Marks the bundle's widget types as deprecated (keeping the type entities) and deletes ONLY the bundle entity.
|
||||
|
private void deprecateTypesAndDeleteBundle(String alias) { |
||||
|
WidgetsBundle bundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, alias); |
||||
|
if (bundle == null) { |
||||
|
return; // already removed — idempotent
|
||||
|
} |
||||
|
List<WidgetTypeDetails> types = widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(TenantId.SYS_TENANT_ID, bundle.getId()); |
||||
|
for (WidgetTypeDetails type : types) { |
||||
|
if (!type.isDeprecated()) { |
||||
|
type.setDeprecated(true); |
||||
|
widgetTypeService.saveWidgetType(type); |
||||
|
} |
||||
|
} |
||||
|
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, bundle.getId()); |
||||
|
log.info("Deprecated {} widget type(s) and removed obsolete system widget bundle: {}", types.size(), alias); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
/** |
||||
|
* 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.service.install.lts; |
||||
|
|
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
import org.mockito.InjectMocks; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.WidgetTypeId; |
||||
|
import org.thingsboard.server.common.data.id.WidgetsBundleId; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
||||
|
import org.thingsboard.server.common.data.widget.WidgetsBundle; |
||||
|
import org.thingsboard.server.dao.widget.WidgetTypeService; |
||||
|
import org.thingsboard.server.dao.widget.WidgetsBundleService; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.junit.jupiter.api.Assertions.assertEquals; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.argThat; |
||||
|
import static org.mockito.Mockito.never; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@ExtendWith(MockitoExtension.class) |
||||
|
class V4_3_1_3MigrationTest { |
||||
|
|
||||
|
@Mock |
||||
|
private WidgetsBundleService widgetsBundleService; |
||||
|
|
||||
|
@Mock |
||||
|
private WidgetTypeService widgetTypeService; |
||||
|
|
||||
|
@InjectMocks |
||||
|
private V4_3_1_3Migration migration; |
||||
|
|
||||
|
@Test |
||||
|
void versionIs4313() { |
||||
|
assertEquals("4.3.1.3", migration.getVersion()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void deprecatesTypesThenDeletesBundleEntityOnly() { |
||||
|
WidgetsBundle bundle = new WidgetsBundle(); |
||||
|
bundle.setId(new WidgetsBundleId(UUID.randomUUID())); |
||||
|
bundle.setAlias("air_quality"); |
||||
|
|
||||
|
WidgetTypeDetails fresh = new WidgetTypeDetails(); |
||||
|
fresh.setId(new WidgetTypeId(UUID.randomUUID())); |
||||
|
fresh.setDeprecated(false); |
||||
|
WidgetTypeDetails already = new WidgetTypeDetails(); |
||||
|
already.setId(new WidgetTypeId(UUID.randomUUID())); |
||||
|
already.setDeprecated(true); |
||||
|
|
||||
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "air_quality")).thenReturn(bundle); |
||||
|
when(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(TenantId.SYS_TENANT_ID, bundle.getId())).thenReturn(List.of(fresh, already)); |
||||
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "indoor_environment")).thenReturn(null); |
||||
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "industrial_widgets")).thenReturn(null); |
||||
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, "outdoor_environment")).thenReturn(null); |
||||
|
|
||||
|
migration.apply(); |
||||
|
|
||||
|
// only the previously non-deprecated type is re-saved, now deprecated
|
||||
|
verify(widgetTypeService).saveWidgetType(argThat(WidgetTypeDetails::isDeprecated)); |
||||
|
// widget types are NOT deleted
|
||||
|
verify(widgetTypeService, never()).deleteWidgetTypesByBundleId(any(), any()); |
||||
|
// only the bundle entity is removed
|
||||
|
verify(widgetsBundleService).deleteWidgetsBundle(TenantId.SYS_TENANT_ID, bundle.getId()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void absentBundleIsNoOp() { |
||||
|
when(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(any(), any())).thenReturn(null); |
||||
|
|
||||
|
migration.apply(); |
||||
|
|
||||
|
verify(widgetTypeService, never()).saveWidgetType(any()); |
||||
|
verify(widgetsBundleService, never()).deleteWidgetsBundle(any(), any()); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue