Deploy IRQL Graph Functions
The Lift_To_Graph, Graph_Render_View, and Graph_Fold_By_Property functions must be present in the target Kusto database. They are pre-deployed on the kc7001 example cluster but may be missing on other clusters.
Check for existing functions
.show functions
| where Name in~ ("Lift_To_Graph", "Graph_Render_View", "Graph_Fold_By_Property")
| project NameIf any required function is missing, deploy it using the .create-or-alter commands below.
Lift_To_Graph
.create-or-alter function with (folder="irql_draft", docstring="Transforms a generic table to a Kusto graph table using the given JSON mapping")
Lift_To_Graph(T:(), mappingJson:string)
{
let calcIcon = (T:(type:string, defIcon:string)) {
T
| extend iconUrl = defIcon
| project-away defIcon
};
let mapping = (mapping_json:string) {
parse_json(mapping_json)
};
let Tpacked = (T:()) {
T | extend _row = pack_all()
};
let KustoResultsToNodes = (T:(), mapping_json:dynamic) {
let NodeExpanded =
Tpacked(T)
| mv-expand nodeDef = mapping(mapping_json).node_types to typeof(dynamic)
| extend name=tostring(_row[tostring(nodeDef.key)]),
type=tostring(nodeDef.type),
_nodeKeys=iif(isnull(nodeDef.props),dynamic([]),nodeDef.props),
defaults=nodeDef.defaults
| extend nodeColor=iff(isnotnull(nodeDef.color), tostring(_row[tostring(nodeDef.color)]), "")
| extend nodeSize=iff(isnotnull(nodeDef.size), toreal(_row[tostring(nodeDef.size)]), 1.0)
| extend iconColor=iff(isnotnull(nodeDef.iconColor), tostring(_row[tostring(nodeDef.iconColor)]), "")
| extend id = strcat(nodeDef.id,"/",name)
| extend nodeDisplayName=iff(isnotnull(nodeDef.displayName),
strcat(type,'/',tostring(_row[tostring(nodeDef.displayName)])), id)
| extend defIcon = iif(isnotempty(nodeDef.defIcon), nodeDef.defIcon, "")
| where isnotempty(split(id, "/")[-1])
| extend type = tostring(nodeDef.type)
| extend iconUrl=""
| invoke calcIcon();
let NodePropsFilled = (T:(_row:dynamic, _nodeKeys:dynamic, id:string, type:string,
nodeDisplayName:string, nodeColor:string, nodeSize:real,
iconUrl:string, iconColor:string, defaults:dynamic)) {
T
| mv-expand k=_nodeKeys to typeof(string)
| extend v=_row[k], def=defaults[k]
| extend v = iif(isnull(v) or isempty(tostring(v)), iif(isnull(def), v, def), v)
| summarize properties=make_bag(bag_pack(k,v))
by id,type,nodeDisplayName,nodeColor,nodeSize,iconUrl,iconColor
};
let NodeNoProps = (T:(_nodeKeys:dynamic, id:string, type:string,
nodeDisplayName:string, nodeColor:string, nodeSize:real,
iconUrl:string, iconColor:string)) {
T
| where array_length(_nodeKeys)==0
| extend properties=dynamic({})
| project id,type,properties,nodeDisplayName,nodeColor,nodeSize,iconUrl, iconColor
};
let Nodes = (T:(_row:dynamic, _nodeKeys:dynamic, id:string, type:string,
nodeDisplayName:string, nodeColor:string, nodeSize:real,
iconUrl:string, iconColor:string, defaults:dynamic))
{
union
NodePropsFilled(T),
NodeNoProps(T)
| project id,type,properties,nodeDisplayName,nodeColor,nodeSize,iconUrl, iconColor
};
union
(T | extend EntityType = "data"),
(Nodes(NodeExpanded) | extend EntityType = "node")
};
let KustoResultsToEdges = (T:(EntityType:string, ),mapping_json:dynamic) {
let edges = datatable(SourceId:string, TargetId:string) [];
let EdgeExpanded =
Tpacked((T | where EntityType == "data"))
| extend nodeDef = mapping(mapping_json).node_types
| mv-expand edgeDef = mapping(mapping_json).edges to typeof(dynamic)
| mv-apply nodeDefSrc = nodeDef on (
where tostring(nodeDefSrc["type"]) == tostring(edgeDef.source.type))
| extend SourceId = strcat(nodeDefSrc.id,"/",tostring(_row[tostring(nodeDefSrc.key)]))
| mv-apply nodeDefTgt = nodeDef on (
where tostring(nodeDefTgt["type"]) == tostring(edgeDef.target.type))
| extend TargetId = strcat(nodeDefTgt.id,"/",tostring(_row[tostring(nodeDefTgt.key)]))
| extend edgeType=tostring(edgeDef.type),
_edgeKeys=iif(isnull(edgeDef.props),dynamic([]),edgeDef.props)
| extend edgeDisplayName = iff(isnotnull(edgeDef.displayName),
strcat(edgeType,'/',tostring(_row[tostring(edgeDef.displayName)])), edgeType)
| extend edgeColor= iff(isnotnull(edgeDef.color),
tostring(_row[tostring(edgeDef.color)]), edgeType);
let EdgePropsFilled = (T:(_row:dynamic, _edgeKeys:dynamic,
SourceId:string, TargetId:string, edgeType:string,
edgeDisplayName:string, edgeColor:string)) {
T
| mv-expand k=_edgeKeys to typeof(string)
| extend v=_row[k]
| summarize edgeProperties=make_bag(bag_pack(k,v))
by SourceId,TargetId,edgeType,edgeDisplayName,edgeColor
};
let EdgeNoProps = (T:(_edgeKeys:dynamic, SourceId:string, TargetId:string,
edgeType:string, edgeDisplayName:string, edgeColor:string)) {
T
| where array_length(_edgeKeys)==0
| extend edgeProperties=dynamic({})
| project SourceId,TargetId,edgeType, edgeProperties,edgeDisplayName,edgeColor
};
let Edges = (T:(_edgeKeys:dynamic, SourceId:string, TargetId:string,
edgeType:string, edgeDisplayName:string, edgeColor:string)) {
union
EdgePropsFilled(EdgeExpanded),
EdgeNoProps(EdgeExpanded)
| where isnotempty(split(SourceId, "/")[-1]) and isnotempty(split(TargetId, "/")[-1])
| project-reorder SourceId,TargetId,edgeType,edgeProperties,edgeDisplayName,edgeColor
};
union
(T | where EntityType=="node"),
(Edges(EdgeExpanded) | extend EntityType = "edge")
};
T
| invoke KustoResultsToNodes(mappingJson)
| invoke KustoResultsToEdges(mappingJson)
| where EntityType != "data"
| project EntityType, id, type, properties, nodeDisplayName, nodeColor, nodeSize,
iconUrl, iconColor, SourceId, TargetId, edgeType, edgeProperties,
edgeDisplayName, edgeColor
}Graph_Render_View
.create-or-alter function with (folder="irql_draft", docstring="Renders a graph table using make-graph in Kusto Explorer")
Graph_Render_View(T:(id:string, type:string, properties:dynamic, nodeDisplayName:string,
nodeColor:string, nodeSize:real, iconUrl:string, iconColor:string,
SourceId:string, TargetId:string, edgeType:string, edgeProperties:dynamic,
edgeDisplayName:string, edgeColor:string, EntityType:string))
{
let NodesTable =
T
| where EntityType=="node"
| project id, type, properties, nodeDisplayName, nodeColor, nodeSize, iconUrl, iconColor;
let EdgesTable =
T
| where EntityType=="edge"
| project SourceId, TargetId, type=edgeType, properties=edgeProperties, edgeDisplayName, edgeColor;
// #graph-style("Default")
let Default = dynamic({
"name":"Default",
"graph_style":{
"layout":{"kind":"Grouped"},
"nodes_config":{
"density":80.0,
"label_by":"id",
"color_by":"iconUrl",
"lifetime_start_by":"",
"lifetime_end_by":"",
"image_url_by":"iconUrl",
"image_size":2.0
},
"edges_config":{
"lifetime_start_by":"",
"lifetime_end_by":""
}
},
"script":"// Use right-click on the nodes to explore interactive operations over the graph.",
"matches":[]
});
EdgesTable
| make-graph SourceId --> TargetId with (NodesTable) on id
}Graph_Fold_By_Property
.create-or-alter function with (folder="irql_draft", docstring="Folds nodes of a given type by a shared property value into single collapsed nodes")
Graph_Fold_By_Property(T:(EntityType:string, id:string, type:string, properties:dynamic,
nodeDisplayName:string, nodeColor:string, nodeSize:real,
iconUrl:string, iconColor:string,
SourceId:string, TargetId:string, edgeType:string, edgeProperties:dynamic,
edgeDisplayName:string, edgeColor:string), NodeType:string, PropertyName:string)
{
let Nodes =
T
| where EntityType == "node"
| project EntityType, id, type, properties, nodeDisplayName, nodeColor, nodeSize, iconUrl, iconColor,
SourceId="", TargetId="", edgeType="", edgeProperties=dynamic(null),
edgeDisplayName="", edgeColor="";
let Edges =
T
| where EntityType == "edge"
| project EntityType, id="", type="", properties=dynamic({}),
nodeDisplayName="", nodeColor="", nodeSize=real(0), iconUrl="", iconColor="",
SourceId, TargetId, edgeType, edgeProperties, edgeDisplayName, edgeColor;
let FoldedNodes =
Nodes
| where type == NodeType
| where isnotempty(properties[PropertyName])
| extend val = tostring(properties[PropertyName])
| summarize members = make_list(id), memberCount = count() by val
| where memberCount > 1
| extend
id = strcat(PropertyName, "/", val),
type = PropertyName,
EntityType = "node",
properties = pack("folded", val,
"memberCount", memberCount,
"members", members),
nodeDisplayName = strcat(PropertyName, "/", val),
nodeColor = "", nodeSize = real(0),
iconUrl = "", iconColor = ""
| project EntityType, id, type, properties, nodeDisplayName, nodeColor, nodeSize, iconUrl, iconColor,
SourceId="", TargetId="", edgeType="", edgeProperties=dynamic(null),
edgeDisplayName="", edgeColor="";
let MemberToFold =
Nodes
| where type == NodeType
| where isnotempty(properties[PropertyName])
| extend val = tostring(properties[PropertyName])
| join kind=inner (
FoldedNodes
| extend val = tostring(properties["folded"])
| project val, foldId=id
) on val
| project memberId=id, foldId;
let RewiredEdges =
Edges
| lookup kind=leftouter (MemberToFold | project SourceId=memberId, FoldSourceId=foldId) on SourceId
| lookup kind=leftouter (MemberToFold | project TargetId=memberId, FoldTargetId=foldId) on TargetId
| extend
NewSourceId = coalesce(FoldSourceId, SourceId),
NewTargetId = coalesce(FoldTargetId, TargetId)
| where NewSourceId != NewTargetId
| project EntityType="edge",
id="", type="", properties=dynamic({}),
nodeDisplayName="", nodeColor="", nodeSize=real(0), iconUrl="", iconColor="",
SourceId=NewSourceId, TargetId=NewTargetId, edgeType, edgeProperties,
edgeDisplayName, edgeColor;
let FoldedMemberIds = MemberToFold | distinct memberId;
union
(Nodes | where id !in (FoldedMemberIds)),
FoldedNodes,
RewiredEdges
}Deploying all three functions
Run each .create-or-alter block above in Kusto Explorer or the ADX web UI against your target database. The functions are placed in the irql_draft folder.
After deployment, verify:
.show functions
| where Name in~ ("Lift_To_Graph", "Graph_Render_View", "Graph_Fold_By_Property")
| project Name, Folder, DocString