feat(api): service tree search by keywords (#471)

This commit is contained in:
pycook 2024-04-15 20:04:56 +08:00 committed by GitHub
parent 32529fba9b
commit 3626b1a97e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 106 additions and 13 deletions

View File

@ -8,6 +8,8 @@ from flask import current_app
from flask_login import current_user from flask_login import current_user
from api.extensions import rd from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.ci_type import CITypeRelationManager from api.lib.cmdb.ci_type import CITypeRelationManager
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
@ -18,6 +20,7 @@ from api.lib.cmdb.perms import CIFilterPermsCRUD
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci.es.search import Search as SearchFromES from api.lib.cmdb.search.ci.es.search import Search as SearchFromES
from api.lib.cmdb.utils import TableMap
from api.lib.perm.acl.acl import ACLManager from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import is_app_admin from api.lib.perm.acl.acl import is_app_admin
from api.models.cmdb import CI from api.models.cmdb import CI
@ -236,7 +239,7 @@ class Search(object):
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2]))) type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
_tmp = [] _tmp, tmp_res = [], []
level2ids = {} level2ids = {}
for lv in range(1, self.level + 1): for lv in range(1, self.level + 1):
level2ids[lv] = [] level2ids[lv] = []
@ -303,25 +306,26 @@ class Search(object):
if key: if key:
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]] res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
if type_ids and lv == self.level: if type_ids and lv == self.level:
__tmp = [[i for i in x if i[1] in type_ids and tmp_res = [[i for i in x if i[1] in type_ids and
(not id_filter_limit or ( (not id_filter_limit or (
key[idx] not in id_filter_limit or key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)] int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
else: else:
__tmp = [[i for i in x if (not id_filter_limit or ( tmp_res = [[i for i in x if (not id_filter_limit or (
key[idx] not in id_filter_limit or key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)] int(i[0]) in id_filter_limit)] for idx, x in
enumerate(res)]
if ci_filter_limit: if ci_filter_limit:
__tmp = [[j for j in i if j[1] not in ci_filter_limit or tmp_res = [[j for j in i if j[1] not in ci_filter_limit or
int(j[0]) in ci_filter_limit[j[1]]] for i in __tmp] int(j[0]) in ci_filter_limit[j[1]]] for i in tmp_res]
else: else:
__tmp = [] tmp_res = []
if __tmp: if tmp_res:
_tmp[idx] = [j for i in __tmp for j in i] _tmp[idx] = [j for i in tmp_res for j in i]
else: else:
_tmp[idx] = [] _tmp[idx] = []
level2ids[lv].append([]) level2ids[lv].append([])
@ -332,3 +336,71 @@ class Search(object):
detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)}) detail={str(_id): dict(Counter([i[1] for i in _tmp[idx]]).items()) for idx, _id in enumerate(ids)})
return result return result
def search_full(self, type_ids):
def _get_id2name(_type_id):
ci_type = CITypeCache.get(_type_id)
attr = AttributeCache.get(ci_type.show_id) or AttributeCache.get(ci_type.unique_id)
value_table = TableMap(attr=attr).table
return {i.ci_id: i.value for i in value_table.get_by(attr_id=attr.id, to_dict=False)}
self.level = int(self.level)
acl = ACLManager('cmdb')
type2filter_perms = dict()
if not self.is_app_admin:
res2 = acl.get_resources(ResourceTypeEnum.CI_FILTER)
if res2:
type2filter_perms = CIFilterPermsCRUD().get_by_ids(list(map(int, [i['name'] for i in res2])))
ids = [self.root_id] if not isinstance(self.root_id, list) else self.root_id
level_ids = [str(i) for i in ids]
result = []
id2children = {}
id2name = _get_id2name(type_ids[0])
for i in level_ids:
item = dict(id=int(i),
type_id=type_ids[0],
isLeaf=False,
title=id2name.get(int(i)),
children=[])
result.append(item)
id2children[str(i)] = item['children']
for lv in range(1, self.level):
if len(type_ids or []) >= lv and type2filter_perms.get(type_ids[lv]):
id_filter_limit, _ = self._get_ci_filter(type2filter_perms[type_ids[lv]])
else:
id_filter_limit = {}
if self.has_m2m and lv != 1:
key, prefix = [i for i in level_ids], REDIS_PREFIX_CI_RELATION2
else:
key, prefix = [i.split(',')[-1] for i in level_ids], REDIS_PREFIX_CI_RELATION
res = [json.loads(x).items() for x in [i or '{}' for i in rd.get(key, prefix) or []]]
res = [[i for i in x if (not id_filter_limit or (key[idx] not in id_filter_limit or
int(i[0]) in id_filter_limit[key[idx]]) or
int(i[0]) in id_filter_limit)] for idx, x in enumerate(res)]
_level_ids = []
type_id = type_ids[lv]
id2name = _get_id2name(type_id)
for idx, _id in enumerate(level_ids):
for child_id, _ in (res[idx] or []):
item = dict(id=int(child_id),
type_id=type_id,
isLeaf=True if lv == self.level - 1 else False,
title=id2name.get(int(child_id)),
children=[])
id2children[_id.split(',')[-1]].append(item)
id2children[child_id] = item['children']
_level_ids.append("{},{}".format(_id, child_id))
level_ids = _level_ids
return result

View File

@ -30,6 +30,7 @@ class CIRelationSearchView(APIView):
level: default is 1 level: default is 1
facet: statistic facet: statistic
""" """
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
count = get_page_size(request.values.get("count") or request.values.get("page_size")) count = get_page_size(request.values.get("count") or request.values.get("page_size"))
@ -86,6 +87,26 @@ class CIRelationStatisticsView(APIView):
return self.jsonify(result) return self.jsonify(result)
class CIRelationSearchFullView(APIView):
url_prefix = "/ci_relations/search/full"
def get(self):
root_ids = list(map(int, handle_arg_list(request.values.get('root_ids'))))
level = request.values.get('level', 1)
type_ids = list(map(int, handle_arg_list(request.values.get('type_ids', []))))
has_m2m = request.values.get("has_m2m") in current_app.config.get('BOOL_TRUE')
start = time.time()
s = Search(root_ids, level, has_m2m=has_m2m)
try:
result = s.search_full(type_ids)
except SearchError as e:
return abort(400, str(e))
current_app.logger.debug("search time is :{0}".format(time.time() - start))
return self.jsonify(result)
class GetSecondCIsView(APIView): class GetSecondCIsView(APIView):
url_prefix = "/ci_relations/<int:first_ci_id>/second_cis" url_prefix = "/ci_relations/<int:first_ci_id>/second_cis"