Browse Source

Merge pull request #15268 from GordonSmith/HPCC-26344-LabelTpl

HPCC-26344 ECL Watch add label templates to metrics

Reviewed-by: Jeremy Clements
Merged-by: Gavin Halliday <ghalliday@hpccsystems.com>
Gavin Halliday 3 years ago
parent
commit
686baa18f9

+ 10 - 1
esp/src/src-react/components/MetricsOptions.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
-import { IDragOptions, ContextualMenu, DialogType, Dialog, DialogFooter, DefaultButton, PrimaryButton, Checkbox, Pivot, PivotItem } from "@fluentui/react";
+import { IDragOptions, ContextualMenu, DialogType, Dialog, DialogFooter, DefaultButton, PrimaryButton, Checkbox, Pivot, PivotItem, TextField } from "@fluentui/react";
 import nlsHPCC from "src/nlsHPCC";
 import { useMetricMeta, useMetricsOptions } from "../hooks/metrics";
 
@@ -73,6 +73,15 @@ export const MetricsOptions: React.FunctionComponent<MetricsOptionsProps> = ({
                 <Checkbox label={nlsHPCC.IgnoreGlobalStoreOutEdges} checked={options.ignoreGlobalStoreOutEdges} onChange={(ev, checked) => {
                     setOptions({ ...options, ignoreGlobalStoreOutEdges: !!checked });
                 }} />
+                <TextField label={nlsHPCC.SubgraphLabel} value={options.subgraphTpl} multiline autoAdjustHeight onChange={(evt, newValue) => {
+                    setOptions({ ...options, subgraphTpl: newValue });
+                }} />
+                <TextField label={nlsHPCC.ActivityLabel} value={options.activityTpl} multiline autoAdjustHeight onChange={(evt, newValue) => {
+                    setOptions({ ...options, activityTpl: newValue });
+                }} />
+                <TextField label={nlsHPCC.EdgeLabel} value={options.edgeTpl} multiline autoAdjustHeight onChange={(evt, newValue) => {
+                    setOptions({ ...options, edgeTpl: newValue });
+                }} />
             </PivotItem>
             <PivotItem headerText={nlsHPCC.Layout}>
             </PivotItem>

+ 8 - 2
esp/src/src-react/hooks/metrics.ts

@@ -5,16 +5,22 @@ import { userKeyValStore } from "src/KeyValStore";
 import { useCounter, useWorkunit } from "./workunit";
 
 const defaults = {
-    ignoreGlobalStoreOutEdges: true,
     scopeTypes: ["graph", "subgraph", "activity", "edge"],
     properties: ["TimeElapsed"],
+    ignoreGlobalStoreOutEdges: true,
+    subgraphTpl: "%id% - %TimeElapsed%",
+    activityTpl: "%Label%",
+    edgeTpl: "%Label%\n%NumRowsProcessed%\n%SkewMinRowsProcessed% / %SkewMaxRowsProcessed%",
     layout: undefined
 };
 
 export interface MetricsOptions {
-    ignoreGlobalStoreOutEdges: boolean;
     scopeTypes: string[];
     properties: string[];
+    ignoreGlobalStoreOutEdges: boolean;
+    subgraphTpl;
+    activityTpl;
+    edgeTpl;
     layout: object
 }
 

+ 4 - 4
esp/src/src-react/util/metricGraph.ts

@@ -1,7 +1,7 @@
 import { Button, d3Event, select as d3Select, Spacer, SVGZoomWidget } from "@hpcc-js/common";
 import { graphviz } from "@hpcc-js/graph";
 import { Graph2 } from "@hpcc-js/util";
-import { decodeHTML } from "src/Utility";
+import { format } from "src/Utility";
 import { MetricsOptions } from "../hooks/metrics";
 
 declare const dojoConfig;
@@ -186,7 +186,7 @@ export class MetricGraph extends Graph2<IScope, IScopeEdge, IScope> {
     }
 
     vertexTpl(v: IScope, options: MetricsOptions): string {
-        return `"${v.id}" [id="${encodeID(v.name)}" label="[${decodeHTML(v.Kind)}]\n${decodeHTML(v.Label) || v.id}" shape="${shape(v.Kind)}"]`;
+        return `"${v.id}" [id="${encodeID(v.name)}" label="${format(options.activityTpl, v)}" shape="${shape(v.Kind)}"]`;
     }
 
     protected _dedupEdges: { [id: string]: boolean } = {};
@@ -209,7 +209,7 @@ export class MetricGraph extends Graph2<IScope, IScopeEdge, IScope> {
         if (options.ignoreGlobalStoreOutEdges && this.vertex(this._activityIndex[e.IdSource]).Kind === "22") {
             return "";
         }
-        return `"${e.IdSource}" -> "${e.IdTarget}" [id="${encodeID(e.name)}" label="" style="${this.vertexParent(this._activityIndex[e.IdSource]) === this.vertexParent(this._activityIndex[e.IdTarget]) ? "solid" : "dashed"}"]`;
+        return `\"${e.IdSource}" -> "${e.IdTarget}" [id="${encodeID(e.name)}" label="${format(options.edgeTpl, e)}" style="${this.vertexParent(e.IdSource) === this.vertexParent(e.IdTarget) ? "solid" : "dashed"}"]`;
     }
 
     subgraphTpl(sg: IScope, options: MetricsOptions): string {
@@ -229,7 +229,7 @@ subgraph cluster_${sg.id} {
     fillcolor="white";
     style="filled";
     id="${encodeID(sg.name)}";
-    label="${sg.id}";
+    label="${format(options.subgraphTpl, sg)}";
 
     ${childTpls.join("\n")}
 

+ 53 - 1
esp/src/src/Utility.ts

@@ -1,4 +1,4 @@
-import { Palette } from "@hpcc-js/common";
+import { format as d3Format, Palette } from "@hpcc-js/common";
 import * as arrayUtil from "dojo/_base/array";
 import * as domConstruct from "dojo/dom-construct";
 import * as entities from "dojox/html/entities";
@@ -1032,4 +1032,56 @@ export function downloadText(content: string, fileName: string) {
     document.body.appendChild(link);
     link.click();
     document.body.removeChild(link);
+}
+
+const d3FormatNum = d3Format(",");
+
+export function formatNum(str): string {
+    if (isNaN(str)) {
+        return str;
+    }
+    return d3FormatNum(str);
+}
+
+export function formatNums(obj) {
+    for (const key in obj) {
+        obj[key] = formatNum(obj[key]);
+    }
+    return obj;
+}
+
+export function formatLine(labelTpl, obj): string {
+    let retVal = "";
+    let lpos = labelTpl.indexOf("%");
+    let rpos = -1;
+    let replacementFound = lpos >= 0 ? false : true;  //  If a line has no symbols always include it, otherwise only include that line IF a replacement was found  ---
+    while (lpos >= 0) {
+        retVal += labelTpl.substring(rpos + 1, lpos);
+        rpos = labelTpl.indexOf("%", lpos + 1);
+        if (rpos < 0) {
+            console.log("Invalid Label Template");
+            break;
+        }
+        const key = labelTpl.substring(lpos + 1, rpos);
+        replacementFound = replacementFound || !!obj[labelTpl.substring(lpos + 1, rpos)];
+        retVal += !key ? "%" : (obj[labelTpl.substring(lpos + 1, rpos)] || "");
+        lpos = labelTpl.indexOf("%", rpos + 1);
+    }
+    retVal += labelTpl.substring(rpos + 1, labelTpl.length);
+    return replacementFound ? retVal : "";
+}
+
+export function format(labelTpl, obj) {
+    labelTpl = labelTpl.split("\\n").join("\n");
+    return labelTpl
+        .split("\n")
+        .map(line => formatLine(line, obj))
+        .filter(d => d.trim().length > 0)
+        .map(decodeHtml)
+        .join("\n")
+        ;
+}
+
+export function isSpill(sourceKind: string, targetKind: string): boolean {
+    return sourceKind === "2" || targetKind === "71";
 }

+ 21 - 69
esp/src/src/WUScopeController.ts

@@ -1,10 +1,14 @@
-import { format as d3Format, Icon, Palette } from "@hpcc-js/common";
+import { Icon, Palette } from "@hpcc-js/common";
 import { BaseScope, ScopeEdge, ScopeGraph, ScopeSubgraph, ScopeVertex } from "@hpcc-js/comms";
 import { Edge, IEdge, IGraphData, IGraphData2, IHierarchy, ISubgraph, IVertex, Lineage, Subgraph, Vertex } from "@hpcc-js/graph";
 import { Edge as UtilEdge, Subgraph as UtilSubgraph, Vertex as UtilVertex } from "@hpcc-js/util";
-import { decodeHtml } from "./Utility";
+import { decodeHtml, format, formatNums, isSpill as _isSPill } from "./Utility";
 
-const formatNum = d3Format(",");
+function isSpill(edge: ScopeEdge) {
+    const sourceKind = edge.source._.attr("Kind").RawValue;
+    const targetKind = edge.target._.attr("Kind").RawValue;
+    return _isSPill(sourceKind, targetKind);
+}
 
 export type VertexType = Vertex | Icon;
 
@@ -204,58 +208,6 @@ export abstract class WUScopeControllerBase<ISubgraph, IVertex, IEdge, IGraphDat
 
     abstract graphGui(graphDB: ScopeGraph): IGraphData;
 
-    formatNum(str): string {
-        if (isNaN(str)) {
-            return str;
-        }
-        return formatNum(str);
-    }
-
-    formatNums(obj) {
-        for (const key in obj) {
-            obj[key] = this.formatNum(obj[key]);
-        }
-        return obj;
-    }
-
-    formatLine(labelTpl, obj): string {
-        let retVal = "";
-        let lpos = labelTpl.indexOf("%");
-        let rpos = -1;
-        let replacementFound = lpos >= 0 ? false : true;  //  If a line has no symbols always include it, otherwise only include that line IF a replacement was found  ---
-        while (lpos >= 0) {
-            retVal += labelTpl.substring(rpos + 1, lpos);
-            rpos = labelTpl.indexOf("%", lpos + 1);
-            if (rpos < 0) {
-                console.log("Invalid Label Template");
-                break;
-            }
-            const key = labelTpl.substring(lpos + 1, rpos);
-            replacementFound = replacementFound || !!obj[labelTpl.substring(lpos + 1, rpos)];
-            retVal += !key ? "%" : (obj[labelTpl.substring(lpos + 1, rpos)] || "");
-            lpos = labelTpl.indexOf("%", rpos + 1);
-        }
-        retVal += labelTpl.substring(rpos + 1, labelTpl.length);
-        return replacementFound ? retVal : "";
-    }
-
-    format(labelTpl, obj) {
-        labelTpl = labelTpl.split("\\n").join("\n");
-        return labelTpl
-            .split("\n")
-            .map(line => this.formatLine(line, obj))
-            .filter(d => d.trim().length > 0)
-            .map(decodeHtml)
-            .join("\n")
-            ;
-    }
-
-    isSpill(edge: ScopeEdge): boolean {
-        const sourceKind = edge.source._.attr("Kind").RawValue;
-        const targetKind = edge.target._.attr("Kind").RawValue;
-        return sourceKind === "2" || targetKind === "71";
-    }
-
     spansSubgraph(edge: ScopeEdge): boolean {
         return edge.source.parent._.Id !== edge.target.parent._.Id;
     }
@@ -327,7 +279,7 @@ export abstract class WUScopeControllerBase<ISubgraph, IVertex, IEdge, IGraphDat
             //  TODO Move into BaseScope  ---
             retVal[key] = decodeHtml(retVal[key]);
         }
-        retVal.__formatted = this.formatNums(item._.formattedAttrs());
+        retVal.__formatted = formatNums(item._.formattedAttrs());
         return retVal;
     }
 
@@ -381,7 +333,7 @@ export abstract class WUScopeControllerBase<ISubgraph, IVertex, IEdge, IGraphDat
             rows.push(`<tr><td class="key">Parent ID:</td><td class="value">${highlightText("Parent ID", parentScope.Id)}</td></tr>`);
         }
         rows.push(`<tr><td class="key">Scope:</td><td class="value">${highlightText("Scope", scope.ScopeName)}</td></tr>`);
-        const attrs = this.formatNums(scope.formattedAttrs());
+        const attrs = formatNums(scope.formattedAttrs());
         for (const key in attrs) {
             if (key === "Label") {
                 label = attrs[key];
@@ -515,7 +467,7 @@ export class WUScopeController extends WUScopeControllerBase<Subgraph, VertexTyp
 
     createVertex(vertex: ScopeVertex): VertexType {
         const rawAttrs = vertex._.rawAttrs();
-        const formattedAttrs = this.formatNums(vertex._.formattedAttrs());
+        const formattedAttrs = formatNums(vertex._.formattedAttrs());
         formattedAttrs["ID"] = vertex._.Id;
         formattedAttrs["Parent ID"] = vertex.parent && vertex.parent._.Id;
         formattedAttrs["Scope"] = vertex._.ScopeName;
@@ -568,7 +520,7 @@ export class WUScopeController extends WUScopeControllerBase<Subgraph, VertexTyp
         }
         this.scopeVerticesMap[v.id()] = vertex;
         if (v instanceof Vertex) {
-            const label = this.format(this.vertexLabelTpl(), formattedAttrs);
+            const label = format(this.vertexLabelTpl(), formattedAttrs);
             v
                 .icon_diameter(this.showIcon() ? 24 : 0)
                 .text(label)
@@ -581,14 +533,14 @@ export class WUScopeController extends WUScopeControllerBase<Subgraph, VertexTyp
         const sourceV = this.verticesMap[edge.source._.Id];
         const targetV = this.verticesMap[edge.target._.Id];
         const rawAttrs = edge._.rawAttrs();
-        const formattedAttrs = this.formatNums(edge._.formattedAttrs());
+        const formattedAttrs = formatNums(edge._.formattedAttrs());
         formattedAttrs["ID"] = edge._.Id;
         formattedAttrs["Parent ID"] = edge.parent && edge.parent._.Id;
         formattedAttrs["Scope"] = edge._.ScopeName;
         let e = this.edgesMap[edge._.Id];
         if (!e) {
             if (sourceV && targetV) {
-                const isSpill = this.isSpill(edge);
+                const is_spill = isSpill(edge);
                 const spansSubgraph = this.spansSubgraph(edge);
 
                 let strokeDasharray = null;
@@ -598,7 +550,7 @@ export class WUScopeController extends WUScopeControllerBase<Subgraph, VertexTyp
                     strokeDasharray = "1,2";
                 } else if (rawAttrs["_childGraph"]) {
                     strokeDasharray = "5,5";
-                } else if (isSpill) {
+                } else if (is_spill) {
                     weight = 25;
                     strokeDasharray = "5,5,10,5";
                 } else if (spansSubgraph) {
@@ -618,7 +570,7 @@ export class WUScopeController extends WUScopeControllerBase<Subgraph, VertexTyp
         }
         if (e instanceof Edge) {
             this.scopeEdgesMap[e.id()] = edge;
-            const label = this.format(this.edgeLabelTpl(), formattedAttrs);
+            const label = format(this.edgeLabelTpl(), formattedAttrs);
             e.text(label);
             e.sourceVertex(sourceV);
             e.targetVertex(targetV);
@@ -922,7 +874,7 @@ export class WUScopeController8 extends WUScopeControllerBase<ISubgraph, IVertex
 
     private createVertex(vertex: ScopeVertex): IVertex {
         const rawAttrs = vertex._.rawAttrs();
-        const formattedAttrs = this.formatNums(vertex._.formattedAttrs());
+        const formattedAttrs = formatNums(vertex._.formattedAttrs());
         formattedAttrs["ID"] = vertex._.Id;
         formattedAttrs["Parent ID"] = vertex.parent && vertex.parent._.Id;
         formattedAttrs["Scope"] = vertex._.ScopeName;
@@ -967,7 +919,7 @@ export class WUScopeController8 extends WUScopeControllerBase<ISubgraph, IVertex
             this.verticesMap[vertex._.Id] = v;
         }
         this.scopeVerticesMap[v.id] = vertex;
-        const label = this.format(this.vertexLabelTpl(), formattedAttrs);
+        const label = format(this.vertexLabelTpl(), formattedAttrs);
         v.text = label;
         return v;
     }
@@ -976,14 +928,14 @@ export class WUScopeController8 extends WUScopeControllerBase<ISubgraph, IVertex
         const sourceV = this.verticesMap[edge.source._.Id];
         const targetV = this.verticesMap[edge.target._.Id];
         const rawAttrs = edge._.rawAttrs();
-        const formattedAttrs = this.formatNums(edge._.formattedAttrs());
+        const formattedAttrs = formatNums(edge._.formattedAttrs());
         formattedAttrs["ID"] = edge._.Id;
         formattedAttrs["Parent ID"] = edge.parent && edge.parent._.Id;
         formattedAttrs["Scope"] = edge._.ScopeName;
         let e = this.edgesMap[edge._.Id];
         if (!e) {
             if (sourceV && targetV) {
-                const isSpill = this.isSpill(edge);
+                const is_spill = isSpill(edge);
                 const spansSubgraph = this.spansSubgraph(edge);
 
                 let strokeDasharray;
@@ -993,7 +945,7 @@ export class WUScopeController8 extends WUScopeControllerBase<ISubgraph, IVertex
                     strokeDasharray = "1,2";
                 } else if (rawAttrs["_childGraph"]) {
                     strokeDasharray = "5,5";
-                } else if (isSpill) {
+                } else if (is_spill) {
                     weight = 25;
                     strokeDasharray = "5,5,10,5";
                 } else if (spansSubgraph) {
@@ -1012,7 +964,7 @@ export class WUScopeController8 extends WUScopeControllerBase<ISubgraph, IVertex
         }
         if (e) {
             this.scopeEdgesMap[e.id] = edge;
-            const label = this.format(this.edgeLabelTpl(), formattedAttrs);
+            const label = format(this.edgeLabelTpl(), formattedAttrs);
             e.label = label;
             e.source = sourceV;
             e.target = targetV;

+ 3 - 0
esp/src/src/nls/hpcc.ts

@@ -20,6 +20,7 @@ export = {
         ActiveWorkunit: "Active Workunit",
         Activities: "Activities",
         Activity: "Activity",
+        ActivityLabel: "Activity Label",
         ActivityMap: "Activity Map",
         ActualSize: "Actual Size",
         Add: "Add",
@@ -231,6 +232,7 @@ export = {
         ECLWatchRequiresCookies: "ECL Watch requires cookies enabled to continue.",
         ECLWatchSessionManagement: "ECL Watch session management",
         ECLWorkunit: "ECL Workunit",
+        EdgeLabel: "Edge Label",
         Edges: "Edges",
         Edit: "Edit",
         EditDOT: "Edit DOT",
@@ -772,6 +774,7 @@ export = {
         Stopping: "Stopping",
         StorageInformation: "Storage Information",
         Subgraph: "Subgraph",
+        SubgraphLabel: "Subgraph Label",
         Subgraphs: "Subgraphs",
         Submit: "Submit",
         Subtype: "Subtype",