Skip to content
Open
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
11 changes: 11 additions & 0 deletions docs/DORA.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ If you are not familiar with Grafana, please refer to our [Grafana doc](Configur

<br/><br/>

## DORA for a monorepo

DevLake measures DORA at the project level, and a project's scope is a repository. If a
single repository hosts several logically separate projects — each with its own deploy job
and its own PR label — all of them collapse into one set of DORA numbers.

The [monorepo plugin](Plugins/monorepo.md) splits such a repository into sub-projects and
computes per-sub-project Deployment Frequency and Lead Time for Changes.

<br/>

## Try it Out

To create the DORA dashboard with your own toolchain, please look at the [configuration tutorial](Configuration/Tutorial.md) and [project organization documentation](Configuration/HowToOrganizeDevlakeProjects.md) for more details.
Expand Down
17 changes: 17 additions & 0 deletions docs/DataModels/DevLakeDomainLayerSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ Pull requests are the abstraction of GitHub pull requests, GitLab merge requests
| `head_ref` | varchar | 255 | The branch name in the head repo that contains the changes that will be added to the base | |
| `base_commit_sha` | char | 40 | The base commit of this PR. | |
| `head_commit_sha` | char | 40 | The head commit of this PR. | |
| `sub_project` | varchar | 100 | The monorepo sub-project this PR belongs to, matched by label. Written by the [monorepo plugin](../Plugins/monorepo.md); NULL when the project has no monorepo configuration. | |

#### pull_request_labels

Expand All @@ -489,6 +490,7 @@ The list is additive. This means if a rebase with commit squashing takes place a
| `commit_author_name` | varchar | 255 | The name of the person who authored the commit | |
| `commit_author_email` | varchar | 255 | The email address of the person who authored the commit. | |
| `commit_authored_date` | varchar | 255 | The date and time when the commit was authored. | |
| `sub_project` | varchar | 100 | Mirrors the owning pull request's `sub_project`. | |

#### pull_request_comments

Expand Down Expand Up @@ -656,6 +658,20 @@ Normally, one deployment only deploy to one repo. But in some cases, one deploym
| `repo_url` | varchar | 191 | The url of the repo | |
| `prev_success_deployment_commit_id` | varchar | 255 | The last successful deployment_commit_id before this one in the same `cicd_scope`, `repo` and `environment`, which is used to calculate the new commits deployed by this deployment, thereby measuring [DORA - change lead time](../Metrics/LeadTimeForChanges.md). | |

#### cicd_deployment_subprojects

Maps a deployment to the monorepo sub-project(s) it deployed. Rows are written by the [monorepo plugin](../Plugins/monorepo.md), which matches the name of the deploying CI job against each sub-project's configured pattern.

The relationship is many-to-many: a pipeline that runs the deploy jobs of several sub-projects produces one row per sub-project, so dashboards can `GROUP BY sub_project` without double counting.

Projects without monorepo configuration have no rows in this table at all; dashboards treat a missing mapping as the single, implicit `All` group.

| **field** | **type** | **length** | **description** | **key** |
|:---------------------|:---------|:-----------|:------------------------------------------------------------------------------------------|:--------|
| `project_name` | varchar | 100 | The project this mapping belongs to | PK |
| `cicd_deployment_id` | varchar | 255 | The deployment, matching [table.cicd_deployment_commits](#cicd_deployment_commits).`cicd_deployment_id` | PK |
| `sub_project` | varchar | 100 | The sub-project this deployment deployed | PK |

### Domain 5 - Code Quality

The names of tables in the 'Code Quality' domain will start with a prefix cq\_
Expand Down Expand Up @@ -929,6 +945,7 @@ metrics, such as _'No. of Issue closed by contributor', 'No. of commits by contr
| `deployment_id` | longtext | | The id of cicd_task which deploy the commits of this PR | |
| `pr_deploy_time` | bigint | | The time it takes from when a PR is merged to when it is deployed | |
| `pr_cycle_time` | bigint | | The total time from the first commit to when the PR is deployed | |
| `sub_project` | varchar | 100 | The monorepo sub-project of this PR, copied from [table.pull_requests](#pull_requests) | |

#### project_issue_metrics

Expand Down
147 changes: 147 additions & 0 deletions docs/Plugins/monorepo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: "Monorepo"
description: >
Monorepo Plugin
---

## Summary

DevLake's scope model is one scope per repository, so a repository that hosts several
logically separate projects collapses into a single set of DORA numbers. The `monorepo`
plugin splits such a repository into **sub-projects** and computes per-sub-project
delivery metrics.

Like [DORA](../DORA.md) and [Linker](./linker.md), it is a metric plugin: it works on the
domain layer after data collection, and runs after `dora`.

It attributes:

- **Deployments** to a sub-project by matching the name of the CI job that performed the
deployment against a regular expression.
- **Pull requests** to a sub-project by matching the PR's labels.

From those two attributions it produces per-sub-project
[Deployment Frequency](../Metrics/DeploymentFrequency.md) and
[Lead Time for Changes](../Metrics/LeadTimeForChanges.md).

[Change Failure Rate](../Metrics/CFR.md) and
[Median Time to Restore Service](../Metrics/MTTR.md) are **not** computed per sub-project,
because they require incident data and incidents are not attributed to sub-projects.

## Supported Versions

The monorepo plugin was added after the `v1.0.3-beta18` release and is available in all
later releases.

## Configuration

The plugin is configured per project, on the project's settings page.

1. Open **Projects** → your project → **Settings**.
2. Tick **Enable Monorepo Sub-Projects**.
3. Add one row per sub-project and fill in the three fields below.
4. Click **Save**, then re-run the project's blueprint.

| Field | Description | Example |
|:--------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:---------------------|
| **Sub-project name** | Identifies the sub-project in the output tables and dashboards. Must be unique within the project. | `serviceA` |
| **Labels** | Comma-separated pull request labels that mark a PR as belonging to this sub-project. Matching is **exact and case-sensitive**. | `serviceA,svc-a` |
| **Deploy job pattern** | A [Go regular expression](https://pkg.go.dev/regexp/syntax) matched against `cicd_tasks.name` to recognise this sub-project's deploy jobs. | `^deploy-serviceA$` |

`unattributed` and `All` are reserved and cannot be used as sub-project names.

The same options can be set through the API or the Config UI's
[advanced mode](../Configuration/AdvancedMode.md) as the project's `monorepo` metric
plugin option:

```json
{
"pluginName": "monorepo",
"enable": true,
"pluginOption": {
"subProjects": [
{
"name": "serviceA",
"prLabels": ["serviceA", "svc-a"],
"deployJobPattern": "^deploy-serviceA$"
},
{
"name": "serviceB",
"prLabels": ["serviceB"],
"deployJobPattern": "^deploy-serviceB$"
}
],
"includeUnattributed": true
}
}
```

`includeUnattributed` (default `true`) controls what happens to pull requests and
deployments that belong to the project but match none of the configured sub-projects:
with `true` they are recorded under the sentinel sub-project `unattributed`, so you can
see how much of the repository is not yet classified; with `false` they are left
unclassified.

## How attribution works

### Deployments

Every deployment of the project is joined to the CI/CD tasks of the pipeline that produced
it, and each task of type `Deployment` has its name matched against each sub-project's
`deployJobPattern`.

- A pipeline that runs the deploy jobs of several sub-projects produces **one row per
sub-project**, so dashboards can `GROUP BY sub_project` without double counting.
- Deployments imported directly from a provider's deployment API (rather than generated
from a pipeline) carry no job name and cannot be attributed.

### Pull requests

All pull requests of the project are attributed — open, closed and merged — by label.
A pull request belongs to exactly one sub-project: when it carries the labels of more than
one, the **first matching sub-project in the configured order wins**, so order the list
from most to least specific.

PR coding, pickup, review, deploy and cycle time are not recomputed by this plugin; they
are taken from DORA's own `project_pr_metrics` rows and tagged with the sub-project.

### Mismatch warnings

After tagging, the plugin cross-checks each PR's label-derived sub-project against the
sub-project of the deployment that DORA says actually shipped it. Disagreements are logged
as warnings and never fail the pipeline — labels always win. They usually point at a
`prLabels` or `deployJobPattern` entry that needs fixing, though a pipeline deploying
several sub-projects at once will produce such warnings legitimately.

## Output

| Table | Description |
|:------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------|
| [`cicd_deployment_subprojects`](../DataModels/DevLakeDomainLayerSchema.md#cicd_deployment_subprojects) | Maps each deployment to the sub-project(s) it deployed. |
| [`pull_requests.sub_project`](../DataModels/DevLakeDomainLayerSchema.md#pull_requests) | The sub-project a pull request belongs to. |
| [`pull_request_commits.sub_project`](../DataModels/DevLakeDomainLayerSchema.md#pull_request_commits) | Mirrors the owning pull request's sub-project. |
| [`project_pr_metrics.sub_project`](../DataModels/DevLakeDomainLayerSchema.md#project_pr_metrics) | Tags DORA's per-PR metrics with the sub-project. |

A `NULL` `sub_project` means the row was never classified — single-repo projects, or rows
the plugin has not processed. Dashboards render these as the single implicit group `All`
via `COALESCE(sub_project, 'All')`.

Attribution is rebuilt from scratch on every run, so changing the configuration and
re-running the blueprint reclassifies existing data.

:::note
The plugin also populates `monorepo_subproject_deployments` and
`monorepo_subproject_pr_metrics`. Both are deprecated and will be removed in a future
release — query the tables above instead.
:::

## Dashboard

A built-in Grafana dashboard, **Monorepo Sub-Projects**, visualises the output:

- Deployment Count by Sub-Project
- Deployment Frequency over Time
- Change Lead Time Breakdown by Sub-Project
- Unattributed PRs
- Awaiting Deployment
- Attributed Deployments