From 88161448dc4627dc5ec10d304ce08b428c356e9c Mon Sep 17 00:00:00 2001 From: Acatl Pacheco Date: Wed, 11 Sep 2019 14:11:55 -0400 Subject: [PATCH] feat(data-point-tracers): add mermaid support (#420) closes #419 --- .../examples/mermaid-graph/mermaid-example.js | 66 +++++++++++++ .../examples/mermaid-graph/package.json | 10 ++ packages/data-point-tracers/mermaid/index.js | 1 + .../data-point-tracers/mermaid/mermaid.js | 48 +++++++++ .../mermaid/mermaid.test.js | 98 +++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 documentation/examples/mermaid-graph/mermaid-example.js create mode 100644 documentation/examples/mermaid-graph/package.json create mode 100644 packages/data-point-tracers/mermaid/index.js create mode 100644 packages/data-point-tracers/mermaid/mermaid.js create mode 100644 packages/data-point-tracers/mermaid/mermaid.test.js diff --git a/documentation/examples/mermaid-graph/mermaid-example.js b/documentation/examples/mermaid-graph/mermaid-example.js new file mode 100644 index 00000000..6bd8026e --- /dev/null +++ b/documentation/examples/mermaid-graph/mermaid-example.js @@ -0,0 +1,66 @@ +/* eslint-disable */ + +const path = require("path"); + +const DataPoint = require("@data-point/core"); +const DPModel = require("@data-point/core/model"); +const DPIfThenElse = require("@data-point/core/ifThenElse"); +const DPMap = require("@data-point/core/map"); + +const DPMermaidTracer = require("@data-point/tracers/mermaid"); + +const myModel = DPModel({ + name: "myModel", + + uid: acc => `${acc.reducer.id}${acc.value.a.b}`, + + value: [ + "$a.b", + input => input.toUpperCase(), + DPIfThenElse({ + if: input => input === "FOO", + then: () => { + // return "yes foo!!"; + throw new Error("ohh"); + }, + else: input => `foo no! got ${input}` + }) + ], + + catch(acc) { + return "its all ok"; + } +}); + +async function main() { + const datapoint = DataPoint(); + + const input = [ + { + a: { + b: "foo" + } + }, + { + a: { + b: "bar" + } + }, + { + a: { + b: "baz" + } + } + ]; + + const tracer = DPMermaidTracer(); + + result = await datapoint.resolve(input, DPMap(myModel), { + tracer + }); + + // save to disk + tracer.report(path.join(__dirname, "datapoint-trace-example.mermaid")); +} + +main(); diff --git a/documentation/examples/mermaid-graph/package.json b/documentation/examples/mermaid-graph/package.json new file mode 100644 index 00000000..c3b55395 --- /dev/null +++ b/documentation/examples/mermaid-graph/package.json @@ -0,0 +1,10 @@ +{ + "name": "@data-point/mermaid-example", + "main": "mermaid-example.js", + "private": true, + "version": "1.0.0", + "devDependencies": { + "@data-point/core": "^6.0.0", + "@data-point/tracers": "^1.0.0" + } +} diff --git a/packages/data-point-tracers/mermaid/index.js b/packages/data-point-tracers/mermaid/index.js new file mode 100644 index 00000000..4f9e278d --- /dev/null +++ b/packages/data-point-tracers/mermaid/index.js @@ -0,0 +1 @@ +module.exports = require("./mermaid").Mermaid.create; diff --git a/packages/data-point-tracers/mermaid/mermaid.js b/packages/data-point-tracers/mermaid/mermaid.js new file mode 100644 index 00000000..a9df753f --- /dev/null +++ b/packages/data-point-tracers/mermaid/mermaid.js @@ -0,0 +1,48 @@ +const fs = require("fs").promises; + +function getParent(span) { + return span.root ? "root" : span.parent; +} + +function getName(span) { + return span === "root" ? "root" : `${span.name}${span.context.pid}`; +} + +function graphTD(spans) { + const nodes = spans.map(span => { + return ` ${getName(getParent(span))}-->${getName(span)}`; + }); + + return `graph TD;\n${nodes.join("\n")}`; +} + +class Mermaid { + constructor() { + this.spans = []; + } + + static create() { + return new Mermaid(); + } + + start(dpSpan) { + this.spans.push(dpSpan); + } + + async report(destinationPath) { + const report = graphTD(this.spans); + + if (destinationPath) { + await fs.writeFile(destinationPath, report); + } + + return report; + } +} + +module.exports = { + getParent, + getName, + graphTD, + Mermaid +}; diff --git a/packages/data-point-tracers/mermaid/mermaid.test.js b/packages/data-point-tracers/mermaid/mermaid.test.js new file mode 100644 index 00000000..12a48750 --- /dev/null +++ b/packages/data-point-tracers/mermaid/mermaid.test.js @@ -0,0 +1,98 @@ +const fs = require("fs").promises; +const { getParent, getName, graphTD, Mermaid } = require("./mermaid"); + +function createChildSpan(name, pid, parent) { + return { + name, + parent, + context: { + pid + } + }; +} + +function createSampleSpans() { + const aa = createChildSpan("aa", 1); + aa.root = true; + const bb = createChildSpan("bb", 2, aa); + const cc = createChildSpan("cc", 3, aa); + const dd = createChildSpan("dd", 4, bb); + + const spans = [aa, bb, cc, dd]; + + return spans; +} + +const graphResult = `graph TD; + root-->aa1 + aa1-->bb2 + aa1-->cc3 + bb2-->dd4`; + +describe("getParent", () => { + it("should return `root` if root flag is true", () => { + expect(getParent({ root: true })).toEqual("root"); + }); + it("should return parent if root flag not true", () => { + expect(getParent({ parent: "parent" })).toEqual("parent"); + }); +}); + +describe("getName", () => { + it("should return 'root' if span is root", () => { + expect(getName("root")).toEqual("root"); + }); + it("should return constructed name if span is not root", () => { + const span = createChildSpan("name", 1); + expect(getName(span)).toEqual("name1"); + }); +}); + +describe("graphTD", () => { + it("should create graph tree", () => { + const result = graphTD(createSampleSpans()); + expect(result).toEqual(graphResult); + }); +}); + +describe("Mermaid", () => { + describe("constructor", () => { + it("should create spans array", () => { + const result = new Mermaid(); + expect(result).toHaveProperty("spans", []); + }); + }); + + describe("create", () => { + it("should have static method to create new instance", () => { + const result = Mermaid.create(); + expect(result).toBeInstanceOf(Mermaid); + }); + }); + + describe("start", () => { + it("should track spans", () => { + const result = new Mermaid(); + result.start("span"); + expect(result.spans).toEqual(["span"]); + }); + }); + + describe("report", () => { + it("should create and return mermaid graph", async () => { + const result = new Mermaid(); + result.spans = createSampleSpans(); + expect(await result.report()).toEqual(graphResult); + }); + + it("should save to file", async () => { + const result = new Mermaid(); + result.spans = createSampleSpans(); + + const spyWriteFile = jest.spyOn(fs, "writeFile").mockResolvedValue(true); + + await result.report("/test.mer"); + expect(spyWriteFile).toBeCalledWith("/test.mer", graphResult); + }); + }); +});