Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Starter templates for building reports and dashboards from Moderne CLI telemetry

## Data Source

All templates work with the **trace.csv** produced by the [Moderne CLI](https://docs.moderne.io/user-documentation/moderne-cli/how-to-guides/cli-telemetry). The trace format is hierarchical — each CLI command in the workflow (sync, build, run, apply, commit, push) produces a trace that includes data from all prior stages. `mod publish` produces its own trace branch (sync + build + publish) for LST artifact uploads. See the [data dictionary](data-dictionary/trace-csv.md) for the full column reference.
All templates work with the **trace.csv** produced by the [Moderne CLI](https://docs.moderne.io/user-documentation/moderne-cli/how-to-guides/cli-telemetry). The trace format is hierarchical — each CLI command in the workflow (sync, build, run, apply, commit, push) produces a trace that includes data from all prior stages. `mod publish` produces its own trace branch (sync + build + publish) for LST artifact uploads. Commands can also carry custom `--trace-tag key=value` columns for attribution (see [Trace tags](data-dictionary/trace-csv.md#trace-tags)). See the [data dictionary](data-dictionary/trace-csv.md) for the full column reference.

## Available Templates

Expand All @@ -22,6 +22,7 @@ Templates are sorted by [trace hierarchy](https://docs.moderne.io/user-documenta
| [Top Users](templates/top-users/) | User engagement ranking by recipe runs and commits | `mod git commit` |
| [Top Recipes with Commits](templates/top-recipes-with-commits/) | Recipes that produce real committed code changes | `mod git commit` |
| [Security Recipe Run Trend](templates/security-recipe-run-trend/) | Monthly security remediation trend — committed fixes, repos fixed, and hours saved | `mod git commit` |
| [Tagged Activity](templates/tagged-activity/) | Committed output attributed to a trace tag (e.g. change set, team, region) | `mod git commit` + `--trace-tag` |

## Getting Started

Expand All @@ -47,6 +48,7 @@ moderne-bi-templates/
│ ├── dashboard-kpis-trend.csv
│ ├── recipe-run-trend.csv
│ ├── security-recipe-run-trend.csv
│ ├── tagged-activity.csv
│ ├── top-recipes.csv
│ ├── top-recipes-with-commits.csv
│ └── top-users.csv
Expand All @@ -58,6 +60,7 @@ moderne-bi-templates/
├── dashboard-kpis/
├── recipe-run-trend/
├── security-recipe-run-trend/
├── tagged-activity/
├── top-recipes/
├── top-recipes-with-commits/
└── top-users/ # Each contains README, SQL, notebook, and images/
Expand Down
18 changes: 18 additions & 0 deletions data-dictionary/trace-csv.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,21 @@ Populated after `mod publish`. These columns appear only in traces produced by `
| `organization` | string | Moderne organization identifier | |

**Note:** Push stage columns (`pushOutcome`, `pushStartTime`, `pushEndTime`, `pushId`, `pushElapsedTimeMs`) are present when `mod git push` is used. Traces from `mod exec` and `mod git checkout` are standalone and not part of the main workflow chain.

## Trace tags

Any command that emits a trace accepts the repeatable `--trace-tag key=value` option, which appends a `tag.<key>` column to the trace.csv with the supplied value. Tags let you attribute telemetry to a dimension the CLI does not otherwise capture — a change set, a team, a region, or a ticket id. The option is available on `mod run`, `mod build`, `mod exec`, `mod git apply/add/commit/push/checkout`, `mod publish`, and `mod git sync csv`.

```bash
mod run . --recipe org.openrewrite.java.OrderImports \
--trace-tag changeSetId=CS-2026-0142 \
--trace-tag team=payments
```

The example above adds `tag.changeSetId` and `tag.team` columns. The canonical tag is `changeSetId`: a change set is a higher-level container that can accumulate multiple runs in sequence (recipe run → apply → commit), so tagging each command with the same id lets a report attribute all of that committed output back to one change set. See the [Tagged Activity](../templates/tagged-activity/) template for an example.

| Column | Type | Description | Example |
|--------|------|-------------|---------|
| `tag.<key>` | string | One column per tag supplied via `--trace-tag <key>=<value>` | `tag.changeSetId` = `CS-2026-0142` |

**Querying note:** `tag.<key>` column names contain a dot, which is an identifier separator in SQL, so they must be quoted (`"tag.changeSetId"`) in AWS Athena, Trino, and PostgreSQL. Some loaders sanitize dots to underscores (`tag_changeSetId`); adjust the identifier to match how your table was created.
9 changes: 9 additions & 0 deletions samples/tagged-activity.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
change_set,successful_commits,repos_changed,distinct_recipes,estimated_hours_saved
CS-2026-0142,48,41,3,180.5
CS-2026-0138,37,33,2,142.8
CS-2026-0151,29,27,4,96.2
CS-2026-0129,24,21,1,88.0
CS-2026-0147,19,18,2,61.4
CS-2026-0133,15,14,3,52.7
CS-2026-0155,11,11,1,33.9
CS-2026-0126,8,7,2,21.5
68 changes: 68 additions & 0 deletions templates/tagged-activity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Tagged Activity

Attributes committed recipe output to a **trace tag**, so you can roll commits, repositories, recipes, and estimated hours saved up to whatever dimension you tag runs with — a change set, a team, a region, a ticket. The example groups by `tag.changeSetId`.

## Data Source

This report uses trace data produced by **`mod git commit`** (or later) where runs were tagged on the command line. Commit-stage traces include run-stage data, so committed output can be attributed to the tag.

See the [trace.csv data dictionary](../../data-dictionary/trace-csv.md) for the full column reference, including the [Trace tags](../../data-dictionary/trace-csv.md#trace-tags) section.

## Adding tags

Tags are added with the repeatable `--trace-tag <key>=<value>` option on the commands that emit a trace (`mod run`, `mod build`, `mod exec`, `mod git apply/add/commit/push/checkout`, `mod publish`, and `mod git sync csv`). Each tag becomes a `tag.<key>` column in the trace.csv:

```bash
mod run . --recipe org.openrewrite.java.OrderImports \
--trace-tag changeSetId=CS-2026-0142 \
--trace-tag team=payments
```

A change set is the higher-level container that can accumulate multiple runs in sequence (recipe run → apply → commit), so tagging each command with the same `changeSetId` lets this report attribute all of that committed output back to one change set.

## What This Report Shows

Per-tag committed output, ranked by commit volume:

| Metric | Description |
|--------|-------------|
| **Successful Commits** | Number of commits attributed to the tag value |
| **Repos Changed** | Distinct repositories that received committed changes |
| **Distinct Recipes** | Number of unique recipes that produced committed changes |
| **Estimated Hours Saved** | Total estimated developer time saved by committed changes |

## Suggested Visualization

Grouped bar chart of successful commits and repos changed per tag value, with a line overlay for estimated hours saved on a secondary axis. Cap to the top N tag values when there are many.

![Tagged Activity](images/tagged-activity.png)

See [tagged-activity.ipynb](tagged-activity.ipynb) for a ready-to-run Jupyter notebook that produces this visualization from [sample data](../../samples/tagged-activity.csv).

## Trace.csv Fields Used

| Field | Stage | Purpose |
|-------|-------|---------|
| `tag.changeSetId` | Tag | Group by tag value (swap in any `tag.<key>` column) |
| `commitId` | Commit | Deduplicate to one row per commit, so re-emitted stages (e.g. `mod git push`) are not double-counted |
| `commitOutcome` | Commit | Filter to successful commits |
| `path` | Common | Count distinct for repos changed |
| `runRecipeId` | Run | Count distinct for distinct recipes |
| `runEstimatedEffortTimeSavingsMs` | Run | Sum for estimated hours saved |

## Customization

- **Group by a different tag.** Replace `tag.changeSetId` with any tag you emit, such as `tag.team` or `tag.region`.
- **Quoting.** The column name contains a dot, so it must be quoted as an identifier (`"tag.changeSetId"`) in AWS Athena, Trino, and PostgreSQL. Some loaders sanitize dots to underscores (`tag_changeSetId`); adjust the identifier to match your table.

## Example Output

| change_set | successful_commits | repos_changed | distinct_recipes | estimated_hours_saved |
|------------|--------------------|---------------|------------------|-----------------------|
| CS-2026-0142 | 48 | 41 | 3 | 180.5 |
| CS-2026-0138 | 37 | 33 | 2 | 142.8 |
| CS-2026-0151 | 29 | 27 | 4 | 96.2 |

## Usage

Run `tagged-activity.sql` against your trace data table. The query uses standard SQL compatible with AWS Athena, Trino, PostgreSQL, and most SQL engines.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions templates/tagged-activity/tagged-activity.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Tagged Activity\n",
"\n",
"Committed recipe output grouped by a trace tag \u2014 the example uses `tag.changeSetId`, but any `tag.<key>` column works."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Load sample data \u2014 replace with your own query results\n",
"df = pd.read_csv(\"../../samples/tagged-activity.csv\")\n",
"df = df.sort_values(\"successful_commits\", ascending=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig, ax1 = plt.subplots(figsize=(14, 7))\n",
"\n",
"x = np.arange(len(df))\n",
"bar_width = 0.4\n",
"\n",
"# Grouped bars for commit and repo counts per change set\n",
"ax1.bar(x - bar_width / 2, df[\"successful_commits\"], width=bar_width,\n",
" color=\"#283AF7\", alpha=0.8, label=\"Successful Commits\")\n",
"ax1.bar(x + bar_width / 2, df[\"repos_changed\"], width=bar_width,\n",
" color=\"#E93D82\", alpha=0.8, label=\"Repos Changed\")\n",
"\n",
"ax1.set_xlabel(\"Change Set\", color=\"black\")\n",
"ax1.set_ylabel(\"Count\", color=\"black\")\n",
"ax1.set_xticks(x)\n",
"ax1.set_xticklabels(df[\"change_set\"], rotation=45, ha=\"right\")\n",
"ax1.tick_params(axis=\"both\", labelcolor=\"black\")\n",
"\n",
"# Secondary y-axis for estimated hours saved\n",
"ax2 = ax1.twinx()\n",
"ax2.plot(x, df[\"estimated_hours_saved\"], color=\"#7C3AED\", marker=\"D\",\n",
" linewidth=2, linestyle=\"-\", label=\"Est. Hours Saved\")\n",
"ax2.set_ylabel(\"Estimated Hours Saved\", color=\"black\")\n",
"ax2.tick_params(axis=\"y\", labelcolor=\"black\")\n",
"\n",
"ax1.set_title(\"Tagged Activity (by change set)\", color=\"black\")\n",
"\n",
"# Combine legends from both axes\n",
"lines1, labels1 = ax1.get_legend_handles_labels()\n",
"lines2, labels2 = ax2.get_legend_handles_labels()\n",
"ax1.legend(lines1 + lines2, labels1 + labels2, loc=\"upper right\", fontsize=8)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
44 changes: 44 additions & 0 deletions templates/tagged-activity/tagged-activity.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
-- Tagged Activity
--
-- Groups committed recipe output by a trace tag, so you can attribute commits,
-- repositories, recipes, and estimated hours saved to whatever dimension you tag
-- runs with. The example uses `tag.changeSetId`, the canonical tag injected via
-- `--trace-tag changeSetId=<id>`, but any `tag.<key>` column works (for example
-- `tag.team` or `tag.region`).
--
-- Tags are added on the command line and become extra `tag.<key>` columns in the
-- trace.csv, for example:
-- mod run . --recipe ... --trace-tag changeSetId=CS-2026-0142 --trace-tag team=payments
-- See the README and the data dictionary's "Trace tags" section for details.
--
-- Note: the column name contains a dot, so it must be quoted as an identifier
-- ("tag.changeSetId") in AWS Athena, Trino, and PostgreSQL. Some loaders sanitize
-- dots to underscores (tag_changeSetId) — adjust the identifier to match your table.
--
-- A commit's stage data is re-emitted by every later command (e.g. mod git push
-- carries the commit stage too), so the inner query collapses to one row per
-- commitId first; the counts and sums are then correct even when both commit and
-- push traces are loaded.
--
-- Compatible with: AWS Athena, Trino, PostgreSQL, and other standard SQL engines.

SELECT
"tag.changeSetId" AS change_set,
COUNT(*) AS successful_commits,
COUNT(DISTINCT path) AS repos_changed,
COUNT(DISTINCT runRecipeId) AS distinct_recipes,
ROUND(SUM(runEstimatedEffortTimeSavingsMs) / 3600000.0, 1) AS estimated_hours_saved
FROM (
SELECT
commitId,
MAX("tag.changeSetId") AS "tag.changeSetId",
MAX(path) AS path,
MAX(runRecipeId) AS runRecipeId,
MAX(runEstimatedEffortTimeSavingsMs) AS runEstimatedEffortTimeSavingsMs
FROM trace
WHERE commitOutcome = 'Succeeded'
AND "tag.changeSetId" IS NOT NULL
GROUP BY commitId
) commits
GROUP BY "tag.changeSetId"
ORDER BY successful_commits DESC;