diff --git a/gitlab_pipeline_observability_terraform/.gitignore b/gitlab_pipeline_observability_terraform/.gitignore new file mode 100644 index 0000000..63a0b0a --- /dev/null +++ b/gitlab_pipeline_observability_terraform/.gitignore @@ -0,0 +1,6 @@ +# terraform ignores +terraform.* +.terraform** + +# dynatrace terraform provider export directory +configuration \ No newline at end of file diff --git a/gitlab_pipeline_observability_terraform/README.md b/gitlab_pipeline_observability_terraform/README.md new file mode 100644 index 0000000..354cfb8 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/README.md @@ -0,0 +1,203 @@ +# Observe your GitLab pipelines and merge requests with Dashboards and normalized SDLC events through OpenPipeline + +Excited to dive into your GitLab pipeline performance and uncover the secrets behind your merge request timings? For this use case, you'll + +* Integrate GitLab and Dynatrace. +* Use Dashboards to observe GitLab pipelines and merge requests. +* Use this information to make decisions about streamlining CI/CD pipelines, improving productivity, and getting data-driven insights. + +## Concepts + + +| Concept | Description | +|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Software Development Lifecycle (SDLC) events | [SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events) are events with a separate event kind in Dynatrace that follow a well-defined semantic for capturing data points from a software component's software development lifecycle. The [SDLC event specification](https://docs.dynatrace.com/docs/discover-dynatrace/references/semantic-dictionary/model/sdlc-events) defines the semantics of those events. | +| Why were GitLab webhook events changed into SDLC events? | The main benefit is data normalization and becoming tool agnostic. As a result, Dynatrace Dashboards, Apps, and Workflows can build on SDLC events with well-defined properties rather than tool-specific details. | +| Why going with GitLab webhooks instead of REST API? | Using webhooks has the following advantages over using the API: (1) Webhooks require less effort and less resources than polling an API. (2) Webhooks scale better than API calls. (3) Webhooks allow near real-time updates, since webhooks are triggered when an event happens. See [Choosing webhooks or the REST API](https://docs.github.com/en/webhooks/about-webhooks#choosing-webhooks-or-the-rest-api) for more details. | + +## Target audience + +This information is intended for platform engineers who use GitLab in their Internal Development Platform (IDP). + +## What will you learn + +In this tutorial, you'll learn how to + +* Forward GitLab webhook events to Dynatrace. +* Normalize the ingested event data. +* Use Dashboards to analyze the data and derive improvements. + +## Prerequisites + +* [Install Dynatrace Configuration as Code via Terraform](https://docs.dynatrace.com/docs/deliver/configuration-as-code/terraform/terraform-cli) + +## Setup + +### Prepare the Terraform configuration. + +1. [Create a Platform token](https://docs.dynatrace.com/docs/deliver/configuration-as-code/terraform/terraform-api-support-access-permission-handling#create-platform-tokens) with the following permissions. + * Run apps: `app-engine:apps:run` + * View OpenPipeline configurations: `settings:objects:read` + * Edit OpenPipeline configurations: `settings:objects:write` + * Create and edit documents: `document:documents:write` + * View documents: `document:documents:read` + +2. Store the retrieved platform token in an environment variable. + + Windows: + ``` + $env:DYNATRACE_PLATFORM_TOKEN='' + ``` + + Linux / macOS: + ``` + export DYNATRACE_PLATFORM_TOKEN='' + ``` + +3. Store your Dynatrace environment URL in an environment variable. Make sure to replace `` with your Dynatrace environment ID, e.g. `abc12345`. + + Windows: + ``` + $env:DYNATRACE_ENV_URL='https://.apps.dynatrace.com' + ``` + + Linux / macOS: + ``` + export DYNATRACE_ENV_URL='https://.apps.dynatrace.com' + ``` + +4. Clone the [Dynatrace configuration as code sample](https://github.com/Dynatrace/dynatrace-configuration-as-code-samples) repository using the following commands and move to the `gitlab_pipeline_observability_terraform` directory. + ``` + git clone https://github.com/Dynatrace/dynatrace-configuration-as-code-samples.git + cd dynatrace-configuration-as-code-samples/gitlab_pipeline_observability_terraform + ``` + +### Check the OpenPipeline configuration for SDLC events + +> These steps modify the OpenPipeline configuration for SDLC events. +If your OpenPipeline configuration contains only default/built-in values, you can directly apply the Terraform configuration. If you have any dynamic routes, you'll first need to download your configuration and manually merge it into the provided Terraform configuration. + +> Step 3 will indicate if a configuration merge is needed or if you can apply the provided configuration directly. + +1. Go to **Settings** > **Process and contextualize** > **OpenPipeline** > **Software Development Lifecycle**. +2. Check the **Dynamic routing** section, are there any other routes than **Default route**? +3. If the answer is "yes", follow the steps below. Otherwise, skip ahead to step 4. + * Download your OpenPipeline configuration. You will need to have set up `DYNATRACE_ENV_URL` and `DYNATRACE_PLATFORM_TOKEN` environment variables according to the [Dynatrace Terraform provider installation guide](https://docs.dynatrace.com/docs/deliver/configuration-as-code/terraform/terraform-api-support-access-permission-handling#create-platform-tokens). + Then you can use the [export utility of the Dynatrace Terraform provider](https://docs.dynatrace.com/docs/shortlink/terraform-cli-commands#export) to download all routing entries for SDLC events. + Navigate to the directory that includes the `terraform-provider-dynatrace_vx.y.z` binary and use it to export the `dynatrace_openpipeline_v2_events_sdlc_routing` configurations. E.g. + ``` + ./terraform-provider-dynatrace_v1.86.0 -export dynatrace_openpipeline_v2_events_sdlc_routing + ``` + You should see an output similar to this + ```shell + The environment variable DYNATRACE_TARGET_FOLDER has not been set - using folder 'configuration' as default + Downloading "dynatrace_openpipeline_v2_events_sdlc_routing" Count: 1 + Post-Processing Resources ... + - [POSTPROCESS] dynatrace_openpipeline_v2_events_sdlc_routing - openpipeline_v2_events_sdlc_routingPost-Processing Resources - Group child configs with parent configs ... + Finishing touches ... + Writing ___resources___.tf + Writing ___datasources___.tf + Writing main.tf + Writing ___variables___.tf + Writing main ___providers___.tf + Writing modules ___providers___.tf + Remove Non-Referenced Modules ... + Finish Export ... + Terraform executable path: /usr/local/bin/terraform + Executing 'terraform init' + ... finished after 3 seconds + ``` + * The export utility has created a folder `configuration`. You can now open the following files: + * Your downloaded configuration file containing all available routing entries for SDLC events `./configuration/modules/openpipeline_v2_events_sdlc_routing/openpipeline_v2_events_sdlc_routing.openpipeline_v2_events_sdlc_routing.tf`. + * The provided file `main.tf` which contains the routing configurations for the GitLab pipelines. + * Merge all `routing_entry` blocks of your downloaded routing file into the `routing_entries` block of the resource `events_sdlc_global_routing_table` in `main.tf`, and then save the file. + This is mandatory as the **Dynamic Routing table** is a global configuration and the order of the entries as well as the `matcher` clauses determine the overall routing. +4. Apply the Terraform configuration. + Run this command to apply the provided Terraform configuration. + The configuration consists of (1) Dashboards to analyze GitLab activities and (2) OpenPipeline configuration to normalize [GitLab events](https://docs.gitlab.com/user/project/integrations/webhook_events/) into [SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events). + ``` + terraform apply + ``` + +### Create a Dynatrace access token + +An access token with *openpipeline scopes* is needed for Dynatrace to receive GitLab webhook events processed by OpenPipeline. + +1. In Dynatrace, navigate to **Access Tokens**. +2. Click **Generate new token**. +3. Provide a descriptive name for your token. +4. Select the following scopes: + * OpenPipeline - Ingest Software Development Lifecycle Events (Built-in)(`openpipeline.events_sdlc`) + * OpenPipeline - Ingest Software Development Lifecycle Events (Custom)(`openpipeline.events_sdlc.custom`) +5. Click **Generate token** +6. Save the generated token securely for subsequent steps. It will be referred as ``. + +### Create the GitLab webhook + +1. [Create the GitLab webhook](https://dt-url.net/yt23w6x) with the following settings + * **URL**: enter your placeholders for your Dynatrace environment ID `` and access token ``. + ``` + https://.live.dynatrace.com/platform/ingest/custom/events.sdlc/gitlab + ``` + * You can enter an optional webhook name and description, but skip the **Secret token** setting since a custom header manages request validation. + * In the **Trigger** section, select the following events to trigger the webhook. + * **Merge request events** + * **Job events** + * **Pipeline events** + * **Deployment events** + * **Releases events** + +2. [Add custom header](https://dt-url.net/5203zv5) to your webhook with the name `Authorization` and value `Api-Token `. + +## Unlock enhanced development insights with GitLab Dashboards + +Now that you've successfully configured GitLab and Dynatrace, you can use Dashboards and SDLC events to observe your GitLab pipelines and merge requests within the entire development organization. + +### Analyze + +In Dynatrace, open the **GitLab Pipeline Pulse** and **GitLab Merge Request** dashboards to: + +* Track real-time activities of merge requests. +* Analyze CI/CD pipeline execution details and pipeline health. +* Gain job insights. +* Review step durations for pipelines. +* Analyze deployment and release activities + +| Pipeline details: | Job insights: | Merge request insights: | +|----------------------------------------------------------|-----------------------------------------------------|----------------------------------------------| +| ![image](images/pipeline_dashboard_pipeline_details.png) | ![image](images/pipeline_dashboard_job_details.png) | ![image](images/merge_request_dashboard.png) | + +### Optimize + +Leverage those insights for the following improvement areas: + +* **Streamline CI/CD pipeline**: Observing pipeline executions lets you identify bottlenecks and inefficiencies in your CI/CD pipelines. + Knowing about these bottlenecks and inefficiencies helps optimize build and deployment processes, leading to faster and more reliable releases. + +* **Improve developer productivity**: Automated pipelines reduce the manual effort required for repetitive tasks, such as running tests and checking coding standards. + This automation allows developers to focus more on writing code and less on administrative tasks. + +* **Get data-driven development insights**: Analyzing telemetry data from merge requests and pipelines provides valuable insights into the development process. + You can use the telemetry data to make informed decisions and continuously improve the development flows. + +### Continuous improvements + +Regularly review and tweak your CI/CD pipelines to ensure they are optimized for performance. + +In Dynatrace, adjust the timeframe of the **GitLab Pipeline Pulse** and **GitLab Merge Request** dashboards to monitor the long-term impact of your improvements. + +## Call to action + +We highly value your insights on GitLab pipeline observability. Your feedback is crucial in helping us enhance our tools and services. Visit the Dynatrace Community page to share your experiences, suggestions, and ideas directly on [Feedback channel for CI/CD Pipeline Observability](https://community.dynatrace.com/t5/Platform-Engineering/Feedback-channel-for-CI-CD-Pipeline-Observability/m-p/269193). + +## Further reading + +**Pipeline Observability** + +* [Observability throughout the software development lifecycle increases delivery performance](https://www.dynatrace.com/news/blog/observability-throughout-the-software-development-lifecycle/) (blog post) +* [Concepts](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/pipeline-observability-concepts) (docs) + +**Software Development Lifecycle Events** + +* [Ingest SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events) (docs) +* [SDLC event specification](https://docs.dynatrace.com/docs/discover-dynatrace/references/semantic-dictionary/model/sdlc-events) (docs) \ No newline at end of file diff --git a/gitlab_pipeline_observability_terraform/dashboards.gitlab.tf b/gitlab_pipeline_observability_terraform/dashboards.gitlab.tf new file mode 100644 index 0000000..4e28df0 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/dashboards.gitlab.tf @@ -0,0 +1,20 @@ +resource "dynatrace_document" "dashboard_gitlab_deployments" { + type = "dashboard" + name = "GitLab Deployments" + custom_id = "gitlab-deployments" + content = file("${path.module}/dashboards/gitlab.deployments.json") +} + +resource "dynatrace_document" "dashboard_gitlab_pipeline_pulse" { + type = "dashboard" + name = "GitLab Pipeline Pulse" + custom_id = "gitlab-pipeline-pulse" + content = file("${path.module}/dashboards/gitlab.workflow.json") +} + +resource "dynatrace_document" "dashboard_gitlab_merge_requests" { + type = "dashboard" + name = "GitLab Merge Requests" + custom_id = "gitlab-merge-requests" + content = file("${path.module}/dashboards/gitlab.merge_request.json") +} diff --git a/gitlab_pipeline_observability_terraform/dashboards/gitlab.deployments.json b/gitlab_pipeline_observability_terraform/dashboards/gitlab.deployments.json new file mode 100644 index 0000000..43d9e29 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/dashboards/gitlab.deployments.json @@ -0,0 +1,1143 @@ +{ + "importedWithCode": false, + "layouts": { + "0": { + "h": 3, + "w": 6, + "x": 0, + "y": 2 + }, + "1": { + "h": 6, + "w": 9, + "x": 6, + "y": 2 + }, + "10": { + "h": 2, + "w": 24, + "x": 0, + "y": 31 + }, + "11": { + "h": 7, + "w": 24, + "x": 0, + "y": 24 + }, + "12": { + "h": 6, + "w": 9, + "x": 15, + "y": 18 + }, + "13": { + "h": 4, + "w": 3, + "x": 3, + "y": 33 + }, + "14": { + "h": 4, + "w": 9, + "x": 6, + "y": 33 + }, + "15": { + "h": 4, + "w": 9, + "x": 15, + "y": 33 + }, + "16": { + "h": 3, + "w": 6, + "x": 0, + "y": 5 + }, + "17": { + "h": 6, + "w": 9, + "x": 6, + "y": 18 + }, + "18": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "19": { + "h": 5, + "w": 9, + "x": 15, + "y": 13 + }, + "2": { + "h": 6, + "w": 9, + "x": 15, + "y": 2 + }, + "20": { + "h": 5, + "w": 6, + "x": 0, + "y": 13 + }, + "21": { + "h": 8, + "w": 24, + "x": 0, + "y": 43 + }, + "3": { + "h": 5, + "w": 15, + "x": 0, + "y": 8 + }, + "4": { + "h": 6, + "w": 3, + "x": 0, + "y": 18 + }, + "5": { + "h": 6, + "w": 3, + "x": 3, + "y": 18 + }, + "6": { + "h": 4, + "w": 3, + "x": 0, + "y": 33 + }, + "7": { + "h": 5, + "w": 9, + "x": 15, + "y": 8 + }, + "8": { + "h": 5, + "w": 9, + "x": 6, + "y": 13 + }, + "9": { + "h": 6, + "w": 24, + "x": 0, + "y": 37 + } + }, + "settings": {}, + "tiles": { + "0": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Finished Deployments", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "count()", + "labelMode": "none", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "1": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize count() , by:{task.outcome}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Status", + "type": "data", + "visualization": "donutChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "task.outcome" + ], + "categoryAxisLabel": "task.outcome", + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": { + "canceled": { + "added": 15472341.400000006, + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-blue-steel-color-05-default, #134fc9)" + } + }, + "failure": { + "added": 5312688.200000003, + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-fireplace-color-01-default, #ae132d)" + } + }, + "success": { + "added": 329768.60000000894, + "color": { + "Default": "var(--dt-colors-charts-apdex-excellent-default, #2a7453)" + } + } + }, + "circleChartSettings": { + "groupingThresholdType": "relative", + "valueType": "relative" + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "10": { + "content": "### Releases\n\nThis section provides a comprehensive overview of GitLab release activity, including the number of created and deployed releases, deployment statuses, environment-specific release metrics, and details of the 20 most recent deployments.\n\n---", + "type": "markdown" + }, + "11": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n Started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n Finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n Status = takeAny(if(event.status == \"finished\",task.outcome)),\n JobRunUrl = takeAny(deployment.url.full),\n JobRunId = takeAny(ext.deployment.job.id)\n}, by: {task.id,vcs.repository.name,vcs.ref.base.name,deployment.environment.name,vcs.repository.url.full}\n| filter isNotNull(Finished)\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"build\" and event.status == \"finished\" | fields task.id,cicd.pipeline.run.id, task.name , task.outcome.failure.reason, task.queued.duration, task.retry,ext.task.stage],kind: leftOuter,on:{left[JobRunId] == right[task.id]},prefix:\"j.\"\n| fields \nEnvironment = upper(deployment.environment.name),\nStatus ,\nDuration=Finished-Started,\nStarted,\nFinished,\nJobName = j.task.name,\nJobQueuedDuration = j.task.queued.duration,\nJobRunUrl ,\nJobRetry = j.task.retry,\nJobFailureReason = if(isNotNull(j.task.outcome.failure.reason),j.task.outcome.failure.reason,else: \"n/a\"),\nProject = vcs.repository.name,\nPipelineRunId = j.cicd.pipeline.run.id,\nRepository = vcs.repository.url.full\n| sort Finished desc\n| limit 20", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployments | last 20", + "type": "data", + "visualization": "table", + "visualizationSettings": { + "autoSelectVisualization": false, + "table": { + "colorThresholdTarget": "background", + "columnOrder": [ + "[\"Environment\"]", + "[\"Status\"]", + "[\"Duration\"]", + "[\"Finished\"]", + "[\"JobName\"]", + "[\"JobQueuedDuration\"]", + "[\"JobFailureReason\"]", + "[\"Project\"]", + "[\"JobRunUrl\"]", + "[\"JobRetry\"]", + "[\"Started\"]", + "[\"PipelineRunId\"]", + "[\"Repository\"]" + ], + "linewrapEnabled": true, + "rowDensity": "default", + "selectedColumnForRowThreshold": "Status" + }, + "thresholds": [ + { + "field": "Status", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": "#ABF57D", + "comparator": "=", + "id": 0, + "label": "", + "value": "success" + }, + { + "color": "#B8FFF6", + "comparator": "=", + "id": 1, + "label": "", + "value": "canceled" + }, + { + "color": "#FF5F5F", + "comparator": "=", + "id": 2, + "label": "", + "value": "failure" + } + ], + "title": "" + } + ] + } + }, + "12": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n status = takeAny (if(event.status == \"finished\",task.outcome))\n\n}, by: {task.id,deployment.environment.name, vcs.repository.url.full,vcs.repository.name}\n| filter isNotNull(finished)\n| filter status!=\"canceled\"\n| fieldsAdd duration = finished - started\n| filter isNotNull(duration)\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize p50 = median(duration), by: {upper(deployment.environment.name), vcs.repository.name}\n| sort p50 desc | limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Duration ( p50 ) | top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "upper(deployment.environment.name)", + "vcs.repository.name" + ], + "categoryAxisLabel": "upper(deployment.environment.name),vcs.repository.name", + "isCategoryLabelVisible": false, + "isValueLabelVisible": false, + "valueAxis": [ + "p50" + ], + "valueAxisLabel": "p50" + }, + "categoryOverrides": {}, + "colorPalette": "blue-steel", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "13": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"release\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" | fields vcs.ref.base.name,ext.project.id,vcs.repository.name, release.name,release.description, start_time,vcs.repository.url.full\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" and event.status == \"finished\" | fields task.outcome,vcs.ref.base.name,ext.project.id, ext.deployment.job.id, deployment.environment.name, end_time,vcs.repository.url.full ],on:{vcs.ref.base.name,ext.project.id}\n| filter in(vcs.repository.url.full, $Project) \n| filter in(right.deployment.environment.name,$Environment)\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Releases deployed", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "count()", + "labelMode": "none", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "14": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"release\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" | fields vcs.ref.base.name,ext.project.id,vcs.repository.name, release.name,release.description, start_time,vcs.repository.url.full\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" and event.status == \"finished\" | fields task.outcome,vcs.ref.base.name,ext.project.id, ext.deployment.job.id, deployment.environment.name, end_time,vcs.repository.url.full ],on:{vcs.ref.base.name,ext.project.id}\n| filter in(vcs.repository.url.full, $Project) \n| filter in(right.deployment.environment.name,$Environment)\n| summarize count(), by:{ right.task.outcome }", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Release Deployments Status", + "type": "data", + "visualization": "donutChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "right.task.outcome" + ], + "categoryAxisLabel": "right.task.outcome", + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": { + "canceled": { + "added": 15410943.600000009, + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-blue-steel-color-05-default, #134fc9)" + } + }, + "failure": { + "added": 15985262.900000006, + "color": { + "Default": "var(--dt-colors-charts-security-risk-level-critical-default, #8a0012)" + } + }, + "success": { + "added": 14652673.5, + "color": { + "Default": "var(--dt-colors-charts-apdex-good-default, #1c520a)" + } + } + }, + "circleChartSettings": { + "groupingThresholdType": "relative", + "valueType": "relative" + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "15": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"release\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" | fields vcs.ref.base.name,ext.project.id,vcs.repository.name, release.name,release.description, start_time,vcs.repository.url.full\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" and event.status == \"finished\" | fields task.outcome,vcs.ref.base.name,ext.project.id, ext.deployment.job.id, deployment.environment.name, end_time,vcs.repository.url.full ],on:{vcs.ref.base.name,ext.project.id}\n| filter in(vcs.repository.url.full, $Project) \n| filter in(right.deployment.environment.name,$Environment)\n| summarize count(), by:{ upper(right.deployment.environment.name) }\n| sort `count()` desc", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Release Deployments by Environment", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "upper(right.deployment.environment.name)" + ], + "categoryAxisLabel": "upper(right.deployment.environment.name)", + "isCategoryLabelVisible": false, + "isValueLabelVisible": false, + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": { + "success": { + "added": 14652673.5, + "color": { + "Default": "var(--dt-colors-charts-apdex-good-default, #1c520a)" + } + } + }, + "colorPalette": "blue-steel", + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "16": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize failureRate = if(count() == 0, 0, else: toDouble(countIf(task.outcome==\"failure\")) / toDouble(count())*100)\n", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Failure Rate", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "isIconVisible": true, + "label": "count()", + "labelMode": "none", + "prefixIcon": "CriticalFailedIcon", + "recordField": "failureRate", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "failureRate", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": "≥", + "id": 0, + "label": "" + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-warning-default, #eca440)" + }, + "comparator": "≥", + "id": 1, + "label": "" + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-critical-default, #c4233b)" + }, + "comparator": "≥", + "id": 2, + "label": "", + "value": 0 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1747844158473, + "baseUnit": "percent", + "decimals": 2, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "17": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n status = takeAny (if(event.status == \"finished\",task.outcome))\n\n}, by: {task.id,deployment.environment.name, vcs.repository.url.full}\n| fieldsAdd duration = finished - started\n| filter isNotNull(finished)\n| filter status!=\"canceled\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| makeTimeseries p50 = median(duration),p90 = percentile(duration,90),time: finished\n", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Duration", + "type": "data", + "visualization": "lineChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "colorPalette": "blue-steel", + "fieldMapping": { + "leftAxisValues": [ + "p50", + "p90" + ], + "timestamp": "timeframe" + }, + "gapPolicy": "connect", + "hiddenLegendFields": [ + "interval" + ], + "truncationMode": "middle", + "xAxisLabel": "timeframe", + "xAxisScaling": "analyzedTimeframe" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1747845759722, + "baseUnit": "nanosecond", + "decimals": 2, + "delimiter": false, + "displayUnit": null, + "identifier": "p50", + "suffix": "", + "unitCategory": "time" + }, + { + "added": 1747845921745, + "baseUnit": "nanosecond", + "decimals": 2, + "delimiter": false, + "displayUnit": null, + "identifier": "p90", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "18": { + "content": "### GitLab Deployment Pulse\n\nThis dashboard provides a comprehensive overview of deployment activities within GitLab pipelines, enabling engineering and DevOps teams to monitor, analyze, and optimize their software delivery processes. \n\n---", + "type": "markdown" + }, + "19": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n Started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n Finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n Status = takeAny(if(event.status == \"finished\",task.outcome)),\n JobRunUrl = takeAny(deployment.url.full),\n JobRunId = takeAny(ext.deployment.job.id)\n}, by: {task.id,vcs.repository.name,vcs.ref.base.name,deployment.environment.name,vcs.repository.url.full}\n| filter isNotNull(Finished)\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"build\" and event.status == \"finished\" | fields task.id,task.outcome.failure.reason],kind: leftOuter,on:{left[JobRunId] == right[task.id]},prefix:\"j.\"\n| filter isNotNull(j.task.outcome.failure.reason)\n| summarize Count=count(), by:{j.task.outcome.failure.reason}\n| sort Count desc\n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Failure Reasons | top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "j.task.outcome.failure.reason" + ], + "categoryAxisLabel": "j.task.outcome.failure.reason", + "isCategoryLabelVisible": false, + "isValueLabelVisible": false, + "valueAxis": [ + "Count" + ], + "valueAxisLabel": "Count" + }, + "categoryOverrides": { + "success": { + "added": 329768.60000000894, + "color": { + "Default": "var(--dt-colors-charts-apdex-excellent-default, #2a7453)" + } + } + }, + "colorPalette": "purple-rain", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "legend": { + "ratio": 47 + }, + "thresholds": [] + } + }, + "2": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize Count = count() , by:{upper(deployment.environment.name)}\n| sort Count desc", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Finished Deployments by Environment", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "upper(deployment.environment.name)" + ], + "categoryAxisLabel": "upper(deployment.environment.name)", + "isCategoryLabelVisible": false, + "isValueLabelVisible": false, + "valueAxis": [ + "Count" + ], + "valueAxisLabel": "Count" + }, + "categoryOverrides": { + "success": { + "added": 329768.60000000894, + "color": { + "Default": "var(--dt-colors-charts-apdex-excellent-default, #2a7453)" + } + } + }, + "colorPalette": "blue-steel", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "20": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n Started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n Finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n Status = takeAny(if(event.status == \"finished\",task.outcome)),\n JobRunUrl = takeAny(deployment.url.full),\n JobRunId = takeAny(ext.deployment.job.id)\n}, by: {task.id,vcs.repository.name,vcs.ref.base.name,deployment.environment.name,vcs.repository.url.full}\n| filter isNotNull(Finished)\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"build\" and event.status == \"finished\" | fields task.id,task.outcome.failure.reason],kind: leftOuter,on:{left[JobRunId] == right[task.id]},prefix:\"j.\"\n| filter isNotNull(j.task.outcome.failure.reason) and j.task.outcome.failure.reason == \"deployment_rejected\"\n| summarize Count=count(), by:{j.task.outcome.failure.reason}\n| sort Count desc\n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Failure caused by acceptance rejection", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "Count", + "labelMode": "none", + "recordField": "Count", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "Count", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": "≥", + "id": 0, + "label": "" + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-warning-default, #eca440)" + }, + "comparator": "≥", + "id": 1, + "label": "" + }, + { + "color": { + "Default": "var(--dt-colors-charts-apdex-poor-default, #d56b1a)" + }, + "comparator": "≥", + "id": 2, + "label": "", + "value": 0 + } + ], + "title": "" + } + ] + } + }, + "21": { + "content": "#### Share feedback \nWe welcome your feedback on this dashboard to help us improve and deliver a more effective observability solution, ensuring it provides maximum value to your software development lifecycle and delivery processes. Please post a comment here: [Feedback channel for CI/CD Pipeline Observability](https://community.dynatrace.com/t5/Platform-Engineering/Feedback-channel-for-CI-CD-Pipeline-Observability/m-p/269193/highlight/true#M1) (Dynatrace Community).\n#\n---\n\n#### Additional resources\n**Pipeline Observability**\n* [Observability throughout the software development lifecycle increases delivery performance](https://www.dynatrace.com/news/blog/observability-throughout-the-software-development-lifecycle/) (Blog Post)\n* [Concepts](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/pipeline-observability-concepts) (Docs)\n\n**Software Development Lifecycle Events**\n* [Ingest SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events) (Docs)\n* [SDLC event specification]() (Docs)\n#\n---\n\n#### About this dashboard\n\nPreview Release v0.1.0, [Origin](https://github.com/Dynatrace/dynatrace-configuration-as-code-samples)", + "type": "markdown" + }, + "3": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| makeTimeseries Count = count() , by:{upper(deployment.environment.name)}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Frequency", + "type": "data", + "visualization": "barChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "colorPalette": "fireplace", + "fieldMapping": { + "leftAxisValues": [ + "Count" + ], + "timestamp": "timeframe" + }, + "hiddenLegendFields": [ + "deployment.environment", + "interval", + "Count" + ], + "truncationMode": "middle", + "xAxisLabel": "timeframe", + "xAxisScaling": "analyzedTimeframe" + }, + "thresholds": [] + } + }, + "4": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n status = takeAny (if(event.status == \"finished\",task.outcome))\n}, by: {task.id,vcs.repository.url.full,deployment.environment.name}\n| filter isNotNull(finished)\n| filter status != \"canceled\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| fieldsAdd duration = finished - started\n| summarize p50 = median(duration), p90 = percentile(duration,90)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Duration | p50", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "p50", + "labelMode": "none", + "recordField": "p50", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "5": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events\n| filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize {\n started = takeMin(if(event.status == \"started\", toTimestamp(start_time))),\n finished = takeMax(if(event.status == \"finished\", toTimestamp(end_time))),\n status = takeAny (if(event.status == \"finished\",task.outcome))\n}, by: {task.id,vcs.repository.url.full,deployment.environment.name}\n| filter isNotNull(finished)\n| filter status!=\"canceled\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| fieldsAdd duration = finished - started\n| summarize p50 = median(duration), p90 = percentile(duration,90)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployment Duration | p90", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "p50", + "labelMode": "none", + "recordField": "p90", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "6": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"release\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"created\"\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Releases created", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "count()", + "labelMode": "none", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "7": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize Count = count() , by:{vcs.repository.name}\n| sort Count desc", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Deployments by Project", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "vcs.repository.name" + ], + "categoryAxisLabel": "vcs.repository.name", + "valueAxis": [ + "Count" + ], + "valueAxisLabel": "Count" + }, + "categoryOverrides": { + "success": { + "added": 329768.60000000894, + "color": { + "Default": "var(--dt-colors-charts-apdex-excellent-default, #2a7453)" + } + } + }, + "circleChartSettings": { + "groupingThresholdType": "relative" + }, + "colorPalette": "blue-steel-inverted", + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "8": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| filter event.status == \"finished\" and task.outcome == \"failure\"\n| filter in(vcs.repository.url.full, $Project) \n| filter in(deployment.environment.name,$Environment)\n| summarize Count = count() , by:{ upper(deployment.environment.name),vcs.repository.name}\n| sort Count desc | limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Error-prone Deployments | top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "upper(deployment.environment.name)", + "vcs.repository.name" + ], + "categoryAxisLabel": "upper(deployment.environment.name),vcs.repository.name", + "isCategoryLabelVisible": false, + "isValueLabelVisible": false, + "valueAxis": [ + "Count" + ], + "valueAxisLabel": "Count" + }, + "categoryOverrides": { + "success": { + "added": 329768.60000000894, + "color": { + "Default": "var(--dt-colors-charts-apdex-excellent-default, #2a7453)" + } + } + }, + "colorPalette": "fireplace", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "legend": { + "ratio": 47 + }, + "thresholds": [] + } + }, + "9": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events | filter event.provider == \"gitlab\" and event.type == \"release\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"| fields vcs.ref.base.name,ext.project.id,vcs.repository.name, release.name,release.description, start_time,vcs.repository.url.full, release.url.full\n| join [fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\" and event.status == \"finished\" | fields task.outcome,vcs.ref.base.name,ext.project.id, ext.deployment.job.id, deployment.environment.name, end_time,vcs.repository.url.full ],on:{vcs.ref.base.name,ext.project.id}\n| filter in(vcs.repository.url.full, $Project) \n| filter in(right.deployment.environment.name,$Environment)\n| fields \nReleaseName=release.name,\nReleaseDescription = release.description,\nStatus = right.task.outcome,\nEnvironment = upper(right.deployment.environment.name),\nReleaseTag = vcs.ref.base.name,\nFinished = right.end_time,\nProject = vcs.repository.name,\nReleaseUrl = release.url.full ,\nRepository = vcs.repository.url.full\n| sort Finished desc | limit 20", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Release Deployments | last 20", + "type": "data", + "visualization": "table", + "visualizationSettings": { + "autoSelectVisualization": false, + "table": { + "colorThresholdTarget": "background", + "columnOrder": [ + "[\"ReleaseName\"]", + "[\"ReleaseDescription\"]", + "[\"Status\"]", + "[\"Environment\"]", + "[\"ReleaseTag\"]", + "[\"Finished\"]", + "[\"Project\"]", + "[\"ReleaseUrl\"]", + "[\"Repository\"]" + ], + "columnWidths": { + "[\"Repository\"]": 330 + }, + "rowDensity": "default" + }, + "thresholds": [ + { + "field": "Status", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": "=", + "id": 0, + "label": "", + "value": "success" + }, + { + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-blue-steel-color-03-default, #627cfe)" + }, + "comparator": "=", + "id": 1, + "label": "", + "value": "canceled" + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-critical-default, #c4233b)" + }, + "comparator": "=", + "id": 2, + "label": "", + "value": "failure" + } + ], + "title": "" + } + ] + } + } + }, + "variables": [ + { + "defaultValue": [], + "editable": true, + "input": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize projects = collectDistinct(vcs.repository.url.full)\n| expand projects\n| sort projects", + "key": "Project", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + }, + { + "defaultValue": [], + "editable": true, + "input": "fetch events | filter event.provider == \"gitlab\" and event.type == \"deployment\" and event.kind == \"SDLC_EVENT\" and event.category == \"task\"\n| summarize env = collectDistinct(deployment.environment.name)\n| expand env\n| sort env", + "key": "Environment", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + } + ], + "version": 20 +} diff --git a/gitlab_pipeline_observability_terraform/dashboards/gitlab.merge_request.json b/gitlab_pipeline_observability_terraform/dashboards/gitlab.merge_request.json new file mode 100644 index 0000000..662e8be --- /dev/null +++ b/gitlab_pipeline_observability_terraform/dashboards/gitlab.merge_request.json @@ -0,0 +1,606 @@ +{ + "importedWithCode": false, + "layouts": { + "0": { + "h": 3, + "w": 4, + "x": 0, + "y": 4 + }, + "1": { + "h": 3, + "w": 4, + "x": 4, + "y": 7 + }, + "10": { + "h": 3, + "w": 4, + "x": 0, + "y": 7 + }, + "11": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "12": { + "h": 2, + "w": 24, + "x": 0, + "y": 15 + }, + "13": { + "h": 2, + "w": 24, + "x": 0, + "y": 2 + }, + "15": { + "h": 8, + "w": 24, + "x": 0, + "y": 29 + }, + "16": { + "h": 1, + "w": 24, + "x": 0, + "y": 28 + }, + "18": { + "h": 6, + "w": 8, + "x": 16, + "y": 4 + }, + "2": { + "h": 5, + "w": 24, + "x": 0, + "y": 10 + }, + "20": { + "h": 3, + "w": 4, + "x": 4, + "y": 4 + }, + "21": { + "h": 6, + "w": 8, + "x": 16, + "y": 17 + }, + "3": { + "h": 3, + "w": 4, + "x": 0, + "y": 17 + }, + "4": { + "h": 3, + "w": 4, + "x": 4, + "y": 20 + }, + "5": { + "h": 3, + "w": 4, + "x": 4, + "y": 17 + }, + "6": { + "h": 5, + "w": 24, + "x": 0, + "y": 23 + }, + "7": { + "h": 6, + "w": 8, + "x": 8, + "y": 4 + }, + "8": { + "h": 6, + "w": 8, + "x": 8, + "y": 17 + } + }, + "settings": {}, + "tiles": { + "0": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| dedup {task.id,vcs.change.url.full},sort: {ext.task.action.made_at desc}\n| filter not in(event.status,array(\"merged\",\"closed\")) \n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Open PRs", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "Open PRs", + "labelMode": "none", + "prefixIcon": "", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "1": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| dedup {task.id,vcs.change.url.full},sort: {ext.task.action.made_at desc}\n| filter not in(event.status,array(\"merged\",\"closed\")) \n| fieldsAdd OpenedPrDuration = toTimestamp(now()) - toTimestamp(start_time) \n| summarize max(OpenedPrDuration)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Oldest Open PR", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "max(OpenedPrDuration)", + "labelMode": "none", + "prefixIcon": "", + "recordField": "max(OpenedPrDuration)", + "trend": { + "isVisible": true + } + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727701272906, + "baseUnit": "nanosecond", + "decimals": 1, + "delimiter": true, + "displayUnit": null, + "identifier": "max(OpenedPrDuration)", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "10": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| sort ext.task.action.made_at desc\n| summarize { statuses = collectArray(event.status), actions = collectArray(ext.task.action) }, by: { task.id,vcs.change.url.full } \n| filter not in(statuses[0],array(\"merged\", \"closed\")) \n| filter contains(toString(actions),\"reopen\")\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Re-opened PRs", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "Open PRs", + "labelMode": "none", + "prefixIcon": "", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "11": { + "content": "### GitLab Merge Request Pulse: Open & Merged Insights\nThis dashboard provides a comprehensive overview of the status of merge requests (MRs) in your GitLab repositories. Track open and merged MRs in real-time, analyze trends, and gain insights into your team's development workflow. [Data ingest](https://dt-rnd.atlassian.net/wiki/x/WwLNLg) ", + "type": "markdown" + }, + "12": { + "content": "### Merged Merge Requests\n\nThis section provides a clear and detailed view of the merged merge requests, helping you to understand the efficiency and effectiveness of your code integration process.\n\n-----", + "type": "markdown" + }, + "13": { + "content": "### Open Merge Requests\n\nThis section provides a clear and detailed view of the current state of open merge requests, enabling you to manage and prioritize them effectively. \n\n-----", + "type": "markdown" + }, + "15": { + "content": "#### Share feedback \nWe welcome your feedback on this dashboard to help us improve and deliver a more effective observability solution, ensuring it provides maximum value to your software development lifecycle and delivery processes. Please post a comment here: [Feedback channel for CI/CD Pipeline Observability](https://community.dynatrace.com/t5/Platform-Engineering/Feedback-channel-for-CI-CD-Pipeline-Observability/m-p/269193/highlight/true#M1) (Dynatrace Community).\n#\n---\n\n#### Additional resources\n**Pipeline Observability**\n* [Observability throughout the software development lifecycle increases delivery performance](https://www.dynatrace.com/news/blog/observability-throughout-the-software-development-lifecycle/) (Blog Post)\n* [Concepts](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/pipeline-observability-concepts) (Docs)\n\n**Software Development Lifecycle Events**\n* [Ingest SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events) (Docs)\n* [SDLC event specification]() (Docs)\n#\n---\n\n#### About this dashboard\n\nPreview Release v0.3.1, [Origin](https://github.com/Dynatrace/dynatrace-configuration-as-code-samples)", + "type": "markdown" + }, + "16": { + "content": " ", + "type": "markdown" + }, + "18": { + "content": "Space for additional KPI", + "type": "markdown" + }, + "2": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| dedup {task.id,vcs.change.url.full},sort: {ext.task.action.made_at desc}\n| filter not in(event.status,array(\"merged\",\"closed\")) \n| fields \nIn_open_status = toTimestamp(now()) - toTimestamp(start_time),\nPR = vcs.change.id, \nTitle = vcs.change.title,\nURL = vcs.change.url.full,\nHeadName = vcs.ref.head.name, \nHeadRevision = vcs.ref.head.revision, \nBaseName = vcs.ref.base.name, \nCreatedAt = start_time,\nRepository = vcs.repository.url.full\n| sort In_open_status desc", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Open PRs Details", + "type": "data", + "visualization": "table", + "visualizationSettings": { + "table": { + "columnTypeOverrides": [ + { + "fields": [], + "id": 36911602, + "value": "date" + } + ], + "columnWidths": { + "[\"OpenFrom\"]": 138, + "[\"PR_number\"]": 146.4600830078125, + "[\"PR_title\"]": 404.0850830078125, + "[\"PR_url\"]": 423.39935302734375, + "[\"SourceRepository\"]": 233.796875, + "[\"Source_ref\"]": 279.58856201171875, + "[\"Source_repo\"]": 266.47222900390625, + "[\"Target_repo\"]": 244.47222900390625 + }, + "linewrapEnabled": true, + "rowDensity": "default" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727701272906, + "baseUnit": "nanosecond", + "decimals": 2, + "delimiter": true, + "displayUnit": null, + "identifier": "max(Duration)", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "20": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| dedup {task.id,vcs.change.url.full},sort: {ext.task.action.made_at desc}\n| filter not in(event.status,array(\"merged\",\"closed\")) \n| fieldsAdd OpenedPrDuration = toTimestamp(now()) - toTimestamp(start_time) \n| summarize avg(OpenedPrDuration)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Average Time Open", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "autoSelectVisualization": false, + "singleValue": { + "label": "avg(OpenedPrDuration)", + "labelMode": "none", + "prefixIcon": "", + "recordField": "avg(OpenedPrDuration)", + "trend": { + "isVisible": true + } + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1740648009785, + "baseUnit": "nanosecond", + "decimals": 1, + "delimiter": false, + "displayUnit": null, + "identifier": "avg(OpenedPrDuration)", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "21": { + "content": "Space for additional KPI", + "type": "markdown" + }, + "3": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.status == \"merged\" \n| filter event.provider == \"gitlab\"\n| filter ext.task.action == \"merge\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Merged PRs", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "Merged PRs", + "labelMode": "none", + "prefixIcon": "", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "4": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.status == \"merged\" \n| filter event.provider == \"gitlab\"\n| filter ext.task.action == \"merge\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| summarize percentile(duration, 50)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Merge Time (p50)", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "Median", + "labelMode": "none", + "prefixIcon": "", + "recordField": "percentile(duration, 50)", + "trend": { + "isVisible": true + } + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1740648063968, + "baseUnit": "nanosecond", + "decimals": 1, + "delimiter": false, + "displayUnit": null, + "identifier": "percentile(duration, 50)", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "5": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "description": "Shows the average time taken to merge pull requests, providing insights into the efficiency of the review and merge process.", + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.status == \"merged\" \n| filter event.provider == \"gitlab\"\n| filter ext.task.action == \"merge\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n\n| summarize avg(duration)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Average Merge Time", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "Avg", + "labelMode": "none", + "prefixIcon": "", + "recordField": "avg(duration)", + "trend": { + "isVisible": true + } + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1740648048011, + "baseUnit": "nanosecond", + "decimals": 1, + "delimiter": false, + "displayUnit": null, + "identifier": "avg(duration)", + "suffix": "", + "unitCategory": "time" + } + ] + } + }, + "6": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.status == \"merged\" \n| filter event.provider ==\"gitlab\" \n| filter ext.task.action == \"merge\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| fields \nMergedAt = end_time,\nDuration = duration, \nPR = vcs.change.id, \nTitle = vcs.change.title,\nURL = vcs.change.url.full,\nHeadName = vcs.ref.head.name, \nHeadRevision = vcs.ref.head.revision, \nBaseName = vcs.ref.base.name, \nCreatedAt = start_time,\nRepository = vcs.repository.url.full\n| sort Duration desc", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Merged PRs Details", + "type": "data", + "visualization": "table", + "visualizationSettings": { + "table": { + "rowDensity": "default", + "sortBy": { + "columnId": "[\"duration\"]", + "direction": "descending" + } + }, + "thresholds": [] + } + }, + "7": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| dedup {task.id,vcs.change.url.full},sort: {ext.task.action.made_at desc}\n| filter not in(event.status,array(\"merged\",\"closed\")) \n| summarize count(), by:{vcs.ref.base.name}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Open PRs by Branch", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "vcs.ref.base.name" + ], + "categoryAxisLabel": "vcs.ref.base.name", + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": {}, + "circleChartSettings": { + "groupingThresholdType": "relative" + }, + "colorPalette": "swamps", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "8": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.status == \"merged\" \n| filter event.provider == \"gitlab\"\n| filter ext.task.action == \"merge\"\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(vcs.ref.base.name, $Branch)\n| summarize count = count(), by:{vcs.repository.name}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Merged PRs by Repository", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "vcs.repository.name" + ], + "categoryAxisLabel": "vcs.repository.name", + "valueAxis": [ + "count" + ], + "valueAxisLabel": "count" + }, + "categoryOverrides": {}, + "circleChartSettings": { + "groupingThresholdType": "relative" + }, + "colorPalette": "blue-steel", + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + } + }, + "variables": [ + { + "input": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\" \n| summarize distinctRepositories = collectDistinct(vcs.repository.url.full)\n| expand distinctRepositories\n| sort distinctRepositories", + "key": "Repository", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + }, + { + "defaultValue": [ + "main" + ], + "input": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"task\" \n| filter event.type == \"change\" \n| filter event.provider == \"gitlab\"\n| summarize distinctBase = collectDistinct(vcs.ref.base.name)\n| expand distinctBase\n| sort distinctBase", + "key": "Branch", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + } + ], + "version": 20 +} diff --git a/gitlab_pipeline_observability_terraform/dashboards/gitlab.workflow.json b/gitlab_pipeline_observability_terraform/dashboards/gitlab.workflow.json new file mode 100644 index 0000000..0930bd6 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/dashboards/gitlab.workflow.json @@ -0,0 +1,1398 @@ +{ + "importedWithCode": false, + "layouts": { + "0": { + "h": 6, + "w": 8, + "x": 8, + "y": 4 + }, + "1": { + "h": 4, + "w": 4, + "x": 0, + "y": 4 + }, + "10": { + "h": 4, + "w": 4, + "x": 0, + "y": 12 + }, + "11": { + "h": 6, + "w": 4, + "x": 20, + "y": 4 + }, + "18": { + "h": 6, + "w": 8, + "x": 0, + "y": 33 + }, + "2": { + "h": 4, + "w": 4, + "x": 0, + "y": 8 + }, + "20": { + "h": 2, + "w": 24, + "x": 0, + "y": 0 + }, + "24": { + "h": 2, + "w": 24, + "x": 0, + "y": 2 + }, + "25": { + "h": 2, + "w": 24, + "x": 0, + "y": 22 + }, + "3": { + "h": 6, + "w": 16, + "x": 8, + "y": 27 + }, + "30": { + "h": 3, + "w": 4, + "x": 12, + "y": 24 + }, + "32": { + "h": 1, + "w": 24, + "x": 0, + "y": 45 + }, + "33": { + "h": 9, + "w": 24, + "x": 0, + "y": 46 + }, + "34": { + "h": 3, + "w": 4, + "x": 0, + "y": 24 + }, + "35": { + "h": 2, + "w": 4, + "x": 16, + "y": 4 + }, + "36": { + "h": 2, + "w": 4, + "x": 16, + "y": 6 + }, + "37": { + "h": 2, + "w": 4, + "x": 16, + "y": 8 + }, + "38": { + "h": 3, + "w": 4, + "x": 4, + "y": 24 + }, + "39": { + "h": 3, + "w": 4, + "x": 8, + "y": 24 + }, + "4": { + "h": 6, + "w": 4, + "x": 4, + "y": 4 + }, + "42": { + "h": 6, + "w": 8, + "x": 0, + "y": 27 + }, + "44": { + "h": 6, + "w": 16, + "x": 8, + "y": 33 + }, + "46": { + "h": 6, + "w": 24, + "x": 0, + "y": 39 + }, + "5": { + "h": 6, + "w": 12, + "x": 4, + "y": 10 + }, + "6": { + "h": 6, + "w": 8, + "x": 16, + "y": 10 + }, + "7": { + "h": 3, + "w": 8, + "x": 16, + "y": 24 + }, + "8": { + "h": 6, + "w": 24, + "x": 0, + "y": 16 + } + }, + "settings": {}, + "tiles": { + "0": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize count(), by: {cicd.pipeline.run.outcome}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Pipeline Completion Status", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "cicd.pipeline.run.outcome" + ], + "categoryAxisLabel": "cicd.pipeline.run.outcome", + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": { + "": { + "added": 1726836121804, + "color": "#805100" + }, + "failure": { + "added": 1726836109108, + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-fireplace-color-01-default, #ae132d)" + } + }, + "success": { + "added": 1726836111136, + "color": "#2F6863" + } + }, + "circleChartSettings": { + "groupingThresholdType": "relative", + "valueType": "relative" + }, + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "1": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger\n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Total Pipeline Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "Count", + "labelMode": "none", + "prefixIcon": "", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "10": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, duration\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize median(duration)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Median Execution Time", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "avg(duration)", + "labelMode": "none", + "prefixIcon": "", + "recordField": "median(duration)", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "11": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize count(), by: {cicd.pipeline.run.trigger}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Pipeline triggers", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "cicd.pipeline.run.trigger" + ], + "categoryAxisLabel": "cicd.pipeline.run.trigger", + "valueAxis": [ + "count()" + ], + "valueAxisLabel": "count()" + }, + "categoryOverrides": { + "": { + "added": 1726836121804, + "color": { + "Default": "var(--dt-colors-charts-categorical-color-11-default, #627cfe)" + } + }, + "failure": { + "added": 1726836109108, + "color": { + "Default": "var(--dt-colors-charts-categorical-themed-fireplace-color-01-default, #ae132d)" + } + }, + "success": { + "added": 1726836111136, + "color": { + "Default": "var(--dt-colors-charts-categorical-color-09-default, #649438)" + } + } + }, + "circleChartSettings": { + "groupingThresholdType": "relative", + "valueType": "relative" + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "18": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name, task.runner.name), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize Count=count(), by:{tasks[task.runner.name]} \n| sort Count desc\n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Job Runners | Top 5", + "type": "data", + "visualization": "donutChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "tasks[task.runner.name]" + ], + "categoryAxisLabel": "tasks[task.runner.name]", + "valueAxis": [ + "Count" + ], + "valueAxisLabel": "Count" + }, + "categoryOverrides": {}, + "circleChartSettings": { + "groupingThresholdType": "number-of-slices" + }, + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727680127964, + "baseUnit": "percent", + "decimals": 0, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "2": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, duration\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize avg(duration)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Average Execution Time", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "median(duration)", + "labelMode": "none", + "prefixIcon": "", + "recordField": "avg(duration)", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "20": { + "content": "### GitLab Pipeline Pulse\n\nThis dashboard offers a detailed overview of your GitLab pipeline executions, providing valuable insights into the performance and efficiency of your CI/CD pipelines.", + "type": "markdown" + }, + "24": { + "content": "### Pipeline Details\n\nThis section provides a clear and detailed view of the current state of GitLab pipeline executions, enabling you to manage and improve them effectively. \n\n-----", + "type": "markdown" + }, + "25": { + "content": "### Job Details\n\nThis section will give you a comprehensive view of the detailed performance and status of individual jobs within your GitLab pipelines, enabling you to monitor, analyze, and optimize your CI/CD processes effectively. \n\n-----", + "type": "markdown" + }, + "3": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name, duration), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize {\n duration = percentile(tasks[duration], 50)\n}, by: { task.name = tasks[task.name] }\n| sort duration desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Job Duration (p50) | Top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "task.name" + ], + "categoryAxisLabel": "task.name", + "valueAxis": [ + "duration" + ], + "valueAxisLabel": "duration" + }, + "categoryOverrides": {}, + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "30": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name,duration), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize avg(tasks[duration])", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Average Job Duration", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "median(duration)", + "labelMode": "none", + "prefixIcon": "", + "recordField": "avg(tasks[duration])", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "32": { + "content": " ", + "type": "markdown" + }, + "33": { + "content": "#### Share feedback \nWe welcome your feedback on this dashboard to help us improve and deliver a more effective observability solution, ensuring it provides maximum value to your software development lifecycle and delivery processes. Please post a comment here: [Feedback channel for CI/CD Pipeline Observability](https://community.dynatrace.com/t5/Platform-Engineering/Feedback-channel-for-CI-CD-Pipeline-Observability/m-p/269193/highlight/true#M1) (Dynatrace Community).\n#\n---\n\n#### Additional resources\n**Pipeline Observability**\n* [Observability throughout the software development lifecycle increases delivery performance](https://www.dynatrace.com/news/blog/observability-throughout-the-software-development-lifecycle/) (Blog Post)\n* [Concepts](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/pipeline-observability-concepts) (Docs)\n\n**Software Development Lifecycle Events**\n* [Ingest SDLC events](https://docs.dynatrace.com/docs/deliver/pipeline-observability-sdlc-events/sdlc-events) (Docs)\n* [SDLC event specification]() (Docs)\n#\n---\n\n#### About this dashboard\n\nPreview Release v0.3.1, [Origin](https://github.com/Dynatrace/dynatrace-configuration-as-code-samples)", + "type": "markdown" + }, + "34": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize {\n count() \n}", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Total Job Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "Count", + "labelMode": "none", + "prefixIcon": "", + "recordField": "count()", + "trend": { + "isVisible": true + } + }, + "thresholds": [] + } + }, + "35": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, cicd.pipeline.run.outcome\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| filter in(cicd.pipeline.name,$Pipeline)\n| summarize successful = countIf(cicd.pipeline.run.outcome == \"success\")", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Successful Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "isIconVisible": true, + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "SuccessIcon", + "recordField": "successful", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "successful", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": ">", + "id": 0, + "label": "", + "value": 0 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "36": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, cicd.pipeline.run.outcome\n| filter in(vcs.repository.url.full, $Repository)\n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize failed = countIf(cicd.pipeline.run.outcome == \"failure\")", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Failed Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "isIconVisible": true, + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "CriticalFailedIcon", + "recordField": "failed", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "failed", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-critical-default, #c4233b)" + }, + "comparator": "≥", + "id": 2, + "label": "", + "value": 1 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "37": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, cicd.pipeline.run.outcome\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize other = countIf(isNull(cicd.pipeline.run.outcome) or (cicd.pipeline.run.outcome != \"failure\" and cicd.pipeline.run.outcome != \"success\"))", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Other Execution States", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "isIconVisible": true, + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "WarningIcon", + "recordField": "other", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "other", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-warning-default, #eca440)" + }, + "comparator": "≥", + "id": 1, + "label": "", + "value": 1 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "38": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\" and task.outcome == \"success\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize successful=count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Successful Job Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "isIconVisible": true, + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "SuccessIcon", + "recordField": "successful", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "successful", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": ">", + "id": 0, + "label": "", + "value": 0 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "39": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\" and task.outcome == \"failure\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize failure=count()", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Failed Job Executions", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "isIconVisible": true, + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "SuccessIcon", + "recordField": "failure", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "failure", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-categorical-color-12-default, #cd3741)" + }, + "comparator": "≥", + "id": 0, + "label": "", + "value": 1 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "4": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| fields vcs.repository.url.full, cicd.pipeline.name, cicd.pipeline.run.trigger, cicd.pipeline.run.outcome\n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize failureRate = if(count() == 0, 0, else: toDouble(countIf(cicd.pipeline.run.outcome==\"failure\")) / toDouble(count())*100)", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "timeframe": { + "tileTimeframe": { + "from": "now()-7d", + "to": "now()" + }, + "tileTimeframeEnabled": false + }, + "title": "Error Rate", + "type": "data", + "visualization": "singleValue", + "visualizationSettings": { + "singleValue": { + "label": "failureRate", + "labelMode": "none", + "prefixIcon": "", + "recordField": "failureRate", + "trend": { + "isVisible": true + } + }, + "thresholds": [ + { + "field": "failureRate", + "id": 1, + "isEnabled": true, + "rules": [ + { + "color": { + "Default": "var(--dt-colors-charts-status-ideal-default, #2f6863)" + }, + "comparator": "≤", + "id": 0, + "label": "", + "value": 0 + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-warning-default, #eca440)" + }, + "comparator": "≥", + "id": 1, + "label": "" + }, + { + "color": { + "Default": "var(--dt-colors-charts-status-critical-default, #c4233b)" + }, + "comparator": "≥", + "id": 2, + "label": "", + "value": 0 + } + ], + "title": "" + } + ], + "unitsOverrides": [ + { + "added": 1727678780356, + "baseUnit": "percent", + "decimals": 0, + "delimiter": true, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "42": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\" and task.outcome == \"failure\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.outcome.failure.reason), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize {\n count = count() \n}, by: {task.failure = tasks[task.outcome.failure.reason] }\n| sort count desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "General reasons for job failure | Top 5", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "task.failure" + ], + "categoryAxisLabel": "task.failure", + "valueAxis": [ + "count" + ], + "valueAxisLabel": "count" + }, + "categoryOverrides": {}, + "truncationMode": "middle" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727680127964, + "baseUnit": "percent", + "decimals": 0, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "44": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.runner.name = if(task.runner.name==\"\",\"unknown\", else: task.runner.name ),task.queued.duration), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize {\n queued = percentile(tasks[task.queued.duration], 50)\n}, by: { runner.name=tasks[task.runner.name] }\n\n| sort queued desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Queuing time for runners (p50) | Top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "runner.name" + ], + "categoryAxisLabel": "runner.name", + "valueAxis": [ + "queued" + ], + "valueAxisLabel": "queued" + }, + "categoryOverrides": {}, + "truncationMode": "middle" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727680127964, + "baseUnit": "percent", + "decimals": 0, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "46": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(end_time,task.queued.duration), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| fieldsAdd queued = tasks[task.queued.duration],end_time = tasks[end_time]\n\n\n", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": " Job Queued Durations", + "type": "data", + "visualization": "areaChart", + "visualizationSettings": { + "autoSelectVisualization": false, + "chartSettings": { + "curve": "smooth", + "fieldMapping": { + "leftAxisValues": [ + "queued" + ], + "timestamp": "end_time" + }, + "gapPolicy": "connect", + "hiddenLegendFields": [ + "cicd.pipeline.run.id", + "cicd.pipeline.run.attempt", + "timeToStart", + "queueTime", + "has_matching_pipeline", + "avg(task.queued.duration)", + "task.queued.duration", + "has_matching_runs", + "queued" + ], + "leftYAxisSettings": { + "isLabelVisible": true, + "label": "queued" + }, + "legend": { + "hidden": true + }, + "truncationMode": "middle", + "xAxisLabel": "end_time", + "xAxisScaling": "analyzedTimeframe" + }, + "dataMapping": { + "displayedFields": [ + "start_time", + "cicd.pipeline.run.id", + "cicd.pipeline.run.attempt", + "timeToStart" + ] + }, + "thresholds": [] + } + }, + "5": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| filter cicd.pipeline.run.outcome != \"canceled\" \n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| summarize percentile(duration,50), alias:Median ,by:{cicd.pipeline.name, alias: name} \n| sort Median desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Pipeline Duration (p50) | Top 5", + "type": "data", + "visualization": "categoricalBarChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "name" + ], + "categoryAxisLabel": "name", + "valueAxis": [ + "Median" + ], + "valueAxisLabel": "Median" + }, + "categoryOverrides": {}, + "legend": { + "hidden": true + }, + "truncationMode": "middle" + }, + "thresholds": [] + } + }, + "6": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| filter in(cicd.pipeline.name,$Pipeline)\n| summarize failureRate = if(count() == 0, 0, else: toDouble(countIf(cicd.pipeline.run.outcome==\"failure\")) / toDouble(count())*100), by: {vcs.repository.name,cicd.pipeline.name }\n| filter failureRate>0 \n| sort failureRate desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Error-prune Pipelines | Top 5", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "vcs.repository.name", + "cicd.pipeline.name" + ], + "categoryAxisLabel": "vcs.repository.name,cicd.pipeline.name", + "valueAxis": [ + "failureRate" + ], + "valueAxisLabel": "failureRate" + }, + "categoryOverrides": {}, + "circleChartSettings": { + "groupingThresholdType": "number-of-slices" + }, + "truncationMode": "middle" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727680127964, + "baseUnit": "percent", + "decimals": 0, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "7": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.provider == \"gitlab\"\n| filter (event.category == \"task\" and event.status == \"finished\") \n or\n ( event.category == \"pipeline\" and event.type == \"run\" and event.status == \"finished\" \n and in(vcs.repository.url.full, $Repository) \n and in(cicd.pipeline.name, $Pipeline) \n and in(cicd.pipeline.run.trigger, $Trigger)\n )\n| summarize { \n tasks = arrayRemoveNulls(collectArray(if(event.category == \"task\", record(task.name, failure = if(task.outcome==\"failure\",1)), else: null))),\n has_matching_runs = countif(event.category == \"pipeline\")\n}, by: { cicd.pipeline.run.id }\n| expand tasks\n| filterOut has_matching_runs == 0 \n| summarize {\n failureRate = 100 * sum(tasks[failure]) / count() \n}, by: {task.name = tasks[task.name] }\n| filter failureRate > 0 \n| sort failureRate desc \n| limit 5", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Error-prone Jobs | Top 5", + "type": "data", + "visualization": "pieChart", + "visualizationSettings": { + "chartSettings": { + "categoricalBarChartSettings": { + "categoryAxis": [ + "task.name" + ], + "categoryAxisLabel": "task.name", + "valueAxis": [ + "failureRate" + ], + "valueAxisLabel": "failureRate" + }, + "categoryOverrides": {}, + "truncationMode": "middle" + }, + "thresholds": [], + "unitsOverrides": [ + { + "added": 1727680127964, + "baseUnit": "percent", + "decimals": 0, + "delimiter": false, + "displayUnit": null, + "identifier": "failureRate", + "suffix": "", + "unitCategory": "percentage" + } + ] + } + }, + "8": { + "davis": { + "davisVisualization": { + "isAvailable": true + }, + "enabled": false + }, + "query": "fetch events \n| filter event.kind == \"SDLC_EVENT\" \n| filter event.category == \"pipeline\" \n| filter event.type == \"run\" \n| filter event.status == \"finished\" \n| filter event.provider == \"gitlab\" \n| filter in(vcs.repository.url.full, $Repository) \n| filter in(cicd.pipeline.name,$Pipeline)\n| filter in(cicd.pipeline.run.trigger, $Trigger)\n| fields \nDuration = duration,\nName = cicd.pipeline.name,\nStatus = cicd.pipeline.run.outcome,\nRun = cicd.pipeline.run.url.full,\nTrigger = cicd.pipeline.run.trigger,\nStart = start_time,\nEnd = end_time,\nRepository = vcs.repository.name \n| sort Duration desc \n", + "querySettings": { + "defaultSamplingRatio": 10, + "defaultScanLimitGbytes": 500, + "enableSampling": false, + "maxResultMegaBytes": 1, + "maxResultRecords": 1000 + }, + "title": "Pipeline Execution Details", + "type": "data", + "visualization": "table", + "visualizationSettings": { + "table": { + "columnWidths": { + "[\"Repository\"]": 273.09375, + "[\"RunUrl\"]": 346.84375 + }, + "lineWrapIds": [ + [ + "RunUrl" + ] + ], + "linewrapEnabled": true, + "rowDensity": "comfortable" + }, + "thresholds": [] + } + } + }, + "variables": [ + { + "defaultValue": [], + "input": "fetch events\n| filter event.category == \"pipeline\" and event.status == \"finished\" and event.provider==\"gitlab\" and event.type == \"run\"\n| summarize distinctRepositories = collectDistinct(vcs.repository.url.full)\n| expand distinctRepositories\n| sort distinctRepositories", + "key": "Repository", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + }, + { + "defaultValue": [], + "input": "fetch events\n| filter event.category == \"pipeline\" and event.status == \"finished\" and event.provider==\"gitlab\" and event.type == \"run\"\n| filter in(vcs.repository.url.full, $Repository)\n| summarize distinctWorkflows = collectDistinct(cicd.pipeline.name)\n| expand distinctWorkflows\n| sort distinctWorkflows", + "key": "Pipeline", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + }, + { + "defaultValue": [], + "input": "fetch events\n| filter event.category == \"pipeline\" and event.status == \"finished\" and event.provider==\"gitlab\" and event.type == \"run\"\n| summarize distinctTriggers = collectDistinct(cicd.pipeline.run.trigger)\n| expand distinctTriggers\n| sort distinctTriggers", + "key": "Trigger", + "multiple": true, + "type": "query", + "version": 2, + "visible": true + } + ], + "version": 20 +} diff --git a/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.ingest-sources.tf b/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.ingest-sources.tf new file mode 100644 index 0000000..51327a7 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.ingest-sources.tf @@ -0,0 +1,180 @@ +resource "dynatrace_openpipeline_v2_events_sdlc_ingestsources" "events_sdlc_ingest_sources_gitlab" { + display_name = local.ingest_source_display_name + path_segment = local.ingest_source_path_segment + default_bucket = "default_events" + enabled = true + processing { + processors { + processor { + type = "fieldsAdd" + matcher = "isNull(event.kind)" + description = "Add event.kind if not set" + id = "processor_add_event_kind_if_not_set" + enabled = true + fields_add { + fields { + field { + name = "event.kind" + value = "SDLC_EVENT" + } + } + } + } + processor { + type = "dql" + matcher = "isNull(event.id)" + description = "Add event.id if not set" + id = "processor_add_event_id_if_not_set" + enabled = true + dql { + script = "fieldsAdd event.id = hashSha1(concat(toString(toLong(now())), toString(RANDOM())))" + } + } + processor { + type = "fieldsAdd" + matcher = <<-DQL + object_kind == "pipeline" + DQL + description = "Add pipeline run properties" + id = "processor_add_pipeline_run_properties" + enabled = true + fields_add { + fields { + field { + name = "event.version" + value = local.sdlc_event_version + } + field { + name = "event.provider" + value = local.sdlc_event_provider + } + field { + name = "event.category" + value = "pipeline" + } + field { + name = "event.type" + value = "run" + } + } + } + } + processor { + type = "fieldsAdd" + matcher = <<-DQL + object_kind == "build" + DQL + description = "Add build task properties" + id = "processor_add_build_task_properties" + enabled = true + fields_add { + fields { + field { + name = "event.version" + value = local.sdlc_event_version + } + field { + name = "event.provider" + value = local.sdlc_event_provider + } + field { + name = "event.category" + value = "task" + } + field { + name = "event.type" + value = "build" + } + } + } + } + processor { + type = "fieldsAdd" + matcher = <<-DQL + object_kind == "merge_request" + DQL + description = "Add change task properties" + id = "processor_add_change_task_properties" + enabled = true + fields_add { + fields { + field { + name = "event.version" + value = local.sdlc_event_version + } + field { + name = "event.provider" + value = local.sdlc_event_provider + } + field { + name = "event.category" + value = "task" + } + field { + name = "event.type" + value = "change" + } + } + } + } + processor { + type = "fieldsAdd" + matcher = <<-DQL + object_kind == "deployment" + DQL + description = "Add deployment category properties" + id = "processor_add_deployment_category_properties" + enabled = true + fields_add { + fields { + field { + name = "event.version" + value = local.sdlc_event_version + } + field { + name = "event.provider" + value = local.sdlc_event_provider + } + field { + name = "event.category" + value = "task" + } + field { + name = "event.type" + value = "deployment" + } + } + } + } + processor { + type = "fieldsAdd" + matcher = <<-DQL + object_kind == "release" + DQL + description = "Add release task properties" + id = "processor_add_release_task_properties" + enabled = true + fields_add { + fields { + field { + name = "event.version" + value = local.sdlc_event_version + } + field { + name = "event.provider" + value = local.sdlc_event_provider + } + field { + name = "event.category" + value = "task" + } + field { + name = "event.type" + value = "release" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.pipelines.tf b/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.pipelines.tf new file mode 100644 index 0000000..0727b2e --- /dev/null +++ b/gitlab_pipeline_observability_terraform/events.sdlc.gitlab.pipelines.tf @@ -0,0 +1,671 @@ +resource "dynatrace_openpipeline_v2_events_sdlc_pipelines" "events_sdlc_pipeline_gitlab_deployment" { + display_name = local.deployment_pipeline_display_name + custom_id = local.deployment_pipeline_custom_id + processing { + processors { + processor { + type = "dql" + matcher = "isNotNull(status)" + description = "add event.status" + id = "processor_add_event_status" + enabled = true + dql { + script = <<-DQL + fieldsAdd event.status = if(status == "running", "started", else: if(status != "running", "finished")) + DQL + } + } + processor { + type = "dql" + matcher = <<-DQL + isNotNull(status) and status == "running" + DQL + description = "add start_time" + id = "processor_add_start_time" + enabled = true + dql { + script = "fieldsAdd start_time = toTimestamp(status_changed_at)" + } + } + processor { + type = "dql" + matcher = <<-DQL + isNotNull(status) and status != "running" + DQL + description = "add end_time" + id = "processor_add_end_time" + enabled = true + dql { + script = <<-DQL + fieldsAdd end_time = toTimestamp(status_changed_at) + | fieldsAdd task.outcome = status + DQL + } + } + processor { + type = "fieldsAdd" + matcher = "true" + description = "add task.name" + id = "processor_add_task_name" + enabled = true + fields_add { + fields { + field { + name = "task.name" + value = "GitLab Deployment" + } + } + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "deployment" + DQL + description = "add deployment properties" + id = "processor_add_deployment_properties" + enabled = true + dql { + script = <<-DQL + fieldsAdd task.id = deployment_id + | fieldsAdd deployment.environment.name = environment + | fieldsAdd deployment.url.full = deployable_url + | fieldsAdd ext.deployment.job.id = deployable_id + | fieldsAdd vcs.ref.base.revision = short_sha + | fieldsAdd vcs.ref.base.name = ref + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(project)" + description = "add project properties" + id = "processor_add_project_properties" + enabled = true + dql { + script = <<-DQL + parse project, "JSON:project_j" + | fieldsAdd ext.project.id = project_j[id] + | fieldsAdd vcs.repository.url.full = project_j[web_url] + | fieldsAdd vcs.repository.name = project_j[name] + | fieldsRemove project_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(task.outcome)" + description = "add task.outcome" + id = "processor_add_task_outcome" + enabled = true + dql { + script = <<-DQL + fieldsAdd task.outcome = if(task.outcome == "failed", "failure", else:task.outcome) + DQL + } + } + processor { + type = "fieldsRemove" + matcher = "true" + description = "Clean up" + id = "processor_fields_remove" + enabled = true + fields_remove { + fields = [ + "object_kind", + "status", + "status_changed_at", + "deployment_id", + "deployable_id", + "deployable_url", + "environment", + "environment_tier", + "environment_slug", + "environment_external_url", + "project", + "short_sha", + "user", + "user_url", + "commit_url", + "commit_title" + ] + } + } + } + } + security_context {} + cost_allocation {} + data_extraction {} + metric_extraction {} + product_allocation {} + storage {} + davis {} +} + +resource "dynatrace_openpipeline_v2_events_sdlc_pipelines" "events_sdlc_pipeline_gitlab_merge_request" { + display_name = local.merge_request_pipeline_display_name + custom_id = local.merge_request_pipeline_custom_id + processing { + processors { + processor { + type = "dql" + matcher = "isNotNull(object_attributes)" + description = "add object attribute properties" + id = "processor_add_object_attribute_properties" + enabled = true + dql { + script = <<-DQL + parse object_attributes, "JSON:obj_attr_j" + | fieldsAdd duration = if(obj_attr_j[state] == "merged" and obj_attr_j[action] == "merge", toDuration(toTimestamp(obj_attr_j[updated_at]) - toTimestamp(obj_attr_j[created_at])), else: toDuration(0)) + | fieldsAdd event.status = obj_attr_j[state] + | fieldsAdd start_time = toTimestamp(obj_attr_j[created_at]) + | fieldsAdd end_time = if(in(obj_attr_j[state], {"merged", "closed"}) and in(obj_attr_j[action], {"close", "merge"}), toTimestamp(obj_attr_j[updated_at])) + | fieldsAdd task.id = obj_attr_j[id] + | fieldsAdd vcs.change.id = obj_attr_j[iid] + | fieldsAdd vcs.change.title = obj_attr_j[title] + | fieldsAdd vcs.repository.ref.revision = obj_attr_j[merge_commit_sha] + | fieldsAdd vcs.ref.head.name = obj_attr_j[source_branch] + | fieldsAdd vcs.ref.head.revision = obj_attr_j[last_commit][id] + | fieldsAdd vcs.ref.base.name = obj_attr_j[target_branch] + | fieldsAdd ext.task.action = obj_attr_j[action] + | fieldsAdd ext.task.action.made_at = toTimestamp(obj_attr_j[updated_at]) + | fieldsAdd vcs.change.url.full = obj_attr_j[url] + | fieldsRemove obj_attr_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(project)" + description = "add repository properties" + id = "processor_add_repository_properties" + enabled = true + dql { + script = <<-DQL + parse project, "JSON:project_j" + | fieldsAdd vcs.repository.name = project_j[name] + | fieldsAdd vcs.repository.url.full = project_j[web_url] + | fieldsRemove project_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(labels)" + description = "add ext.task.labels" + id = "processor_add_ext_task_labels" + enabled = true + dql { + script = "fieldsAdd ext.task.labels = labels" + } + } + processor { + type = "dql" + matcher = "isNotNull(user)" + description = "add ext.task.sender.name" + id = "processor_add_ext_task_sender_name" + enabled = true + dql { + script = <<-DQL + parse user, "JSON:user_j" + | fieldsAdd ext.task.sender.name = user_j[name] + | fieldsRemove user_j + DQL + } + } + processor { + type = "fieldsRemove" + matcher = "true" + description = "Clean up" + id = "processor_remove_fields" + enabled = true + fields_remove { + fields = [ + "object_kind", + "event_type", + "user", + "project", + "object_attributes", + "labels", + "changes", + "repository" + ] + } + } + } + } + security_context {} + cost_allocation {} + data_extraction {} + metric_extraction {} + product_allocation {} + storage {} + davis {} +} + +resource "dynatrace_openpipeline_v2_events_sdlc_pipelines" "events_sdlc_pipeline_gitlab_pipeline" { + display_name = local.workflow_pipeline_display_name + custom_id = local.workflow_pipeline_custom_id + processing { + processors { + processor { + type = "dql" + matcher = "isNotNull(object_attributes)" + description = "add object attribute properties" + id = "processor_add_object_attribute_properties" + enabled = true + dql { + script = <<-DQL + parse object_attributes, "JSON:obj_attr_j" + | fieldsAdd duration = if(isNotNull(obj_attr_j[finished_at]), toDuration(toTimestamp(obj_attr_j[finished_at]) - toTimestamp(obj_attr_j[created_at])), else: toDuration(0)) + | fieldsAdd pipeline.queued.duration = if(isNotNull(obj_attr_j[queued_duration]), toDuration(obj_attr_j[queued_duration]*1000000000), else: toDuration(0)) + | fieldsAdd cicd.pipeline.run.id = obj_attr_j[id] + | fieldsAdd cicd.pipeline.run.url.full = obj_attr_j[url] + | fieldsAdd cicd.pipeline.run.trigger = obj_attr_j[source] + | fieldsAdd vcs.ref.head.name = obj_attr_j[ref] + | fieldsAdd vcs.ref.head.revision = obj_attr_j[sha] + | fieldsAdd cicd.pipeline.run.variable = obj_attr_j[variables] + | fieldsAdd event.status = if(isNotNull(obj_attr_j[finished_at]) and obj_attr_j[status] != "running", "finished", else:obj_attr_j[status]) + | fieldsAdd cicd.pipeline.run.outcome = if(isNotNull(obj_attr_j[finished_at]) and obj_attr_j[status] != "running", obj_attr_j[status]) + | fieldsAdd end_time = if(isNotNull(obj_attr_j[finished_at]) and obj_attr_j[status] != "running", toTimestamp(obj_attr_j[finished_at])) + | fieldsAdd start_time = if(isNotNull(obj_attr_j[created_at]), toTimestamp(obj_attr_j[created_at])) + | fieldsAdd cicd.pipeline.name = if(isNotNull(obj_attr_j[name]), obj_attr_j[name], else: "unknown") + | fieldsRemove obj_attr_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(source_pipeline)" + description = "add source pipeline properties" + id = "processor_add_source_pipeline_properties" + enabled = true + dql { + script = <<-DQL + parse source_pipeline, "JSON:src_pipe_j" + | fieldsAdd cicd.upstream_pipeline.id = src_pipe_j[project][id] + | fieldsAdd cicd.upstream_pipeline.run.id = src_pipe_j[pipeline_id] + | fieldsAdd ext.upstream_pipeline.run.job.id = src_pipe_j[job_id] + | fieldsRemove src_pipe_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(project)" + description = "add project properties" + id = "processor_add_project_properties" + enabled = true + dql { + script = <<-DQL + parse project, "JSON:proj_j" + | fieldsAdd ext.pipeline.project.name = proj_j[name] + | fieldsAdd ext.pipeline.project.namespace = proj_j[namespace] + | fieldsAdd vcs.repository.url.full = proj_j[web_url] + | fieldsAdd vcs.repository.name = proj_j[name] + | fieldsAdd cicd.pipeline.id = proj_j[id] + | fieldsRemove proj_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(merge_request)" + description = "add vcs.change.id" + id = "processor_add_vcs_change_id" + enabled = true + dql { + script = <<-DQL + parse merge_request, "JSON:mr_j" + | fieldsAdd vcs.change.id = mr_j[iid] + | fieldsRemove mr_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(user)" + description = "add ext.pipeline.run.trigger.user" + id = "processor_add_ext_pipeline_run_trigger_user" + enabled = true + dql { + script = <<-DQL + parse user, "JSON:user_j" + | fieldsAdd ext.pipeline.run.trigger.user = user_j + | fieldsRemove user_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(cicd.pipeline.run.outcome)" + description = "add cicd.pipeline.run.outcome" + id = "processor_add_cicd_pipeline_run_outcome" + enabled = true + dql { + script = <<-DQL + fieldsAdd cicd.pipeline.run.outcome = if(cicd.pipeline.run.outcome == "failed", "failure", else:cicd.pipeline.run.outcome) + DQL + } + } + processor { + type = "fieldsRemove" + matcher = "true" + description = "Clean up" + id = "processor_remove_fields" + enabled = true + fields_remove { + fields = [ + "object_kind", + "object_attributes", + "merge_request", + "user", + "project", + "commit", + "source_pipeline", + "builds" + ] + } + } + } + } + security_context {} + cost_allocation {} + data_extraction {} + metric_extraction {} + product_allocation {} + storage {} + davis {} +} + +resource "dynatrace_openpipeline_v2_events_sdlc_pipelines" "events_sdlc_pipeline_gitlab_job" { + display_name = local.job_pipeline_display_name + custom_id = local.job_pipeline_custom_id + processing { + processors { + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" + DQL + description = "add build task properties" + id = "processor_add_build_task_properties" + enabled = true + dql { + script = <<-DQL + fieldsAdd event.status = if(isNotNull(build_finished_at) and build_status != "running", "finished", else:build_status) + | fieldsAdd duration = if(isNotNull(build_duration), toDuration(build_duration*1000000000), else: toDuration(0)) + | fieldsAdd task.queued.duration = if(isNotNull(build_queued_duration), toDuration(build_queued_duration*1000000000), else: toDuration(0)) + | fieldsAdd task.outcome = if(isNotNull(build_finished_at) and build_status != "running", build_status) + | fieldsAdd cicd.pipeline.run.id = pipeline_id + | fieldsAdd task.id = build_id + | fieldsAdd task.name = build_name + | fieldsAdd task.retry = retries_count + DQL + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" and build_status == "running" and isNotNull(build_started_at) + DQL + description = "add start_time" + id = "processor_add_start_time" + enabled = true + dql { + script = "fieldsAdd start_time = toTimestamp(build_started_at)" + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" and build_status != "running" and isNotNull(build_finished_at) + DQL + description = "add end_time" + id = "processor_add_end_time" + enabled = true + dql { + script = "fieldsAdd end_time = toTimestamp(build_finished_at)" + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" and build_status == "failed" + DQL + description = "add task.outcome.failure.reason" + id = "processor_add_task_outcome_failure_reason" + enabled = true + dql { + script = "fieldsAdd task.outcome.failure.reason = build_failure_reason" + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" and isNotNull(environment) + DQL + description = "add ext.task.build.environment" + id = "processor_add_ext_task_build_environment" + enabled = true + dql { + script = "fieldsAdd ext.task.build.environment = environment" + } + } + processor { + type = "dql" + matcher = "isNotNull(project)" + description = "add vcs.repository.name" + id = "processor_add_vcs_repository_name" + enabled = true + dql { + script = <<-DQL + parse project, "JSON:proj_j" + | fieldsAdd vcs.repository.name = proj_j[name] + | fieldsRemove proj_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(runner)" + description = "add task.runner.name" + id = "processor_add_task_runner_name" + enabled = true + dql { + script = <<-DQL + parse runner, "JSON:runner_j" + | fieldsAdd task.runner.name = runner_j[description] + | fieldsRemove runner_j + DQL + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "build" and isNotNull(build_stage) + DQL + description = "add ext.task.stage.name" + id = "processor_add_ext_task_stage_name" + enabled = true + dql { + script = "fieldsAdd ext.task.stage.name = build_stage" + } + } + processor { + type = "dql" + matcher = "isNotNull(task.outcome)" + description = "mappings" + id = "processor_map_task_outcome" + enabled = true + dql { + script = <<-DQL + fieldsAdd task.outcome = if(task.outcome == "failed", "failure", else:task.outcome) + DQL + } + } + processor { + type = "fieldsRemove" + matcher = <<-DQL + object_kind == "build" + DQL + description = "Clean" + id = "processor_fields_remove" + enabled = true + fields_remove { + fields = [ + "runner", + "project_id", + "project_name", + "user", + "commit", + "repository", + "project", + "environment", + "object_kind", + "ref", + "tag", + "before_sha", + "sha", + "retries_count", + "build_id", + "build_name", + "build_stage", + "build_status", + "build_created_at", + "build_started_at", + "build_finished_at", + "build_duration", + "build_queued_duration", + "build_allow_failure", + "build_failure_reason", + "pipeline_id", + "build_created_at_iso", + "build_started_at_iso", + "build_finished_at_iso" + ] + } + } + } + } + security_context {} + cost_allocation {} + data_extraction {} + metric_extraction {} + product_allocation {} + storage {} + davis {} +} + +resource "dynatrace_openpipeline_v2_events_sdlc_pipelines" "events_sdlc_pipeline_gitlab_release" { + display_name = local.release_pipeline_display_name + custom_id = local.release_pipeline_custom_id + processing { + processors { + processor { + type = "dql" + matcher = "isNotNull(action)" + description = "add event.status" + id = "processor_add_event_status" + enabled = true + dql { + script = <<-DQL + fieldsAdd event.status = concat(action, "d") + DQL + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "release" and action == "create" + DQL + description = "add start_time" + id = "processor_add_start_time" + enabled = true + dql { + script = "fieldsAdd start_time = toTimestamp(created_at)" + } + } + processor { + type = "dql" + matcher = <<-DQL + object_kind == "release" + DQL + description = "add release properties" + id = "processor_add_release_properties" + enabled = true + dql { + script = <<-DQL + fieldsAdd task.id = id + | fieldsAdd task.name = "GitLab Release" + | fieldsAdd release.name = name + | fieldsAdd release.description = description + | fieldsAdd release.url.full = url + | fieldsAdd vcs.ref.base.name = tag + | fieldsAdd ext.release.time = toTimestamp(released_at) + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(commit)" + description = "add vcs.ref.base.revision" + id = "processor_add_vcs_ref_base_revision" + enabled = true + dql { + script = <<-DQL + parse commit, "JSON:commit_j" + | fieldsAdd vcs.ref.base.revision = commit_j[id] + | fieldsRemove commit_j + DQL + } + } + processor { + type = "dql" + matcher = "isNotNull(project)" + description = "add project properties" + id = "processor_add_project_properties" + enabled = true + dql { + script = <<-DQL + parse project, "JSON:project_j" + | fieldsAdd ext.project.id = project_j[id] + | fieldsAdd vcs.repository.url.full = project_j[web_url] + | fieldsAdd vcs.repository.name = project_j[name] + | fieldsRemove project_j + DQL + } + } + processor { + type = "fieldsRemove" + matcher = "true" + description = "Clean up" + id = "processor_fields_remove" + enabled = true + fields_remove { + fields = [ + "id", + "created_at", + "description", + "name", + "released_at", + "tag", + "object_kind", + "project", + "url", + "action", + "assets", + "commit" + ] + } + } + } + } + security_context {} + cost_allocation {} + data_extraction {} + metric_extraction {} + product_allocation {} + storage {} + davis {} +} diff --git a/gitlab_pipeline_observability_terraform/locals.tf b/gitlab_pipeline_observability_terraform/locals.tf new file mode 100644 index 0000000..81e8198 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/locals.tf @@ -0,0 +1,22 @@ +locals { + sdlc_event_version = "0.1.0" + sdlc_event_provider = "gitlab" + + ingest_source_display_name = "Gitlab" + ingest_source_path_segment = "gitlab" + + job_pipeline_display_name = "Gitlab Job" + job_pipeline_custom_id = "pipeline_gitlab_job_v2" + + workflow_pipeline_display_name = "Gitlab Pipeline" + workflow_pipeline_custom_id = "pipeline_gitlab_pipeline_v2" + + merge_request_pipeline_display_name = "Gitlab Merge Request" + merge_request_pipeline_custom_id = "pipeline_gitlab_merge_request_v2" + + release_pipeline_display_name = "Gitlab Release" + release_pipeline_custom_id = "pipeline_gitlab_release_v2" + + deployment_pipeline_display_name = "Gitlab Deployment" + deployment_pipeline_custom_id = "pipeline_gitlab_deployment_v2" +} diff --git a/gitlab_pipeline_observability_terraform/main.tf b/gitlab_pipeline_observability_terraform/main.tf new file mode 100644 index 0000000..ff8be6c --- /dev/null +++ b/gitlab_pipeline_observability_terraform/main.tf @@ -0,0 +1,56 @@ +resource "dynatrace_openpipeline_v2_events_sdlc_routing" "events_sdlc_global_routing_table" { + routing_entries { + routing_entry { + enabled = true + pipeline_type = "custom" + matcher = <<-DQL + object_kind == "pipeline" + DQL + description = "Route all GitLab events into workflow pipeline (v2)" + pipeline_id = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_pipeline.id + } + routing_entry { + enabled = true + pipeline_type = "custom" + matcher = <<-DQL + object_kind == "build" + DQL + description = "Route all GitLab events to job pipeline (v2)" + pipeline_id = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_job.id + } + routing_entry { + enabled = true + pipeline_type = "custom" + matcher = <<-DQL + object_kind == "merge_request" + DQL + description = "Route all GitLab events into merge request pipeline (v2)" + pipeline_id = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_merge_request.id + } + routing_entry { + enabled = true + pipeline_type = "custom" + matcher = <<-DQL + object_kind == "deployment" + DQL + description = "Route all GitLab events into deployment pipeline (v2)" + pipeline_id = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_deployment.id + } + routing_entry { + enabled = true + pipeline_type = "custom" + matcher = <<-DQL + object_kind == "release" + DQL + description = "Route all GitLab events into release pipeline (v2)" + pipeline_id = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_release.id + } + } + depends_on = [ + dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_deployment, + dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_job, + dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_merge_request, + dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_pipeline, + dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_release + ] +} diff --git a/gitlab_pipeline_observability_terraform/outputs.tf b/gitlab_pipeline_observability_terraform/outputs.tf new file mode 100644 index 0000000..d069419 --- /dev/null +++ b/gitlab_pipeline_observability_terraform/outputs.tf @@ -0,0 +1,26 @@ +### Pipelines + +output "events_sdlc_pipelines_gitlab_job_pipeline_id" { + description = "The ID of the SDLC events Gitlab job pipeline" + value = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_job.id +} + +output "events_sdlc_pipelines_gitlab_release_pipeline_id" { + description = "The ID of the SDLC events Gitlab release pipeline" + value = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_release.id +} + +output "events_sdlc_pipelines_gitlab_merge_request_pipeline_id" { + description = "The ID of the SDLC events Gitlab merge request pipeline" + value = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_merge_request.id +} + +output "events_sdlc_pipelines_gitlab_pipeline_pipeline_id" { + description = "The ID of the SDLC events Gitlab workflow pipeline" + value = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_pipeline.id +} + +output "events_sdlc_pipelines_gitlab_deployment_pipeline_id" { + description = "The ID of the SDLC events Gitlab deployment pipeline" + value = dynatrace_openpipeline_v2_events_sdlc_pipelines.events_sdlc_pipeline_gitlab_deployment.id +} diff --git a/gitlab_pipeline_observability_terraform/providers.tf b/gitlab_pipeline_observability_terraform/providers.tf new file mode 100644 index 0000000..fe80f2f --- /dev/null +++ b/gitlab_pipeline_observability_terraform/providers.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + dynatrace = { + version = "~> 1.86" + source = "dynatrace-oss/dynatrace" + } + } + + backend "local" { + path = "./terraform.tfstate" + } +} + +# The following environment variables must be set +# +# * DYNATRACE_PLATFORM_TOKEN +# +# * DYNATRACE_ENV_URL +# https://.apps.dynatrace.com +# E.g. https://abc12345.apps.dynatrace.com diff --git a/gitlab_pipeline_observability_terraform/variables.tf b/gitlab_pipeline_observability_terraform/variables.tf new file mode 100644 index 0000000..e69de29