Repository: tonybaloney/wily

Issue:
Allow standalone plotly.min.js when creating graphs
When creating graphs, wily currently inlines the contents of `plotly.min.js` in HTML files, making their size around 3.4MB each. When [creating a lot of graphs](https://gist.github.com/devdanzin/513dfb256686a5d33f727f8247a87184), this quickly adds up to a lot of space. For example, bulk creating graphs for every metric for every file that ever existed in wily's repository takes around 5GB in about 1500 HTML files.

Plotly has an option to create `plotly.min.js` once, in the same directory, and reference it from the HTML file. It's enabled by calling `plotly.offline.plot` with `include_plotlyjs="directory"`. It reduces the size of the same 1500 graphs from 5GB to under 100MB.

I'm not sure adding this feature would be in scope for wily, but I have it working locally and could contribute a PR if it's desirable. It would also add a new CLI option for graph to enable this behavior.


Comments:

Patch:
diff --git a/docs/source/commands/graph.rst b/docs/source/commands/graph.rst
index a0cc9dd..70bdbfb 100644
--- a/docs/source/commands/graph.rst
+++ b/docs/source/commands/graph.rst
@@ -6,11 +6,11 @@ The graph command generates HTML graphs for metrics, trends and data in the wily
 Examples
 --------
 
-``wily graph`` will take 1 or 2 metrics as the 2nd and 3rd arguments. The first metric will be the Y-axis and the 3rd metric (if provided) will control the size of the bubble.
+``wily graph`` will take 1 or 2 comma-separated metrics as the -m option. The first metric will be the Y-axis and the 2nd metric (if provided) will control the size of the bubble.
 
 .. code-block:: none
 
-   $ wily graph example.py loc
+   $ wily graph example.py -m loc
 
 .. image:: ../_static/single_metric_graph.png
    :align: center
@@ -19,7 +19,7 @@ You can provide a second metric which will be used to control the size of the bu
 
 .. code-block:: none
 
-   $ wily graph example.py loc complexity
+   $ wily graph example.py loc,complexity
 
 .. image:: ../_static/two_metric_graph.png
    :align: center
@@ -28,7 +28,7 @@ The x-axis will be the historic revisions (typically git commits) on a scale of
 
 .. code-block:: none
 
-   $ wily graph example.py loc complexity --x-axis sloc
+   $ wily graph example.py -m loc,complexity --x-axis sloc
 
 .. image:: ../_static/custom_x_axis_graph.png
    :align: center
@@ -56,7 +56,15 @@ To save the output to a specific HTML file and not open it, provide the ``-o`` f
 
 .. code-block:: none
 
-   $ wily report example.py loc -o example.html
+   $ wily report example.py -m loc -o example.html
+
+By default, ``wily graph`` will create an HTML file containing all the JS necessary to render the graph.
+To create a standalone plotly.min.js file in the same directory as the HTML file instead, pass the ``--shared-js`´ option.
+To point the HTML file to a CDN hosted plotly.min.js instead, pass the ``--cdn-js`´ option.
+
+.. code-block:: none
+
+   $ wily report example.py -m loc --shared=js
 
 
 Command Line Usage
diff --git a/src/wily/__main__.py b/src/wily/__main__.py
index 1c3b0cc..4489ae5 100644
--- a/src/wily/__main__.py
+++ b/src/wily/__main__.py
@@ -397,6 +397,10 @@ def diff(ctx, files, metrics, all, detail, revision, wrap):
     Graph test.py against raw.loc and raw.sloc on the x-axis
 
         $ wily graph src/test.py -m raw.loc --x-axis raw.sloc
+
+    Graph test.py against raw.loc creating a standalone plotly.min.js file
+
+        $ wily graph src/test.py -m raw.loc --shared-js
     """
     )
 )
@@ -422,14 +426,34 @@ def diff(ctx, files, metrics, all, detail, revision, wrap):
     default=False,
     help=_("Aggregate if path is directory"),
 )
+@click.option(
+    "--shared-js/--no-shared-js",
+    default=False,
+    type=click.BOOL,
+    help=_("Create standalone plotly.min.js in the graph directory."),
+)
+@click.option(
+    "--cdn-js/--no-cdn-js",
+    default=False,
+    type=click.BOOL,
+    help=_("Point to a CDN hosted plotly.min.js."),
+)
 @click.pass_context
-def graph(ctx, path, metrics, output, x_axis, changes, aggregate):
+def graph(ctx, path, metrics, output, x_axis, changes, aggregate, shared_js, cdn_js):
     """Output report to specified HTML path, e.g. reports/out.html."""
     config = ctx.obj["CONFIG"]
 
     if not exists(config):
         handle_no_cache(ctx)
 
+    # Embed plotly.min.js in the HTML file by default
+    plotlyjs = True
+    if shared_js:
+        plotlyjs = "directory"
+    # CDN takes precedence over directory
+    if cdn_js:
+        plotlyjs = "cdn"
+
     from wily.commands.graph import graph
 
     logger.debug("Running report on %s for metrics %s", path, metrics)
@@ -441,6 +465,7 @@ def graph(ctx, path, metrics, output, x_axis, changes, aggregate):
         x_axis=x_axis,
         changes=changes,
         aggregate=aggregate,
+        plotlyjs=plotlyjs,
     )
 
 
diff --git a/src/wily/commands/graph.py b/src/wily/commands/graph.py
index 43bbdbd..b4dc99d 100644
--- a/src/wily/commands/graph.py
+++ b/src/wily/commands/graph.py
@@ -5,7 +5,7 @@ Draw graph in HTML for a specific metric.
 """
 
 from pathlib import Path
-from typing import Optional, Tuple
+from typing import Optional, Tuple, Union
 
 import plotly.graph_objs as go
 import plotly.offline
@@ -38,6 +38,7 @@ def graph(
     changes: bool = True,
     text: bool = False,
     aggregate: bool = False,
+    plotlyjs: Union[bool, str] = True,
 ) -> None:
     """
     Graph information about the cache and runtime.
@@ -50,6 +51,7 @@ def graph(
     :param changes: Only graph changes.
     :param text: Show commit message inline in graph.
     :param aggregate: Aggregate values for graph.
+    :param plotlyjs: How to include plotly.min.js.
     """
     logger.debug("Running graph command")
 
@@ -169,4 +171,5 @@ def graph(
         },
         auto_open=auto_open,
         filename=filename,
+        include_plotlyjs=plotlyjs,  # type: ignore
     )


Test patch:
diff --git a/test/integration/test_graph.py b/test/integration/test_graph.py
index 7596f1b..78b576a 100644
--- a/test/integration/test_graph.py
+++ b/test/integration/test_graph.py
@@ -45,6 +45,36 @@ def test_graph(builddir):
     assert result.exit_code == 0, result.stdout
 
 
+def test_graph_shared_js(builddir):
+    """Test the graph feature with --shared-js option"""
+    runner = CliRunner()
+    with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
+        result = runner.invoke(
+            main.cli,
+            [
+                "--path",
+                builddir,
+                "graph",
+                _path,
+                "-m",
+                "raw.loc",
+                "--shared-js",
+            ],
+        )
+    assert result.exit_code == 0, result.stdout
+
+
+def test_graph_plotlyjs_cdn_js(builddir):
+    """Test the graph feature with --cdn_js option"""
+    runner = CliRunner()
+    with patch.dict("os.environ", values=PATCHED_ENV, clear=True):
+        result = runner.invoke(
+            main.cli,
+            ["--path", builddir, "graph", _path, "-m", "raw.loc", " --cdn_js"],
+        )
+    assert result.exit_code == 0, result.stdout
+
+
 def test_graph_all(builddir):
     """Test the graph feature"""
     runner = CliRunner()