From f355ca4bccad3f74fa53d2a0a687e011a58b8b7a Mon Sep 17 00:00:00 2001 From: pycook Date: Thu, 11 Jan 2024 17:58:33 +0800 Subject: [PATCH] feat(api): My subscription supports CIType sorting --- cmdb-api/api/lib/cmdb/cache.py | 33 +++++++- cmdb-api/api/lib/cmdb/ci_type.py | 11 +-- cmdb-api/api/lib/cmdb/perms.py | 2 + cmdb-api/api/lib/cmdb/preference.py | 106 +++++++++++++++++++++----- cmdb-api/api/views/cmdb/ci_type.py | 8 +- cmdb-api/api/views/cmdb/preference.py | 13 ++++ 6 files changed, 142 insertions(+), 31 deletions(-) diff --git a/cmdb-api/api/lib/cmdb/cache.py b/cmdb-api/api/lib/cmdb/cache.py index 6795b5a..b036c40 100644 --- a/cmdb-api/api/lib/cmdb/cache.py +++ b/cmdb-api/api/lib/cmdb/cache.py @@ -11,6 +11,8 @@ from api.models.cmdb import Attribute from api.models.cmdb import CI from api.models.cmdb import CIType from api.models.cmdb import CITypeAttribute +from api.models.cmdb import PreferenceShowAttributes +from api.models.cmdb import PreferenceTreeView from api.models.cmdb import RelationType @@ -228,8 +230,9 @@ class CITypeAttributeCache(object): class CMDBCounterCache(object): - KEY = 'CMDB::Counter' - KEY2 = 'CMDB::Counter2' + KEY = 'CMDB::Counter::dashboard' + KEY2 = 'CMDB::Counter::adc' + KEY3 = 'CMDB::Counter::sub' @classmethod def get(cls): @@ -450,3 +453,29 @@ class CMDBCounterCache(object): @classmethod def get_adc_counter(cls): return cache.get(cls.KEY2) or cls.flush_adc_counter() + + @classmethod + def flush_sub_counter(cls): + result = dict(type_id2users=dict()) + + types = db.session.query(PreferenceShowAttributes.type_id, + PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter( + PreferenceShowAttributes.deleted.is_(False)).group_by( + PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id) + for i in types: + result['type_id2users'].setdefault(i.type_id, []).append(i.uid) + + types = PreferenceTreeView.get_by(to_dict=False) + for i in types: + + result['type_id2users'].setdefault(i.type_id, []) + if i.uid not in result['type_id2users'][i.type_id]: + result['type_id2users'][i.type_id].append(i.uid) + + cache.set(cls.KEY3, result, timeout=0) + + return result + + @classmethod + def get_sub_counter(cls): + return cache.get(cls.KEY3) or cls.flush_sub_counter() diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index e241da8..6559358 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -1,7 +1,6 @@ # -*- coding:utf-8 -*- import copy - import toposort from flask import abort from flask import current_app @@ -46,6 +45,7 @@ from api.models.cmdb import CITypeRelation from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CustomDashboard +from api.models.cmdb import PreferenceCITypeOrder from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceShowAttributes @@ -75,12 +75,13 @@ class CITypeManager(object): return CIType.get_by_id(ci_type.id) @staticmethod - def get_ci_types(type_name=None): + def get_ci_types(type_name=None, like=True): resources = None if current_app.config.get('USE_ACL') and not is_app_admin('cmdb'): resources = set([i.get('name') for i in ACLManager().get_resources(ResourceTypeEnum.CI_TYPE)]) - ci_types = CIType.get_by() if type_name is None else CIType.get_by_like(name=type_name) + ci_types = CIType.get_by() if type_name is None else ( + CIType.get_by_like(name=type_name) if like else CIType.get_by(name=type_name)) res = list() for type_dict in ci_types: attr = AttributeCache.get(type_dict["unique_id"]) @@ -222,7 +223,7 @@ class CITypeManager(object): for table in [PreferenceTreeView, PreferenceShowAttributes, PreferenceSearchOption, CustomDashboard, CITypeGroupItem, CITypeAttributeGroup, CITypeAttribute, CITypeUniqueConstraint, CITypeTrigger, - AutoDiscoveryCIType, CIFilterPerms]: + AutoDiscoveryCIType, CIFilterPerms, PreferenceCITypeOrder]: for item in table.get_by(type_id=type_id, to_dict=False): item.soft_delete(commit=False) @@ -1274,7 +1275,7 @@ class CITypeTemplateManager(object): from api.lib.common_setting.upload_file import CommonFileCRUD tpt = dict( - ci_types=CITypeManager.get_ci_types(type_name=ci_type.name), + ci_types=CITypeManager.get_ci_types(type_name=ci_type.name, like=False), ci_type_auto_discovery_rules=list(), type2attributes=dict(), type2attribute_group=dict(), diff --git a/cmdb-api/api/lib/cmdb/perms.py b/cmdb-api/api/lib/cmdb/perms.py index f034f37..85bfa5f 100644 --- a/cmdb-api/api/lib/cmdb/perms.py +++ b/cmdb-api/api/lib/cmdb/perms.py @@ -114,6 +114,8 @@ class CIFilterPermsCRUD(DBMixin): obj.soft_delete() + return obj + else: if not kwargs.get('ci_filter') and not kwargs.get('attr_filter'): return diff --git a/cmdb-api/api/lib/cmdb/preference.py b/cmdb-api/api/lib/cmdb/preference.py index e7e06ed..c191710 100644 --- a/cmdb-api/api/lib/cmdb/preference.py +++ b/cmdb-api/api/lib/cmdb/preference.py @@ -2,7 +2,6 @@ import copy - import six import toposort from flask import abort @@ -14,6 +13,7 @@ from api.lib.cmdb.attribute import AttributeManager from api.lib.cmdb.cache import AttributeCache from api.lib.cmdb.cache import CITypeAttributesCache from api.lib.cmdb.cache import CITypeCache +from api.lib.cmdb.cache import CMDBCounterCache from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import ResourceTypeEnum @@ -24,6 +24,7 @@ from api.lib.exception import AbortException from api.lib.perm.acl.acl import ACLManager from api.models.cmdb import CITypeAttribute from api.models.cmdb import CITypeRelation +from api.models.cmdb import PreferenceCITypeOrder from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceShowAttributes @@ -38,13 +39,22 @@ class PreferenceManager(object): @staticmethod def get_types(instance=False, tree=False): + ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order) + types = db.session.query(PreferenceShowAttributes.type_id).filter( PreferenceShowAttributes.uid == current_user.uid).filter( PreferenceShowAttributes.deleted.is_(False)).group_by( PreferenceShowAttributes.type_id).all() if instance else [] + types = sorted(types, key=lambda x: {i.type_id: idx for idx, i in enumerate( + ci_type_order) if not i.is_tree}.get(x.type_id, 1)) tree_types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) if tree else [] - type_ids = set([i.type_id for i in types + tree_types]) + tree_types = sorted(tree_types, key=lambda x: {i.type_id: idx for idx, i in enumerate( + ci_type_order) if i.is_tree}.get(x.type_id, 1)) + + type_ids = [i.type_id for i in types + tree_types] + if types and tree_types: + type_ids = set(type_ids) return [CITypeCache.get(type_id).to_dict() for type_id in type_ids] @@ -59,32 +69,36 @@ class PreferenceManager(object): :param tree: :return: """ - result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict()), - type_id2users=dict()) + result = dict(self=dict(instance=[], tree=[], type_id2subs_time=dict())) + + result.update(CMDBCounterCache.get_sub_counter()) + + ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, to_dict=False), key=lambda x: x.order) if instance: types = db.session.query(PreferenceShowAttributes.type_id, PreferenceShowAttributes.uid, PreferenceShowAttributes.created_at).filter( - PreferenceShowAttributes.deleted.is_(False)).group_by( + PreferenceShowAttributes.deleted.is_(False)).filter( + PreferenceShowAttributes.uid == current_user.uid).group_by( PreferenceShowAttributes.uid, PreferenceShowAttributes.type_id) for i in types: - if i.uid == current_user.uid: - result['self']['instance'].append(i.type_id) - if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): - result['self']['type_id2subs_time'][i.type_id] = i.created_at + result['self']['instance'].append(i.type_id) + if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): + result['self']['type_id2subs_time'][i.type_id] = i.created_at - result['type_id2users'].setdefault(i.type_id, []).append(i.uid) + instance_order = [i.type_id for i in ci_type_order if not i.is_tree] + if len(instance_order) == len(result['self']['instance']): + result['self']['instance'] = instance_order if tree: - types = PreferenceTreeView.get_by(to_dict=False) + types = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=False) for i in types: - if i.uid == current_user.uid: - result['self']['tree'].append(i.type_id) - if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): - result['self']['type_id2subs_time'][i.type_id] = i.created_at + result['self']['tree'].append(i.type_id) + if str(i.created_at) > str(result['self']['type_id2subs_time'].get(i.type_id, "")): + result['self']['type_id2subs_time'][i.type_id] = i.created_at - result['type_id2users'].setdefault(i.type_id, []) - if i.uid not in result['type_id2users'][i.type_id]: - result['type_id2users'][i.type_id].append(i.uid) + tree_order = [i.type_id for i in ci_type_order if i.is_tree] + if len(tree_order) == len(result['self']['tree']): + result['self']['tree'] = tree_order return result @@ -151,9 +165,22 @@ class PreferenceManager(object): if i.attr_id not in attr_dict: i.soft_delete() + if not existed_all and attr_order: + cls.add_ci_type_order_item(type_id, is_tree=False) + + elif not PreferenceShowAttributes.get_by(type_id=type_id, uid=current_user.uid, to_dict=False): + cls.delete_ci_type_order_item(type_id, is_tree=False) + @staticmethod def get_tree_view(): + ci_type_order = sorted(PreferenceCITypeOrder.get_by(uid=current_user.uid, is_tree=True, to_dict=False), + key=lambda x: x.order) + res = PreferenceTreeView.get_by(uid=current_user.uid, to_dict=True) + if ci_type_order: + res = sorted(res, key=lambda x: {ii.type_id: idx for idx, ii in enumerate( + ci_type_order)}.get(x['type_id'], 1)) + for item in res: if item["levels"]: ci_type = CITypeCache.get(item['type_id']).to_dict() @@ -172,8 +199,8 @@ class PreferenceManager(object): return res - @staticmethod - def create_or_update_tree_view(type_id, levels): + @classmethod + def create_or_update_tree_view(cls, type_id, levels): attrs = CITypeAttributesCache.get(type_id) for idx, i in enumerate(levels): for attr in attrs: @@ -185,9 +212,12 @@ class PreferenceManager(object): if existed is not None: if not levels: existed.soft_delete() + cls.delete_ci_type_order_item(type_id, is_tree=True) return existed return existed.update(levels=levels) elif levels: + cls.add_ci_type_order_item(type_id, is_tree=True) + return PreferenceTreeView.create(levels=levels, type_id=type_id, uid=current_user.uid) @staticmethod @@ -356,6 +386,9 @@ class PreferenceManager(object): for i in PreferenceTreeView.get_by(type_id=type_id, uid=uid, to_dict=False): i.soft_delete() + for i in PreferenceCITypeOrder.get_by(type_id=type_id, uid=uid, to_dict=False): + i.soft_delete() + @staticmethod def can_edit_relation(parent_id, child_id): views = PreferenceRelationView.get_by(to_dict=False) @@ -381,3 +414,36 @@ class PreferenceManager(object): return False return True + + @staticmethod + def add_ci_type_order_item(type_id, is_tree=False): + max_order = PreferenceCITypeOrder.get_by( + uid=current_user.uid, is_tree=is_tree, only_query=True).order_by(PreferenceCITypeOrder.order.desc()).first() + order = (max_order and max_order.order + 1) or 1 + + PreferenceCITypeOrder.create(type_id=type_id, is_tree=is_tree, uid=current_user.uid, order=order) + + @staticmethod + def delete_ci_type_order_item(type_id, is_tree=False): + existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree, + first=True, to_dict=False) + + existed and existed.soft_delete() + + @staticmethod + def upsert_ci_type_order(type_ids, is_tree=False): + for idx, type_id in enumerate(type_ids): + order = idx + 1 + existed = PreferenceCITypeOrder.get_by(uid=current_user.uid, type_id=type_id, is_tree=is_tree, + to_dict=False, first=True) + if existed is not None: + existed.update(order=order, flush=True) + else: + PreferenceCITypeOrder.create(uid=current_user.uid, type_id=type_id, is_tree=is_tree, order=order, + flush=True) + try: + db.session.commit() + except Exception as e: + db.session.rollback() + current_app.logger.error("upsert citype order failed: {}".format(e)) + return abort(400, ErrFormat.unknown_error) diff --git a/cmdb-api/api/views/cmdb/ci_type.py b/cmdb-api/api/views/cmdb/ci_type.py index 4eb6150..b77b0f8 100644 --- a/cmdb-api/api/views/cmdb/ci_type.py +++ b/cmdb-api/api/views/cmdb/ci_type.py @@ -465,16 +465,16 @@ class CITypeGrantView(APIView): acl.grant_resource_to_role_by_rid(type_name, rid, ResourceTypeEnum.CI_TYPE, perms, rebuild=False) + resource = None if 'ci_filter' in request.values or 'attr_filter' in request.values: - CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values) - else: + resource = CIFilterPermsCRUD().add(type_id=type_id, rid=rid, **request.values) + + if not resource: from api.tasks.acl import role_rebuild from api.lib.perm.acl.const import ACL_QUEUE app_id = AppCache.get('cmdb').id - current_app.logger.info((rid, app_id)) role_rebuild.apply_async(args=(rid, app_id), queue=ACL_QUEUE) - current_app.logger.info('done') return self.jsonify(code=200) diff --git a/cmdb-api/api/views/cmdb/preference.py b/cmdb-api/api/views/cmdb/preference.py index cc3dab6..681319d 100644 --- a/cmdb-api/api/views/cmdb/preference.py +++ b/cmdb-api/api/views/cmdb/preference.py @@ -2,6 +2,7 @@ from flask import abort +from flask import current_app from flask import request from api.lib.cmdb.ci_type import CITypeManager @@ -187,3 +188,15 @@ class PreferenceRelationRevokeView(APIView): acl.revoke_resource_from_role_by_rid(name, rid, ResourceTypeEnum.RELATION_VIEW, perms) return self.jsonify(code=200) + + +class PreferenceCITypeOrderView(APIView): + url_prefix = ("/preference/ci_types/order",) + + def post(self): + type_ids = request.values.get("type_ids") + is_tree = request.values.get("is_tree") in current_app.config.get('BOOL_TRUE') + + PreferenceManager.upsert_ci_type_order(type_ids, is_tree) + + return self.jsonify(type_ids=type_ids, is_tree=is_tree)