{"id":16584869,"url":"https://github.com/mikaelvesavuori/gitmetrix","last_synced_at":"2025-10-19T15:17:17.517Z","repository":{"id":65362055,"uuid":"583756247","full_name":"mikaelvesavuori/gitmetrix","owner":"mikaelvesavuori","description":"Helps you find your team-level engineering metrics.","archived":false,"fork":false,"pushed_at":"2024-11-12T17:01:46.000Z","size":1896,"stargazers_count":7,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-01T23:51:06.613Z","etag":null,"topics":["devops","engineering-metrics","engops","git-metrics","gitmetrix","metrics","software-delivery"],"latest_commit_sha":null,"homepage":"https://gitmetrix.pages.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikaelvesavuori.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"mikaelvesavuori"}},"created_at":"2022-12-30T20:15:26.000Z","updated_at":"2025-01-14T09:01:40.000Z","dependencies_parsed_at":"2024-09-09T18:32:16.480Z","dependency_job_id":"84ed2d1a-f683-4435-ac16-cd0c68fedb5d","html_url":"https://github.com/mikaelvesavuori/gitmetrix","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikaelvesavuori%2Fgitmetrix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikaelvesavuori%2Fgitmetrix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikaelvesavuori%2Fgitmetrix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikaelvesavuori%2Fgitmetrix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikaelvesavuori","download_url":"https://codeload.github.com/mikaelvesavuori/gitmetrix/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238795300,"owners_count":19531707,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","engineering-metrics","engops","git-metrics","gitmetrix","metrics","software-delivery"],"created_at":"2024-10-11T22:46:05.274Z","updated_at":"2025-10-19T15:17:17.457Z","avatar_url":"https://github.com/mikaelvesavuori.png","language":"TypeScript","funding_links":["https://github.com/sponsors/mikaelvesavuori"],"categories":[],"sub_categories":[],"readme":"# Gitmetrix 🚀 🧑‍🚀 🧑🏿‍🚀 🧑🏻‍🚀 👩‍🚀 📈\n\n![Build Status](https://github.com/mikaelvesavuori/gitmetrix/workflows/main/badge.svg) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mikaelvesavuori_gitmetrix\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=mikaelvesavuori_gitmetrix) [![CodeScene Code Health](https://codescene.io/projects/33250/status-badges/code-health)](https://codescene.io/projects/33250) [![CodeScene System Mastery](https://codescene.io/projects/33250/status-badges/system-mastery)](https://codescene.io/projects/33250) [![codecov](https://codecov.io/gh/mikaelvesavuori/gitmetrix/branch/main/graph/badge.svg?token=1VZWBO88Q8)](https://codecov.io/gh/mikaelvesavuori/gitmetrix) [![Maintainability](https://api.codeclimate.com/v1/badges/adb152e805d9447c83b2/maintainability)](https://codeclimate.com/github/mikaelvesavuori/gitmetrix/maintainability)\n\n## Helps you find your team-level engineering metrics from GitHub.\n\n---\n\nWith Gitmetrix you get the possibility to extract a set of core Git metrics (\"engineering metrics\") for a given repository and time span. An example with completely made-up data might look like this:\n\n```json\n{\n  \"repo\": \"SOMEORG/SOMEREPO\",\n  \"period\": {\n    \"from\": \"20221005\",\n    \"to\": \"20221006\",\n    \"offset\": 0\n  },\n  \"total\": {\n    \"additions\": 74,\n    \"approved\": 136,\n    \"changedFiles\": 187,\n    \"changesRequested\": 158,\n    \"closed\": 146,\n    \"comments\": 100,\n    \"deletions\": 76,\n    \"merged\": 105,\n    \"opened\": 27,\n    \"pickupTime\": \"01:04:57:46\",\n    \"pushed\": 55,\n    \"reviewTime\": \"00:16:05:56\"\n  },\n  \"average\": {\n    \"additions\": 37,\n    \"approved\": 68,\n    \"changedFiles\": 94,\n    \"changesRequested\": 79,\n    \"closed\": 73,\n    \"comments\": 50,\n    \"deletions\": 38,\n    \"merged\": 53,\n    \"opened\": 14,\n    \"pickupTime\": \"00:14:28:53\",\n    \"pushed\": 28,\n    \"reviewTime\": \"00:08:02:58\"\n  },\n  \"daily\": {\n    \"20221005\": {\n      \"additions\": 35,\n      \"approved\": 65,\n      \"changedFiles\": 97,\n      \"changesRequested\": 73,\n      \"closed\": 86,\n      \"comments\": 61,\n      \"deletions\": 12,\n      \"merged\": 66,\n      \"opened\": 18,\n      \"pickupTime\": \"00:22:30:38\",\n      \"pushed\": 3,\n      \"reviewTime\": \"00:03:30:59\"\n    },\n    \"20221006\": {\n      \"additions\": 39,\n      \"approved\": 71,\n      \"changedFiles\": 90,\n      \"changesRequested\": 85,\n      \"closed\": 60,\n      \"comments\": 39,\n      \"deletions\": 64,\n      \"merged\": 39,\n      \"opened\": 9,\n      \"pickupTime\": \"00:06:27:08\",\n      \"pushed\": 52,\n      \"reviewTime\": \"00:12:34:57\"\n    }\n  }\n}\n```\n\nOr in plain English, for each day (or over a given period), you can now answer questions like:\n\n- How many times is code pushed?\n- How many pull requests are opened?\n- How many pull requests are closed?\n- How many pull requests are merged?\n- How many code reviews are approved?\n- How many code reviews are closed?\n- How many code review comments are made?\n\nIt also helps you get some more interesting metrics:\n\n- **Review size**: How many additions/deletions/files changed are there in a pull request that is \"ready for review\"?\n- **Pick-up time**: How long does it take to start doing a code review, from \"ready for review\" to \"review submitted\"?\n- **Review time**: How long does a code review take, from a review being completed to the commit being merged/closed?\n\nAnd it's all quite simple: Just deploy Gitmetrix and pass your repository's GitHub webhooks to it!\n\n## How Gitmetrix works\n\nLike [dorametrix](https://github.com/mikaelvesavuori/dorametrix), Gitmetrix is a serverless web service that collects and represents specific delivery-related webhook events sent to it, which are then stored in a database. As a user, you can request these metrics which are calculated from those same stored events.\n\n**Because all metrics are stored beginning on the date at which you start sending webhook events to Gitmetrix you will not be able to retrieve statistics from any time before that.**\n\nGitmetrix **currently integrates only through GitHub via webhooks and is adapted (out-of-the-box) for an AWS environment**. See the [Support](#support) section for more details — it's not impossible getting it to work in other clouds or Git providers!\n\n## Need even more metrics?\n\n**Looking for DORA metrics?** Then consider [dorametrix](https://github.com/mikaelvesavuori/dorametrix).\n\n**Looking for Individual Contributor metrics from GitHub?** Then consider [this simple Gist](https://gist.github.com/mikaelvesavuori/a0b75f0ebc617e20caab42a2b25c66f3) as a basis.\n\n---\n\n## Prerequisites\n\n- Recent [Node.js](https://nodejs.org/en/) (ideally 18+) installed.\n- Amazon Web Services (AWS) account with sufficient permissions so that you can deploy infrastructure. A naive but simple policy would be full rights for CloudWatch, Lambda, API Gateway, DynamoDB, and S3.\n- Ideally, some experience with [Serverless Framework](https://www.serverless.com) as that's what we will use to deploy the service and infrastructure.\n- You will need to deploy the stack before working with it locally as it uses actual infrastructure even in local mode.\n\n## Installation\n\nClone, fork, or download the repo as you normally would. Run `npm install`.\n\n## Commands\n\nThe below commands are the most critical ones. See `package.json` for more commands! Substitute `npm` for `yarn` or whatever floats your boat.\n\n- `npm start`: Run Serverless Framework in offline mode\n- `npm test`: Run tests on the codebase\n- `npm run deploy`: Deploy with Serverless Framework\n- `npm run build`: Package and build the code with Serverless Framework\n- `npm run teardown`: Removes the deployed stack\n\n## Configuration\n\n### Application settings\n\n#### Required\n\n- `custom.config.awsAccountNumber`: Your AWS account number.\n- `custom.config.apiKey`: The \"API key\" or authorization token you want to use to secure your service.\n\nNote that all unit tests use a separate authorization token that you don't have to care about in regular use.\n\n#### Optional\n\n- `custom.config.maxDateRange`: This defaults to `30` but can be changed.\n- `custom.config.maxLifeInDays`: This defaults to `90` but can be changed.\n- `custom.config.tableName`: This defaults to `gitmetrix` but can be changed.\n\n#### Environment variables\n\n- `REGION`: The AWS region you want to use. Takes the value from `provider.region`.\n- `TABLE_NAME`: The DynamoDB table name you want to use. Takes the value from `custom.config.tableName`.\n- `API_KEY`: Only available in the authorizer function. Takes the value from `custom.config.apiKey`.\n\n## Running locally\n\nRun `npm start`.\n\nNote that it will attempt to connect to a database, so deploy the application and infrastructure before any local development.\n\n## Testing\n\nRun `npm run test` to run all unit tests.\n\n### Create test data\n\nIf you want a bit of test data to toy around with, run `npm run test:createdata`. You can modify the settings of the test data creation by modifying the constants in `tests/createTestData.ts`. This is especially important if you have changed the region of the deployment or the name of the table.\n\n**Note that all primary keys for test data are generated with `SOMEORG/SOMEREPO` as the repository name.**\n\n## Deployment\n\nFirst make sure that you have a fallback value for your AWS account number in `serverless.yml`, for example: `awsAccountNumber: ${opt:awsAccountNumber, '123412341234'}` or that you set the deployment script to use the flag, for example `npx sls deploy --awsAccountNumber 123412341234`.\n\nThen you can deploy with `npm run deploy`.\n\n## Logging and metrics\n\nGitmetrix uses [mikrolog](https://github.com/mikaelvesavuori/mikrolog) and [mikrometric](https://github.com/mikaelvesavuori/mikrometric) for logging and metrics respectively.\n\nLogs will have a richly structured format and metrics for cached and uncached reads will be output to CloudWatch Logs (using Embedded Metrics Format, under the covers). See the below image for a basic example of how you can see the number of uncached vs cached reads in CloudWatch.\n\n![Example of metrics in CloudWatch](./readme/metrics.png)\n\n## Creating the GitHub webhook\n\nCreate a webhook in your repository's `Settings` page. Under the `Code and automation` pane, you should see `Webhooks`. _[See this guide if you need more exact instructions](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks)_.\n\nFor `Payload URL`—assuming you are using the default API endpoint—add your endpoint and auth token in the general format of\n\n```\nhttps://RANDOM.execute-api.REGION.amazonaws.com/STAGE/metrics?authorization=API_KEY\n```\n\nNext, set the content type to `application/json`, skip secrets, make sure SSL is enabled, and select the following event types to trigger the webhook:\n\n- `Issue comments`\n- `Pull requests`\n- `Pull request reviews`\n- `Pushes`\n\n_Note that not all of the individual fine-grained events are actually used, but the above four represent the four overall categories or types we need_.\n\n### Note on security\n\nNormally, if possible, you should use [GitHub webhook secrets](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). These need to be verified against a hash constructed based on the request body and a secret. The \"secret\" is provided by you so this is easy enough to do, but in AWS the Lambda Authorizer will not have access to the request body. This makes it practically unfeasible to implement webhook secrets — for AWS, at least in this way.\n\nThe approach used in Gitmetrix is instead to make the best of the situation and require an `authorization` query string parameter with a custom authorization token. This then gets verified by a [Lambda Authorizer function](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html).\n\nAll GET requests require that same token but in a more practical `Authorization` header.\n\nThis approach adds a minimal security measure but is flexible enough to also work effortlessly with any integration tests you might want to run. At the end of the day an acceptable compromise solution, I hope.\n\n## Using the service\n\n_Remember to pass your authorization token in the `Authorization` header!_\n\n### Example request: From date YYYYMMDD to date YYYYMMDD\n\nGet metrics for a specific interval:\n\n```bash\nGET {BASE_URL}/metrics?repo=SOMEORG/SOMEREPO\u0026from=20221228\u0026to=20221229\n```\n\n| Parameter | Required | Format     | Example                     |  Description                                                   |\n| --------- | -------- | ---------- | --------------------------- | -------------------------------------------------------------- |\n| `repo`    | Yes      | `ORG/REPO` | `mikaelvesavuori/gitmetrix` | Name of repository to get metrics for                          |\n| `from`    | Yes      | `YYYYMMDD` | `20221020`                  | Set a specific date to start from                              |\n| `to`      | Yes      | `YYYYMMDD` | `20221020`                  | Set a specific date to end with (defaults to yesterday's date) |\n\n### Example request: Last X days\n\nGet metrics for a specific sliding window of time:\n\n```bash\nGET {BASE_URL}/metrics?repo=SOMEORG/SOMEREPO\u0026last=30\n```\n\n| Parameter | Required | Format     | Example                     |  Description                               |\n| --------- | -------- | ---------- | --------------------------- | ------------------------------------------ |\n| `repo`    | Yes      | `ORG/REPO` | `mikaelvesavuori/gitmetrix` | Name of repository to get metrics for      |\n| `last`    | Yes      | Number     | `30`                        | Set a number of days to use in query range |\n\n**Note that the last and from/to patterns are mutually exclusive!**\n\n### Offset for time zone differences\n\nYou can optionally offset the query to adapt to your own time zone, for example:\n\n```bash\nGET {BASE_URL}/metrics?repo=SOMEORG/SOMEREPO\u0026last=30\u0026offset=-4\n```\n\n| Parameter | Required | Format                        | Example |  Description                                                  |\n| --------- | -------- | ----------------------------- | ------- | ------------------------------------------------------------- |\n| `offset`  | No       | Number between `-12` and `12` | `30`    | Set an offset in hours to adapt query to time zone difference |\n\n### Example response\n\n```ts\n{\n  // Dynamically set by the response\n  \"repo\": \"SOMEORG/SOMEREPO\",\n  \"period\": {\n    \"from\": \"20221005\",\n    \"to\": \"20221006\",\n    \"offset\": 0\n  },\n  // Aggregated results for the period\n  \"total\": {\n    \"additions\": 74,\n    \"approved\": 136,\n    \"changedFiles\": 187,\n    \"changesRequested\": 158,\n    \"closed\": 146,\n    \"comments\": 100,\n    \"deletions\": 76,\n    \"merged\": 105,\n    \"opened\": 27,\n    \"pickupTime\": \"01:04:57:46\",\n    \"pushed\": 55,\n    \"reviewTime\": \"00:16:05:56\"\n  },\n  \"average\": {\n    \"additions\": 37,\n    \"approved\": 68,\n    \"changedFiles\": 94,\n    \"changesRequested\": 79,\n    \"closed\": 73,\n    \"comments\": 50,\n    \"deletions\": 38,\n    \"merged\": 53,\n    \"opened\": 14,\n    \"pickupTime\": \"00:14:28:53\",\n    \"pushed\": 28,\n    \"reviewTime\": \"00:08:02:58\"\n  },\n  // For each day...\n  \"daily\": {\n    \"20221005\": {\n      \"additions\": 35,\n      \"approved\": 65,\n      \"changedFiles\": 97,\n      \"changesRequested\": 73,\n      \"closed\": 86,\n      \"comments\": 61,\n      \"deletions\": 12,\n      \"merged\": 66,\n      \"opened\": 18,\n      \"pickupTime\": \"00:22:30:38\",\n      \"pushed\": 3,\n      \"reviewTime\": \"00:03:30:59\"\n    },\n    \"20221006\": {\n      \"additions\": 39,\n      \"approved\": 71,\n      \"changedFiles\": 90,\n      \"changesRequested\": 85,\n      \"closed\": 60,\n      \"comments\": 39,\n      \"deletions\": 64,\n      \"merged\": 39,\n      \"opened\": 9,\n      \"pickupTime\": \"00:06:27:08\",\n      \"pushed\": 52,\n      \"reviewTime\": \"00:12:34:57\"\n    }\n  }\n}\n```\n\n---\n\n## Details on the technical implementation\n\n### Anonymous data\n\nGitmetrix does not collect, store, or process any details on a given individual and their work. All data is strictly anonymous and aggregated. You should feel entirely confident that nothing invasive is happening with the data handled with Gitmetrix.\n\n### Data is removed after a period of time\n\nTo keep the volume of data manageable, version `2.1.0` introduces a `maxLifeInDays` setting. It defaults to `90` days, after which DynamoDB will remove the record after the given period + 1 day. You can set the value to any other value, as needed.\n\n### What about the authorization token in the query string parameter?\n\nThis is a totally normal and acceptable way of passing the value. However, the value could potentially be logged by intermediary layers. Gitmetrix does nothing with the value and it's unlikely that there is anything in the AWS infrastructure-as-code that logs the value either.\n\n### Metrics and history\n\n**The most recent date you can get metrics for is the day prior, i.e. \"yesterday\"**. The reason for this is partly because it makes no real sense to get incomplete datasets, as well as because Gitmetrix caches all data requests. Caching a dataset with incomplete data would not be very good.\n\n### Time\n\n#### Time zone used\n\nGitmetrix uses UTC/GMT+0/Zulu time.\n\n#### How timestamps are set\n\nTimestamps are set internally in Gitmetrix and generated based on the UTC/GMT+0/Zulu time.\n\nTo cater for more precise queries, you can use the `offset` parameter with values between `-12` and `12` (default is `0`) to adjust for a particular time zone.\n\n### Database design\n\n| Primary Key          | Secondary Key      | Attribute names |\n| -------------------- | ------------------ | --------------- |\n| `METRICS_{ORG/REPO}` | `{Unix timestamp}` | See below       |\n\nAttribute names are shortened and may look a bit mysterious, but it's really just about optimizing them to the smallest values so that they don't eat unnecessary bandwidth, especially if you are fetching longer periods.\n\nThe below outlines all of the attributes on a given day such as `20221020`:\n\n| Attribute | Type    |  Description           |\n| --------- | ------- | ---------------------- |\n|  `pk`     | String  | Primary key (system)   |\n|  `sk`     | String  | Sort key (system)      |\n|  `p`      | Number  | Pushed                 |\n|  `o`      | Number  | Opened                 |\n|  `m`      | Number  | Merged                 |\n|  `cl`     | Number  | Closed                 |\n|  `cm`     | Number  | Commented              |\n|  `ap`     | Number  | Approved               |\n|  `chr`    | Number  | Changes requested      |\n|  `ad`     | Number  | Additions              |\n|  `chf`    | Number  | Changed files          |\n|  `d`      | Number  | Deletions              |\n|  `pt`     | Number  | Pickup time in seconds |\n|  `rt`     | Number  | Review time in seconds |\n\nMetrics are [incremented atomically](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters).\n\n### Caching\n\nOn any given metrics retrieval request, Gitmetrix will behave in one of two ways:\n\n- **Cached filled**: Return the cached content.\n- **Cache empty**: Query \u003e Store response in cache \u003e Return response.\n\nCaching is always done for a range of dates. All subsequent lookups will use the cached data only if the exact same \"from\" and \"to\" date ranges are cached.\n\n| Primary Key                 | Secondary Key           | Value (example)           |\n| --------------------------- | ----------------------- | ------------------------- |\n| `METRICS_CACHED_{ORG/REPO}` | `{FROM_DATE}_{TO_DATE}` | `Items` array of response |\n\n## How the metrics are calculated\n\nThe majority of metrics are very simple additions to numeric counts. Beyond these basic ones, there are also a few that need to do a bit more, ending up with 2 or more calculations for a single change.\n\nThe basic ones are:\n\n| Add +1 to | When                      |\n| --------- | ------------------------- |\n|  `p`      | Code is pushed            |\n|  `m`      | Code is merged            |\n|  `o`      | GitHub Issue is opened    |\n|  `cl`     | GitHub Issue is closed    |\n|  `cm`     | GitHub Issue gets comment |\n\nThe somewhat more complicated ones are detailed below.\n\n### Review size (PR size)\n\n_Known when a PR review is opened/requested_.\n\nMeasures the number of concrete file-level changes in files for a given PR review.\n\n#### Matches:\n\n| Webhook        | Action             | PR State |\n| -------------- | ------------------ | -------- |\n| `pull_request` | `ready_for_review` | `open`   |\n\n#### Affects:\n\n| Attribute | Description   |\n| --------- | ------------- |\n|  `ad`     | Additions     |\n|  `chf`    | Changed files |\n|  `d`      | Deletions     |\n\nAdds the numeric values from `body.pull_request.additions`, `body.pull_request.deletions`, and `body.pull_request.changed_files` to their current daily values.\n\n### Pick-up time\n\n_Known when a review is approved or changes are requested_.\n\nMeasures the time from opening a PR to submitting the first PR review (i.e. approving or requesting changes).\n\n#### When a change is approved - Matches:\n\n| Webhook               | Action      | Review State |\n| --------------------- | ----------- | ------------ |\n| `pull_request_review` | `submitted` | `approved`   |\n\n#### When a change is approved - Affects:\n\n| Attribute | Description                     |\n| --------- | ------------------------------- |\n|  `pt`     | Pickup time                     |\n|  `ap`     | Pull request review is approved |\n\n#### When changes are requested - Matches:\n\n| Webhook               | Action      | Review State        |\n| --------------------- | ----------- | ------------------- |\n| `pull_request_review` | `submitted` | `changes_requested` |\n\n#### When changes are requested - Affects:\n\n| Attribute | Description                                  |\n| --------- | -------------------------------------------- |\n|  `pt`     | Pickup time                                  |\n|  `chr`    | Pull request review gets \"Changes requested\" |\n\nCompares the diff between `body.pull_request.created_at` and `body.review.submitted_at` and adds this difference in seconds to the current value of `PICKUP_TIME_{ORG/REPO}`.\n\n### Review time\n\n_Known when a PR is closed and we have some merge and comment activity to measure._\n\nMeasures the time from the initial PR code review to when the PR is merged. While technically we don't need PR comments, without them effectively we can't infer a review even took place. This is imperfect but better than not having such a safeguard.\n\n#### Matches:\n\n| Webhook        | Action   | PR State | Conditions                                                                                  |\n| -------------- | -------- | -------- | ------------------------------------------------------------------------------------------- |\n| `pull_request` | `closed` | `closed` | `body.pull_request.merged_at` is not empty, i.e. it's not just closed, it's actually merged |\n|                |          |          | `body.pull_request.review_comments` is more than zero                                       |\n\n#### Affects:\n\n| Attribute | Description             |\n| --------- | ----------------------- |\n|  `rt`     | Review time             |\n|  `m`      | Merged (only if merged) |\n|  `c`      | Closed                  |\n\nCompares the diff between `body.pull_request.created_at` and `body.pull_request.merged_at`.\n\n---\n\n## Diagrams\n\n### Solution diagram\n\n_As it stands currently, Gitmetrix is implemented in an AWS-oriented manner. This should be fairly easy to modify so it works with other cloud platforms and with other persistence technologies. If there is sufficient demand, I might add extended support. Or you do it! Just make a PR and I'll see how we can proceed._\n\n![\"gitmetrix diagram\"](./diagrams/gitmetrix-diagram.png)\n\n### Code flow diagram\n\nThe below diagram is generated by [Madge](https://github.com/pahen/madge).\n\n![\"gitmetrix code diagram\"](./diagrams/code-diagram.svg)\n\nPlease see the [generated documentation site](https://gitmetrix.pages.dev) for more detailed information.\n\n---\n\n## Support\n\n### What about more Git integrations?\n\nGitmetrix **currently integrates only through GitHub via webhooks**. The internal logic however allows for extending with any number of \"parsers\" that are specific to any version control software (VCS) such as Bitbucket or Azure DevOps. Ideally, to function similarly, the VCS should support webhooks so the experience is equivalent to the current state of Gitmetrix.\n\n_Consider making a pull request, starting an Issue, or otherwise informing of your interest in this, if it's important to you or if you have ideas for resolving this in a good way._\n\n### What about using a non-AWS stack?\n\nThat's absolutely doable!\n\nThe code is already prepared to be extensible for other databases (repositories) and other compute solutions than AWS Lambda. You could relatively easily make the changes by adding a repository to handle the concrete implementation details of your chosen database and adding some other variant of the wrapping handler functions, while still being able to use all the same internal logic. Except for these bigger details, there might be smaller stuff we need to take care of to make Gitmetrix truly support more platforms—but none of this is a real blocker.\n\n_Consider making a pull request, starting an Issue, or otherwise informing of your interest in this, if it's important to you or if you have ideas for resolving this in a good way._\n\n---\n\n## Ideas for improvements\n\n- \"Direct parser\", for direct API calls rather than using webhooks?\n- \"Coding time metric\", measuring the time between an initial commit and when a PR is ready to review?\n- Integration and system tests?\n- Cache with offset - currently caches on date range/timestamp range, but the query will be incorrect if using other (subsequent) offset\n\n---\n\n## References\n\n- [GitHub: Webhook events and payloads](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikaelvesavuori%2Fgitmetrix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikaelvesavuori%2Fgitmetrix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikaelvesavuori%2Fgitmetrix/lists"}