feat(api): attribute association supports multiple groups (#527)

This commit is contained in:
pycook 2024-06-04 11:34:54 +08:00 committed by GitHub
parent 65ef58dea9
commit 50134e6a0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 115 additions and 61 deletions

View File

@ -1274,52 +1274,83 @@ class CIRelationManager(object):
def build_by_attribute(cls, ci_dict):
type_id = ci_dict['_type']
child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter(
CITypeRelation.parent_attr_id.isnot(None))
CITypeRelation.parent_attr_ids.isnot(None))
for item in child_items:
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False)
relations = None
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(parent_attr.name)
value_table = TableMap(attr=child_attr).table
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
_relations.add((ci_dict['_id'], child.ci_id))
if relations is None:
relations = _relations
else:
relations &= _relations
for parent_ci_id, child_ci_id in relations:
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
CITypeRelation.child_attr_id.isnot(None))
CITypeRelation.child_attr_ids.isnot(None))
for item in parent_items:
parent_attr = AttributeCache.get(item.parent_attr_id)
child_attr = AttributeCache.get(item.child_attr_id)
attr_value = ci_dict.get(child_attr.name)
value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False)
relations = None
for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids):
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(child_attr.name)
value_table = TableMap(attr=parent_attr).table
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
_relations.add((parent.ci_id, ci_dict['_id']))
if relations is None:
relations = _relations
else:
relations &= _relations
for parent_ci_id, child_ci_id in relations:
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False)
@classmethod
def rebuild_all_by_attribute(cls, ci_type_relation):
parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id'])
child_attr = AttributeCache.get(ci_type_relation['child_attr_id'])
if not parent_attr or not child_attr:
return
relations = None
for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
ci_type_relation['child_attr_ids'] or []):
parent_value_table = TableMap(attr=parent_attr).table
child_value_table = TableMap(attr=child_attr).table
_relations = set()
parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id)
if not parent_attr or not child_attr:
continue
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
parent_value_table = TableMap(attr=parent_attr).table
child_value_table = TableMap(attr=child_attr).table
child_value2ci_ids = {}
for child in child_values:
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join(
CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id'])
child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join(
CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id'])
for parent in parent_values:
for child_ci_id in child_value2ci_ids.get(parent.value, []):
try:
cls.add(parent.ci_id, child_ci_id, valid=False)
except:
pass
child_value2ci_ids = {}
for child in child_values:
child_value2ci_ids.setdefault(child.value, []).append(child.ci_id)
for parent in parent_values:
for child_ci_id in child_value2ci_ids.get(parent.value, []):
_relations.add((parent.ci_id, child_ci_id))
if relations is None:
relations = _relations
else:
relations &= _relations
for parent_ci_id, child_ci_id in (relations or []):
try:
cls.add(parent_ci_id, child_ci_id, valid=False)
except:
pass
class CITriggerManager(object):

View File

@ -35,6 +35,7 @@ from api.lib.perm.acl.acl import validate_permission
from api.models.cmdb import Attribute
from api.models.cmdb import AutoDiscoveryCI
from api.models.cmdb import AutoDiscoveryCIType
from api.models.cmdb import AutoDiscoveryCITypeRelation
from api.models.cmdb import CI
from api.models.cmdb import CIFilterPerms
from api.models.cmdb import CIType
@ -49,12 +50,12 @@ 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 TopologyView
from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceSearchOption
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.models.cmdb import RelationType
from api.models.cmdb import TopologyView
class CITypeManager(object):
@ -251,6 +252,12 @@ class CITypeManager(object):
for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False):
item.delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(ad_type_id=type_id, to_dict=False):
item.delete(commit=False)
for item in AutoDiscoveryCITypeRelation.get_by(peer_type_id=type_id, to_dict=False):
item.delete(commit=False)
for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False):
item.delete(commit=False)
@ -686,9 +693,19 @@ class CITypeAttributeManager(object):
item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True)
item and item.soft_delete(commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)):
item.soft_delete(commit=False)
for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) +
CITypeRelation.get_by(child_id=type_id, to_dict=False)):
if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.parent_attr_ids.index(attr_id)
elif item.child_id == type_id and attr_id in (item.child_attr_ids or []):
item_dict = item.to_dict()
pop_idx = item.child_attr_ids.index(attr_id)
else:
continue
item.update(parent_attr_ids=item_dict['parent_attr_ids'].pop(pop_idx),
child_attr_ids=item_dict['child_attr_ids'].pop(pop_idx),
commit=False)
db.session.commit()
@ -757,10 +774,12 @@ class CITypeRelationManager(object):
res[idx] = _item
res[idx]['parent'] = item.parent.to_dict()
if item.parent_id not in type2attributes:
type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)]
type2attributes[item.parent_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.parent_id)]
res[idx]['child'] = item.child.to_dict()
if item.child_id not in type2attributes:
type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)]
type2attributes[item.child_id] = [i[1].to_dict() for i in
CITypeAttributeManager.get_all_attributes(item.child_id)]
res[idx]['relation_type'] = item.relation_type.to_dict()
return res, type2attributes
@ -795,8 +814,8 @@ class CITypeRelationManager(object):
ci_type_dict["relation_type"] = relation_inst.relation_type.name
ci_type_dict["constraint"] = relation_inst.constraint
ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id
ci_type_dict["child_attr_id"] = relation_inst.child_attr_id
ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids
ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids
return ci_type_dict
@ -906,7 +925,7 @@ class CITypeRelationManager(object):
@classmethod
def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many,
parent_attr_id=None, child_attr_id=None):
parent_attr_ids=None, child_attr_ids=None):
p = CITypeManager.check_is_existed(parent)
c = CITypeManager.check_is_existed(child)
@ -921,21 +940,22 @@ class CITypeRelationManager(object):
current_app.logger.warning(str(e))
return abort(400, ErrFormat.circular_dependency_error)
old_parent_attr_id = None
old_parent_attr_ids, old_child_attr_ids = None, None
existed = cls._get(p.id, c.id)
if existed is not None:
old_parent_attr_id = existed.parent_attr_id
old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids)
old_child_attr_ids = copy.deepcopy(existed.child_attr_ids)
existed = existed.update(relation_type_id=relation_type_id,
constraint=constraint,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
parent_attr_ids=parent_attr_ids,
child_attr_ids=child_attr_ids,
filter_none=False)
else:
existed = CITypeRelation.create(parent_id=p.id,
child_id=c.id,
relation_type_id=relation_type_id,
parent_attr_id=parent_attr_id,
child_attr_id=child_attr_id,
parent_attr_ids=parent_attr_ids,
child_attr_ids=child_attr_ids,
constraint=constraint)
if current_app.config.get("USE_ACL"):
@ -949,10 +969,10 @@ class CITypeRelationManager(object):
current_user.username,
ResourceTypeEnum.CI_TYPE_RELATION)
if parent_attr_id and parent_attr_id != old_parent_attr_id:
if parent_attr_id and parent_attr_id != existed.parent_attr_id:
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict()))
if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or
(child_attr_ids and child_attr_ids != old_child_attr_ids)):
from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(),))
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))
@ -1246,8 +1266,8 @@ class CITypeTemplateManager(object):
id2obj_dicts[added_id].get('child_id'),
id2obj_dicts[added_id].get('relation_type_id'),
id2obj_dicts[added_id].get('constraint'),
id2obj_dicts[added_id].get('parent_attr_id'),
id2obj_dicts[added_id].get('child_attr_id'),
id2obj_dicts[added_id].get('parent_attr_ids'),
id2obj_dicts[added_id].get('child_attr_ids'),
)
else:
obj = cls.create(flush=True, **id2obj_dicts[added_id])

View File

@ -79,8 +79,11 @@ class CITypeRelation(Model):
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many)
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id"))
parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated
parent_attr_ids = db.Column(db.JSON) # [parent_attr_id, ]
child_attr_ids = db.Column(db.JSON) # [child_attr_id, ]
parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id")
child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id")

View File

@ -57,10 +57,10 @@ class CITypeRelationView(APIView):
def post(self, parent_id, child_id):
relation_type_id = request.values.get("relation_type_id")
constraint = request.values.get("constraint")
parent_attr_id = request.values.get("parent_attr_id")
child_attr_id = request.values.get("child_attr_id")
parent_attr_ids = request.values.get("parent_attr_ids")
child_attr_ids = request.values.get("child_attr_ids")
ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint,
parent_attr_id, child_attr_id)
parent_attr_ids, child_attr_ids)
return self.jsonify(ctr_id=ctr_id)