Merge with master
This commit is contained in:
commit
0360e655ae
@ -14,5 +14,8 @@
|
||||
-- limitations under the License.
|
||||
--
|
||||
|
||||
ALTER TABLE widget_type
|
||||
ADD COLUMN IF NOT EXISTS tags text[];
|
||||
|
||||
ALTER TABLE api_usage_state ADD COLUMN IF NOT EXISTS tbel_exec varchar(32);
|
||||
UPDATE api_usage_state SET tbel_exec = js_exec WHERE tbel_exec IS NULL;
|
||||
UPDATE api_usage_state SET tbel_exec = js_exec WHERE tbel_exec IS NULL;
|
||||
|
||||
@ -24,7 +24,7 @@ import jakarta.mail.MessagingException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -174,10 +174,11 @@ import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIEL
|
||||
import static org.thingsboard.server.controller.UserController.YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION;
|
||||
import static org.thingsboard.server.dao.service.Validator.validateId;
|
||||
|
||||
@Slf4j
|
||||
@TbCoreComponent
|
||||
public abstract class BaseController {
|
||||
|
||||
private final Logger log = org.slf4j.LoggerFactory.getLogger(getClass());
|
||||
|
||||
/*Swagger UI description*/
|
||||
|
||||
@Autowired
|
||||
|
||||
@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -37,6 +38,7 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
@ -48,6 +50,8 @@ import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService;
|
||||
import org.thingsboard.server.service.security.permission.Operation;
|
||||
import org.thingsboard.server.service.security.permission.Resource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
|
||||
@ -75,7 +79,9 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
private static final String WIDGET_TYPE_INFO_DESCRIPTION = "Widget Type Info is a lightweight object that represents Widget Type but does not contain the heavyweight widget descriptor JSON";
|
||||
private static final String TENANT_ONLY_PARAM_DESCRIPTION = "Optional boolean parameter indicating whether only tenant widget types should be returned";
|
||||
private static final String FULL_SEARCH_PARAM_DESCRIPTION = "Optional boolean parameter indicating whether search widgets by description not only by name";
|
||||
private static final String DEPRECATED_FILTER_PARAM_DESCRIPTION = "Optional string parameter indicating whether to include deprecated widgets";
|
||||
private static final String UPDATE_EXISTING_BY_FQN_PARAM_DESCRIPTION = "Optional boolean parameter indicating whether to update existing widget type by FQN if present instead of creating new one";
|
||||
private static final String WIDGET_TYPE_ARRAY_DESCRIPTION = "A list of string values separated by comma ',' representing one of the widget type value";
|
||||
|
||||
@ApiOperation(value = "Get Widget Type Details (getWidgetTypeById)",
|
||||
notes = "Get the Widget Type Details based on the provided Widget Type Id. " + WIDGET_TYPE_DETAILS_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
|
||||
@ -166,15 +172,22 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
@Parameter(description = TENANT_ONLY_PARAM_DESCRIPTION)
|
||||
@RequestParam(required = false) Boolean tenantOnly,
|
||||
@Parameter(description = FULL_SEARCH_PARAM_DESCRIPTION)
|
||||
@RequestParam(required = false) Boolean fullSearch) throws ThingsboardException {
|
||||
@RequestParam(required = false) Boolean fullSearch,
|
||||
@Parameter(description = DEPRECATED_FILTER_PARAM_DESCRIPTION, schema = @Schema(allowableValues = {"ALL", "ACTUAL", "DEPRECATED"}))
|
||||
@RequestParam(required = false) String deprecatedFilter,
|
||||
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"timeseries", "latest", "control", "alarm", "static"}))
|
||||
@RequestParam(required = false) String[] widgetTypeList) throws ThingsboardException {
|
||||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
|
||||
List<String> widgetTypes = widgetTypeList != null ? Arrays.asList(widgetTypeList) : Collections.emptyList();
|
||||
boolean fullSearchBool = fullSearch != null && fullSearch;
|
||||
DeprecatedFilter widgetTypeDeprecatedFilter = StringUtils.isNotEmpty(deprecatedFilter) ? DeprecatedFilter.valueOf(deprecatedFilter) : DeprecatedFilter.ALL;
|
||||
if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
|
||||
return checkNotNull(widgetTypeService.findSystemWidgetTypesByPageLink(getTenantId(), fullSearch != null && fullSearch, pageLink));
|
||||
return checkNotNull(widgetTypeService.findSystemWidgetTypesByPageLink(getTenantId(), fullSearchBool, widgetTypeDeprecatedFilter, widgetTypes, pageLink));
|
||||
} else {
|
||||
if (tenantOnly != null && tenantOnly) {
|
||||
return checkNotNull(widgetTypeService.findTenantWidgetTypesByTenantIdAndPageLink(getTenantId(), fullSearch != null && fullSearch, pageLink));
|
||||
return checkNotNull(widgetTypeService.findTenantWidgetTypesByTenantIdAndPageLink(getTenantId(), fullSearchBool, widgetTypeDeprecatedFilter, widgetTypes, pageLink));
|
||||
} else {
|
||||
return checkNotNull(widgetTypeService.findAllTenantWidgetTypesByTenantIdAndPageLink(getTenantId(), fullSearch != null && fullSearch, pageLink));
|
||||
return checkNotNull(widgetTypeService.findAllTenantWidgetTypesByTenantIdAndPageLink(getTenantId(), fullSearchBool, widgetTypeDeprecatedFilter, widgetTypes, pageLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,19 +285,40 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
tenantId = getCurrentUser().getTenantId();
|
||||
}
|
||||
WidgetsBundle widgetsBundle = checkNotNull(widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(tenantId, bundleAlias));
|
||||
return checkNotNull(widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(getTenantId(), widgetsBundle.getId()));
|
||||
return checkNotNull(widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(getTenantId(), widgetsBundle.getId(), false, DeprecatedFilter.ALL,
|
||||
null, new PageLink(1024))).getData();
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Get Widget Type Info objects (getBundleWidgetTypesInfos)",
|
||||
notes = "Get the Widget Type Info objects based on the provided parameters. " + WIDGET_TYPE_INFO_DESCRIPTION + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/widgetTypesInfos", params = {"widgetsBundleId"}, method = RequestMethod.GET)
|
||||
@RequestMapping(value = "/widgetTypesInfos", params = {"widgetsBundleId", "pageSize", "page"}, method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public List<WidgetTypeInfo> getBundleWidgetTypesInfos(
|
||||
public PageData<WidgetTypeInfo> getBundleWidgetTypesInfos(
|
||||
@Parameter(description = "Widget Bundle Id", required = true)
|
||||
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
|
||||
@RequestParam("widgetsBundleId") String strWidgetsBundleId,
|
||||
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
|
||||
@RequestParam int pageSize,
|
||||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
|
||||
@RequestParam int page,
|
||||
@Parameter(description = WIDGET_TYPE_TEXT_SEARCH_DESCRIPTION)
|
||||
@RequestParam(required = false) String textSearch,
|
||||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "deprecated", "tenantId"}))
|
||||
@RequestParam(required = false) String sortProperty,
|
||||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
|
||||
@RequestParam(required = false) String sortOrder,
|
||||
@Parameter(description = FULL_SEARCH_PARAM_DESCRIPTION)
|
||||
@RequestParam(required = false) Boolean fullSearch,
|
||||
@Parameter(description = DEPRECATED_FILTER_PARAM_DESCRIPTION, schema = @Schema(allowableValues = {"ALL", "ACTUAL", "DEPRECATED"}))
|
||||
@RequestParam(required = false) String deprecatedFilter,
|
||||
@Parameter(description = WIDGET_TYPE_ARRAY_DESCRIPTION, schema = @Schema(allowableValues = {"timeseries", "latest", "control", "alarm", "static"}))
|
||||
@RequestParam(required = false) String[] widgetTypeList) throws ThingsboardException {
|
||||
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
|
||||
return checkNotNull(widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(getTenantId(), widgetsBundleId));
|
||||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
|
||||
List<String> widgetTypes = widgetTypeList != null ? Arrays.asList(widgetTypeList) : Collections.emptyList();
|
||||
DeprecatedFilter widgetTypeDeprecatedFilter = StringUtils.isNotEmpty(deprecatedFilter) ? DeprecatedFilter.valueOf(deprecatedFilter) : DeprecatedFilter.ALL;
|
||||
return checkNotNull(widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(getTenantId(), widgetsBundleId, fullSearch != null && fullSearch,
|
||||
widgetTypeDeprecatedFilter, widgetTypes, pageLink));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Get Widget Type (getWidgetTypeByBundleAliasAndTypeAlias) (Deprecated)",
|
||||
|
||||
@ -67,6 +67,7 @@ public class WidgetsBundleController extends BaseController {
|
||||
private final TbWidgetsBundleService tbWidgetsBundleService;
|
||||
|
||||
private static final String WIDGET_BUNDLE_DESCRIPTION = "Widget Bundle represents a group(bundle) of widgets. Widgets are grouped into bundle by type or use case. ";
|
||||
private static final String FULL_SEARCH_PARAM_DESCRIPTION = "Optional boolean parameter indicating extended search of widget bundles by description and by name / description of related widget types";
|
||||
|
||||
@ApiOperation(value = "Get Widget Bundle (getWidgetsBundleById)",
|
||||
notes = "Get the Widget Bundle based on the provided Widget Bundle Id. " + WIDGET_BUNDLE_DESCRIPTION + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@ -182,13 +183,15 @@ public class WidgetsBundleController extends BaseController {
|
||||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "title", "tenantId"}))
|
||||
@RequestParam(required = false) String sortProperty,
|
||||
@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
|
||||
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
|
||||
@RequestParam(required = false) String sortOrder,
|
||||
@Parameter(description = FULL_SEARCH_PARAM_DESCRIPTION)
|
||||
@RequestParam(required = false) Boolean fullSearch) throws ThingsboardException {
|
||||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
|
||||
if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
|
||||
return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), pageLink));
|
||||
return checkNotNull(widgetsBundleService.findSystemWidgetsBundlesByPageLink(getTenantId(), fullSearch != null && fullSearch, pageLink));
|
||||
} else {
|
||||
TenantId tenantId = getCurrentUser().getTenantId();
|
||||
return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink));
|
||||
return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, fullSearch != null && fullSearch, pageLink));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -262,7 +262,6 @@ public class ThingsboardInstallService {
|
||||
databaseEntitiesUpgradeService.upgradeDatabase("3.5.1");
|
||||
dataUpdateService.updateData("3.5.1");
|
||||
systemDataLoaderService.updateDefaultNotificationConfigs();
|
||||
break;
|
||||
case "3.6.0":
|
||||
log.info("Upgrading ThingsBoard from version 3.6.0 to 3.6.1 ...");
|
||||
databaseEntitiesUpgradeService.upgradeDatabase("3.6.0");
|
||||
|
||||
@ -24,6 +24,8 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
|
||||
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Component
|
||||
@TbCoreComponent
|
||||
public class WidgetTypeMsgConstructor {
|
||||
@ -59,6 +61,9 @@ public class WidgetTypeMsgConstructor {
|
||||
builder.setDescription(widgetTypeDetails.getDescription());
|
||||
}
|
||||
builder.setDeprecated(widgetTypeDetails.isDeprecated());
|
||||
if (widgetTypeDetails.getTags() != null) {
|
||||
builder.addAllTags(Arrays.asList(widgetTypeDetails.getTags()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.dao.widget.WidgetTypeService;
|
||||
|
||||
@ -31,6 +32,6 @@ public class SystemWidgetTypesEdgeEventFetcher extends BaseWidgetTypesEdgeEventF
|
||||
|
||||
@Override
|
||||
protected PageData<WidgetTypeInfo> findWidgetTypes(TenantId tenantId, PageLink pageLink) {
|
||||
return widgetTypeService.findSystemWidgetTypesByPageLink(tenantId, false, pageLink);
|
||||
return widgetTypeService.findSystemWidgetTypesByPageLink(tenantId, false, DeprecatedFilter.ALL, null, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,6 @@ public class SystemWidgetsBundlesEdgeEventFetcher extends BaseWidgetsBundlesEdge
|
||||
|
||||
@Override
|
||||
protected PageData<WidgetsBundle> findWidgetsBundles(TenantId tenantId, PageLink pageLink) {
|
||||
return widgetsBundleService.findSystemWidgetsBundlesByPageLink(tenantId, pageLink);
|
||||
return widgetsBundleService.findSystemWidgetsBundlesByPageLink(tenantId, false, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.dao.widget.WidgetTypeService;
|
||||
|
||||
@ -30,6 +31,6 @@ public class TenantWidgetTypesEdgeEventFetcher extends BaseWidgetTypesEdgeEventF
|
||||
}
|
||||
@Override
|
||||
protected PageData<WidgetTypeInfo> findWidgetTypes(TenantId tenantId, PageLink pageLink) {
|
||||
return widgetTypeService.findTenantWidgetTypesByTenantIdAndPageLink(tenantId, false, pageLink);
|
||||
return widgetTypeService.findTenantWidgetTypesByTenantIdAndPageLink(tenantId, false, DeprecatedFilter.ALL, null, pageLink);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,6 +63,7 @@ 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.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
|
||||
@ -83,6 +84,8 @@ import org.thingsboard.server.common.data.security.UserCredentials;
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
|
||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||
import org.thingsboard.server.dao.attributes.AttributesService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
@ -489,10 +492,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
||||
public void deleteSystemWidgetBundle(String bundleAlias) throws Exception {
|
||||
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias);
|
||||
if (widgetsBundle != null) {
|
||||
var widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
|
||||
for (var widgetType : widgetTypes) {
|
||||
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
|
||||
}
|
||||
PageData<WidgetTypeInfo> widgetTypes;
|
||||
var pageLink = new PageLink(1024);
|
||||
do {
|
||||
widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink);
|
||||
for (var widgetType : widgetTypes.getData()) {
|
||||
widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId());
|
||||
}
|
||||
pageLink.nextPageLink();
|
||||
} while (widgetTypes.hasNext());
|
||||
widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
|
||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
@ -39,17 +40,17 @@ public interface WidgetTypeService extends EntityDaoService {
|
||||
|
||||
void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId);
|
||||
|
||||
PageData<WidgetTypeInfo> findSystemWidgetTypesByPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findSystemWidgetTypesByPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
List<WidgetType> findWidgetTypesByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId);
|
||||
|
||||
List<WidgetTypeDetails> findWidgetTypesDetailsByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId);
|
||||
|
||||
List<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId);
|
||||
PageData<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
List<String> findWidgetFqnsByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId);
|
||||
|
||||
|
||||
@ -34,13 +34,13 @@ public interface WidgetsBundleService extends EntityDaoService {
|
||||
|
||||
WidgetsBundle findWidgetsBundleByTenantIdAndAlias(TenantId tenantId, String alias);
|
||||
|
||||
PageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, PageLink pageLink);
|
||||
PageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
|
||||
List<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId);
|
||||
|
||||
PageData<WidgetsBundle> findTenantWidgetsBundlesByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
|
||||
PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink);
|
||||
PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
|
||||
List<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(TenantId tenantId);
|
||||
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.common.data.widget;
|
||||
|
||||
public enum DeprecatedFilter {
|
||||
ALL,
|
||||
ACTUAL,
|
||||
DEPRECATED
|
||||
}
|
||||
@ -38,6 +38,9 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI
|
||||
@Length(fieldName = "description", max = 1024)
|
||||
@Schema(description = "Description of the widget")
|
||||
private String description;
|
||||
@NoXss
|
||||
@Schema(description = "Tags of the widget type")
|
||||
private String[] tags;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -59,6 +62,7 @@ public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantI
|
||||
super(widgetTypeDetails);
|
||||
this.image = widgetTypeDetails.getImage();
|
||||
this.description = widgetTypeDetails.getDescription();
|
||||
this.tags = widgetTypeDetails.getTags();
|
||||
this.externalId = widgetTypeDetails.getExternalId();
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,9 @@ public class WidgetTypeInfo extends BaseWidgetType {
|
||||
@Schema(description = "Description of the widget type", accessMode = Schema.AccessMode.READ_ONLY)
|
||||
private String description;
|
||||
@NoXss
|
||||
@Schema(description = "Tags of the widget type", accessMode = Schema.AccessMode.READ_ONLY)
|
||||
private String[] tags;
|
||||
@NoXss
|
||||
@Schema(description = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = Schema.AccessMode.READ_ONLY)
|
||||
private String widgetType;
|
||||
|
||||
@ -48,6 +51,7 @@ public class WidgetTypeInfo extends BaseWidgetType {
|
||||
super(widgetTypeInfo);
|
||||
this.image = widgetTypeInfo.getImage();
|
||||
this.description = widgetTypeInfo.getDescription();
|
||||
this.tags = widgetTypeInfo.getTags();
|
||||
this.widgetType = widgetTypeInfo.getWidgetType();
|
||||
}
|
||||
|
||||
@ -55,6 +59,7 @@ public class WidgetTypeInfo extends BaseWidgetType {
|
||||
super(widgetTypeDetails);
|
||||
this.image = widgetTypeDetails.getImage();
|
||||
this.description = widgetTypeDetails.getDescription();
|
||||
this.tags = widgetTypeDetails.getTags();
|
||||
if (widgetTypeDetails.getDescriptor() != null && widgetTypeDetails.getDescriptor().has("type")) {
|
||||
this.widgetType = widgetTypeDetails.getDescriptor().get("type").asText();
|
||||
} else {
|
||||
|
||||
@ -377,6 +377,7 @@ message WidgetTypeUpdateMsg {
|
||||
optional string description = 10;
|
||||
optional string fqn = 11;
|
||||
bool deprecated = 12;
|
||||
repeated string tags = 13;
|
||||
}
|
||||
|
||||
message AdminSettingsUpdateMsg {
|
||||
|
||||
@ -236,6 +236,10 @@
|
||||
<groupId>org.thingsboard.rule-engine</groupId>
|
||||
<artifactId>rule-engine-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.hypersistence</groupId>
|
||||
<artifactId>hypersistence-utils-hibernate-62</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@ -313,10 +313,15 @@ public class ModelConstants {
|
||||
public static final String WIDGET_TYPE_NAME_PROPERTY = "name";
|
||||
public static final String WIDGET_TYPE_IMAGE_PROPERTY = "image";
|
||||
public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description";
|
||||
public static final String WIDGET_TYPE_TAGS_PROPERTY = "tags";
|
||||
public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
|
||||
|
||||
public static final String WIDGET_TYPE_DEPRECATED_PROPERTY = "deprecated";
|
||||
|
||||
public static final String WIDGET_TYPE_WIDGET_TYPE_PROPERTY = "widget_type";
|
||||
|
||||
public static final String WIDGET_TYPE_INFO_VIEW_TABLE_NAME = "widget_type_info_view";
|
||||
|
||||
/**
|
||||
* Widgets bundle widget constants.
|
||||
*/
|
||||
|
||||
@ -16,12 +16,14 @@
|
||||
package org.thingsboard.server.dao.model.sql;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.hypersistence.utils.hibernate.type.array.StringArrayType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
|
||||
import org.thingsboard.server.common.data.widget.BaseWidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
@ -42,6 +44,10 @@ public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity<WidgetType
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTION_PROPERTY)
|
||||
private String description;
|
||||
|
||||
@Type(StringArrayType.class)
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_TAGS_PROPERTY, columnDefinition = "text[]")
|
||||
private String[] tags;
|
||||
|
||||
@Convert(converter = JsonConverter.class)
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTOR_PROPERTY)
|
||||
private JsonNode descriptor;
|
||||
@ -57,6 +63,7 @@ public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity<WidgetType
|
||||
super(widgetTypeDetails);
|
||||
this.image = widgetTypeDetails.getImage();
|
||||
this.description = widgetTypeDetails.getDescription();
|
||||
this.tags = widgetTypeDetails.getTags();
|
||||
this.descriptor = widgetTypeDetails.getDescriptor();
|
||||
if (widgetTypeDetails.getExternalId() != null) {
|
||||
this.externalId = widgetTypeDetails.getExternalId().getId();
|
||||
@ -69,6 +76,7 @@ public class WidgetTypeDetailsEntity extends AbstractWidgetTypeEntity<WidgetType
|
||||
WidgetTypeDetails widgetTypeDetails = new WidgetTypeDetails(baseWidgetType);
|
||||
widgetTypeDetails.setImage(image);
|
||||
widgetTypeDetails.setDescription(description);
|
||||
widgetTypeDetails.setTags(tags);
|
||||
widgetTypeDetails.setDescriptor(descriptor);
|
||||
if (externalId != null) {
|
||||
widgetTypeDetails.setExternalId(new WidgetTypeId(externalId));
|
||||
|
||||
@ -15,40 +15,50 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.model.sql;
|
||||
|
||||
import io.hypersistence.utils.hibernate.type.array.StringArrayType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.annotations.Immutable;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.thingsboard.server.common.data.widget.BaseWidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.dao.model.ModelConstants;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Entity
|
||||
@Immutable
|
||||
@Table(name = ModelConstants.WIDGET_TYPE_INFO_VIEW_TABLE_NAME)
|
||||
public final class WidgetTypeInfoEntity extends AbstractWidgetTypeEntity<WidgetTypeInfo> {
|
||||
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_IMAGE_PROPERTY)
|
||||
private String image;
|
||||
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_DESCRIPTION_PROPERTY)
|
||||
private String description;
|
||||
|
||||
@Type(StringArrayType.class)
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_TAGS_PROPERTY, columnDefinition = "text[]")
|
||||
private String[] tags;
|
||||
|
||||
@Column(name = ModelConstants.WIDGET_TYPE_WIDGET_TYPE_PROPERTY)
|
||||
private String widgetType;
|
||||
|
||||
public WidgetTypeInfoEntity() {
|
||||
super();
|
||||
}
|
||||
|
||||
public WidgetTypeInfoEntity(WidgetTypeDetailsEntity widgetTypeDetailsEntity) {
|
||||
super(widgetTypeDetailsEntity);
|
||||
this.image = widgetTypeDetailsEntity.getImage();
|
||||
this.description = widgetTypeDetailsEntity.getDescription();
|
||||
if (widgetTypeDetailsEntity.getDescriptor() != null && widgetTypeDetailsEntity.getDescriptor().has("type")) {
|
||||
this.widgetType = widgetTypeDetailsEntity.getDescriptor().get("type").asText();
|
||||
} else {
|
||||
this.widgetType = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WidgetTypeInfo toData() {
|
||||
BaseWidgetType baseWidgetType = super.toBaseWidgetType();
|
||||
WidgetTypeInfo widgetTypeInfo = new WidgetTypeInfo(baseWidgetType);
|
||||
widgetTypeInfo.setImage(image);
|
||||
widgetTypeInfo.setDescription(description);
|
||||
widgetTypeInfo.setTags(tags);
|
||||
widgetTypeInfo.setWidgetType(widgetType);
|
||||
return widgetTypeInfo;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
@ -35,6 +36,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractDao;
|
||||
import org.thingsboard.server.dao.util.SqlDao;
|
||||
import org.thingsboard.server.dao.widget.WidgetTypeDao;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -54,6 +56,9 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
|
||||
@Autowired
|
||||
private WidgetTypeRepository widgetTypeRepository;
|
||||
|
||||
@Autowired
|
||||
private WidgetTypeInfoRepository widgetTypeInfoRepository;
|
||||
|
||||
@Autowired
|
||||
private WidgetsBundleWidgetRepository widgetsBundleWidgetRepository;
|
||||
|
||||
@ -78,36 +83,57 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findSystemWidgetTypes(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
public PageData<WidgetTypeInfo> findSystemWidgetTypes(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter);
|
||||
boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter);
|
||||
boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty();
|
||||
return DaoUtil.toPageData(
|
||||
widgetTypeRepository
|
||||
widgetTypeInfoRepository
|
||||
.findSystemWidgetTypes(
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
fullSearch,
|
||||
deprecatedFilterEnabled,
|
||||
deprecatedFilterBool,
|
||||
widgetTypesEmpty,
|
||||
widgetTypes == null ? Collections.emptyList() : widgetTypes,
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
public PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter);
|
||||
boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter);
|
||||
boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty();
|
||||
return DaoUtil.toPageData(
|
||||
widgetTypeRepository
|
||||
widgetTypeInfoRepository
|
||||
.findAllTenantWidgetTypesByTenantId(
|
||||
tenantId,
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
fullSearch,
|
||||
deprecatedFilterEnabled,
|
||||
deprecatedFilterBool,
|
||||
widgetTypesEmpty,
|
||||
widgetTypes == null ? Collections.emptyList() : widgetTypes,
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter);
|
||||
boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter);
|
||||
boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty();
|
||||
return DaoUtil.toPageData(
|
||||
widgetTypeRepository
|
||||
widgetTypeInfoRepository
|
||||
.findTenantWidgetTypesByTenantId(
|
||||
tenantId,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
fullSearch,
|
||||
deprecatedFilterEnabled,
|
||||
deprecatedFilterBool,
|
||||
widgetTypesEmpty,
|
||||
widgetTypes == null ? Collections.emptyList() : widgetTypes,
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@ -122,8 +148,21 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId) {
|
||||
return DaoUtil.convertDataList(widgetTypeRepository.findWidgetTypesInfosByWidgetsBundleId(widgetsBundleId));
|
||||
public PageData<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
boolean deprecatedFilterEnabled = !DeprecatedFilter.ALL.equals(deprecatedFilter);
|
||||
boolean deprecatedFilterBool = DeprecatedFilter.DEPRECATED.equals(deprecatedFilter);
|
||||
boolean widgetTypesEmpty = widgetTypes == null || widgetTypes.isEmpty();
|
||||
return DaoUtil.toPageData(
|
||||
widgetTypeInfoRepository
|
||||
.findWidgetTypesInfosByWidgetsBundleId(
|
||||
widgetsBundleId,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
fullSearch,
|
||||
deprecatedFilterEnabled,
|
||||
deprecatedFilterBool,
|
||||
widgetTypesEmpty,
|
||||
widgetTypes == null ? Collections.emptyList() : widgetTypes,
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -62,13 +62,22 @@ public class JpaWidgetsBundleDao extends JpaAbstractDao<WidgetsBundleEntity, Wid
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findSystemWidgetsBundles(
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
public PageData<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
if (fullSearch) {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findSystemWidgetsBundlesFullSearch(
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
} else {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findSystemWidgetsBundles(
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,14 +91,24 @@ public class JpaWidgetsBundleDao extends JpaAbstractDao<WidgetsBundleEntity, Wid
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findAllTenantWidgetsBundlesByTenantId(
|
||||
tenantId,
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
public PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
if (fullSearch) {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findAllTenantWidgetsBundlesByTenantIdFullSearch(
|
||||
tenantId,
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
} else {
|
||||
return DaoUtil.toPageData(
|
||||
widgetsBundleRepository
|
||||
.findAllTenantWidgetsBundlesByTenantId(
|
||||
tenantId,
|
||||
NULL_UUID,
|
||||
Objects.toString(pageLink.getTextSearch(), ""),
|
||||
DaoUtil.toPageable(pageLink)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.sql.widget;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface WidgetTypeInfoRepository extends JpaRepository<WidgetTypeInfoEntity, UUID> {
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widget_type_info_view wti WHERE wti.tenant_id = :systemTenantId " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))",
|
||||
// "OR to_tsvector(lower(array_to_string(wti.tags, ' '))) @@ to_tsquery(lower(:searchText)))))",
|
||||
countQuery = "SELECT count(*) FROM widget_type_info_view wti WHERE wti.tenant_id = :systemTenantId " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))"
|
||||
)
|
||||
Page<WidgetTypeInfoEntity> findSystemWidgetTypes(@Param("systemTenantId") UUID systemTenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
@Param("deprecatedFilterEnabled") boolean deprecatedFilterEnabled,
|
||||
@Param("deprecatedFilter") boolean deprecatedFilter,
|
||||
@Param("widgetTypesEmpty") boolean widgetTypesEmpty,
|
||||
@Param("widgetTypes") List<String> widgetTypes,
|
||||
Pageable pageable);
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widget_type_info_view wti WHERE wti.tenant_id IN (:tenantId, :nullTenantId) " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))",
|
||||
countQuery = "SELECT count(*) FROM widget_type_info_view wti WHERE wti.tenant_id IN (:tenantId, :nullTenantId) " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))"
|
||||
)
|
||||
Page<WidgetTypeInfoEntity> findAllTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@Param("nullTenantId") UUID nullTenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
@Param("deprecatedFilterEnabled") boolean deprecatedFilterEnabled,
|
||||
@Param("deprecatedFilter") boolean deprecatedFilter,
|
||||
@Param("widgetTypesEmpty") boolean widgetTypesEmpty,
|
||||
@Param("widgetTypes") List<String> widgetTypes,
|
||||
Pageable pageable);
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widget_type_info_view wti WHERE wti.tenant_id = :tenantId " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))",
|
||||
countQuery = "SELECT count(*) FROM widget_type_info_view wti WHERE wti.tenant_id = :tenantId " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))"
|
||||
)
|
||||
Page<WidgetTypeInfoEntity> findTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
@Param("deprecatedFilterEnabled") boolean deprecatedFilterEnabled,
|
||||
@Param("deprecatedFilter") boolean deprecatedFilter,
|
||||
@Param("widgetTypesEmpty") boolean widgetTypesEmpty,
|
||||
@Param("widgetTypes") List<String> widgetTypes,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT wti FROM WidgetTypeInfoEntity wti, WidgetsBundleWidgetEntity wbw " +
|
||||
"WHERE wbw.widgetsBundleId = :widgetsBundleId " +
|
||||
"AND wbw.widgetTypeId = wti.id ORDER BY wbw.widgetTypeOrder")
|
||||
List<WidgetTypeInfoEntity> findWidgetTypesInfosByWidgetsBundleId(@Param("widgetsBundleId") UUID widgetsBundleId);
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widget_type_info_view wti, widgets_bundle_widget wbw " +
|
||||
"WHERE wbw.widgets_bundle_id = :widgetsBundleId " +
|
||||
"AND wbw.widget_type_id = wti.id " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' ')))) " +
|
||||
"ORDER BY wbw.widget_type_order",
|
||||
countQuery = "SELECT count(*) FROM widget_type_info_view wti, widgets_bundle_widget wbw " +
|
||||
"WHERE wbw.widgets_bundle_id = :widgetsBundleId " +
|
||||
"AND wbw.widget_type_id = wti.id " +
|
||||
"AND ((:deprecatedFilterEnabled) IS FALSE OR wti.deprecated = :deprecatedFilter) " +
|
||||
"AND ((:widgetTypesEmpty) IS TRUE OR wti.widget_type IN (:widgetTypes)) " +
|
||||
"AND (wti.name ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR ((:fullSearch) IS TRUE AND (wti.description ILIKE CONCAT('%', :searchText, '%') " +
|
||||
"OR lower(wti.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:searchText), ' '))))"
|
||||
)
|
||||
Page<WidgetTypeInfoEntity> findWidgetTypesInfosByWidgetsBundleId(@Param("widgetsBundleId") UUID widgetsBundleId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
@Param("deprecatedFilterEnabled") boolean deprecatedFilterEnabled,
|
||||
@Param("deprecatedFilter") boolean deprecatedFilter,
|
||||
@Param("widgetTypesEmpty") boolean widgetTypesEmpty,
|
||||
@Param("widgetTypes") List<String> widgetTypes,
|
||||
Pageable pageable);
|
||||
|
||||
}
|
||||
@ -24,8 +24,6 @@ import org.thingsboard.server.dao.ExportableEntityRepository;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetTypeDetailsEntity;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetTypeEntity;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetTypeIdFqnEntity;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity;
|
||||
import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@ -37,31 +35,6 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
|
||||
|
||||
boolean existsByTenantIdAndId(UUID tenantId, UUID id);
|
||||
|
||||
@Query("SELECT new org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity(wtd) FROM WidgetTypeDetailsEntity wtd WHERE wtd.tenantId = :systemTenantId " +
|
||||
"AND (LOWER(wtd.name) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
|
||||
"OR ((:fullSearch) = TRUE AND LOWER(wtd.description) LIKE LOWER(CONCAT('%', :searchText, '%'))))")
|
||||
Page<WidgetTypeInfoEntity> findSystemWidgetTypes(@Param("systemTenantId") UUID systemTenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT new org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity(wtd) FROM WidgetTypeDetailsEntity wtd WHERE wtd.tenantId IN (:tenantId, :nullTenantId) " +
|
||||
"AND (LOWER(wtd.name) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
|
||||
"OR ((:fullSearch) = TRUE AND LOWER(wtd.description) LIKE LOWER(CONCAT('%', :searchText, '%'))))")
|
||||
Page<WidgetTypeInfoEntity> findAllTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@Param("nullTenantId") UUID nullTenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT new org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity(wtd) FROM WidgetTypeDetailsEntity wtd WHERE wtd.tenantId = :tenantId " +
|
||||
"AND (LOWER(wtd.name) LIKE LOWER(CONCAT('%', :searchText, '%')) " +
|
||||
"OR ((:fullSearch) = TRUE AND LOWER(wtd.description) LIKE LOWER(CONCAT('%', :searchText, '%'))))")
|
||||
Page<WidgetTypeInfoEntity> findTenantWidgetTypesByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("fullSearch") boolean fullSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT wtd FROM WidgetTypeDetailsEntity wtd WHERE wtd.tenantId = :tenantId " +
|
||||
"AND LOWER(wtd.name) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
|
||||
Page<WidgetTypeDetailsEntity> findTenantWidgetTypeDetailsByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@ -78,10 +51,6 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
|
||||
"AND wbw.widgetTypeId = wtd.id ORDER BY wbw.widgetTypeOrder")
|
||||
List<WidgetTypeDetailsEntity> findWidgetTypesDetailsByWidgetsBundleId(@Param("widgetsBundleId") UUID widgetsBundleId);
|
||||
|
||||
@Query("SELECT new org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity(wtd) FROM WidgetTypeDetailsEntity wtd, WidgetsBundleWidgetEntity wbw " +
|
||||
"WHERE wbw.widgetsBundleId = :widgetsBundleId " +
|
||||
"AND wbw.widgetTypeId = wtd.id ORDER BY wbw.widgetTypeOrder")
|
||||
List<WidgetTypeInfoEntity> findWidgetTypesInfosByWidgetsBundleId(@Param("widgetsBundleId") UUID widgetsBundleId);
|
||||
|
||||
@Query("SELECT wtd.fqn FROM WidgetTypeDetailsEntity wtd, WidgetsBundleWidgetEntity wbw " +
|
||||
"WHERE wbw.widgetsBundleId = :widgetsBundleId " +
|
||||
|
||||
@ -33,11 +33,33 @@ public interface WidgetsBundleRepository extends JpaRepository<WidgetsBundleEnti
|
||||
WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias);
|
||||
|
||||
@Query("SELECT wb FROM WidgetsBundleEntity wb WHERE wb.tenantId = :systemTenantId " +
|
||||
"AND LOWER(wb.title) LIKE LOWER(CONCAT('%', :searchText, '%'))")
|
||||
"AND LOWER(wb.title) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
|
||||
Page<WidgetsBundleEntity> findSystemWidgetsBundles(@Param("systemTenantId") UUID systemTenantId,
|
||||
@Param("searchText") String searchText,
|
||||
@Param("textSearch") String textSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widgets_bundle wb WHERE wb.tenant_id = :systemTenantId " +
|
||||
"AND (wb.title ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.id in (SELECT wbw.widgets_bundle_id FROM widgets_bundle_widget wbw, widget_type wtd " +
|
||||
"WHERE wtd.id = wbw.widget_type_id " +
|
||||
"AND (wtd.name ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wtd.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR lower(wtd.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:textSearch), ' '))))",
|
||||
countQuery = "SELECT count(*) FROM widgets_bundle wb WHERE wb.tenant_id = :systemTenantId " +
|
||||
"AND (wb.title ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.id in (SELECT wbw.widgets_bundle_id FROM widgets_bundle_widget wbw, widget_type wtd " +
|
||||
"WHERE wtd.id = wbw.widget_type_id " +
|
||||
"AND (wtd.name ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wtd.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR lower(wtd.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:textSearch), ' '))))"
|
||||
)
|
||||
Page<WidgetsBundleEntity> findSystemWidgetsBundlesFullSearch(@Param("systemTenantId") UUID systemTenantId,
|
||||
@Param("textSearch") String textSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query("SELECT wb FROM WidgetsBundleEntity wb WHERE wb.tenantId = :tenantId " +
|
||||
"AND LOWER(wb.title) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
|
||||
Page<WidgetsBundleEntity> findTenantWidgetsBundlesByTenantId(@Param("tenantId") UUID tenantId,
|
||||
@ -51,6 +73,29 @@ public interface WidgetsBundleRepository extends JpaRepository<WidgetsBundleEnti
|
||||
@Param("textSearch") String textSearch,
|
||||
Pageable pageable);
|
||||
|
||||
@Query(nativeQuery = true,
|
||||
value = "SELECT * FROM widgets_bundle wb WHERE wb.tenant_id IN (:tenantId, :nullTenantId) " +
|
||||
"AND (wb.title ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.id in (SELECT wbw.widgets_bundle_id FROM widgets_bundle_widget wbw, widget_type wtd " +
|
||||
"WHERE wtd.id = wbw.widget_type_id " +
|
||||
"AND (wtd.name ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wtd.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR lower(wtd.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:textSearch), ' '))))",
|
||||
countQuery = "SELECT count(*) FROM widgets_bundle wb WHERE wb.tenant_id IN (:tenantId, :nullTenantId) " +
|
||||
"AND (wb.title ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wb.id in (SELECT wbw.widgets_bundle_id FROM widgets_bundle_widget wbw, widget_type wtd " +
|
||||
"WHERE wtd.id = wbw.widget_type_id " +
|
||||
"AND (wtd.name ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR wtd.description ILIKE CONCAT('%', :textSearch, '%') " +
|
||||
"OR lower(wtd.tags\\:\\:text)\\:\\:text[] && string_to_array(lower(:textSearch), ' '))))"
|
||||
)
|
||||
Page<WidgetsBundleEntity> findAllTenantWidgetsBundlesByTenantIdFullSearch(@Param("tenantId") UUID tenantId,
|
||||
@Param("nullTenantId") UUID nullTenantId,
|
||||
@Param("textSearch") String textSearch,
|
||||
Pageable pageable);
|
||||
|
||||
WidgetsBundleEntity findFirstByTenantIdAndTitle(UUID tenantId, String title);
|
||||
|
||||
@Query("SELECT externalId FROM WidgetsBundleEntity WHERE id = :id")
|
||||
|
||||
@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
@ -53,11 +54,11 @@ public interface WidgetTypeDao extends Dao<WidgetTypeDetails>, ExportableEntityD
|
||||
|
||||
boolean existsByTenantIdAndId(TenantId tenantId, UUID widgetTypeId);
|
||||
|
||||
PageData<WidgetTypeInfo> findSystemWidgetTypes(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findSystemWidgetTypes(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink);
|
||||
PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantId(UUID tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
/**
|
||||
* Find widget types by widgetsBundleId.
|
||||
@ -77,14 +78,7 @@ public interface WidgetTypeDao extends Dao<WidgetTypeDetails>, ExportableEntityD
|
||||
*/
|
||||
List<WidgetTypeDetails> findWidgetTypesDetailsByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId);
|
||||
|
||||
/**
|
||||
* Find widget types infos by widgetsBundleId.
|
||||
*
|
||||
* @param tenantId the tenantId
|
||||
* @param widgetsBundleId the widgets bundle id
|
||||
* @return the list of widget types infos objects
|
||||
*/
|
||||
List<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId);
|
||||
PageData<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink);
|
||||
|
||||
List<String> findWidgetFqnsByWidgetsBundleId(UUID tenantId, UUID widgetsBundleId);
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
|
||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
@ -110,26 +111,28 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findSystemWidgetTypesByPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
log.trace("Executing findSystemWidgetTypesByPageLink, fullSearch [{}] pageLink [{}]", fullSearch, pageLink);
|
||||
public PageData<WidgetTypeInfo> findSystemWidgetTypesByPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
log.trace("Executing findSystemWidgetTypesByPageLink, fullSearch [{}], deprecatedFilter [{}], widgetTypes [{}], pageLink [{}]", fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetTypeDao.findSystemWidgetTypes(tenantId, fullSearch, pageLink);
|
||||
return widgetTypeDao.findSystemWidgetTypes(tenantId, fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
log.trace("Executing findAllTenantWidgetTypesByTenantIdAndPageLink, tenantId [{}], fullSearch [{}], pageLink [{}]", tenantId, fullSearch, pageLink);
|
||||
public PageData<WidgetTypeInfo> findAllTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
log.trace("Executing findAllTenantWidgetTypesByTenantIdAndPageLink, tenantId [{}], fullSearch [{}], deprecatedFilter [{}], widgetTypes [{}], pageLink [{}]",
|
||||
tenantId, fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetTypeDao.findAllTenantWidgetTypesByTenantId(tenantId.getId(), fullSearch, pageLink);
|
||||
return widgetTypeDao.findAllTenantWidgetTypesByTenantId(tenantId.getId(), fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
log.trace("Executing findTenantWidgetTypesByTenantIdAndPageLink, tenantId [{}], fullSearch [{}], pageLink [{}]", tenantId, fullSearch, pageLink);
|
||||
public PageData<WidgetTypeInfo> findTenantWidgetTypesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
log.trace("Executing findTenantWidgetTypesByTenantIdAndPageLink, tenantId [{}], fullSearch [{}], deprecatedFilter [{}], widgetTypes [{}], pageLink [{}]",
|
||||
tenantId, fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetTypeDao.findTenantWidgetTypesByTenantId(tenantId.getId(), fullSearch, pageLink);
|
||||
return widgetTypeDao.findTenantWidgetTypesByTenantId(tenantId.getId(), fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,11 +153,14 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId) {
|
||||
log.trace("Executing findWidgetTypesInfosByWidgetsBundleId, tenantId [{}], widgetsBundleId [{}]", tenantId, widgetsBundleId);
|
||||
public PageData<WidgetTypeInfo> findWidgetTypesInfosByWidgetsBundleId(TenantId tenantId, WidgetsBundleId widgetsBundleId, boolean fullSearch,
|
||||
DeprecatedFilter deprecatedFilter, List<String> widgetTypes, PageLink pageLink) {
|
||||
log.trace("Executing findWidgetTypesInfosByWidgetsBundleId, tenantId [{}], widgetsBundleId [{}], fullSearch [{}], deprecatedFilter [{}], widgetTypes [{}], pageLink [{}]",
|
||||
tenantId, widgetsBundleId, fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
Validator.validateId(widgetsBundleId, INCORRECT_WIDGETS_BUNDLE_ID + widgetsBundleId);
|
||||
return widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(tenantId.getId(), widgetsBundleId.getId());
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(tenantId.getId(), widgetsBundleId.getId(), fullSearch, deprecatedFilter, widgetTypes, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -230,7 +236,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
|
||||
|
||||
@Override
|
||||
protected PageData<WidgetTypeInfo> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
|
||||
return widgetTypeDao.findTenantWidgetTypesByTenantId(id.getId(), false, pageLink);
|
||||
return widgetTypeDao.findTenantWidgetTypesByTenantId(id.getId(), false, DeprecatedFilter.ALL, null, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -54,7 +54,7 @@ public interface WidgetsBundleDao extends Dao<WidgetsBundle>, ExportableEntityDa
|
||||
* @param pageLink the page link
|
||||
* @return the list of widgets bundles objects
|
||||
*/
|
||||
PageData<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId, PageLink pageLink);
|
||||
PageData<WidgetsBundle> findSystemWidgetsBundles(TenantId tenantId, boolean fullSearch, PageLink pageLink);
|
||||
|
||||
/**
|
||||
* Find tenant widgets bundles by tenantId and page link.
|
||||
@ -72,7 +72,7 @@ public interface WidgetsBundleDao extends Dao<WidgetsBundle>, ExportableEntityDa
|
||||
* @param pageLink the page link
|
||||
* @return the list of widgets bundles objects
|
||||
*/
|
||||
PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, PageLink pageLink);
|
||||
PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantId(UUID tenantId, boolean fullSearch, PageLink pageLink);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -104,10 +104,10 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, PageLink pageLink) {
|
||||
log.trace("Executing findSystemWidgetsBundles, pageLink [{}]", pageLink);
|
||||
public PageData<WidgetsBundle> findSystemWidgetsBundlesByPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
log.trace("Executing findSystemWidgetsBundles, fullSearch [{}], pageLink [{}]", fullSearch, pageLink);
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetsBundleDao.findSystemWidgetsBundles(tenantId, pageLink);
|
||||
return widgetsBundleDao.findSystemWidgetsBundles(tenantId, fullSearch, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,7 +117,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
|
||||
PageLink pageLink = new PageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT);
|
||||
PageData<WidgetsBundle> pageData;
|
||||
do {
|
||||
pageData = findSystemWidgetsBundlesByPageLink(tenantId, pageLink);
|
||||
pageData = findSystemWidgetsBundlesByPageLink(tenantId, false, pageLink);
|
||||
widgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
@ -135,11 +135,11 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, PageLink pageLink) {
|
||||
log.trace("Executing findAllTenantWidgetsBundlesByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
|
||||
public PageData<WidgetsBundle> findAllTenantWidgetsBundlesByTenantIdAndPageLink(TenantId tenantId, boolean fullSearch, PageLink pageLink) {
|
||||
log.trace("Executing findAllTenantWidgetsBundlesByTenantIdAndPageLink, tenantId [{}], fullSearch [{}], pageLink [{}]", tenantId, fullSearch, pageLink);
|
||||
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
Validator.validatePageLink(pageLink);
|
||||
return widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId.getId(), pageLink);
|
||||
return widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId.getId(), fullSearch, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,7 +150,7 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
|
||||
PageLink pageLink = new PageLink(DEFAULT_WIDGETS_BUNDLE_LIMIT);
|
||||
PageData<WidgetsBundle> pageData;
|
||||
do {
|
||||
pageData = findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
|
||||
pageData = findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, false, pageLink);
|
||||
widgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
|
||||
@ -489,6 +489,7 @@ CREATE TABLE IF NOT EXISTS widget_type (
|
||||
image varchar(1000000),
|
||||
deprecated boolean NOT NULL DEFAULT false,
|
||||
description varchar(1024),
|
||||
tags text[],
|
||||
external_id uuid,
|
||||
CONSTRAINT uq_widget_type_fqn UNIQUE (tenant_id, fqn),
|
||||
CONSTRAINT widget_type_external_id_unq_key UNIQUE (tenant_id, external_id)
|
||||
|
||||
@ -285,3 +285,9 @@ BEGIN
|
||||
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text;
|
||||
END
|
||||
$$;
|
||||
|
||||
DROP VIEW IF EXISTS widget_type_info_view CASCADE;
|
||||
CREATE OR REPLACE VIEW widget_type_info_view AS
|
||||
SELECT t.*
|
||||
, COALESCE((t.descriptor::json->>'type')::text, '') as widget_type
|
||||
FROM widget_type t;
|
||||
|
||||
@ -174,7 +174,7 @@ public class WidgetsBundleServiceTest extends AbstractServiceTest {
|
||||
PageLink pageLink = new PageLink(19);
|
||||
PageData<WidgetsBundle> pageData = null;
|
||||
do {
|
||||
pageData = widgetsBundleService.findSystemWidgetsBundlesByPageLink(tenantId, pageLink);
|
||||
pageData = widgetsBundleService.findSystemWidgetsBundlesByPageLink(tenantId, false, pageLink);
|
||||
loadedWidgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
@ -297,7 +297,7 @@ public class WidgetsBundleServiceTest extends AbstractServiceTest {
|
||||
PageLink pageLink = new PageLink(17);
|
||||
PageData<WidgetsBundle> pageData = null;
|
||||
do {
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, false, pageLink);
|
||||
loadedWidgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
@ -314,7 +314,7 @@ public class WidgetsBundleServiceTest extends AbstractServiceTest {
|
||||
loadedWidgetsBundles.clear();
|
||||
pageLink = new PageLink(14);
|
||||
do {
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, false, pageLink);
|
||||
loadedWidgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
@ -336,7 +336,7 @@ public class WidgetsBundleServiceTest extends AbstractServiceTest {
|
||||
loadedWidgetsBundles.clear();
|
||||
pageLink = new PageLink(18);
|
||||
do {
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink);
|
||||
pageData = widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, false, pageLink);
|
||||
loadedWidgetsBundles.addAll(pageData.getData());
|
||||
if (pageData.hasNext()) {
|
||||
pageLink = pageLink.nextPageLink();
|
||||
|
||||
@ -20,10 +20,17 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.page.SortOrder;
|
||||
import org.thingsboard.server.common.data.widget.BaseWidgetType;
|
||||
import org.thingsboard.server.common.data.widget.DeprecatedFilter;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundleWidget;
|
||||
import org.thingsboard.server.dao.AbstractJpaDaoTest;
|
||||
@ -31,7 +38,10 @@ import org.thingsboard.server.dao.widget.WidgetTypeDao;
|
||||
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@ -43,7 +53,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
|
||||
|
||||
final String BUNDLE_ALIAS = "BUNDLE_ALIAS";
|
||||
final int WIDGET_TYPE_COUNT = 3;
|
||||
List<WidgetType> widgetTypeList;
|
||||
List<WidgetTypeDetails> widgetTypeList;
|
||||
WidgetsBundle widgetsBundle;
|
||||
|
||||
@Autowired
|
||||
@ -64,17 +74,24 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
|
||||
this.widgetsBundle = widgetsBundleDao.save(TenantId.SYS_TENANT_ID, widgetsBundle);
|
||||
|
||||
for (int i = 0; i < WIDGET_TYPE_COUNT; i++) {
|
||||
var widgetType = createAndSaveWidgetType(i);
|
||||
var widgetType = createAndSaveWidgetType(TenantId.SYS_TENANT_ID, i);
|
||||
widgetTypeList.add(widgetType);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(this.widgetsBundle.getId(), widgetType.getId(), i));
|
||||
}
|
||||
widgetTypeList.sort(Comparator.comparing(BaseWidgetType::getName));
|
||||
}
|
||||
|
||||
WidgetType createAndSaveWidgetType(int number) {
|
||||
WidgetTypeDetails createAndSaveWidgetType(TenantId tenantId, int number) {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setTenantId(TenantId.SYS_TENANT_ID);
|
||||
widgetType.setTenantId(tenantId);
|
||||
widgetType.setName("WIDGET_TYPE_" + number);
|
||||
widgetType.setDescription("WIDGET_TYPE_DESCRIPTION" + number);
|
||||
widgetType.setFqn("FQN_" + number);
|
||||
var descriptor = JacksonUtil.newObjectNode();
|
||||
descriptor.put("type", number % 2 == 0 ? "latest" : "static");
|
||||
widgetType.setDescriptor(descriptor);
|
||||
String[] tags = new String[]{"Tag1_"+number, "Tag2_"+number, "TEST_"+number};
|
||||
widgetType.setTags(tags);
|
||||
return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType);
|
||||
}
|
||||
|
||||
@ -92,6 +109,48 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
|
||||
assertEquals(WIDGET_TYPE_COUNT, widgetTypes.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSystemWidgetTypes() {
|
||||
PageData<WidgetTypeInfo> widgetTypes = widgetTypeDao.findSystemWidgetTypes(TenantId.SYS_TENANT_ID, true, DeprecatedFilter.ALL, Collections.singletonList("static"),
|
||||
new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("name")));
|
||||
assertEquals(1, widgetTypes.getData().size());
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(1)), widgetTypes.getData().get(0));
|
||||
|
||||
widgetTypes = widgetTypeDao.findSystemWidgetTypes(TenantId.SYS_TENANT_ID, true, DeprecatedFilter.ALL, Collections.emptyList(),
|
||||
new PageLink(1024, 0, "hfgfd tag2_2 ghg", new SortOrder("name")));
|
||||
assertEquals(1, widgetTypes.getData().size());
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindTenantWidgetTypesByTenantId() {
|
||||
UUID tenantId = Uuids.timeBased();
|
||||
for (int i = 0; i < WIDGET_TYPE_COUNT; i++) {
|
||||
var widgetType = createAndSaveWidgetType(new TenantId(tenantId), i);
|
||||
widgetTypeList.add(widgetType);
|
||||
}
|
||||
PageData<WidgetTypeInfo> widgetTypes = widgetTypeDao.findTenantWidgetTypesByTenantId(tenantId, true, DeprecatedFilter.ALL, null,
|
||||
new PageLink(10, 0, "", new SortOrder("name")));
|
||||
assertEquals(WIDGET_TYPE_COUNT, widgetTypes.getData().size());
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(3)), widgetTypes.getData().get(0));
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(4)), widgetTypes.getData().get(1));
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(5)), widgetTypes.getData().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindByWidgetTypeInfosByBundleId() {
|
||||
PageData<WidgetTypeInfo> widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(),true, DeprecatedFilter.ALL, Collections.singletonList("latest"),
|
||||
new PageLink(1024, 0, "TYPE_DESCRIPTION", new SortOrder("name")));
|
||||
assertEquals(2, widgetTypes.getData().size());
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0));
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(2)), widgetTypes.getData().get(1));
|
||||
|
||||
widgetTypes = widgetTypeDao.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID.getId(), widgetsBundle.getUuidId(), true, DeprecatedFilter.ALL, Collections.emptyList(),
|
||||
new PageLink(1024, 0, "hfgfd TEST_0 ghg", new SortOrder("name")));
|
||||
assertEquals(1, widgetTypes.getData().size());
|
||||
assertEquals(new WidgetTypeInfo(widgetTypeList.get(0)), widgetTypes.getData().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindByTenantIdAndFqn() {
|
||||
WidgetType result = widgetTypeList.get(0);
|
||||
|
||||
@ -17,18 +17,27 @@ package org.thingsboard.server.dao.sql.widget;
|
||||
|
||||
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.WidgetsBundleId;
|
||||
import org.thingsboard.server.common.data.page.PageData;
|
||||
import org.thingsboard.server.common.data.page.PageLink;
|
||||
import org.thingsboard.server.common.data.page.SortOrder;
|
||||
import org.thingsboard.server.common.data.widget.WidgetType;
|
||||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundle;
|
||||
import org.thingsboard.server.common.data.widget.WidgetsBundleWidget;
|
||||
import org.thingsboard.server.dao.AbstractJpaDaoTest;
|
||||
import org.thingsboard.server.dao.widget.WidgetTypeDao;
|
||||
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@ -38,15 +47,27 @@ import static org.junit.Assert.assertEquals;
|
||||
public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest {
|
||||
|
||||
List<WidgetsBundle> widgetsBundles;
|
||||
List<WidgetType> widgetTypeList;
|
||||
|
||||
@Autowired
|
||||
private WidgetsBundleDao widgetsBundleDao;
|
||||
|
||||
@Autowired
|
||||
private WidgetTypeDao widgetTypeDao;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
widgetTypeList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
for (WidgetsBundle widgetsBundle : widgetsBundles) {
|
||||
widgetsBundleDao.removeById(widgetsBundle.getTenantId(), widgetsBundle.getUuidId());
|
||||
}
|
||||
|
||||
for (WidgetType widgetType : widgetTypeList) {
|
||||
widgetTypeDao.removeById(TenantId.SYS_TENANT_ID, widgetType.getUuidId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -72,14 +93,53 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest {
|
||||
assertEquals(30, widgetsBundles.size());
|
||||
// Get first page
|
||||
PageLink pageLink = new PageLink(10, 0, "WB");
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, false, pageLink);
|
||||
assertEquals(10, widgetsBundles1.getData().size());
|
||||
// Get next page
|
||||
pageLink = pageLink.nextPageLink();
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, false, pageLink);
|
||||
assertEquals(10, widgetsBundles2.getData().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSystemWidgetsBundlesFullSearch() {
|
||||
createSystemWidgetBundles(30, "WB_");
|
||||
widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID).stream().sorted(Comparator.comparing(WidgetsBundle::getTitle)).collect(Collectors.toList());
|
||||
assertEquals(30, widgetsBundles.size());
|
||||
|
||||
var widgetType1 = createAndSaveWidgetType(TenantId.SYS_TENANT_ID,1, "Test widget type 1", "This is the widget type 1", new String[]{"tag1", "Tag2", "TEST_TAG"});
|
||||
var widgetType2 = createAndSaveWidgetType(TenantId.SYS_TENANT_ID,2, "Test widget type 2", "This is the widget type 2", new String[]{"tag3", "Tag5", "TEST_Tag2"});
|
||||
|
||||
var widgetsBundle1 = widgetsBundles.get(10);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle1.getId(), widgetType1.getId(), 0));
|
||||
|
||||
var widgetsBundle2 = widgetsBundles.get(15);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle2.getId(), widgetType2.getId(), 0));
|
||||
|
||||
|
||||
var widgetsBundle3 = widgetsBundles.get(28);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle3.getId(), widgetType1.getId(), 0));
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle3.getId(), widgetType2.getId(), 1));
|
||||
|
||||
PageLink pageLink = new PageLink(10, 0, "widget type 1", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, true, pageLink);
|
||||
assertEquals(2, widgetsBundles1.getData().size());
|
||||
assertEquals(widgetsBundle1, widgetsBundles1.getData().get(0));
|
||||
assertEquals(widgetsBundle3, widgetsBundles1.getData().get(1));
|
||||
|
||||
pageLink = new PageLink(10, 0, "Test widget type 2", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, true, pageLink);
|
||||
assertEquals(2, widgetsBundles2.getData().size());
|
||||
assertEquals(widgetsBundle2, widgetsBundles2.getData().get(0));
|
||||
assertEquals(widgetsBundle3, widgetsBundles2.getData().get(1));
|
||||
|
||||
pageLink = new PageLink(10, 0, "ppp Fd v TAG1 tt", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles3 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, true, pageLink);
|
||||
assertEquals(2, widgetsBundles3.getData().size());
|
||||
assertEquals(widgetsBundle1, widgetsBundles3.getData().get(0));
|
||||
assertEquals(widgetsBundle3, widgetsBundles3.getData().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindWidgetsBundlesByTenantId() {
|
||||
UUID tenantId1 = Uuids.timeBased();
|
||||
@ -120,22 +180,71 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest {
|
||||
assertEquals(100, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size());
|
||||
|
||||
PageLink pageLink = new PageLink(30, 0, "WB");
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, false, pageLink);
|
||||
assertEquals(30, widgetsBundles1.getData().size());
|
||||
|
||||
pageLink = pageLink.nextPageLink();
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, false, pageLink);
|
||||
assertEquals(30, widgetsBundles2.getData().size());
|
||||
|
||||
pageLink = pageLink.nextPageLink();
|
||||
PageData<WidgetsBundle> widgetsBundles3 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles3 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, false, pageLink);
|
||||
assertEquals(10, widgetsBundles3.getData().size());
|
||||
|
||||
pageLink = pageLink.nextPageLink();
|
||||
PageData<WidgetsBundle> widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, false, pageLink);
|
||||
assertEquals(0, widgetsBundles4.getData().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllWidgetsBundlesByTenantIdFullSearch() {
|
||||
UUID tenantId1 = Uuids.timeBased();
|
||||
UUID tenantId2 = Uuids.timeBased();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
createWidgetBundles(5, tenantId1, "WB1_" + i + "_");
|
||||
createWidgetBundles(3, tenantId2, "WB2_" + i + "_");
|
||||
createSystemWidgetBundles(2, "WB_SYS_" + i + "_");
|
||||
}
|
||||
widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID).stream().sorted(Comparator.comparing(WidgetsBundle::getTitle)).collect(Collectors.toList());;
|
||||
assertEquals(100, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size());
|
||||
|
||||
var widgetType1 = createAndSaveWidgetType(new TenantId(tenantId1), 1, "Test widget type 1", "This is the widget type 1", new String[]{"tag1", "Tag2", "TEST_TAG"});
|
||||
var widgetType2 = createAndSaveWidgetType(new TenantId(tenantId2), 2, "Test widget type 2", "This is the widget type 2", new String[]{"tag3", "Tag5", "TEST_Tag2"});
|
||||
|
||||
var widgetsBundle1 = widgetsBundles.stream().filter(widgetsBundle -> widgetsBundle.getTenantId().getId().equals(tenantId1)).collect(Collectors.toList()).get(10);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle1.getId(), widgetType1.getId(), 0));
|
||||
|
||||
var widgetsBundle2 = widgetsBundles.stream().filter(widgetsBundle -> widgetsBundle.getTenantId().getId().equals(tenantId2)).collect(Collectors.toList()).get(15);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle2.getId(), widgetType2.getId(), 0));
|
||||
|
||||
var widgetsBundle3 = widgetsBundles.stream().filter(widgetsBundle -> widgetsBundle.getTenantId().getId().equals(tenantId2)).collect(Collectors.toList()).get(28);
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle3.getId(), widgetType1.getId(), 0));
|
||||
widgetTypeDao.saveWidgetsBundleWidget(new WidgetsBundleWidget(widgetsBundle3.getId(), widgetType2.getId(), 1));
|
||||
|
||||
PageLink pageLink = new PageLink(10, 0, "widget type 1", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, true, pageLink);
|
||||
assertEquals(1, widgetsBundles1.getData().size());
|
||||
assertEquals(widgetsBundle1, widgetsBundles1.getData().get(0));
|
||||
|
||||
pageLink = new PageLink(10, 0, "Test widget type 2", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles2 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, true, pageLink);
|
||||
assertEquals(0, widgetsBundles2.getData().size());
|
||||
|
||||
PageData<WidgetsBundle> widgetsBundles3 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId2, true, pageLink);
|
||||
assertEquals(2, widgetsBundles3.getData().size());
|
||||
assertEquals(widgetsBundle2, widgetsBundles3.getData().get(0));
|
||||
assertEquals(widgetsBundle3, widgetsBundles3.getData().get(1));
|
||||
|
||||
pageLink = new PageLink(10, 0, "ttt Tag2 ffff hhhh", new SortOrder("title"));
|
||||
PageData<WidgetsBundle> widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, true, pageLink);
|
||||
assertEquals(1, widgetsBundles4.getData().size());
|
||||
assertEquals(widgetsBundle1, widgetsBundles4.getData().get(0));
|
||||
|
||||
PageData<WidgetsBundle> widgetsBundles5 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId2, true, pageLink);
|
||||
assertEquals(1, widgetsBundles5.getData().size());
|
||||
assertEquals(widgetsBundle3, widgetsBundles5.getData().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchTextNotFound() {
|
||||
UUID tenantId = Uuids.timeBased();
|
||||
@ -144,7 +253,7 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest {
|
||||
widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID);
|
||||
assertEquals(10, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size());
|
||||
PageLink textPageLink = new PageLink(30, 0, "TEXT_NOT_FOUND");
|
||||
PageData<WidgetsBundle> widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId, textPageLink);
|
||||
PageData<WidgetsBundle> widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId, false, textPageLink);
|
||||
assertEquals(0, widgetsBundles4.getData().size());
|
||||
}
|
||||
|
||||
@ -169,4 +278,16 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest {
|
||||
widgetsBundleDao.save(TenantId.SYS_TENANT_ID, widgetsBundle);
|
||||
}
|
||||
}
|
||||
|
||||
WidgetType createAndSaveWidgetType(TenantId tenantId, int number, String name, String description, String[] tags) {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setTenantId(tenantId);
|
||||
widgetType.setName(name);
|
||||
widgetType.setDescription(description);
|
||||
widgetType.setTags(tags);
|
||||
widgetType.setFqn("FQN_" + number);
|
||||
var saved = widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType);
|
||||
this.widgetTypeList.add(saved);
|
||||
return saved;
|
||||
}
|
||||
}
|
||||
|
||||
6
pom.xml
6
pom.xml
@ -125,6 +125,7 @@
|
||||
<wire-schema.version>3.4.0</wire-schema.version>
|
||||
<twilio.version>9.6.1</twilio.version>
|
||||
<hibernate-validator.version>8.0.0.Final</hibernate-validator.version>
|
||||
<hypersistence-utils.version>3.5.2</hypersistence-utils.version>
|
||||
<jakarta.el.version>4.0.2</jakarta.el.version>
|
||||
<jakarta.validation-api.version>3.0.2</jakarta.validation-api.version>
|
||||
<antisamy.version>1.7.3</antisamy.version>
|
||||
@ -1968,6 +1969,11 @@
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.hypersistence</groupId>
|
||||
<artifactId>hypersistence-utils-hibernate-62</artifactId>
|
||||
<version>${hypersistence-utils.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>jakarta.el</artifactId>
|
||||
|
||||
@ -416,7 +416,7 @@ export class EntityService {
|
||||
break;
|
||||
case EntityType.WIDGETS_BUNDLE:
|
||||
pageLink.sortOrder.property = 'title';
|
||||
entitiesObservable = this.widgetService.getWidgetBundles(pageLink, config);
|
||||
entitiesObservable = this.widgetService.getWidgetBundles(pageLink, false, config);
|
||||
break;
|
||||
case EntityType.NOTIFICATION_TARGET:
|
||||
pageLink.sortOrder.property = 'name';
|
||||
|
||||
@ -23,6 +23,7 @@ import { PageData } from '@shared/models/page/page-data';
|
||||
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
|
||||
import {
|
||||
BaseWidgetType,
|
||||
DeprecatedFilter,
|
||||
fullWidgetTypeFqn,
|
||||
WidgetType,
|
||||
widgetType,
|
||||
@ -46,8 +47,6 @@ export class WidgetService {
|
||||
private systemWidgetsBundles: Array<WidgetsBundle>;
|
||||
private tenantWidgetsBundles: Array<WidgetsBundle>;
|
||||
|
||||
private widgetTypeInfosCache = new Map<string, Array<WidgetTypeInfo>>();
|
||||
|
||||
private widgetsInfoInMemoryCache = new Map<string, WidgetInfo>();
|
||||
|
||||
private loadWidgetsBundleCacheSubject: ReplaySubject<void>;
|
||||
@ -86,8 +85,8 @@ export class WidgetService {
|
||||
);
|
||||
}
|
||||
|
||||
public getWidgetBundles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<WidgetsBundle>> {
|
||||
return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}`,
|
||||
public getWidgetBundles(pageLink: PageLink, fullSearch = false, config?: RequestConfig): Observable<PageData<WidgetsBundle>> {
|
||||
return this.http.get<PageData<WidgetsBundle>>(`/api/widgetsBundles${pageLink.toQuery()}&fullSearch=${fullSearch}`,
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
@ -109,21 +108,13 @@ export class WidgetService {
|
||||
public updateWidgetsBundleWidgetTypes(widgetsBundleId: string, widgetTypeIds: Array<string>,
|
||||
config?: RequestConfig): Observable<void> {
|
||||
return this.http.post<void>(`/api/widgetsBundle/${widgetsBundleId}/widgetTypes`, widgetTypeIds,
|
||||
defaultHttpOptionsFromConfig(config)).pipe(
|
||||
tap(() => {
|
||||
this.widgetTypeInfosCache.delete(widgetsBundleId);
|
||||
})
|
||||
);
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public updateWidgetsBundleWidgetFqns(widgetsBundleId: string, widgetTypeFqns: Array<string>,
|
||||
config?: RequestConfig): Observable<void> {
|
||||
return this.http.post<void>(`/api/widgetsBundle/${widgetsBundleId}/widgetTypeFqns`, widgetTypeFqns,
|
||||
defaultHttpOptionsFromConfig(config)).pipe(
|
||||
tap(() => {
|
||||
this.widgetTypeInfosCache.delete(widgetsBundleId);
|
||||
})
|
||||
);
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public deleteWidgetsBundle(widgetsBundleId: string, config?: RequestConfig) {
|
||||
@ -155,16 +146,27 @@ export class WidgetService {
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public getBundleWidgetTypeInfos(widgetsBundleId: string,
|
||||
config?: RequestConfig): Observable<Array<WidgetTypeInfo>> {
|
||||
if (this.widgetTypeInfosCache.has(widgetsBundleId)) {
|
||||
return of(this.widgetTypeInfosCache.get(widgetsBundleId));
|
||||
} else {
|
||||
return this.http.get<Array<WidgetTypeInfo>>(`/api/widgetTypesInfos?widgetsBundleId=${widgetsBundleId}`,
|
||||
defaultHttpOptionsFromConfig(config)).pipe(
|
||||
tap((res) => this.widgetTypeInfosCache.set(widgetsBundleId, res) )
|
||||
);
|
||||
public getBundleWidgetTypeInfosList(widgetsBundleId: string,
|
||||
config?: RequestConfig): Observable<Array<WidgetTypeInfo>> {
|
||||
return this.getBundleWidgetTypeInfos(new PageLink(1024), widgetsBundleId, false, DeprecatedFilter.ALL, null, config).pipe(
|
||||
map((data) => data.data)
|
||||
);
|
||||
}
|
||||
|
||||
public getBundleWidgetTypeInfos(pageLink: PageLink,
|
||||
widgetsBundleId: string,
|
||||
fullSearch = false,
|
||||
deprecatedFilter = DeprecatedFilter.ALL,
|
||||
widgetTypes: Array<widgetType> = null,
|
||||
config?: RequestConfig): Observable<PageData<WidgetTypeInfo>> {
|
||||
|
||||
let url =
|
||||
`/api/widgetTypesInfos${pageLink.toQuery()}&widgetsBundleId=${widgetsBundleId}` +
|
||||
`&fullSearch=${fullSearch}&deprecatedFilter=${deprecatedFilter}`;
|
||||
if (widgetTypes && widgetTypes.length) {
|
||||
url += `&widgetTypeList=${widgetTypes.join(',')}`;
|
||||
}
|
||||
return this.http.get<PageData<WidgetTypeInfo>>(url, defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public getWidgetType(fullFqn: string, config?: RequestConfig): Observable<WidgetType> {
|
||||
@ -224,10 +226,14 @@ export class WidgetService {
|
||||
}
|
||||
|
||||
public getWidgetTypes(pageLink: PageLink, tenantOnly = false,
|
||||
fullSearch = false, config?: RequestConfig): Observable<PageData<WidgetTypeInfo>> {
|
||||
return this.http.get<PageData<WidgetTypeInfo>>(
|
||||
`/api/widgetTypes${pageLink.toQuery()}&tenantOnly=${tenantOnly}&fullSearch=${fullSearch}`,
|
||||
defaultHttpOptionsFromConfig(config));
|
||||
fullSearch = false, deprecatedFilter = DeprecatedFilter.ALL, widgetTypes: Array<widgetType> = null,
|
||||
config?: RequestConfig): Observable<PageData<WidgetTypeInfo>> {
|
||||
let url =
|
||||
`/api/widgetTypes${pageLink.toQuery()}&tenantOnly=${tenantOnly}&fullSearch=${fullSearch}&deprecatedFilter=${deprecatedFilter}`;
|
||||
if (widgetTypes && widgetTypes.length) {
|
||||
url += `&widgetTypeList=${widgetTypes.join(',')}`;
|
||||
}
|
||||
return this.http.get<PageData<WidgetTypeInfo>>(url, defaultHttpOptionsFromConfig(config));
|
||||
}
|
||||
|
||||
public getWidgetTemplate(widgetTypeParam: widgetType,
|
||||
@ -301,6 +307,5 @@ export class WidgetService {
|
||||
this.systemWidgetsBundles = undefined;
|
||||
this.tenantWidgetsBundles = undefined;
|
||||
this.loadWidgetsBundleCacheSubject = undefined;
|
||||
this.widgetTypeInfosCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,7 +386,7 @@
|
||||
</tb-details-panel>
|
||||
<tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex
|
||||
headerTitle="{{ isAddingWidget ?
|
||||
((dashboardWidgetSelectComponent?.selectWidgetMode === 'allWidgets' ? ('widget.select-widget' | translate) :
|
||||
((dashboardWidgetSelectComponent?.selectWidgetMode === 'allWidgets' ? ('dashboard.select-widget-value' | translate: { title: ('widget.all-widgets' | translate) }) :
|
||||
(!dashboardWidgetSelectComponent?.widgetsBundle ?
|
||||
'widget.select-widgets-bundle' : 'dashboard.select-widget-value') | translate: dashboardWidgetSelectComponent?.widgetsBundle)) : ''
|
||||
}}"
|
||||
@ -396,32 +396,24 @@
|
||||
[isEdit]="false"
|
||||
backgroundColor="#cfd8dc"
|
||||
(closeDetails)="onAddWidgetClosed()"
|
||||
(closeSearch)="onCloseSearchBundle()">
|
||||
<div class="prefix-title-buttons" [fxShow]="(isAddingWidget && dashboardWidgetSelectComponent?.widgetsBundle) ? true : false" style="height: 28px; margin-right: 12px">
|
||||
(closeSearch)="dashboardWidgetSelectComponent.search = ''">
|
||||
<div class="prefix-title-buttons" [fxShow]="!!(isAddingWidget && (dashboardWidgetSelectComponent?.widgetsBundle || dashboardWidgetSelectComponent?.selectWidgetMode === 'allWidgets'))" style="height: 28px; margin-right: 12px">
|
||||
<button class="tb-mat-28" mat-icon-button type="button" (click)="clearSelectedWidgetBundle()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="search-pane" *ngIf="isAddingWidget" fxLayout="row">
|
||||
<div class="search-pane" *ngIf="isAddingWidget && dashboardWidgetSelectComponent" fxLayout="row">
|
||||
<tb-widgets-bundle-search fxFlex
|
||||
[(ngModel)]="searchBundle"
|
||||
placeholder="{{ ((!dashboardWidgetSelectComponent?.widgetsBundle && dashboardWidgetSelectComponent?.selectWidgetMode === 'bundles')
|
||||
? 'widgets-bundle.search' : 'widget.search') | translate }}"
|
||||
(ngModelChange)="searchBundle = $event">
|
||||
[(ngModel)]="dashboardWidgetSelectComponent.search"
|
||||
placeholder="{{ ((!dashboardWidgetSelectComponent.widgetsBundle && dashboardWidgetSelectComponent.selectWidgetMode === 'bundles')
|
||||
? 'widgets-bundle.search' : 'widget.search') | translate }}">
|
||||
</tb-widgets-bundle-search>
|
||||
</div>
|
||||
<div class="details-buttons" *ngIf="isAddingWidget" fxLayout="row" fxLayoutAlign="start center">
|
||||
<button mat-button type="button" (click)="importWidget($event)"
|
||||
*ngIf="!dashboardWidgetSelectComponent?.widgetsBundle">
|
||||
*ngIf="dashboardWidgetSelectComponent?.selectWidgetMode === 'bundles' && !dashboardWidgetSelectComponent?.widgetsBundle">
|
||||
<mat-icon>file_upload</mat-icon>{{ 'dashboard.import-widget' | translate }}</button>
|
||||
<button mat-icon-button type="button"
|
||||
*ngIf="dashboardWidgetSelectComponent?.widgetTypes.size > 1"
|
||||
(click)="editWidgetsTypesToDisplay($event)"
|
||||
matTooltip="{{ 'widget.filter' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
</button>
|
||||
<tb-toggle-select *ngIf="dashboardWidgetSelectComponent && !dashboardWidgetSelectComponent.widgetsBundle"
|
||||
<tb-toggle-select *ngIf="dashboardWidgetSelectComponent?.selectWidgetMode === 'bundles' && !dashboardWidgetSelectComponent?.widgetsBundle"
|
||||
appearance="fill-invert"
|
||||
disablePagination
|
||||
selectMediaBreakpoint="xs"
|
||||
@ -429,22 +421,27 @@
|
||||
<tb-toggle-option value="bundles">{{ 'widgets-bundle.widgets-bundles' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="allWidgets">{{ 'widget.all-widgets' | translate }}</tb-toggle-option>
|
||||
</tb-toggle-select>
|
||||
<button mat-icon-button type="button"
|
||||
*ngIf="dashboardWidgetSelectComponent?.widgetTypes.size > 1"
|
||||
(click)="editWidgetsTypesToDisplay($event)"
|
||||
matTooltip="{{ 'widget.filter' | translate }}"
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
</button>
|
||||
<tb-toggle-select *ngIf="dashboardWidgetSelectComponent?.hasDeprecated"
|
||||
appearance="fill-invert"
|
||||
disablePagination
|
||||
selectMediaBreakpoint="xs"
|
||||
[(ngModel)]="dashboardWidgetSelectComponent.widgetsListMode">
|
||||
<tb-toggle-option value="all">{{ 'widget.all' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="actual">{{ 'widget.actual' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="deprecated">{{ 'widget.deprecated' | translate }}</tb-toggle-option>
|
||||
[(ngModel)]="dashboardWidgetSelectComponent.deprecatedFilter">
|
||||
<tb-toggle-option value="ALL">{{ 'widget.all' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="ACTUAL">{{ 'widget.actual' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="DEPRECATED">{{ 'widget.deprecated' | translate }}</tb-toggle-option>
|
||||
</tb-toggle-select>
|
||||
</div>
|
||||
<tb-dashboard-widget-select #dashboardWidgetSelect
|
||||
*ngIf="isAddingWidget"
|
||||
[aliasController]="dashboardCtx.aliasController"
|
||||
[searchBundle]="searchBundle"
|
||||
[filterWidgetTypes]="filterWidgetTypes"
|
||||
(widgetsBundleSelected)="widgetBundleSelected()"
|
||||
(widgetSelected)="addWidgetFromType($event)">
|
||||
</tb-dashboard-widget-select>
|
||||
</tb-details-panel>
|
||||
|
||||
@ -230,7 +230,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
forceDashboardMobileMode = false;
|
||||
isAddingWidget = false;
|
||||
isAddingWidgetClosed = true;
|
||||
searchBundle = '';
|
||||
filterWidgetTypes: widgetType[] = null;
|
||||
|
||||
isToolbarOpened = false;
|
||||
@ -1153,7 +1152,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
|
||||
addWidgetFromType(widget: WidgetInfo) {
|
||||
this.onAddWidgetClosed();
|
||||
this.searchBundle = '';
|
||||
this.widgetComponentService.getWidgetInfo(widget.typeFullFqn).subscribe(
|
||||
(widgetTypeInfo) => {
|
||||
const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo);
|
||||
@ -1426,13 +1424,10 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
return widgetContextActions;
|
||||
}
|
||||
|
||||
widgetBundleSelected(){
|
||||
this.searchBundle = '';
|
||||
}
|
||||
|
||||
clearSelectedWidgetBundle() {
|
||||
this.searchBundle = '';
|
||||
this.dashboardWidgetSelectComponent.search = '';
|
||||
this.dashboardWidgetSelectComponent.widgetsBundle = null;
|
||||
this.dashboardWidgetSelectComponent.selectWidgetMode = 'bundles';
|
||||
}
|
||||
|
||||
editWidgetsTypesToDisplay($event: Event) {
|
||||
@ -1482,10 +1477,6 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
onCloseSearchBundle() {
|
||||
this.searchBundle = '';
|
||||
}
|
||||
|
||||
public updateDashboardImage($event: Event) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
|
||||
@ -15,79 +15,105 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div class="widget-select">
|
||||
<div class="tb-dashboard-widget-select">
|
||||
<ng-container *ngIf="selectWidgetMode === 'bundles'; else allWidgets">
|
||||
<div *ngIf="widgetsBundle; else bundles">
|
||||
<ng-container *ngTemplateOutlet="widgets; context:{ currentWidgets$: widgets$ }"></ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="widgetsBundle; else bundles">
|
||||
<ng-container *ngTemplateOutlet="widgets; context:{ fetchFunction: widgetsFetchFunction, filter: widgetsFilter }"></ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #allWidgets>
|
||||
<ng-container *ngTemplateOutlet="widgets; context:{ currentWidgets$: allWidgets$ }"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="widgets; context:{ fetchFunction: allWidgetsFetchFunction, filter: allWidgetsFilter }"></ng-container>
|
||||
</ng-template>
|
||||
<ng-template #widgets let-currentWidgets$="currentWidgets$">
|
||||
<div *ngIf="(currentWidgets$ | async)?.length; else loadingWidgets" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
|
||||
<div *ngFor="let widget of currentWidgets$ | async" class="mat-card-container">
|
||||
<mat-card appearance="raised" fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, widget)">
|
||||
<div class="preview-container" fxFlex="45">
|
||||
<img class="preview" [src]="getPreviewImage(widget.image)" alt="{{ widget.title }}">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>{{widget.title}}<div *ngIf="widget.deprecated" class="tb-deprecated" translate>widget.deprecated</div></mat-card-title>
|
||||
<mat-card-subtitle>{{ 'widget.' + widget.type | translate }}</mat-card-subtitle>
|
||||
<mat-card-content *ngIf="widget.description">
|
||||
{{ widget.description }}
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #loadingWidgets>
|
||||
<div *ngIf="loadingWidgets$ | async; else emptyBundle" fxLayout="column"
|
||||
fxLayoutAlign="center center" class="tb-absolute-fill">
|
||||
<ng-template #widgets let-fetchFunction="fetchFunction" let-filter="filter">
|
||||
<tb-scroll-grid
|
||||
[columns]="columns"
|
||||
[fetchFunction]="fetchFunction"
|
||||
[filter]="filter"
|
||||
[itemCard]="widgetCard"
|
||||
[loadingCell]="widgetLoadingCard"
|
||||
[dataLoading]="loadingWidgets"
|
||||
[noData]="noWidgets">
|
||||
</tb-scroll-grid>
|
||||
<ng-template #widgetCard let-item="item">
|
||||
<mat-card class="tb-widget-preview-card" appearance="raised" fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="onWidgetClicked($event, item)">
|
||||
<div class="preview-container" fxFlex="45">
|
||||
<img class="preview" [src]="getPreviewImage(item.image)" alt="{{ item.title }}">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>{{item.name}}<div *ngIf="item.deprecated" class="tb-deprecated" translate>widget.deprecated</div></mat-card-title>
|
||||
<mat-card-subtitle>{{ 'widget.' + item.widgetType | translate }}</mat-card-subtitle>
|
||||
<mat-card-content *ngIf="item.description">
|
||||
{{ item.description }}
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</mat-card>
|
||||
</ng-template>
|
||||
<ng-template #loadingWidgets>
|
||||
<div fxLayout="column"
|
||||
fxLayoutAlign="center center" class="tb-absolute-fill">
|
||||
<span class="mat-headline-5" style="padding-bottom: 20px;">
|
||||
{{ 'widget.loading-widgets' | translate }}
|
||||
</span>
|
||||
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #emptyBundle>
|
||||
<span translate
|
||||
style="display: flex;"
|
||||
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #noWidgets>
|
||||
<span style="display: flex;"
|
||||
fxLayoutAlign="center center"
|
||||
class="mat-headline-5 tb-absolute-fill">{{(selectWidgetMode === 'bundles' ? 'widgets-bundle.empty' : 'widget.no-widgets-text') | translate}}</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #bundles>
|
||||
<div *ngIf="(widgetsBundles$ | async)?.length; else loadingWidgetBundles" fxFlexFill fxLayoutGap="12px grid" fxLayout="row wrap">
|
||||
<div *ngFor="let widgetsBundle of widgetsBundles$ | async" class="mat-card-container">
|
||||
<mat-card appearance="raised" fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, widgetsBundle)">
|
||||
<div class="preview-container" fxFlex="45">
|
||||
<img class="preview" [src]=getPreviewImage(widgetsBundle.image) alt="{{ widgetsBundle.title }}">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>{{ widgetsBundle.title }}</mat-card-title>
|
||||
<mat-card-subtitle *ngIf="isSystem(widgetsBundle)" translate>widgets-bundle.system</mat-card-subtitle>
|
||||
<mat-card-content *ngIf="widgetsBundle.description">
|
||||
{{ widgetsBundle.description }}
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<tb-scroll-grid
|
||||
[columns]="columns"
|
||||
[fetchFunction]="widgetBundlesFetchFunction"
|
||||
[filter]="widgetsBundleFilter"
|
||||
[itemCard]="widgetsBundleCard"
|
||||
[loadingCell]="widgetLoadingCard"
|
||||
[dataLoading]="loadingWidgetBundles"
|
||||
[noData]="noWidgetBundles">
|
||||
</tb-scroll-grid>
|
||||
<ng-template #widgetsBundleCard let-item="item">
|
||||
<mat-card class="tb-widget-preview-card" appearance="raised" fxFlexFill fxLayout="row" fxLayoutGap="16px" (click)="selectBundle($event, item)">
|
||||
<div class="preview-container" fxFlex="45">
|
||||
<img class="preview" [src]=getPreviewImage(item.image) alt="{{ item.title }}">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>{{ item.title }}</mat-card-title>
|
||||
<mat-card-subtitle *ngIf="isSystem(item)" translate>widgets-bundle.system</mat-card-subtitle>
|
||||
<mat-card-content *ngIf="item.description">
|
||||
{{ item.description }}
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</mat-card>
|
||||
</ng-template>
|
||||
<ng-template #loadingWidgetBundles>
|
||||
<div *ngIf="loadingWidgetBundles$ | async; else noWidgetBundles" fxLayout="column"
|
||||
<div fxLayout="column"
|
||||
fxLayoutAlign="center center" class="tb-absolute-fill">
|
||||
<span class="mat-headline-5" style="padding-bottom: 20px;">
|
||||
{{ 'widgets-bundle.loading-widgets-bundles' | translate }}
|
||||
</span>
|
||||
<mat-spinner strokeWidth="5"></mat-spinner>
|
||||
</div>
|
||||
<ng-template #noWidgetBundles>
|
||||
</ng-template>
|
||||
<ng-template #noWidgetBundles>
|
||||
<span translate
|
||||
style="display: flex;"
|
||||
fxLayoutAlign="center center"
|
||||
class="mat-headline-5 tb-absolute-fill">widgets-bundle.no-widgets-bundles-text</span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template #widgetLoadingCard>
|
||||
<mat-card class="tb-widget-preview-card loading-cell" appearance="raised" fxLayout="row" fxLayoutGap="16px">
|
||||
<div class="preview-container" fxFlex="45">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>
|
||||
</mat-card-title>
|
||||
<mat-card-content>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</mat-card>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
@ -13,83 +13,89 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@import '../../../../../scss/constants';
|
||||
|
||||
:host{
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
position: absolute;
|
||||
.tb-dashboard-widget-select {
|
||||
background-color: #cfd8dc;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
.widget-select {
|
||||
padding: 12px 0 12px 12px;
|
||||
background-color: #cfd8dc;
|
||||
.mat-card-container {
|
||||
flex: 0 0 100%;
|
||||
.mat-mdc-card.tb-widget-preview-card {
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s;
|
||||
padding: 16px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%)
|
||||
}
|
||||
|
||||
&.loading-cell {
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
|
||||
.mat-mdc-card-title {
|
||||
height: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
height: 80%;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.mat-mdc-card-content {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.preview-container, .mat-mdc-card-title, .mat-mdc-card-content {
|
||||
background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
|
||||
border-radius: 5px;
|
||||
background-size: 200% 100%;
|
||||
animation: 1s shine linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
text-align: center;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.preview {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.mat-mdc-card {
|
||||
box-shadow: 0 2px 6px 6px rgb(0 0 0 / 20%), 0 1px 4px 2px rgb(0 0 0 / 14%), 0 1px 6px 0 rgb(0 0 0 / 12%)
|
||||
}
|
||||
}
|
||||
.mat-mdc-card-title {
|
||||
font-size: 20px;
|
||||
line-height: normal;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.mat-mdc-card {
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s;
|
||||
padding: 16px;
|
||||
|
||||
.preview-container {
|
||||
text-align: center;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.preview {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.mat-mdc-card-title {
|
||||
font-size: 20px;
|
||||
line-height: normal;
|
||||
margin-bottom: 8px;
|
||||
.tb-deprecated {
|
||||
font-size: 14px;
|
||||
color: rgba(209, 39, 48, 0.87);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-card-subtitle {
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
line-height: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.mat-mdc-card-content {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
.tb-deprecated {
|
||||
font-size: 14px;
|
||||
color: rgba(209, 39, 48, 0.87);
|
||||
}
|
||||
}
|
||||
|
||||
@media #{$mat-gt-xs} {
|
||||
.mat-card-container {
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
.mat-mdc-card-subtitle {
|
||||
margin-bottom: 16px;
|
||||
margin-top: -4px;
|
||||
line-height: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2000px) {
|
||||
.mat-card-container {
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
.mat-mdc-card-content {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position-x: -200%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,74 +14,68 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
|
||||
import { IAliasController } from '@core/api/widget-api.models';
|
||||
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
||||
import { WidgetService } from '@core/http/widget.service';
|
||||
import { fullWidgetTypeFqn, WidgetInfo, widgetType, WidgetTypeInfo } from '@shared/models/widget.models';
|
||||
import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest, Observable, of, ReplaySubject } from 'rxjs';
|
||||
import {
|
||||
DeprecatedFilter,
|
||||
fullWidgetTypeFqn,
|
||||
WidgetInfo,
|
||||
widgetType,
|
||||
WidgetTypeInfo
|
||||
} from '@shared/models/widget.models';
|
||||
import { debounceTime, distinctUntilChanged, map, skip } from 'rxjs/operators';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||
import { isDefinedAndNotNull } from '@core/utils';
|
||||
import { isDefinedAndNotNull, isObject } from '@core/utils';
|
||||
import { PageLink } from '@shared/models/page/page-link';
|
||||
import { Direction } from '@shared/models/page/sort-order';
|
||||
|
||||
type widgetsListMode = 'all' | 'actual' | 'deprecated';
|
||||
import { GridEntitiesFetchFunction, ScrollGridColumns } from '@home/models/datasource/scroll-grid-datasource';
|
||||
|
||||
type selectWidgetMode = 'bundles' | 'allWidgets';
|
||||
|
||||
interface WidgetsFilter {
|
||||
search: string;
|
||||
filter: widgetType[];
|
||||
deprecatedFilter: DeprecatedFilter;
|
||||
}
|
||||
|
||||
interface BundleWidgetsFilter extends WidgetsFilter {
|
||||
widgetsBundleId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard-widget-select',
|
||||
templateUrl: './dashboard-widget-select.component.html',
|
||||
styleUrls: ['./dashboard-widget-select.component.scss']
|
||||
styleUrls: ['./dashboard-widget-select.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DashboardWidgetSelectComponent implements OnInit {
|
||||
|
||||
private search$ = new BehaviorSubject<string>('');
|
||||
private searchSubject = new BehaviorSubject<string>('');
|
||||
private search$ = this.searchSubject.asObservable().pipe(
|
||||
debounceTime(150));
|
||||
|
||||
private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
|
||||
private widgetsListMode$ = new BehaviorSubject<widgetsListMode>('actual');
|
||||
private deprecatedFilter$ = new BehaviorSubject<DeprecatedFilter>(DeprecatedFilter.ACTUAL);
|
||||
private selectWidgetMode$ = new BehaviorSubject<selectWidgetMode>('bundles');
|
||||
private widgetsInfo: Observable<Array<WidgetInfo>>;
|
||||
private widgetsBundleValue: WidgetsBundle;
|
||||
private widgetsBundle$ = new BehaviorSubject<WidgetsBundle>(null);
|
||||
|
||||
widgetTypes = new Set<widgetType>();
|
||||
hasDeprecated = false;
|
||||
|
||||
allWidgets$: Observable<Array<WidgetInfo>>;
|
||||
widgets$: Observable<Array<WidgetInfo>>;
|
||||
loadingWidgetsSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
loadingWidgets$ = this.loadingWidgetsSubject.pipe(
|
||||
share()
|
||||
);
|
||||
widgetsBundles$: Observable<Array<WidgetsBundle>>;
|
||||
loadingWidgetBundlesSubject: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
||||
loadingWidgetBundles$ = this.loadingWidgetBundlesSubject.pipe(
|
||||
share()
|
||||
);
|
||||
|
||||
set widgetsBundle(widgetBundle: WidgetsBundle) {
|
||||
if (this.widgetsBundleValue !== widgetBundle) {
|
||||
this.widgetsBundleValue = widgetBundle;
|
||||
if (widgetBundle === null) {
|
||||
this.widgetTypes.clear();
|
||||
this.hasDeprecated = false;
|
||||
}
|
||||
this.filterWidgetTypes$.next(null);
|
||||
this.widgetsListMode$.next('actual');
|
||||
this.widgetsInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
get widgetsBundle(): WidgetsBundle {
|
||||
return this.widgetsBundleValue;
|
||||
}
|
||||
|
||||
@Input()
|
||||
aliasController: IAliasController;
|
||||
|
||||
@Input()
|
||||
set searchBundle(search: string) {
|
||||
this.search$.next(search);
|
||||
set search(search: string) {
|
||||
this.searchSubject.next(search);
|
||||
}
|
||||
|
||||
get search(): string {
|
||||
return this.searchSubject.value;
|
||||
}
|
||||
|
||||
@Input()
|
||||
@ -95,7 +89,18 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
set selectWidgetMode(mode: selectWidgetMode) {
|
||||
this.selectWidgetMode$.next(mode);
|
||||
if (this.selectWidgetMode$.value !== mode) {
|
||||
if (mode === 'bundles' && this.widgetsBundle$.value === null) {
|
||||
this.widgetTypes.clear();
|
||||
this.hasDeprecated = false;
|
||||
} else {
|
||||
this.widgetTypes = new Set<widgetType>(Object.keys(widgetType).map(t => t as widgetType));
|
||||
this.hasDeprecated = true;
|
||||
}
|
||||
this.filterWidgetTypes$.next(null);
|
||||
this.deprecatedFilter$.next(DeprecatedFilter.ACTUAL);
|
||||
this.selectWidgetMode$.next(mode);
|
||||
}
|
||||
}
|
||||
|
||||
get selectWidgetMode(): selectWidgetMode {
|
||||
@ -103,77 +108,121 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
}
|
||||
|
||||
@Input()
|
||||
set widgetsListMode(mode: widgetsListMode) {
|
||||
this.widgetsListMode$.next(mode);
|
||||
set deprecatedFilter(filter: DeprecatedFilter) {
|
||||
this.deprecatedFilter$.next(filter);
|
||||
}
|
||||
|
||||
get widgetsListMode(): widgetsListMode {
|
||||
return this.widgetsListMode$.value;
|
||||
get deprecatedFilter(): DeprecatedFilter {
|
||||
return this.deprecatedFilter$.value;
|
||||
}
|
||||
|
||||
set widgetsBundle(widgetBundle: WidgetsBundle) {
|
||||
if (this.widgetsBundle$.value !== widgetBundle) {
|
||||
if (widgetBundle === null && this.selectWidgetMode$.value !== 'allWidgets') {
|
||||
this.widgetTypes.clear();
|
||||
this.hasDeprecated = false;
|
||||
} else {
|
||||
this.widgetTypes = new Set<widgetType>(Object.keys(widgetType).map(t => t as widgetType));
|
||||
this.hasDeprecated = true;
|
||||
}
|
||||
this.filterWidgetTypes$.next(null);
|
||||
this.deprecatedFilter$.next(DeprecatedFilter.ACTUAL);
|
||||
this.widgetsBundle$.next(widgetBundle);
|
||||
}
|
||||
}
|
||||
|
||||
get widgetsBundle(): WidgetsBundle {
|
||||
return this.widgetsBundle$.value;
|
||||
}
|
||||
|
||||
@Output()
|
||||
widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
|
||||
|
||||
@Output()
|
||||
widgetsBundleSelected: EventEmitter<WidgetsBundle> = new EventEmitter<WidgetsBundle>();
|
||||
columns: ScrollGridColumns = {
|
||||
columns: 1,
|
||||
breakpoints: {
|
||||
'screen and (min-width: 2000px)': 3,
|
||||
'screen and (min-width: 600px)': 2
|
||||
}
|
||||
};
|
||||
|
||||
widgetBundlesFetchFunction: GridEntitiesFetchFunction<WidgetsBundle, string>;
|
||||
allWidgetsFetchFunction: GridEntitiesFetchFunction<WidgetTypeInfo, WidgetsFilter>;
|
||||
widgetsFetchFunction: GridEntitiesFetchFunction<WidgetTypeInfo, BundleWidgetsFilter>;
|
||||
|
||||
widgetsBundleFilter = '';
|
||||
allWidgetsFilter: WidgetsFilter = {search: '', filter: null, deprecatedFilter: DeprecatedFilter.ACTUAL};
|
||||
widgetsFilter: BundleWidgetsFilter = {search: '', filter: null, deprecatedFilter: DeprecatedFilter.ACTUAL, widgetsBundleId: null};
|
||||
|
||||
constructor(private widgetsService: WidgetService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private cd: ChangeDetectorRef) {
|
||||
this.widgetsBundles$ = combineLatest([this.search$.asObservable(), this.selectWidgetMode$.asObservable()]).pipe(
|
||||
distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
|
||||
switchMap(search => this.fetchWidgetBundle(...search))
|
||||
private cd: ChangeDetectorRef,
|
||||
private sanitizer: DomSanitizer) {
|
||||
|
||||
this.widgetBundlesFetchFunction = (pageSize, page, filter) => {
|
||||
const pageLink = new PageLink(pageSize, page, filter, {
|
||||
property: 'title',
|
||||
direction: Direction.ASC
|
||||
});
|
||||
return this.widgetsService.getWidgetBundles(pageLink, true);
|
||||
};
|
||||
|
||||
this.allWidgetsFetchFunction = (pageSize, page, filter) => {
|
||||
const pageLink = new PageLink(pageSize, page, filter.search, {
|
||||
property: 'name',
|
||||
direction: Direction.ASC
|
||||
});
|
||||
return this.widgetsService.getWidgetTypes(pageLink, false, true, filter.deprecatedFilter, filter.filter);
|
||||
};
|
||||
|
||||
this.widgetsFetchFunction = (pageSize, page, filter) => {
|
||||
const pageLink = new PageLink(pageSize, page, filter.search, {
|
||||
property: 'name',
|
||||
direction: Direction.ASC
|
||||
});
|
||||
return this.widgetsService.getBundleWidgetTypeInfos(pageLink, filter.widgetsBundleId,
|
||||
true, filter.deprecatedFilter, filter.filter);
|
||||
};
|
||||
|
||||
this.search$.pipe(
|
||||
distinctUntilChanged(),
|
||||
skip(1)
|
||||
).subscribe(
|
||||
(search) => {
|
||||
this.widgetsBundleFilter = search;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
);
|
||||
this.allWidgets$ = combineLatest([this.search$.asObservable().pipe(
|
||||
debounceTime(150)
|
||||
), this.selectWidgetMode$.asObservable()]).pipe(
|
||||
distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
|
||||
switchMap(search => this.fetchAllWidgets(...search)),
|
||||
share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false })
|
||||
);
|
||||
this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable(), this.widgetsListMode$]).pipe(
|
||||
|
||||
combineLatest({search: this.search$, filter: this.filterWidgetTypes$.asObservable(),
|
||||
deprecatedFilter: this.deprecatedFilter$.asObservable()}).pipe(
|
||||
distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
|
||||
switchMap(search => this.fetchWidgets(...search))
|
||||
skip(1)
|
||||
).subscribe(
|
||||
(filter) => {
|
||||
this.allWidgetsFilter = filter;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
);
|
||||
|
||||
combineLatest({search: this.search$, widgetsBundleId: this.widgetsBundle$.pipe(map(wb => wb !== null ? wb.id.id : null)),
|
||||
filter: this.filterWidgetTypes$.asObservable(), deprecatedFilter: this.deprecatedFilter$.asObservable()}).pipe(
|
||||
distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
|
||||
skip(1)
|
||||
).subscribe(
|
||||
(filter) => {
|
||||
if (filter.widgetsBundleId) {
|
||||
this.widgetsFilter = filter;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
private getWidgets(): Observable<Array<WidgetInfo>> {
|
||||
if (!this.widgetsInfo) {
|
||||
if (this.widgetsBundle !== null) {
|
||||
this.loadingWidgetsSubject.next(true);
|
||||
this.widgetsInfo = this.widgetsService.getBundleWidgetTypeInfos(this.widgetsBundle.id.id).pipe(
|
||||
map(widgets => {
|
||||
const widgetTypes = new Set<widgetType>();
|
||||
const hasDeprecated = widgets.some(w => w.deprecated);
|
||||
const widgetInfos = widgets.map((widgetTypeInfo) => {
|
||||
widgetTypes.add(widgetTypeInfo.widgetType);
|
||||
return this.toWidgetInfo(widgetTypeInfo);
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.widgetTypes = widgetTypes;
|
||||
this.hasDeprecated = hasDeprecated;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
return widgetInfos;
|
||||
}),
|
||||
tap(() => {
|
||||
this.loadingWidgetsSubject.next(false);
|
||||
}),
|
||||
share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false })
|
||||
);
|
||||
} else {
|
||||
this.widgetsInfo = of([]);
|
||||
}
|
||||
}
|
||||
return this.widgetsInfo;
|
||||
}
|
||||
|
||||
onWidgetClicked($event: Event, widget: WidgetInfo): void {
|
||||
this.widgetSelected.emit(widget);
|
||||
onWidgetClicked($event: Event, widget: WidgetTypeInfo): void {
|
||||
this.widgetSelected.emit(this.toWidgetInfo(widget));
|
||||
}
|
||||
|
||||
isSystem(item: WidgetsBundle): boolean {
|
||||
@ -183,8 +232,10 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
selectBundle($event: Event, bundle: WidgetsBundle) {
|
||||
$event.preventDefault();
|
||||
this.widgetsBundle = bundle;
|
||||
this.search$.next('');
|
||||
this.widgetsBundleSelected.emit(bundle);
|
||||
if (bundle.title?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||
bundle.description?.toLowerCase().includes(this.search.toLowerCase())) {
|
||||
this.searchSubject.next('');
|
||||
}
|
||||
}
|
||||
|
||||
getPreviewImage(imageUrl: string | null): SafeUrl | string {
|
||||
@ -194,62 +245,8 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
return '/assets/widget-preview-empty.svg';
|
||||
}
|
||||
|
||||
private getWidgetsBundles(): Observable<Array<WidgetsBundle>> {
|
||||
return this.widgetsService.getAllWidgetsBundles().pipe(
|
||||
tap(() => this.loadingWidgetBundlesSubject.next(false)),
|
||||
share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false })
|
||||
);
|
||||
}
|
||||
|
||||
private fetchWidgetBundle(search: string, mode: selectWidgetMode): Observable<Array<WidgetsBundle>> {
|
||||
if (mode === 'bundles') {
|
||||
return this.getWidgetsBundles().pipe(
|
||||
map(bundles => search ? bundles.filter(
|
||||
bundle => (
|
||||
bundle.title?.toLowerCase().includes(search.toLowerCase()) ||
|
||||
bundle.description?.toLowerCase().includes(search.toLowerCase())
|
||||
)) : bundles
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return of([]);
|
||||
}
|
||||
}
|
||||
|
||||
private fetchWidgets(search: string, filter: widgetType[], listMode: widgetsListMode): Observable<Array<WidgetInfo>> {
|
||||
return this.getWidgets().pipe(
|
||||
map(widgets => (listMode && listMode !== 'all') ?
|
||||
widgets.filter((widget) => listMode === 'actual' ? !widget.deprecated : widget.deprecated) : widgets),
|
||||
map(widgets => filter ? widgets.filter((widget) => filter.includes(widget.type)) : widgets),
|
||||
map(widgets => search ? widgets.filter(
|
||||
widget => (
|
||||
widget.title?.toLowerCase().includes(search.toLowerCase()) ||
|
||||
widget.description?.toLowerCase().includes(search.toLowerCase())
|
||||
)) : widgets
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private fetchAllWidgets(search: string, mode: selectWidgetMode): Observable<Array<WidgetInfo>> {
|
||||
if (mode === 'allWidgets') {
|
||||
const pageLink = new PageLink(1024, 0, search, {
|
||||
property: 'name',
|
||||
direction: Direction.ASC
|
||||
});
|
||||
return this.getAllWidgets(pageLink);
|
||||
} else {
|
||||
return of([]);
|
||||
}
|
||||
}
|
||||
|
||||
private getAllWidgets(pageLink: PageLink): Observable<Array<WidgetInfo>> {
|
||||
this.loadingWidgetsSubject.next(true);
|
||||
return this.widgetsService.getWidgetTypes(pageLink, false, true).pipe(
|
||||
map(data => data.data.map(w => this.toWidgetInfo(w))),
|
||||
tap(() => {
|
||||
this.loadingWidgetsSubject.next(false);
|
||||
})
|
||||
);
|
||||
isObject(value: any): boolean {
|
||||
return isObject(value);
|
||||
}
|
||||
|
||||
private toWidgetInfo(widgetTypeInfo: WidgetTypeInfo): WidgetInfo {
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2023 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.
|
||||
|
||||
-->
|
||||
<cdk-virtual-scroll-viewport #viewport class="tb-scroll-grid-viewport" [itemSize]="itemSize" appendOnly>
|
||||
<ng-container *cdkVirtualFor="let itemsRow of dataSource">
|
||||
<div *ngIf="itemsRow" class="tb-scroll-grid-items-row" [style]="{gap: gap+'px'}">
|
||||
<div *ngFor="let item of itemsRow" class="tb-scroll-grid-item-container">
|
||||
<ng-container *ngIf="item === 'loadingCell'">
|
||||
<ng-container *ngTemplateOutlet="loadingCell ? loadingCell : defaultLoadingCell"></ng-container>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isObject(item)">
|
||||
<ng-container *ngTemplateOutlet="itemCard; context:{ item: item }"></ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
<ng-container *ngIf="dataSource.isEmpty">
|
||||
<ng-container *ngTemplateOutlet="loadingItems"></ng-container>
|
||||
</ng-container>
|
||||
<ng-template #loadingItems>
|
||||
<ng-container *ngIf="dataSource.initialDataLoading; else emptyData">
|
||||
<ng-container *ngTemplateOutlet="dataLoading ? dataLoading : defaultDataLoading"></ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #emptyData>
|
||||
<ng-container *ngTemplateOutlet="noData"></ng-container>
|
||||
</ng-template>
|
||||
<ng-template #defaultLoadingCell>
|
||||
<div fxLayout="column" fxLayoutAlign="center center" [style]="{minHeight: itemSize + 'px'}">
|
||||
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #defaultDataLoading>
|
||||
<div fxLayout="column"
|
||||
fxLayoutAlign="center center" class="tb-absolute-fill">
|
||||
<mat-spinner color="accent" strokeWidth="5"></mat-spinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.
|
||||
*/
|
||||
.tb-scroll-grid-viewport {
|
||||
height: 100%;
|
||||
.cdk-virtual-scroll-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.cdk-virtual-scroll-spacer {
|
||||
height: auto !important;
|
||||
}
|
||||
.tb-scroll-grid-items-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.tb-scroll-grid-item-container {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 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,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Renderer2,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {
|
||||
GridEntitiesFetchFunction,
|
||||
ScrollGridColumns,
|
||||
ScrollGridDatasource
|
||||
} from '@home/models/datasource/scroll-grid-datasource';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
import { isObject } from '@app/core/utils';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-scroll-grid',
|
||||
templateUrl: './scroll-grid.component.html',
|
||||
styleUrls: ['./scroll-grid.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class ScrollGridComponent<T, F> implements OnInit, AfterViewInit, OnChanges {
|
||||
|
||||
@ViewChild('viewport')
|
||||
viewport: CdkVirtualScrollViewport;
|
||||
|
||||
@Input()
|
||||
columns: ScrollGridColumns = {columns: 1};
|
||||
|
||||
@Input()
|
||||
fetchFunction: GridEntitiesFetchFunction<T, F>;
|
||||
|
||||
@Input()
|
||||
filter: F;
|
||||
|
||||
@Input()
|
||||
itemSize = 200;
|
||||
|
||||
@Input()
|
||||
gap = 12;
|
||||
|
||||
@Input()
|
||||
itemCard: TemplateRef<{item: T}>;
|
||||
|
||||
@Input()
|
||||
loadingCell: TemplateRef<any>;
|
||||
|
||||
@Input()
|
||||
dataLoading: TemplateRef<any>;
|
||||
|
||||
@Input()
|
||||
noData: TemplateRef<any>;
|
||||
|
||||
dataSource: ScrollGridDatasource<T, F>;
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver,
|
||||
private renderer: Renderer2) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dataSource = new ScrollGridDatasource<T, F>(this.breakpointObserver, this.columns, this.fetchFunction, this.filter);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'gap', this.gap + 'px');
|
||||
this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'padding', this.gap + 'px');
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
for (const propName of Object.keys(changes)) {
|
||||
const change = changes[propName];
|
||||
if (!change.firstChange && change.currentValue !== change.previousValue && propName === 'filter') {
|
||||
this.dataSource.updateFilter(this.filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isObject(value: any): boolean {
|
||||
return isObject(value);
|
||||
}
|
||||
}
|
||||
@ -181,6 +181,7 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet
|
||||
import {
|
||||
ExportWidgetsBundleDialogComponent
|
||||
} from '@home/components/import-export/export-widgets-bundle-dialog.component';
|
||||
import { ScrollGridComponent } from '@home/components/grid/scroll-grid.component';
|
||||
|
||||
@NgModule({
|
||||
declarations:
|
||||
@ -325,7 +326,8 @@ import {
|
||||
RateLimitsComponent,
|
||||
RateLimitsTextComponent,
|
||||
RateLimitsDetailsDialogComponent,
|
||||
SendNotificationButtonComponent
|
||||
SendNotificationButtonComponent,
|
||||
ScrollGridComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -463,7 +465,8 @@ import {
|
||||
RateLimitsComponent,
|
||||
RateLimitsTextComponent,
|
||||
RateLimitsDetailsDialogComponent,
|
||||
SendNotificationButtonComponent
|
||||
SendNotificationButtonComponent,
|
||||
ScrollGridComponent
|
||||
],
|
||||
providers: [
|
||||
WidgetComponentService,
|
||||
|
||||
@ -0,0 +1,250 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 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 { DataSource, ListRange } from '@angular/cdk/collections';
|
||||
import { CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
import { BehaviorSubject, Observable, of, Subscription } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { emptyPageData, PageData } from '@shared/models/page/page-data';
|
||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||
|
||||
export type GridEntitiesFetchFunction<T, F> = (pageSize: number, page: number, filter: F) => Observable<PageData<T>>;
|
||||
|
||||
export type GridCellType = 'emptyCell' | 'loadingCell';
|
||||
|
||||
export interface ScrollGridColumns {
|
||||
columns: number;
|
||||
breakpoints?: {[breakpoint: string]: number};
|
||||
}
|
||||
|
||||
export class ScrollGridDatasource<T, F> extends DataSource<(T | GridCellType)[]> {
|
||||
|
||||
public initialDataLoading = true;
|
||||
|
||||
private _data: T[] = [];
|
||||
private _rows: (T | GridCellType)[][] = Array.from<T[]>({length: 100000});
|
||||
private _hasNext = true;
|
||||
private _columns: number;
|
||||
private _viewport: CdkVirtualScrollViewport;
|
||||
private _pendingRange: ListRange = null;
|
||||
private _fetchingData = false;
|
||||
private _fetchSubscription: Subscription;
|
||||
private _totalElements = 0;
|
||||
|
||||
private _dataStream: BehaviorSubject<(T | GridCellType)[][]>;
|
||||
private _subscription: Subscription;
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver,
|
||||
private columns: ScrollGridColumns,
|
||||
private fetchFunction: GridEntitiesFetchFunction<T, F>,
|
||||
private filter: F) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(collectionViewer: CdkVirtualForOf<(T | GridCellType)[]>): Observable<(T | GridCellType)[][]> {
|
||||
this._viewport = (collectionViewer as any)._viewport;
|
||||
this._init();
|
||||
|
||||
if (this.columns.breakpoints) {
|
||||
const breakpoints = Object.keys(this.columns.breakpoints);
|
||||
this._subscription.add(this.breakpointObserver.observe(breakpoints).subscribe(
|
||||
() => {
|
||||
this._columnsChanged(this._detectColumns());
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
this._subscription.add(
|
||||
collectionViewer.viewChange.subscribe(range => this._fetchDataFromRange(range))
|
||||
);
|
||||
return this._dataStream;
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this._reset();
|
||||
this._subscription.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
get isEmpty(): boolean {
|
||||
return !this._data.length;
|
||||
}
|
||||
|
||||
get active(): boolean {
|
||||
return !!this._subscription && !this._subscription.closed;
|
||||
}
|
||||
|
||||
public updateFilter(filter: F) {
|
||||
this.filter = filter;
|
||||
if (this.active) {
|
||||
const prevLength = this._rows.length;
|
||||
this._reset();
|
||||
const dataLengthChanged = prevLength !== this._rows.length;
|
||||
|
||||
const range = this._viewport.getRenderedRange();
|
||||
|
||||
if (dataLengthChanged) {
|
||||
// Force recalculate new range
|
||||
if (range.start === 0) {
|
||||
range.start = 1;
|
||||
}
|
||||
this._viewport.appendOnly = false;
|
||||
}
|
||||
|
||||
const scrollOffset = this._viewport.measureScrollOffset();
|
||||
if (scrollOffset > 0) {
|
||||
this._viewport.scrollToOffset(0);
|
||||
}
|
||||
|
||||
this._dataUpdated();
|
||||
this._viewport.appendOnly = true;
|
||||
|
||||
if (!dataLengthChanged) {
|
||||
this._fetchDataFromRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _detectColumns(): number {
|
||||
let columns = this.columns.columns;
|
||||
if (this.columns.breakpoints) {
|
||||
for (const breakpont of Object.keys(this.columns.breakpoints)) {
|
||||
if (this.breakpointObserver.isMatched(breakpont)) {
|
||||
columns = this.columns.breakpoints[breakpont];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
private _init() {
|
||||
this._subscription = new Subscription();
|
||||
this._columns = this._detectColumns();
|
||||
if (this._dataStream) {
|
||||
this._dataStream.complete();
|
||||
}
|
||||
this._dataStream = new BehaviorSubject(this._rows);
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
this._data = [];
|
||||
this._totalElements = 0;
|
||||
this.initialDataLoading = true;
|
||||
this._rows = Array.from<T[]>({length: 100000});
|
||||
this._hasNext = true;
|
||||
this._pendingRange = null;
|
||||
this._fetchingData = false;
|
||||
if (this._fetchSubscription) {
|
||||
this._fetchSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private _columnsChanged(columns: number) {
|
||||
if (this._columns !== columns) {
|
||||
const fetchData = columns > this._columns;
|
||||
this._columns = columns;
|
||||
const rowsLength = this._totalElements ? Math.ceil(this._totalElements / this._columns) : 100000;
|
||||
this._rows = Array.from<T[]>({length: rowsLength});
|
||||
this._dataUpdated();
|
||||
if (fetchData && this._hasNext) {
|
||||
this._fetchDataFromRange(this._viewport.getRenderedRange());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fetchDataFromRange(range: ListRange) {
|
||||
if (this._hasNext) {
|
||||
if (this._fetchingData) {
|
||||
this._pendingRange = range;
|
||||
} else {
|
||||
const endIndex = (range.end + 1) * this._columns;
|
||||
if (endIndex > this._data.length) {
|
||||
const startIndex = this._data.length;
|
||||
const minPageSize = endIndex - startIndex;
|
||||
const maxPageSize = minPageSize * 2;
|
||||
let pageSize = minPageSize;
|
||||
let page = Math.floor(startIndex / pageSize);
|
||||
while (startIndex % pageSize !== 0 && pageSize <= maxPageSize) {
|
||||
if (((page + 1) * pageSize) > endIndex) {
|
||||
break;
|
||||
}
|
||||
pageSize++;
|
||||
page = Math.floor(startIndex / pageSize);
|
||||
}
|
||||
const offset = startIndex % pageSize;
|
||||
this._fetchData(offset, pageSize, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fetchData(offset: number, pageSize: number, page: number) {
|
||||
this._fetchingData = true;
|
||||
this._fetchSubscription = this.fetchFunction(pageSize, page, this.filter).pipe(
|
||||
catchError(() => of(emptyPageData<T>()))
|
||||
).subscribe(
|
||||
(data) => {
|
||||
this._hasNext = data.hasNext;
|
||||
if (data.data.length > offset) {
|
||||
for (let i = offset; i < data.data.length; i++) {
|
||||
this._data.push(data.data[i]);
|
||||
}
|
||||
}
|
||||
this._totalElements = data.totalElements;
|
||||
const rowsLength = this._totalElements ? Math.ceil(this._totalElements / this._columns) : 100000;
|
||||
this._rows = Array.from<T[]>({length: rowsLength});
|
||||
this._dataUpdated();
|
||||
this.initialDataLoading = false;
|
||||
this._fetchingData = false;
|
||||
if (this._pendingRange) {
|
||||
const range = this._pendingRange;
|
||||
this._pendingRange = null;
|
||||
this._fetchDataFromRange(range);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _dataUpdated() {
|
||||
for (let index = 0; index < this._data.length; index++) {
|
||||
const row = Math.floor(index / this._columns);
|
||||
const col = index % this._columns;
|
||||
if (!this._rows[row]) {
|
||||
this._rows[row] = [];
|
||||
}
|
||||
this._rows[row][col] = this._data[index];
|
||||
}
|
||||
this._fillGridCells();
|
||||
this._dataStream.next(this._rows);
|
||||
}
|
||||
|
||||
private _fillGridCells() {
|
||||
if (this._totalElements) {
|
||||
const startIndex = this._data.length;
|
||||
const endIndex = this._rows.length * this._columns;
|
||||
for (let index = startIndex; index < endIndex; index++) {
|
||||
const row = Math.floor(index / this._columns);
|
||||
const col = index % this._columns;
|
||||
const cellType: GridCellType = index < this._totalElements ? 'loadingCell' : 'emptyCell';
|
||||
if (!this._rows[row]) {
|
||||
this._rows[row] = [];
|
||||
}
|
||||
this._rows[row][col] = cellType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -516,6 +516,7 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri
|
||||
typeLatestDataKeySettingsSchema?: string | any;
|
||||
image?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
componentType?: Type<IDynamicWidgetComponent>;
|
||||
componentModuleRef?: NgModuleRef<DynamicComponentModule>;
|
||||
}
|
||||
@ -632,6 +633,7 @@ export const detailsToWidgetInfo = (widgetTypeDetailsEntity: WidgetTypeDetails):
|
||||
const widgetInfo = toWidgetInfo(widgetTypeDetailsEntity);
|
||||
widgetInfo.image = widgetTypeDetailsEntity.image;
|
||||
widgetInfo.description = widgetTypeDetailsEntity.description;
|
||||
widgetInfo.tags = widgetTypeDetailsEntity.tags;
|
||||
return widgetInfo;
|
||||
};
|
||||
|
||||
@ -672,6 +674,7 @@ export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, te
|
||||
return {
|
||||
...widgetTypeEntity,
|
||||
description: widgetInfo.description,
|
||||
tags: widgetInfo.tags,
|
||||
image: widgetInfo.image
|
||||
};
|
||||
};
|
||||
|
||||
@ -253,6 +253,11 @@
|
||||
rows="2" maxlength="255"></textarea>
|
||||
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/255</mat-hint>
|
||||
</mat-form-field>
|
||||
<tb-string-items-list
|
||||
label="{{ 'widget.tags' | translate }}"
|
||||
[(ngModel)]="widget.tags"
|
||||
(ngModelChange)="isDirty = true">
|
||||
</tb-string-items-list>
|
||||
<mat-slide-toggle class="mat-block" style="padding-bottom: 16px;"
|
||||
[(ngModel)]="widget.deprecated"
|
||||
(ngModelChange)="isDirty = true">
|
||||
|
||||
@ -58,7 +58,7 @@ export class WidgetsBundleWidgetsResolver implements Resolve<Array<WidgetTypeInf
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<Array<WidgetTypeInfo>> {
|
||||
const widgetsBundleId = route.params.widgetsBundleId;
|
||||
return this.widgetsService.getBundleWidgetTypeInfos(widgetsBundleId);
|
||||
return this.widgetsService.getBundleWidgetTypeInfosList(widgetsBundleId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,10 @@
|
||||
<textarea matInput formControlName="description" rows="2" maxlength="1024" #descriptionInput></textarea>
|
||||
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/1024</mat-hint>
|
||||
</mat-form-field>
|
||||
<tb-string-items-list
|
||||
label="{{ 'widget.tags' | translate }}"
|
||||
formControlName="tags">
|
||||
</tb-string-items-list>
|
||||
<mat-slide-toggle formControlName="deprecated">
|
||||
{{ 'widget.deprecated' | translate }}
|
||||
</mat-slide-toggle>
|
||||
|
||||
@ -52,6 +52,7 @@ export class WidgetTypeComponent extends EntityComponent<WidgetTypeDetails> {
|
||||
name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
|
||||
image: [entity ? entity.image : ''],
|
||||
description: [entity ? entity.description : '', Validators.maxLength(1024)],
|
||||
tags: [entity ? entity.tags : []],
|
||||
deprecated: [entity ? entity.deprecated : false]
|
||||
}
|
||||
);
|
||||
@ -62,6 +63,7 @@ export class WidgetTypeComponent extends EntityComponent<WidgetTypeDetails> {
|
||||
name: entity.name,
|
||||
image: entity.image,
|
||||
description: entity.description,
|
||||
tags: entity.tags,
|
||||
deprecated: entity.deprecated
|
||||
});
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ export class WidgetsBundleWidgetsComponent extends PageComponent implements OnIn
|
||||
|
||||
cancel() {
|
||||
if (this.isDirty) {
|
||||
this.widgetsService.getBundleWidgetTypeInfos(this.widgetsBundle.id.id).subscribe(
|
||||
this.widgetsService.getBundleWidgetTypeInfosList(this.widgetsBundle.id.id).subscribe(
|
||||
(widgets) => {
|
||||
this.widgets = [...widgets];
|
||||
this.isDirty = false;
|
||||
|
||||
@ -232,12 +232,20 @@ export interface WidgetType extends BaseWidgetType {
|
||||
export interface WidgetTypeInfo extends BaseWidgetType {
|
||||
image: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
widgetType: widgetType;
|
||||
}
|
||||
|
||||
export interface WidgetTypeDetails extends WidgetType, ExportableEntity<WidgetTypeId> {
|
||||
image: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export enum DeprecatedFilter {
|
||||
ALL = 'ALL',
|
||||
ACTUAL = 'ACTUAL',
|
||||
DEPRECATED = 'DEPRECATED'
|
||||
}
|
||||
|
||||
export enum LegendDirection {
|
||||
|
||||
@ -4789,6 +4789,7 @@
|
||||
"latest-datakey-settings-schema": "Latest data key settings schema",
|
||||
"widget-settings": "Widget settings",
|
||||
"description": "Description",
|
||||
"tags": "Tags",
|
||||
"image-preview": "Image preview",
|
||||
"settings-form-selector": "Settings form selector",
|
||||
"data-key-settings-form-selector": "Data key settings form selector",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user