diff --git a/CHANGES.md b/CHANGES.md index 317684fad16..4f05a4b88ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Apollo 1.10.0 * [Fix issue: ingress syntax](https://github.com/apolloconfig/apollo/pull/3933) * [refactor: let open api more easier to use and development](https://github.com/apolloconfig/apollo/pull/3943) * [feat(scripts): use bash to call openapi](https://github.com/apolloconfig/apollo/pull/3980) +* [Support search by item](https://github.com/apolloconfig/apollo/pull/3977) ------------------ All issues and pull requests are [here](https://github.com/ctripcorp/apollo/milestone/8?closed=1) diff --git a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java index 70c44779730..2823ce5c082 100644 --- a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java +++ b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java @@ -19,9 +19,13 @@ import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.service.NamespaceService; import com.ctrip.framework.apollo.common.dto.NamespaceDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.utils.BeanUtils; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -87,6 +91,18 @@ public NamespaceDTO get(@PathVariable("namespaceId") Long namespaceId) { return BeanUtils.transform(NamespaceDTO.class, namespace); } + /** + * the returned content's size is not fixed. so please carefully used. + */ + @GetMapping("/namespaces/find-by-item") + public PageDTO<NamespaceDTO> findByItem(@RequestParam String itemKey, Pageable pageable) { + Page<Namespace> namespacePage = namespaceService.findByItem(itemKey, pageable); + + List<NamespaceDTO> namespaceDTOS = BeanUtils.batchTransform(NamespaceDTO.class, namespacePage.getContent()); + + return new PageDTO<>(namespaceDTOS, pageable, namespacePage.getTotalElements()); + } + @GetMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}") public NamespaceDTO get(@PathVariable("appId") String appId, @PathVariable("clusterName") String clusterName, diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java index 572569e2e7c..f8ff6894139 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java @@ -18,6 +18,8 @@ import com.ctrip.framework.apollo.biz.entity.Item; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; @@ -35,6 +37,8 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> { List<Item> findByNamespaceIdAndDataChangeLastModifiedTimeGreaterThan(Long namespaceId, Date date); + Page<Item> findByKey(String key, Pageable pageable); + Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId); @Modifying diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java index 29755bb636d..331d8cf31ef 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java @@ -24,6 +24,7 @@ import org.springframework.data.repository.PagingAndSortingRepository; import java.util.List; +import java.util.Set; public interface NamespaceRepository extends PagingAndSortingRepository<Namespace, Long> { @@ -39,6 +40,8 @@ public interface NamespaceRepository extends PagingAndSortingRepository<Namespac List<Namespace> findByNamespaceName(String namespaceName, Pageable page); + List<Namespace> findByIdIn(Set<Long> namespaceIds); + int countByNamespaceNameAndAppIdNot(String namespaceName, String appId); } diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java index 127f14e0c4d..39ade331f0c 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java @@ -27,6 +27,8 @@ import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.core.utils.StringUtils; import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -138,6 +140,10 @@ public List<Item> findItemsModifiedAfterDate(long namespaceId, Date date) { return itemRepository.findByNamespaceIdAndDataChangeLastModifiedTimeGreaterThan(namespaceId, date); } + public Page<Item> findItemsByKey(String key, Pageable pageable) { + return itemRepository.findByKey(key, pageable); + } + @Transactional public Item save(Item entity) { checkItemKeyLength(entity.getKey()); diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java index ba1f8e1f685..54e5d805799 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/NamespaceService.java @@ -35,6 +35,8 @@ import com.google.common.collect.Maps; import com.google.gson.Gson; import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -104,6 +106,21 @@ public Namespace findOne(String appId, String clusterName, String namespaceName) namespaceName); } + /** + * the returned content's size is not fixed. so please carefully used. + */ + public Page<Namespace> findByItem(String itemKey, Pageable pageable) { + Page<Item> items = itemService.findItemsByKey(itemKey, pageable); + + if (!items.hasContent()) { + return Page.empty(); + } + + Set<Long> namespaceIds = BeanUtils.toPropertySet("namespaceId", items.getContent()); + + return new PageImpl<>(namespaceRepository.findByIdIn(namespaceIds)); + } + public Namespace findPublicNamespaceForAssociatedNamespace(String clusterName, String namespaceName) { AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespaceName); if (appNamespace == null) { diff --git a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/StringUtilsTest.java b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/StringUtilsTest.java index 9f366d6dc3b..5bcd7748c56 100644 --- a/apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/StringUtilsTest.java +++ b/apollo-core/src/test/java/com/ctrip/framework/apollo/core/utils/StringUtilsTest.java @@ -16,7 +16,6 @@ */ package com.ctrip.framework.apollo.core.utils; -import com.ctrip.framework.apollo.core.utils.StringUtils; import org.junit.Assert; import org.junit.Test; diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java index b149501dba0..3409162a333 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java @@ -68,6 +68,10 @@ public void deleteApp(Env env, String appId, String operator) { @Service public static class NamespaceAPI extends API { + private ParameterizedTypeReference<PageDTO<NamespaceDTO>> + namespacePageDTO = new ParameterizedTypeReference<PageDTO<NamespaceDTO>>() { + }; + private ParameterizedTypeReference<Map<String, Boolean>> typeReference = new ParameterizedTypeReference<Map<String, Boolean>>() { }; @@ -79,6 +83,14 @@ public List<NamespaceDTO> findNamespaceByCluster(String appId, Env env, String c return Arrays.asList(namespaceDTOs); } + public PageDTO<NamespaceDTO> findByItem(Env env, String itemKey, int page, int size) { + ResponseEntity<PageDTO<NamespaceDTO>> + entity = + restTemplate.get(env, "/namespaces/find-by-item?itemKey={itemKey}&page={page}&size={size}", + namespacePageDTO, itemKey, page, size); + return entity.getBody(); + } + public NamespaceDTO loadNamespace(String appId, Env env, String clusterName, String namespaceName) { return diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java index 251c001fed1..c1162892565 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java @@ -270,4 +270,7 @@ public String[] webHookUrls() { return getArrayProperty("config.release.webhook.service.url", null); } + public boolean supportSearchByItem() { + return getBooleanProperty("searchByItem.switch", true); + } } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java index 09b4cf67030..421a78491f3 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java @@ -18,7 +18,6 @@ import com.ctrip.framework.apollo.common.dto.AppDTO; -import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.entity.App; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.http.MultiResponseEntity; @@ -103,15 +102,6 @@ public List<App> findApps(@RequestParam(value = "appIds", required = false) Stri return appService.findByAppIds(Sets.newHashSet(appIds.split(","))); } - @GetMapping("/search/by-appid-or-name") - public PageDTO<App> searchByAppIdOrAppName(@RequestParam(value = "query", required = false) String query, - Pageable pageable) { - if (Strings.isNullOrEmpty(query)) { - return appService.findAll(pageable); - } - return appService.searchByAppIdOrAppName(query, pageable); - } - @GetMapping("/by-owner") public List<App> findAppsByOwner(@RequestParam("owner") String owner, Pageable page) { Set<String> appIds = Sets.newHashSet(); @@ -184,7 +174,7 @@ public MultiResponseEntity<EnvClusterInfo> nav(@PathVariable String appId) { public ResponseEntity<Void> create(@PathVariable String env, @Valid @RequestBody App app) { appService.createAppInRemote(Env.valueOf(env), app); - roleInitializationService.initNamespaceSpecificEnvRoles(app.getAppId(), ConfigConsts.NAMESPACE_APPLICATION, + roleInitializationService.initNamespaceSpecificEnvRoles(app.getAppId(), ConfigConsts.NAMESPACE_APPLICATION, env, userInfoHolder.getUser().getUserId()); return ResponseEntity.ok().build(); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SearchController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SearchController.java new file mode 100644 index 00000000000..6d216cb7d30 --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SearchController.java @@ -0,0 +1,122 @@ +/* + * Copyright 2021 Apollo 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 com.ctrip.framework.apollo.portal.controller; + +import com.google.common.collect.Lists; + +import com.ctrip.framework.apollo.common.dto.NamespaceDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; +import com.ctrip.framework.apollo.common.entity.App; +import com.ctrip.framework.apollo.portal.component.PortalSettings; +import com.ctrip.framework.apollo.portal.component.config.PortalConfig; +import com.ctrip.framework.apollo.portal.environment.Env; +import com.ctrip.framework.apollo.portal.service.AppService; +import com.ctrip.framework.apollo.portal.service.NamespaceService; + +import org.springframework.data.domain.Pageable; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author lepdou 2021-09-13 + */ +@RestController("/app") +public class SearchController { + + private AppService appService; + private PortalSettings portalSettings; + private NamespaceService namespaceService; + private PortalConfig portalConfig; + + public SearchController(final AppService appService, + final PortalSettings portalSettings, + final PortalConfig portalConfig, + final NamespaceService namespaceService) { + this.appService = appService; + this.portalConfig = portalConfig; + this.portalSettings = portalSettings; + this.namespaceService = namespaceService; + } + + @GetMapping("/apps/search/by-appid-or-name") + public PageDTO<App> search(@RequestParam(value = "query", required = false) String query, Pageable pageable) { + if (StringUtils.isEmpty(query)) { + return appService.findAll(pageable); + } + + //search app + PageDTO<App> appPageDTO = appService.searchByAppIdOrAppName(query, pageable); + if (appPageDTO.hasContent()) { + return appPageDTO; + } + + if (!portalConfig.supportSearchByItem()) { + return new PageDTO<>(Lists.newLinkedList(), pageable, 0); + } + + //search item + return searchByItem(query, pageable); + } + + private PageDTO<App> searchByItem(String itemKey, Pageable pageable) { + List<App> result = Lists.newLinkedList(); + + if (StringUtils.isEmpty(itemKey)) { + return new PageDTO<>(result, pageable, 0); + } + + //use the env witch has the most namespace as page index. + final AtomicLong maxTotal = new AtomicLong(0); + + List<Env> activeEnvs = portalSettings.getActiveEnvs(); + + activeEnvs.forEach(env -> { + PageDTO<NamespaceDTO> namespacePage = namespaceService.findNamespacesByItem(env, itemKey, pageable); + if (!namespacePage.hasContent()) { + return; + } + + long currentEnvNSTotal = namespacePage.getTotal(); + if (currentEnvNSTotal > maxTotal.get()) { + maxTotal.set(namespacePage.getTotal()); + } + + List<NamespaceDTO> namespaceDTOS = namespacePage.getContent(); + + namespaceDTOS.forEach(namespaceDTO -> { + String cluster = namespaceDTO.getClusterName(); + String namespaceName = namespaceDTO.getNamespaceName(); + + App app = new App(); + app.setAppId(namespaceDTO.getAppId()); + app.setName(env.getName() + " / " + cluster + " / " + namespaceName); + app.setOrgId(env.getName() + "+" + cluster + "+" + namespaceName); + app.setOrgName("SearchByItem" + "+" + itemKey); + + result.add(app); + }); + }); + + return new PageDTO<>(result, pageable, maxTotal.get()); + } + +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java index 34632a28184..ae6bb8eea9d 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java @@ -36,7 +36,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; import java.util.List; import java.util.Set; diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java index eb227651d3e..bf6616265bb 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java @@ -19,6 +19,7 @@ import com.ctrip.framework.apollo.common.constants.GsonType; import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.dto.NamespaceDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.exception.BadRequestException; @@ -48,6 +49,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -181,6 +183,13 @@ public List<NamespaceDTO> findNamespaces(String appId, Env env, String clusterNa return namespaceAPI.findNamespaceByCluster(appId, env, clusterName); } + /** + * the returned content's size is not fixed. so please carefully used. + */ + public PageDTO<NamespaceDTO> findNamespacesByItem(Env env, String itemKey, Pageable pageable) { + return namespaceAPI.findByItem(env, itemKey, pageable.getPageNumber(), pageable.getPageSize()); + } + public List<NamespaceDTO> getPublicAppNamespaceAllNamespaces(Env env, String publicNamespaceName, int page, int size) { diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index 08dafbbf805..f3b9ea60aaf 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -729,7 +729,7 @@ "Valdr.Release.ReleaseName.Required": "Release Name cannot be empty", "Valdr.Release.Comment.Size": "Comment length should not exceed 256 characters", "ApolloConfirmDialog.DefaultConfirmBtnName": "OK", - "ApolloConfirmDialog.SearchPlaceHolder": "Search items (App Id, App Name)", + "ApolloConfirmDialog.SearchPlaceHolder": "Search Apps by appId, appName, configuration key", "RulesModal.ChooseInstances": "Select from the list of instances", "RulesModal.InvalidIp": "Illegal IP Address: '{{ip}}'", "RulesModal.GrayscaleAppIdCanNotBeNull": "Grayscale AppId cannot be empty", @@ -764,4 +764,4 @@ "Rollback.RollbackFailed": "Failed to Rollback", "Revoke.RevokeFailed": "Failed to Revoke", "Revoke.RevokeSuccessfully": "Revoke Successfully" -} \ No newline at end of file +} diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json index a284b6d036a..9fdb8573737 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -729,7 +729,7 @@ "Valdr.Release.ReleaseName.Required": "Release Name不能为空", "Valdr.Release.Comment.Size": "备注长度不能多于256个字符", "ApolloConfirmDialog.DefaultConfirmBtnName": "确认", - "ApolloConfirmDialog.SearchPlaceHolder": "搜索应用(AppId、应用名)", + "ApolloConfirmDialog.SearchPlaceHolder": "搜索应用Id、应用名、配置项Key", "RulesModal.ChooseInstances": "从实例列表中选择", "RulesModal.InvalidIp": "不合法的IP地址: '{{ip}}'", "RulesModal.GrayscaleAppIdCanNotBeNull": "灰度的AppId不能为空", diff --git a/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigBaseInfoController.js b/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigBaseInfoController.js index a5008d92737..213a480ffd2 100644 --- a/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigBaseInfoController.js +++ b/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigBaseInfoController.js @@ -45,7 +45,9 @@ function ConfigBaseInfoController($rootScope, $scope, $window, $location, $trans $rootScope.pageContext = { appId: appId, env: urlParams.env ? urlParams.env : (scene ? scene.env : ''), - clusterName: urlParams.cluster ? urlParams.cluster : (scene ? scene.cluster : 'default') + clusterName: urlParams.cluster ? urlParams.cluster : (scene ? scene.cluster : 'default'), + namespaceName: urlParams.namespace, + item: urlParams.item, }; //storage page context to session storage @@ -201,7 +203,7 @@ function ConfigBaseInfoController($rootScope, $scope, $window, $location, $trans $rootScope.pageContext.env = nodes[0].env; } - EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE); + EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE, {firstLoad: true}); nodes.forEach(function (env) { if (!env.clusters || env.clusters.length == 0) { diff --git a/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigNamespaceController.js b/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigNamespaceController.js index 99825eb1b25..06c6f0f9c82 100644 --- a/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigNamespaceController.js +++ b/apollo-portal/src/main/resources/static/scripts/controller/config/ConfigNamespaceController.js @@ -107,12 +107,12 @@ function controller($rootScope, $scope, $translate, toastr, AppUtil, EventManage if (context.namespace) { refreshSingleNamespace(context.namespace); } else { - refreshAllNamespaces(); + refreshAllNamespaces(context); } }); - function refreshAllNamespaces() { + function refreshAllNamespaces(context) { if ($rootScope.pageContext.env == '') { return; } @@ -126,6 +126,19 @@ function controller($rootScope, $scope, $translate, toastr, AppUtil, EventManage $('.config-item-container').removeClass('hide'); initPublishInfo(); + //If there is a namespace parameter in the URL, expand the corresponding namespace directly + if (context && context.firstLoad && $rootScope.pageContext.namespaceName) { + refreshSingleNamespace({ + baseInfo: { + namespaceName: $rootScope.pageContext.namespaceName + }, + searchInfo: { + showSearchInput: true, + searchItemKey: $rootScope.pageContext.item, + } + + }); + } }, function (result) { toastr.error(AppUtil.errorMsg(result), $translate.instant('Config.LoadingAllNamespaceError')); }); @@ -136,6 +149,9 @@ function controller($rootScope, $scope, $translate, toastr, AppUtil, EventManage return; } + const showSearchItemInput = namespace.searchInfo ? namespace.searchInfo.showSearchInput : false; + const searchItemKey = namespace.searchInfo ? namespace.searchInfo.searchItemKey : ''; + ConfigService.load_namespace($rootScope.pageContext.appId, $rootScope.pageContext.env, $rootScope.pageContext.clusterName, @@ -143,11 +159,13 @@ function controller($rootScope, $scope, $translate, toastr, AppUtil, EventManage function (result) { $scope.namespaces.forEach(function (namespace, index) { - if (namespace.baseInfo.namespaceName == result.baseInfo.namespaceName) { + if (namespace.baseInfo.namespaceName === result.baseInfo.namespaceName) { result.showNamespaceBody = true; result.initialized = true; result.show = namespace.show; $scope.namespaces[index] = result; + result.showSearchItemInput = showSearchItemInput; + result.searchItemKey = searchItemKey; } }); diff --git a/apollo-portal/src/main/resources/static/scripts/directive/directive.js b/apollo-portal/src/main/resources/static/scripts/directive/directive.js index f95e68bebe6..48dee6c94bd 100644 --- a/apollo-portal/src/main/resources/static/scripts/directive/directive.js +++ b/apollo-portal/src/main/resources/static/scripts/directive/directive.js @@ -56,7 +56,9 @@ directive_module.directive('apollonav', data.content.forEach(function (app) { result.push({ id: app.appId, - text: app.appId + ' / ' + app.name + text: app.appId + ' / ' + app.name, + orgId: app.orgId, + orgName: app.orgName }) }); return { @@ -81,17 +83,40 @@ directive_module.directive('apollonav', $('#app-search-list').on('select2:select', function () { var selected = $('#app-search-list').select2('data'); if (selected && selected.length) { - jumpToConfigPage(selected[0].id) + jumpToConfigPage(selected[0].id, selected[0].name, selected[0].orgName, selected[0].orgId) } }); }); - function jumpToConfigPage(selectedAppId) { + function jumpToConfigPage(selectedAppId, name, type, namespaceInfo) { if ($window.location.href.indexOf("config.html") > -1) { - $window.location.hash = "appid=" + selectedAppId; + if (type && type.startsWith('SearchByItem')) { + var namespaceInfos = namespaceInfo.split('+'); + var env = namespaceInfos[0]; + var cluster = namespaceInfos[1]; + var namespaceName = namespaceInfos[2]; + var searchKey = type.split("+")[1]; + $window.location.hash = + "appid=" + selectedAppId + "&env=" + env + "&cluster=" + cluster + "&namespace=" + + namespaceName + "&item=" + searchKey; + } else { + $window.location.hash = "appid=" + selectedAppId; + } + $window.location.reload(); } else { - $window.location.href = AppUtil.prefixPath() + '/config.html?#appid=' + selectedAppId; + if (type && type.startsWith('SearchByItem')) { + var namespaceInfos = namespaceInfo.split('+'); + var env = namespaceInfos[0]; + var cluster = namespaceInfos[1]; + var namespaceName = namespaceInfos[2]; + var searchKey = type.split("+")[1]; + $window.location.href = + AppUtil.prefixPath() + '/config.html?#appid=' + selectedAppId + "&env=" + env + + "&cluster=" + cluster + "&namespace=" + namespaceName + "&item=" + searchKey; + } else { + $window.location.href = AppUtil.prefixPath() + '/config.html?#appid=' + selectedAppId; + } } }; diff --git a/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js b/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js index 59935e8a777..47d4f632809 100644 --- a/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js +++ b/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js @@ -66,6 +66,7 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio scope.toggleItemSearchInput = toggleItemSearchInput; scope.toggleHistorySearchInput = toggleHistorySearchInput; scope.searchItems = searchItems; + scope.resetSearchItems = resetSearchItems; scope.searchHistory = searchHistory; scope.loadCommitHistory = loadCommitHistory; scope.toggleTextEditStatus = toggleTextEditStatus; @@ -130,7 +131,8 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio namespace.isBranch = false; namespace.displayControl = { currentOperateBranch: 'master', - showSearchInput: false, + showSearchInput: namespace.showSearchItemInput, + searchItemKey: namespace.searchItemKey, showHistorySearchInput: false, show: scope.showBody }; @@ -153,6 +155,7 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio initPermission(namespace); initLinkedNamespace(namespace); loadInstanceInfo(namespace); + initSearchItemInput(namespace); function initNamespaceBranch(namespace) { NamespaceBranchService.findNamespaceBranch(scope.appId, scope.env, @@ -398,6 +401,12 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio } + function initSearchItemInput(namespace) { + if (namespace.displayControl.searchItemKey) { + namespace.searchKey = namespace.displayControl.searchItemKey; + searchItems(namespace); + } + } } function initNamespaceInstancesCount(namespace) { @@ -863,6 +872,11 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio namespace.viewItems = items; } + function resetSearchItems(namespace) { + namespace.searchKey = ''; + searchItems(namespace); + } + function toggleHistorySearchInput(namespace) { namespace.displayControl.showHistorySearchInput = !namespace.displayControl.showHistorySearchInput; } diff --git a/apollo-portal/src/main/resources/static/styles/common-style.css b/apollo-portal/src/main/resources/static/styles/common-style.css index f7b20df41aa..99161f733af 100644 --- a/apollo-portal/src/main/resources/static/styles/common-style.css +++ b/apollo-portal/src/main/resources/static/styles/common-style.css @@ -451,7 +451,7 @@ table th { } .namespace-panel .namespace-view-table .search-input input { - width: 350px; + width: 500px; } .namespace-panel .no-config-panel { @@ -836,4 +836,4 @@ table th { .app-search-list .select2-container .select2-selection .select2-selection__rendered { line-height: 34px; font-size: 14px; -} \ No newline at end of file +} diff --git a/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html b/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html index 5d374db50c3..08424c8d4b1 100644 --- a/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html +++ b/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html @@ -253,9 +253,14 @@ <!--not link namespace--> <div ng-if="!namespace.isLinkedNamespace"> <div class="search-input" ng-show="namespace.displayControl.showSearchInput"> - <input type="text" class="form-control" - placeholder="{{'Component.Namespace.Master.Items.Body.FilterByKey' | translate }}" - ng-model="namespace.searchKey" ng-change="searchItems(namespace)"> + <form class="form-inline"> + <div class="form-group"> + <input type="text" class="form-control" id="searchItemKeyInput" + placeholder="{{'Component.Namespace.Master.Items.Body.FilterByKey' | translate }}" + ng-model="namespace.searchKey" ng-change="searchItems(namespace)"> + </div> + <button type="button" class="btn btn-default" ng-click="resetSearchItems(namespace)">{{'Config.Sync.Rest' | translate }}</button> + </form> </div> <table class="table table-bordered table-striped table-hover"> @@ -1140,4 +1145,4 @@ <h5>{{'Component.Namespace.Master.Items.Body.NoPublished.Title' | translate }}</ </section> </div> -</section> \ No newline at end of file +</section> diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/SearchControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/SearchControllerTest.java new file mode 100644 index 00000000000..77b4a181e21 --- /dev/null +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/SearchControllerTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2021 Apollo 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 com.ctrip.framework.apollo.portal.controller; + +import com.google.common.collect.Lists; + +import com.ctrip.framework.apollo.common.dto.NamespaceDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; +import com.ctrip.framework.apollo.common.entity.App; +import com.ctrip.framework.apollo.portal.component.PortalSettings; +import com.ctrip.framework.apollo.portal.component.config.PortalConfig; +import com.ctrip.framework.apollo.portal.environment.Env; +import com.ctrip.framework.apollo.portal.service.AppService; +import com.ctrip.framework.apollo.portal.service.NamespaceService; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +/** + * @author lepdou 2021-09-13 + */ +@RunWith(MockitoJUnitRunner.class) +public class SearchControllerTest { + + @Mock + private AppService appService; + @Mock + private NamespaceService namespaceService; + @Mock + private PortalSettings portalSettings; + @Mock + private PortalConfig portalConfig; + @InjectMocks + private SearchController searchController; + + @Test + public void testSearchByEmptyKey() { + PageRequest request = PageRequest.of(0, 20); + searchController.search("", request); + verify(appService, times(1)).findAll(request); + } + + @Test + public void testSearchApp() { + String query = "timeout"; + PageRequest request = PageRequest.of(0, 20); + + PageDTO<App> apps = genPageApp(10, request, 100); + + when(appService.searchByAppIdOrAppName(query, request)).thenReturn(apps); + + searchController.search(query, request); + + verify(appService, times(0)).findAll(request); + verify(appService, times(1)).searchByAppIdOrAppName(query, request); + } + + @Test + public void testSearchItemSwitch() { + String query = "timeout"; + PageRequest request = PageRequest.of(0, 20); + + PageDTO<App> apps = new PageDTO<>(Lists.newLinkedList(), request, 0); + + when(appService.searchByAppIdOrAppName(query, request)).thenReturn(apps); + when(portalConfig.supportSearchByItem()).thenReturn(false); + + PageDTO<App> result = searchController.search(query, request); + + Assert.assertFalse(result.hasContent()); + verify(appService, times(0)).findAll(request); + verify(appService, times(1)).searchByAppIdOrAppName(query, request); + } + + @Test + public void testSearchItem() { + String query = "timeout"; + PageRequest request = PageRequest.of(0, 20); + + PageDTO<App> apps = new PageDTO<>(Lists.newLinkedList(), request, 0); + PageDTO<NamespaceDTO> devNamespaces = genPageNamespace(10, request, 20); + PageDTO<NamespaceDTO> fatNamespaces = genPageNamespace(15, request, 30); + + when(appService.searchByAppIdOrAppName(query, request)).thenReturn(apps); + when(portalConfig.supportSearchByItem()).thenReturn(true); + when(portalSettings.getActiveEnvs()).thenReturn(Lists.newArrayList(Env.DEV, Env.FAT)); + when(namespaceService.findNamespacesByItem(Env.DEV, query, request)).thenReturn(devNamespaces); + when(namespaceService.findNamespacesByItem(Env.FAT, query, request)).thenReturn(fatNamespaces); + + PageDTO<App> result = searchController.search(query, request); + + Assert.assertTrue(result.hasContent()); + Assert.assertEquals(25, result.getContent().size()); + Assert.assertEquals(30, result.getTotal()); + verify(appService, times(0)).findAll(request); + verify(appService, times(1)).searchByAppIdOrAppName(query, request); + verify(namespaceService).findNamespacesByItem(Env.DEV, query, request); + verify(namespaceService).findNamespacesByItem(Env.FAT, query, request); + } + + private PageDTO<App> genPageApp(int size, Pageable pageable, int total) { + List<App> result = Lists.newLinkedList(); + for (int i = 0; i < size; i++) { + App app = new App(); + result.add(app); + } + return new PageDTO<>(result, pageable, total); + } + + private PageDTO<NamespaceDTO> genPageNamespace(int size, Pageable pageable, int total) { + List<NamespaceDTO> result = Lists.newLinkedList(); + for (int i = 0; i < size; i++) { + NamespaceDTO namespaceDTO = new NamespaceDTO(); + result.add(namespaceDTO); + } + return new PageDTO<>(result, pageable, total); + } +} diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md index e228b7b53f3..1c32f786cd3 100644 --- a/docs/zh/deployment/distributed-deployment-guide.md +++ b/docs/zh/deployment/distributed-deployment-guide.md @@ -1152,6 +1152,12 @@ portal上“帮助”链接的地址,默认是Apollo github的wiki首页,可 ### 3.1.12 admin-service.access.tokens - 设置apollo-portal访问各环境apollo-adminservice所需的access token +### 3.1.13 searchByItem.switch - 控制台搜索框是否支持按配置项搜索 + +默认为 true,可以方便的按配置项快速搜索配置 + +如果设置为 false,则关闭此功能 + > 适用于1.7.1及以上版本 如果对应环境的apollo-adminservice开启了[访问控制](#_326-admin-serviceaccesscontrolenabled-配置apollo-adminservice是否开启访问控制),那么需要在此配置apollo-portal访问该环境apollo-adminservice所需的access token,否则会访问失败 diff --git a/scripts/sql/delta/v190-v1_10_0/apolloconfigdb-v190-v1_10_0.sql b/scripts/sql/delta/v190-v1_10_0/apolloconfigdb-v190-v1_10_0.sql new file mode 100644 index 00000000000..1d4fb655af1 --- /dev/null +++ b/scripts/sql/delta/v190-v1_10_0/apolloconfigdb-v190-v1_10_0.sql @@ -0,0 +1,20 @@ +-- +-- Copyright 2021 Apollo 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. +-- +# delta schema to upgrade apollo config db from v1.9.0 to v1.10.0 + +Use ApolloConfigDB; + +ALTER TABLE Item ADD INDEX IX_key (`Key`);