From 33d1332104201abd483a4eee4570302c4918b83c Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Wed, 19 Mar 2025 20:32:17 -0400 Subject: [PATCH 01/16] Refactors review apps doc --- doc/ci/review_apps/_index.md | 199 ++++++++++++------ .../continuous-delivery-review-apps_v11_4.svg | 48 ----- 2 files changed, 130 insertions(+), 117 deletions(-) delete mode 100644 doc/ci/review_apps/img/continuous-delivery-review-apps_v11_4.svg diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index a31d22a3a88f14..dff74201c48f57 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -12,88 +12,149 @@ title: Review apps {{< /details >}} -Review apps are a collaboration tool that provide an environment to showcase product changes. - -{{< alert type="note" >}} - -If you have a Kubernetes cluster, you can automate this feature in your applications -by using [Auto DevOps](../../topics/autodevops/_index.md). - -{{< /alert >}} - -Review apps: - -- Provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests. -- Allow designers and product managers to see your changes without needing to check out your branch and run your changes in a sandbox environment. -- Are fully integrated with the [GitLab DevOps LifeCycle](https://about.gitlab.com/stages-devops-lifecycle/). -- Allow you to deploy your changes wherever you want. - -![Master and topic branches leading from review apps to production](img/continuous-delivery-review-apps_v11_4.svg) - -In the previous example: - -- A review app is built every time a commit is pushed to `topic branch`. -- The reviewer fails two reviews before passing the third review. -- After the review passes, `topic branch` is merged into the default branch, where it's deployed to staging. -- After its approval in staging, the changes that were merged into the default branch are deployed to production. - -## How review apps work - -A review app is a mapping of a branch with an [environment](../environments/_index.md). -Access to the review app is made available as a link on the [merge request](../../user/project/merge_requests/_index.md) relevant to the branch. +Review apps provide a preview environment for each branch or merge request, +helping streamline the review process by letting stakeholders see and interact with code changes before they're merged. + +With review apps, you can: + +- Preview changes in a real environment without setting up locally. +- Share working versions of features through a URL. +- Test changes in an environment similar to production. +- Get feedback early in the development cycle. + +```mermaid +%%{init: { "fontFamily": "GitLab Sans" }}%% +flowchart LR + accTitle: Continuous delivery workflow with review apps + accDescr: Diagram showing the continuous delivery pipeline with review apps, from topic branch through review, merge, staging, to production. + + subgraph Branches + TopicBranch[Topic branch] + DefaultBranch[Default branch] + end + + subgraph ReviewProcess + Commit[Commit to topic branch] + ReviewApp[Review app] + Review[Code review] + Feedback[Review feedback] + Approval[MR approved] + end + + subgraph Deployment + Merge[Merge to default branch] + Staging[Staging] + StagingApproval[Staging approval] + Production[Production] + end + + TopicBranch -->|Push commit| Commit + Commit -->|Builds| ReviewApp + ReviewApp --> Review + Review -->|Requested changes| Feedback + Feedback -->|Fixes & new commit| ReviewApp + Review -->|Approved| Approval + Approval --> Merge + DefaultBranch -->|Receives| Merge + Merge -->|Auto-deploy| Staging + Staging --> StagingApproval + StagingApproval -->|Approved| Production + + classDef branchNode fill:#e1e1e1,stroke:#666,stroke-width:1px + classDef processNode fill:#f9f9f9,stroke:#666,stroke-width:1px + classDef deployNode fill:#f5f5f5,stroke:#666,stroke-width:1px + classDef feedbackNode fill:#fff0dd,stroke:#f90,stroke-width:1px + classDef approvalNode fill:#dfd,stroke:#6a6,stroke-width:1px + classDef prodNode fill:#d5f5ff,stroke:#0095cd,stroke-width:1px + + class TopicBranch,DefaultBranch branchNode + class Commit,ReviewApp,Review processNode + class Merge,Staging,StagingApproval deployNode + class Feedback feedbackNode + class Approval approvalNode + class Production prodNode +``` -The following is an example of a merge request with an environment set dynamically. +Review apps are built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), +which let you create a unique environment for each branch or merge request. +When you push code to a branch, GitLab automatically builds and deploys your application to a review app. +A link to this environment appears directly in your merge request, making it easy for reviewers to see your changes in action. ![Merged result pipeline status with link to review app](img/review_apps_preview_in_mr_v16_0.png) -In this example, a branch was: - -- Successfully built. -- Deployed under a dynamic environment that can be reached by selecting **View app**. - -After adding review apps to your workflow, you follow the branched Git flow. That is: - -1. Push a branch and let the runner deploy the review app based on the `script` definition of the dynamic environment job. -1. Wait for the runner to build and deploy your web application. -1. To view the changes live, select the link in the merge request related to the branch. - -## Configuring review apps - -Review apps are built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), which allow you to dynamically create a new environment for each branch. +{{< alert type="note" >}} -The process of configuring review apps is as follows: +If you have a Kubernetes cluster, you can set up review apps automatically using [Auto DevOps](../../topics/autodevops/_index.md). -1. Set up the infrastructure to host and deploy the review apps (check the [examples](#review-apps-examples) below). -1. [Install](https://docs.gitlab.com/runner/install/) and [configure](https://docs.gitlab.com/runner/commands/) a runner to do deployment. -1. Set up a job in `.gitlab-ci.yml` that uses the [predefined CI/CD variable](../variables/_index.md) `${CI_COMMIT_REF_SLUG}` - to create dynamic environments and restrict it to run only on branches. - Alternatively, you can get a YAML template for this job by [enabling review apps](#enable-review-apps-button) for your project. -1. Optionally, set a job that [manually stops](../environments/_index.md#stopping-an-environment) the review apps. +{{< /alert >}} -### Enable review apps button +## Configure review apps -When configuring review apps for a project, you add a new job to the `.gitlab-ci.yml` file, -as mentioned above. To facilitate this, and if you are using Kubernetes, you can select -**Enable review apps** and GitLab prompts you with a template code block that -you can copy and paste into `.gitlab-ci.yml` as a starting point. +Configure review apps when you want to provide a preview environment of your application for each branch or merge request. Prerequisites: -- You need at least the Developer role for the project. - -To use the review apps template: - -1. On the left sidebar, select **Search or go to** and - find the project you want to create a review app job for. +- You must have at least the Developer role for the project. +- You must have a GitLab Runner [installed](https://docs.gitlab.com/runner/install/) and [configured for deployment](https://docs.gitlab.com/runner/configuration/advanced-configuration/#the-runners-section). +- You must set up the infrastructure to host and deploy the review apps. See the [examples](#review-apps-examples). + +To configure review apps in your project: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Build > Pipeline editor**. +1. In your `.gitlab-ci.yml` file, add a job that creates a [dynamic environment](../environments/_index.md#create-a-dynamic-environment) using the [`${CI_COMMIT_REF_SLUG}`](../variables/predefined_variables.md#predefined-variables) predefined variable: + ```yaml + review_app: + stage: deploy + script: + - echo "Deploy to review app environment" + # Add your deployment commands here + environment: + name: review/${CI_COMMIT_REF_SLUG} + url: https://${CI_COMMIT_REF_SLUG}.example.com + on_stop: stop_review_app + rules: + - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH + ``` +1. Optional. Add a job that [manually stops](../environments/_index.md#stopping-an-environment) the review app. +1. Enter a commit message and select **Commit changes**. + +### Use the review apps template + +GitLab provides a built-in template that's configured for merge request pipelines by default. + +To use and customize this template: + +1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Operate > Environments**. 1. Select **Enable review apps**. -1. Follow the instructions in the dialog. - You can edit the provided `.gitlab-ci.yml` template as needed. - -## Review apps auto-stop - -See how to [configure review apps environments to expire and auto-stop](../environments/_index.md#stop-an-environment-after-a-certain-time-period) -after a given period of time. +1. In the **Enable Review Apps** dialog that appears: + - Review the required steps. + - Copy the YAML template: + ```yaml + deploy_review: + stage: deploy + script: + - echo "Add script here that deploys the code to your infrastructure" + environment: + name: review/${CI_COMMIT_REF_NAME} + url: https://${CI_ENVIRONMENT_SLUG}.example.com + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + ``` + This template uses `$CI_PIPELINE_SOURCE == "merge_request_event"` to run only in merge request pipelines. You can modify this rule to run for all branches if needed. +1. Select **Build > Pipeline editor**. +1. Paste the template into your `.gitlab-ci.yml` file. +1. Customize the template as needed: + - Modify the deployment script to work with your infrastructure. + - Adjust the rules section if you want to trigger review apps for branches even without merge requests. +1. Enter a commit message and select **Commit changes**. + +### Set an auto-stop time for review apps + +To conserve resources, you can configure review apps to automatically stop after a certain period. + +For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). ## Review apps examples diff --git a/doc/ci/review_apps/img/continuous-delivery-review-apps_v11_4.svg b/doc/ci/review_apps/img/continuous-delivery-review-apps_v11_4.svg deleted file mode 100644 index 90ac763a01e7ae..00000000000000 --- a/doc/ci/review_apps/img/continuous-delivery-review-apps_v11_4.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - review-apps-CD-outlined - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file -- GitLab From 25f9990a0d72899b741787a7597c9f4c95618cc1 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 16:23:23 -0400 Subject: [PATCH 02/16] Improves review apps sections --- doc/ci/review_apps/_index.md | 176 ++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 34 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index dff74201c48f57..fd33c545b5f7ad 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -12,8 +12,13 @@ title: Review apps {{< /details >}} -Review apps provide a preview environment for each branch or merge request, -helping streamline the review process by letting stakeholders see and interact with code changes before they're merged. +Review apps are temporary testing environments that are created automatically for each branch or merge request. +They allow you to preview and validate changes without having to set up a local development environment. + +Review apps are built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), +which let you create a unique environment for each branch or merge request. + +![Merged result pipeline status with link to review app](img/review_apps_preview_in_mr_v16_0.png) With review apps, you can: @@ -22,6 +27,14 @@ With review apps, you can: - Test changes in an environment similar to production. - Get feedback early in the development cycle. +{{< alert type="note" >}} + +If you have a Kubernetes cluster, you can set up review apps automatically using [Auto DevOps](../../topics/autodevops/_index.md). + +{{< /alert >}} + +## Review app workflow + ```mermaid %%{init: { "fontFamily": "GitLab Sans" }}%% flowchart LR @@ -75,19 +88,6 @@ flowchart LR class Production prodNode ``` -Review apps are built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), -which let you create a unique environment for each branch or merge request. -When you push code to a branch, GitLab automatically builds and deploys your application to a review app. -A link to this environment appears directly in your merge request, making it easy for reviewers to see your changes in action. - -![Merged result pipeline status with link to review app](img/review_apps_preview_in_mr_v16_0.png) - -{{< alert type="note" >}} - -If you have a Kubernetes cluster, you can set up review apps automatically using [Auto DevOps](../../topics/autodevops/_index.md). - -{{< /alert >}} - ## Configure review apps Configure review apps when you want to provide a preview environment of your application for each branch or merge request. @@ -96,13 +96,14 @@ Prerequisites: - You must have at least the Developer role for the project. - You must have a GitLab Runner [installed](https://docs.gitlab.com/runner/install/) and [configured for deployment](https://docs.gitlab.com/runner/configuration/advanced-configuration/#the-runners-section). -- You must set up the infrastructure to host and deploy the review apps. See the [examples](#review-apps-examples). +- You must set up the infrastructure to host and deploy the review apps. To configure review apps in your project: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Build > Pipeline editor**. 1. In your `.gitlab-ci.yml` file, add a job that creates a [dynamic environment](../environments/_index.md#create-a-dynamic-environment) using the [`${CI_COMMIT_REF_SLUG}`](../variables/predefined_variables.md#predefined-variables) predefined variable: + ```yaml review_app: stage: deploy @@ -116,7 +117,8 @@ To configure review apps in your project: rules: - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH ``` -1. Optional. Add a job that [manually stops](../environments/_index.md#stopping-an-environment) the review app. + +1. Optional. Add a job to [manually stop](#manual-stop) the review app when it's no longer needed. 1. Enter a commit message and select **Commit changes**. ### Use the review apps template @@ -128,9 +130,8 @@ To use and customize this template: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Operate > Environments**. 1. Select **Enable review apps**. -1. In the **Enable Review Apps** dialog that appears: - - Review the required steps. - - Copy the YAML template: +1. From the **Enable Review Apps** dialog that appears, copy the YAML template: + ```yaml deploy_review: stage: deploy @@ -142,23 +143,130 @@ To use and customize this template: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" ``` - This template uses `$CI_PIPELINE_SOURCE == "merge_request_event"` to run only in merge request pipelines. You can modify this rule to run for all branches if needed. + 1. Select **Build > Pipeline editor**. 1. Paste the template into your `.gitlab-ci.yml` file. -1. Customize the template as needed: - - Modify the deployment script to work with your infrastructure. +1. Customize the template based on your deployment needs: + + - Modify the deployment script and environment URL to work with your infrastructure. - Adjust the rules section if you want to trigger review apps for branches even without merge requests. + + For example, to deploy a Node.js application to Heroku: + + ```yaml + deploy_review: + stage: deploy + image: ruby:latest + script: + - apt-get update -qy + - apt-get install -y ruby-dev + - gem install dpl + - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API_KEY + environment: + name: review/${CI_COMMIT_REF_NAME} + url: https://$HEROKU_APP_NAME.herokuapp.com + on_stop: stop_review_app + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + ``` + 1. Enter a commit message and select **Commit changes**. -### Set an auto-stop time for review apps +### Configure stopping review apps + +You can configure your review apps to be stopped either manually or automatically to conserve resources. -To conserve resources, you can configure review apps to automatically stop after a certain period. +#### Manual stop + +To configure manual stopping for review apps: + +1. In your deployment job, add the `on_stop` parameter that references a stop job: + + ```yaml + deploy_review: + stage: deploy + script: + - echo "Deploy to review app environment" + environment: + name: review/${CI_COMMIT_REF_NAME} + url: https://${CI_ENVIRONMENT_SLUG}.example.com + on_stop: stop_review_app # References the job name defined below + resource_group: review-apps + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + ``` + +1. Define a stop job in your `.gitlab-ci.yml` file that contains your cleanup commands: + + ```yaml + stop_review_app: + stage: deploy + script: + - echo "Stop review app" + # Add your cleanup commands here + environment: + name: review/${CI_COMMIT_REF_NAME} + action: stop + resource_group: review-apps + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + when: manual + ``` + +With this configuration, you can: + +- Run the stop job from the pipeline view. +- Stop the environment from the **Environments** page. + +For more information, see [Stop an environment by using the UI](../environments/_index.md#stop-an-environment-by-using-the-ui). + +#### Automatic stop + +To configure automatic stopping after a certain period: + +1. Configure the [deployment job and stop job](#manual-stop) as shown in the manual stopping section. +1. Add the `auto_stop_in` parameter to the deployment job's environment configuration: + + ```yaml + review_app: + script: deploy-review-app + environment: + name: review/$CI_COMMIT_REF_SLUG + on_stop: stop_review_app + auto_stop_in: 1 week # Automatically stops after one week + resource_group: review-apps + rules: + - if: $CI_MERGE_REQUEST_ID + + stop_review_app: + script: stop-review-app + environment: + name: review/$CI_COMMIT_REF_SLUG + action: stop + resource_group: review-apps + rules: + - if: $CI_MERGE_REQUEST_ID + when: manual + ``` + +With this configuration: + +- Each commit on a merge request triggers the `review_app` job that deploys the latest change and resets the expiry period. +- If the environment is inactive for more than a week, GitLab automatically triggers the `stop_review_app` job to stop the environment. For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). -## Review apps examples +### Access a review app + +To deploy and access a review app: + +1. Go to your merge request. +1. Optional. If the review app job is set to manual, select **play** in the MR pipeline widget to trigger the deployment. +1. When the pipeline finishes, select **View app** to open the review app in your browser. + +## Example implementations -The following are example projects that demonstrate review app configuration: +These projects demonstrate different review app implementations: | Project | Configuration file | | --------------------------------------------------------------------------------------- | ------------------ | @@ -171,13 +279,13 @@ The following are example projects that demonstrate review app configuration: Other examples of review apps: -- +- [Cloud Native Development with GitLab](https://www.youtube.com/watch?v=jfIyQEwrocw). - [Review apps for Android](https://about.gitlab.com/blog/2020/05/06/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/). -## Route Maps +## Route maps -Route Maps allows you to go directly from source files +Route maps allows you to go directly from source files to public pages on the [environment](../environments/_index.md) defined for review apps. @@ -185,16 +293,16 @@ Once set up, the review app link in the merge request widget can take you directly to the pages changed, making it easier and faster to preview proposed modifications. -Configuring Route Maps involves telling GitLab how the paths of files -in your repository map to paths of pages on your website using a Route Map. -When you configure Route Maps, **View on** buttons are displayed. +Configuring route maps involves telling GitLab how the paths of files +in your repository map to paths of pages on your website using a route map. +When you configure route maps, **View on** buttons are displayed. Select these buttons to go to the pages changed directly from merge requests. To set up a route map, add a file inside the repository at `.gitlab/route-map.yml`, which contains a YAML array that maps `source` paths (in the repository) to `public` paths (on the website). -### Route Maps example +### Route maps example The following is an example of a route map for [Middleman](https://middlemanapp.com), a static site generator (SSG) used to build the [GitLab website](https://about.gitlab.com), -- GitLab From 9e629d285799387f49d4e79690cd5f6f07abc63d Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 16:56:42 -0400 Subject: [PATCH 03/16] Updates route maps section --- doc/ci/review_apps/_index.md | 95 ++++++++++-------- .../img/mr_widget_route_maps_v17_11.png | Bin 0 -> 28678 bytes .../img/view_on_mr_widget_v11_5.png | Bin 21942 -> 0 bytes 3 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 doc/ci/review_apps/img/mr_widget_route_maps_v17_11.png delete mode 100644 doc/ci/review_apps/img/view_on_mr_widget_v11_5.png diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index fd33c545b5f7ad..ef650c1a7d3658 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -261,7 +261,7 @@ For more information, see [Stop an environment after a certain time period](../e To deploy and access a review app: 1. Go to your merge request. -1. Optional. If the review app job is set to manual, select **play** in the MR pipeline widget to trigger the deployment. +1. Optional. If the review app job is manual, select **Run** ({{< icon name="play" >}}) to trigger the deployment. 1. When the pipeline finishes, select **View app** to open the review app in your browser. ## Example implementations @@ -285,28 +285,49 @@ Other examples of review apps: ## Route maps -Route maps allows you to go directly from source files -to public pages on the [environment](../environments/_index.md) defined for -review apps. +Route maps let you navigate directly from source files to their corresponding public pages on the [review app environment](../environments/_index.md). +This feature makes it easier to preview specific changes in your merge requests. -Once set up, the review app link in the merge request -widget can take you directly to the pages changed, making it easier -and faster to preview proposed modifications. +When configured, route maps enhance the review app experience by adding: -Configuring route maps involves telling GitLab how the paths of files -in your repository map to paths of pages on your website using a route map. -When you configure route maps, **View on** buttons are displayed. -Select these buttons to go to the pages changed directly from merge requests. +- Direct links to changed pages from the merge request widget. +- **View** buttons in file diffs and the file browser. -To set up a route map, add a file inside the repository at `.gitlab/route-map.yml`, -which contains a YAML array that maps `source` paths (in the repository) to `public` -paths (on the website). +### Configure route maps -### Route maps example +To set up route maps: -The following is an example of a route map for [Middleman](https://middlemanapp.com), -a static site generator (SSG) used to build the [GitLab website](https://about.gitlab.com), -deployed from its [project on GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com): +1. Create a file in your repository at `.gitlab/route-map.yml`. +1. Define mappings between source paths (in your repository) and public paths (on your website). + +The route map is a YAML array where each entry maps a `source` path to a `public` path. + +#### Route map syntax + +Each mapping in the route map follows this format: + +```yaml +- source: 'path/to/source/file' # Source file in repository + public: 'path/to/public/page' # Public page on the website +``` + +You can use two types of mapping: + +- **Exact match**: Uses string literals enclosed in single quotes +- **Pattern match**: Uses regular expressions enclosed in forward slashes + +For pattern matching with regular expressions: + +- The regex must match the entire source path (`^` and `$` anchors are implied). +- You can use capture groups `()` that can be referenced in the `public` path. +- Reference capture groups using `\N` expressions in order of occurrence (`\1`, `\2`, etc.). +- Escape slashes (`/`) as `\/` and periods (`.`) as `\.`. + +GitLab evaluates mappings in order of definition. The first `source` expression that matches determines the `public` path. + +### Example route map + +The following example shows a route map for [Middleman](https://middlemanapp.com), a static site generator used for the [GitLab website](https://about.gitlab.com): ```yaml # Team data @@ -326,38 +347,24 @@ deployed from its [project on GitLab.com](https://gitlab.com/gitlab-com/www-gitl public: '\1' # images/blogimages/around-the-world-in-6-releases-cover.png ``` -Mappings are defined as entries in the root YAML array, and are identified by a `-` prefix. Within an entry, there is a hash map with two keys: +In this example: -- `source` - - A string, starting and ending with `'`, for an exact match. - - A regular expression, starting and ending with `/`, for a pattern match: - - The regular expression needs to match the entire source path - `^` and `$` anchors are implied. - - Can include capture groups denoted by `()` that can be referred to in the `public` path. - - Slashes (`/`) can, but don't have to, be escaped as `\/`. - - Literal periods (`.`) should be escaped as `\.`. -- `public`, a string starting and ending with `'`. - - Can include `\N` expressions to refer to capture groups in the `source` regular expression in order of their occurrence, starting with `\1`. +- The mappings are evaluated in order. +- The third mapping ensures that `source/index.html.haml` matches `/source\/(.+?\.html).*/` instead of the catch-all `/source\/(.*)/`. +- This produces a public path of `index.html` instead of `index.html.haml`. -The public path for a source path is determined by finding the first -`source` expression that matches it, and returning the corresponding -`public` path, replacing the `\N` expressions with the values of the -`()` capture groups if appropriate. +### View mapped pages -In the example above, the fact that mappings are evaluated in order -of their definition is used to ensure that `source/index.html.haml` -matches `/source\/(.+?\.html).*/` instead of `/source\/(.*)/`, -and results in a public path of `index.html`, instead of -`index.html.haml`. +After you configure route maps, you can access the mapped pages. -After you have the route mapping set up, it takes effect in the following locations: +To view the mapped pages: - In the merge request widget: - - The **View app** button takes you to the environment URL set in the `.gitlab-ci.yml` file. - - The list shows the first 5 matched items from the route map, but you can filter them if more - than 5 are available. - ![Merge request widget with matched items and filter bar](img/view_on_mr_widget_v11_5.png) + - Select **View app** to go to the environment URL set in the `.gitlab-ci.yml` file. + The list shows up to 5 matched items from the route map (with filtering if more are available). -- In the diff for a comparison or commit, by selecting **View** ({{< icon name="external-link" >}}) next to the file. + ![Merge request widget with route maps showing matched items and filter bar](img/mr_widget_route_maps_v17_11.png) -- In the blob file view, by selecting **View** ({{< icon name="external-link" >}}) next to the file. +- In file diffs, select **View** ({{< icon name="external-link" >}}) next to the file. +- In the file browser, select **View** ({{< icon name="external-link" >}}) next to the file. diff --git a/doc/ci/review_apps/img/mr_widget_route_maps_v17_11.png b/doc/ci/review_apps/img/mr_widget_route_maps_v17_11.png new file mode 100644 index 0000000000000000000000000000000000000000..7d096f1498e128e27507cd3b17bf403e3a79b811 GIT binary patch literal 28678 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�@CHD2Wb z_YbChRUe-&vst&M`k2_phtFR|&fjxot<$rfpxS#M3YdSlFx(ey)xUA--S18Fy|#z` z{~UCq{2cr8($y`KE4pufl|1Aj!w1@*e!%>|lgj(`U$?}bxIXP!UB;w+TjzeQtBqs% z!L+a8{oxOHtv|2c6@QPPFaB@5RNaYiv6_cG_X_H(7~>C!fB4F-sCVnXt@*{N8WlC~ z^#$TT?U`z>#Ps}pBF6{T8piwWkGsEV`N#O=EI+x~x}rw=qSHs$7b`=W6-|rgEoK8* zpY^OK^X!Gq<{lrurETf#ZdiW~EPj8cT30u(9N3%QE&5C~^3^qK4L5!^Ey+K-=(LY9 ze~8VVm6hM4WZlj7Pgk|RUiYtIpVnJR%c}L0&MtP#I^28W;@WTJU*k-t{aAfMy6;@f zXZT~6^rauC&Of>qdO1~W^Ve-UocsD7IY+swV20`@BM^O z9lf2eR^FW2sxw6^x{x(?jbZ#m+M9)1n~pu`-f`yU;h+m) z{=V0F-!xnfef(5SX6Ko?I-0Uo4>s0+W_>Uq6dl>SdQx=XSE_ zlS1>={=m%>oTr^SGUrmte%Te~r;I(%1)llM``5&G+1VxE(v7`~A~Nr6dN4bygJ+tx z^Q%LyO+kTM8KYTh=I&l(HMiBQ+*x^%cJ`TtV%Jq=rp4@C@_)MhJ!kQ3pR-%vEC2S4 zv%FLM#!In?caEaYjf%jhQ{_T3b?$1!KHD~D*0Pm3%7rU3Kb2N_6`WkCYd5ER7JFA- zZSCYc3}31)I$qhN{W44cp5MAj{Lxm6C4WpypA@&QT4MLdCpR~}yv@GzOP<2YjBjSs zJRj+@PuY4c!>Vh$n!ffvjjQ6Kb5`!3D80oaK+A4HdEm#j2`87#vAKNal55gAt%-aw zs+U{EE?0yRyj(tIacXkO)`TV$IS7kXMqASFCv$=X~qprNBD{?Dz7NSFYe$wxYqaYWn`ayPx|ks?)#2zdzEe z&;46g@Q0_NpC4-XU8UQC@gDCViwnW_0Je(l?{)ot_qJ39(A*0a?d3=`YO zXXmZN8&i0^X42+G9+Q?nvYGivTYPGN{L$#g_t&PL=MM1WNHCF}b~(xYd*ed>@<|-C z`bw%5iwiIMeG2}zDAMF;_^L_#)qFKSIX1PV7+G8Yx$Jgqy^84Bdp``qS%Z4*SDTnm zz8zM0@w+_pzJ$}g`7?_@DaA%6pNN<;mp685vd2lKue!x^eA}uYyBDyiEDm|{Yg%^< z^RcWm{S(g%+%DqVnzQdoVsp^fyEOr;_FbNNPyF-SwZGo9x&4h>H|L?4(bri^KRt2| zFPd6&V@;9HB`3C)n6CdX`GZzVV zJvwDA8SQs{?XPPO=RNoktH)e(aGJ<{(QN0nUQB#Hni%)>&*|*H#mKxbq2ax9c7wje z18$IL0+_`HVm9O_q%l+{oe>xP%JX3NrVq7hTBpSS#EaA|6sSF@P|3zzbC4n4;DGoC rR%TFLU~oX3=L0K<1!mN&{3|cjTXC_m>DeI$1_lOCS3j3^P6 Date: Fri, 21 Mar 2025 17:14:43 -0400 Subject: [PATCH 04/16] Fix anchor links from ui help paths --- .../environments/components/enable_review_app_modal.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/environments/components/enable_review_app_modal.vue b/app/assets/javascripts/environments/components/enable_review_app_modal.vue index aab19cf919ee79..67682121e89b36 100644 --- a/app/assets/javascripts/environments/components/enable_review_app_modal.vue +++ b/app/assets/javascripts/environments/components/enable_review_app_modal.vue @@ -54,10 +54,10 @@ export default { }, i18n, configuringReviewAppsPath: helpPagePath('ci/review_apps/_index.md', { - anchor: 'configuring-review-apps', + anchor: 'configure-review-apps', }), reviewAppsExamplesPath: helpPagePath('ci/review_apps/_index.md', { - anchor: 'review-apps-examples', + anchor: 'example-implementations', }), }; -- GitLab From f20ebbb5ba0353ab3ace2c26932232474c2662f5 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 17:22:18 -0400 Subject: [PATCH 05/16] Mermaid diagram to use TD --- doc/ci/review_apps/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index ef650c1a7d3658..3cc5a2d11c2a48 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -37,7 +37,7 @@ If you have a Kubernetes cluster, you can set up review apps automatically using ```mermaid %%{init: { "fontFamily": "GitLab Sans" }}%% -flowchart LR +flowchart TD accTitle: Continuous delivery workflow with review apps accDescr: Diagram showing the continuous delivery pipeline with review apps, from topic branch through review, merge, staging, to production. -- GitLab From e3becd4b3addc3ba77fff9da2783b8462f722bb7 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 18:02:09 -0400 Subject: [PATCH 06/16] Simplifies mermaid diagram --- doc/ci/review_apps/_index.md | 77 +++++++++++++++++------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 3cc5a2d11c2a48..bd57807a51d179 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -38,54 +38,51 @@ If you have a Kubernetes cluster, you can set up review apps automatically using ```mermaid %%{init: { "fontFamily": "GitLab Sans" }}%% flowchart TD - accTitle: Continuous delivery workflow with review apps - accDescr: Diagram showing the continuous delivery pipeline with review apps, from topic branch through review, merge, staging, to production. + accTitle: Review app workflow + accDescr: Diagram showing how review apps fit into the GitLab development workflow. - subgraph Branches - TopicBranch[Topic branch] - DefaultBranch[Default branch] + subgraph Development["Development Phase"] + TopicBranch["Create topic branch"] + Commit["Make code changes"] + CreateMR["Create merge request"] end - subgraph ReviewProcess - Commit[Commit to topic branch] - ReviewApp[Review app] - Review[Code review] - Feedback[Review feedback] - Approval[MR approved] + subgraph ReviewAppCycle["Review App Cycle"] + direction LR + Pipeline["CI/CD pipeline runs"] + ReviewApp["Review app deployed"] + Testing["Review and testing"] + Feedback["Feedback provided"] + NewCommits["Address feedback\nwith new commits"] end - subgraph Deployment - Merge[Merge to default branch] - Staging[Staging] - StagingApproval[Staging approval] - Production[Production] + subgraph Finalization["Finalization"] + Approval["Merge request approved"] + Merge["Merged to default branch"] + Production["Deployed to production"] end - TopicBranch -->|Push commit| Commit - Commit -->|Builds| ReviewApp - ReviewApp --> Review - Review -->|Requested changes| Feedback - Feedback -->|Fixes & new commit| ReviewApp - Review -->|Approved| Approval + TopicBranch --> Commit + Commit --> CreateMR + CreateMR --> Pipeline + + Pipeline --> ReviewApp + ReviewApp --> Testing + Testing --> Feedback + Feedback --> NewCommits + NewCommits --> Pipeline + + Testing --> Approval Approval --> Merge - DefaultBranch -->|Receives| Merge - Merge -->|Auto-deploy| Staging - Staging --> StagingApproval - StagingApproval -->|Approved| Production - - classDef branchNode fill:#e1e1e1,stroke:#666,stroke-width:1px - classDef processNode fill:#f9f9f9,stroke:#666,stroke-width:1px - classDef deployNode fill:#f5f5f5,stroke:#666,stroke-width:1px - classDef feedbackNode fill:#fff0dd,stroke:#f90,stroke-width:1px - classDef approvalNode fill:#dfd,stroke:#6a6,stroke-width:1px - classDef prodNode fill:#d5f5ff,stroke:#0095cd,stroke-width:1px - - class TopicBranch,DefaultBranch branchNode - class Commit,ReviewApp,Review processNode - class Merge,Staging,StagingApproval deployNode - class Feedback feedbackNode - class Approval approvalNode - class Production prodNode + Merge --> Production + + classDef devNode fill:#e1e1e1,stroke:#666,stroke-width:1px + classDef reviewNode fill:#fff0dd,stroke:#f90,stroke-width:1px + classDef finalNode fill:#d5f5ff,stroke:#0095cd,stroke-width:1px + + class TopicBranch,Commit,CreateMR devNode + class Pipeline,ReviewApp,Testing,Feedback,NewCommits reviewNode + class Approval,Merge,Production finalNode ``` ## Configure review apps -- GitLab From 4d82e4dc3bc54792b9f01dbc825cfed4e0544963 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 18:58:32 -0400 Subject: [PATCH 07/16] Improves stop section --- doc/ci/review_apps/_index.md | 105 ++++++++++++----------------------- 1 file changed, 37 insertions(+), 68 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index bd57807a51d179..4d5fcc7ae41400 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -169,87 +169,56 @@ To use and customize this template: 1. Enter a commit message and select **Commit changes**. -### Configure stopping review apps +### Stop review apps You can configure your review apps to be stopped either manually or automatically to conserve resources. -#### Manual stop - -To configure manual stopping for review apps: - -1. In your deployment job, add the `on_stop` parameter that references a stop job: - - ```yaml - deploy_review: - stage: deploy - script: - - echo "Deploy to review app environment" - environment: - name: review/${CI_COMMIT_REF_NAME} - url: https://${CI_ENVIRONMENT_SLUG}.example.com - on_stop: stop_review_app # References the job name defined below - resource_group: review-apps - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - ``` +Review apps are automatically stopped when the associated merge request is merged or the branch is deleted. -1. Define a stop job in your `.gitlab-ci.yml` file that contains your cleanup commands: +For more information about stopping environments for review apps, see [Stopping an environment](../environments/_index.md#stopping-an-environment). - ```yaml - stop_review_app: - stage: deploy - script: - - echo "Stop review app" - # Add your cleanup commands here - environment: - name: review/${CI_COMMIT_REF_NAME} - action: stop - resource_group: review-apps - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - when: manual - ``` +#### Manual stop -With this configuration, you can: +To configure manual stopping, add the `on_stop` keyword to your deployment job and create a stop job with the `environment:action: stop` keyword: -- Run the stop job from the pipeline view. -- Stop the environment from the **Environments** page. +```yaml +# In your .gitlab-ci.yml file +deploy_review: + # Other configuration... + environment: + name: review/${CI_COMMIT_REF_NAME} + url: https://${CI_ENVIRONMENT_SLUG}.example.com + on_stop: stop_review_app # References the stop job below + +stop_review_app: + stage: deploy + script: + - echo "Stop review app" + # Add your cleanup commands here + environment: + name: review/${CI_COMMIT_REF_NAME} + action: stop + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + when: manual +``` For more information, see [Stop an environment by using the UI](../environments/_index.md#stop-an-environment-by-using-the-ui). #### Automatic stop -To configure automatic stopping after a certain period: +To configure automatic stopping after a period of time, add the `auto_stop_in` keyword to your deployment job: -1. Configure the [deployment job and stop job](#manual-stop) as shown in the manual stopping section. -1. Add the `auto_stop_in` parameter to the deployment job's environment configuration: - - ```yaml - review_app: - script: deploy-review-app - environment: - name: review/$CI_COMMIT_REF_SLUG - on_stop: stop_review_app - auto_stop_in: 1 week # Automatically stops after one week - resource_group: review-apps - rules: - - if: $CI_MERGE_REQUEST_ID - - stop_review_app: - script: stop-review-app - environment: - name: review/$CI_COMMIT_REF_SLUG - action: stop - resource_group: review-apps - rules: - - if: $CI_MERGE_REQUEST_ID - when: manual - ``` - -With this configuration: - -- Each commit on a merge request triggers the `review_app` job that deploys the latest change and resets the expiry period. -- If the environment is inactive for more than a week, GitLab automatically triggers the `stop_review_app` job to stop the environment. +```yaml +# In your .gitlab-ci.yml file +review_app: + script: deploy-review-app + environment: + name: review/$CI_COMMIT_REF_SLUG + auto_stop_in: 1 week # Stops after one week of inactivity + rules: + - if: $CI_MERGE_REQUEST_ID +``` For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). -- GitLab From 2177d426a9446db46b1810fc92558182f4220264 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Fri, 21 Mar 2025 19:04:09 -0400 Subject: [PATCH 08/16] Improves mermaid box titles --- doc/ci/review_apps/_index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 4d5fcc7ae41400..bb574ef2e59c6f 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -41,13 +41,13 @@ flowchart TD accTitle: Review app workflow accDescr: Diagram showing how review apps fit into the GitLab development workflow. - subgraph Development["Development Phase"] + subgraph Development["Development"] TopicBranch["Create topic branch"] Commit["Make code changes"] CreateMR["Create merge request"] end - subgraph ReviewAppCycle["Review App Cycle"] + subgraph ReviewAppCycle["Review app cycle"] direction LR Pipeline["CI/CD pipeline runs"] ReviewApp["Review app deployed"] @@ -56,7 +56,7 @@ flowchart TD NewCommits["Address feedback\nwith new commits"] end - subgraph Finalization["Finalization"] + subgraph Deployment["Deployment"] Approval["Merge request approved"] Merge["Merged to default branch"] Production["Deployed to production"] -- GitLab From f10e84f31b17deda72c56bc018b92f99df105524 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Mon, 24 Mar 2025 15:02:11 -0400 Subject: [PATCH 09/16] More improvements to clarify --- doc/ci/review_apps/_index.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index bb574ef2e59c6f..8d7f97f024ed6d 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -15,17 +15,17 @@ title: Review apps Review apps are temporary testing environments that are created automatically for each branch or merge request. They allow you to preview and validate changes without having to set up a local development environment. -Review apps are built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), -which let you create a unique environment for each branch or merge request. +Built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), +review apps provide a unique environment for each branch or merge request. -![Merged result pipeline status with link to review app](img/review_apps_preview_in_mr_v16_0.png) +![Merged result pipeline status with link to the review app](img/review_apps_preview_in_mr_v16_0.png) -With review apps, you can: +These environments help streamline the development workflow by: -- Preview changes in a real environment without setting up locally. -- Share working versions of features through a URL. -- Test changes in an environment similar to production. -- Get feedback early in the development cycle. +- Eliminating the need for local setup to test changes. +- Providing consistent environments for all team members. +- Enabling stakeholders to preview changes with a URL. +- Facilitating faster feedback cycles before changes reach production. {{< alert type="note" >}} @@ -148,7 +148,7 @@ To use and customize this template: - Modify the deployment script and environment URL to work with your infrastructure. - Adjust the rules section if you want to trigger review apps for branches even without merge requests. - For example, to deploy a Node.js application to Heroku: + For example, for a deployment to Heroku: ```yaml deploy_review: @@ -167,6 +167,9 @@ To use and customize this template: - if: $CI_PIPELINE_SOURCE == "merge_request_event" ``` + This configuration sets up an automated deployment to Heroku whenever a merge request is created. + It uses Ruby's deployment tool to handle the process, and creates a dynamic review environment that can be accessed through the specified URL. + 1. Enter a commit message and select **Commit changes**. ### Stop review apps @@ -222,9 +225,9 @@ review_app: For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). -### Access a review app +### View review apps -To deploy and access a review app: +To deploy and access review apps: 1. Go to your merge request. 1. Optional. If the review app job is manual, select **Run** ({{< icon name="play" >}}) to trigger the deployment. @@ -251,13 +254,13 @@ Other examples of review apps: ## Route maps -Route maps let you navigate directly from source files to their corresponding public pages on the [review app environment](../environments/_index.md). +Route maps let you navigate directly from source files to their corresponding public pages in the review app environment. This feature makes it easier to preview specific changes in your merge requests. When configured, route maps enhance the review app experience by adding: - Direct links to changed pages from the merge request widget. -- **View** buttons in file diffs and the file browser. +- Quick access links to view files directly from diffs and the file browser. ### Configure route maps -- GitLab From 76a6dd5cfda647e44e53abe2920d47d8b08527fd Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Mon, 24 Mar 2025 15:18:57 -0400 Subject: [PATCH 10/16] Changes heading level for "view review apps" --- doc/ci/review_apps/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 8d7f97f024ed6d..031f0ab39b8b1b 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -225,7 +225,7 @@ review_app: For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). -### View review apps +## View review apps To deploy and access review apps: -- GitLab From 13c66ce1e7848b4bf08a54e716aac4a3a78456b4 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Tue, 25 Mar 2025 17:28:17 -0400 Subject: [PATCH 11/16] Adds review feedback --- doc/ci/review_apps/_index.md | 55 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 031f0ab39b8b1b..57009bd9eeb291 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -108,8 +108,8 @@ To configure review apps in your project: - echo "Deploy to review app environment" # Add your deployment commands here environment: - name: review/${CI_COMMIT_REF_SLUG} - url: https://${CI_COMMIT_REF_SLUG}.example.com + name: review/$CI_COMMIT_REF_SLUG + url: https://$CI_COMMIT_REF_SLUG.example.com on_stop: stop_review_app rules: - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH @@ -129,24 +129,25 @@ To use and customize this template: 1. Select **Enable review apps**. 1. From the **Enable Review Apps** dialog that appears, copy the YAML template: - ```yaml - deploy_review: - stage: deploy - script: - - echo "Add script here that deploys the code to your infrastructure" - environment: - name: review/${CI_COMMIT_REF_NAME} - url: https://${CI_ENVIRONMENT_SLUG}.example.com - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - ``` + ```yaml + deploy_review: + stage: deploy + script: + - echo "Add script here that deploys the code to your infrastructure" + environment: + name: review/$CI_COMMIT_REF_NAME + url: https://$CI_ENVIRONMENT_SLUG.example.com + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + ``` 1. Select **Build > Pipeline editor**. 1. Paste the template into your `.gitlab-ci.yml` file. 1. Customize the template based on your deployment needs: - Modify the deployment script and environment URL to work with your infrastructure. - - Adjust the rules section if you want to trigger review apps for branches even without merge requests. + - Adjust [the rules section](../jobs/job_rules.md) if you want to trigger review apps + for branches even without merge requests. For example, for a deployment to Heroku: @@ -160,15 +161,15 @@ To use and customize this template: - gem install dpl - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_API_KEY environment: - name: review/${CI_COMMIT_REF_NAME} + name: review/$CI_COMMIT_REF_NAME url: https://$HEROKU_APP_NAME.herokuapp.com on_stop: stop_review_app rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" ``` - This configuration sets up an automated deployment to Heroku whenever a merge request is created. - It uses Ruby's deployment tool to handle the process, and creates a dynamic review environment that can be accessed through the specified URL. + This configuration sets up an automated deployment to Heroku whenever a pipeline runs for a merge request. + It uses Ruby's `dpl` deployment tool to handle the process, and creates a dynamic review environment that can be accessed through the specified URL. 1. Enter a commit message and select **Commit changes**. @@ -257,22 +258,21 @@ Other examples of review apps: Route maps let you navigate directly from source files to their corresponding public pages in the review app environment. This feature makes it easier to preview specific changes in your merge requests. -When configured, route maps enhance the review app experience by adding: +When configured, route maps enhance the review app experience by adding links to review app versions +of the changed files in: -- Direct links to changed pages from the merge request widget. -- Quick access links to view files directly from diffs and the file browser. +- The merge request widget. +- Commit and file views. ### Configure route maps To set up route maps: 1. Create a file in your repository at `.gitlab/route-map.yml`. -1. Define mappings between source paths (in your repository) and public paths (on your website). +1. Define mappings between source paths (in your repository) and public paths (on your review app infrastructure or website). The route map is a YAML array where each entry maps a `source` path to a `public` path. -#### Route map syntax - Each mapping in the route map follows this format: ```yaml @@ -282,8 +282,8 @@ Each mapping in the route map follows this format: You can use two types of mapping: -- **Exact match**: Uses string literals enclosed in single quotes -- **Pattern match**: Uses regular expressions enclosed in forward slashes +- **Exact match**: String literals enclosed in single quotes +- **Pattern match**: Regular expressions enclosed in forward slashes For pattern matching with regular expressions: @@ -320,11 +320,12 @@ In this example: - The mappings are evaluated in order. - The third mapping ensures that `source/index.html.haml` matches `/source\/(.+?\.html).*/` instead of the catch-all `/source\/(.*)/`. -- This produces a public path of `index.html` instead of `index.html.haml`. + This produces a public path of `index.html` instead of `index.html.haml`. ### View mapped pages -After you configure route maps, you can access the mapped pages. +After you configure route maps, you can find links to the mapped pages after the next +review app deployment. To view the mapped pages: -- GitLab From 28223eb53b2f78248630e685533fb79ec70565e0 Mon Sep 17 00:00:00 2001 From: lyspin Date: Tue, 25 Mar 2025 17:34:30 -0400 Subject: [PATCH 12/16] Resolves merge conflict --- doc/ci/review_apps/_index.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 57009bd9eeb291..eebacf5ed9b43b 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -2,6 +2,7 @@ stage: Verify group: Pipeline Execution info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments +description: Set up and use review apps to create temporary environments for testing changes before merging. title: Review apps --- @@ -13,7 +14,7 @@ title: Review apps {{< /details >}} Review apps are temporary testing environments that are created automatically for each branch or merge request. -They allow you to preview and validate changes without having to set up a local development environment. +You can preview and validate changes without needing to set up a local development environment. Built on [dynamic environments](../environments/_index.md#create-a-dynamic-environment), review apps provide a unique environment for each branch or merge request. @@ -35,6 +36,8 @@ If you have a Kubernetes cluster, you can set up review apps automatically using ## Review app workflow +A review app workflow could be similar to: + ```mermaid %%{init: { "fontFamily": "GitLab Sans" }}%% flowchart TD @@ -99,7 +102,9 @@ To configure review apps in your project: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Build > Pipeline editor**. -1. In your `.gitlab-ci.yml` file, add a job that creates a [dynamic environment](../environments/_index.md#create-a-dynamic-environment) using the [`${CI_COMMIT_REF_SLUG}`](../variables/predefined_variables.md#predefined-variables) predefined variable: +1. In your `.gitlab-ci.yml` file, add a job that creates a [dynamic environment](../environments/_index.md#create-a-dynamic-environment). + You can use a [predefined CI/CD variable](../variables/predefined_variables.md) to differentiate + each environment. For example, using the `CI_COMMIT_REF_SLUG` predefined variable: ```yaml review_app: @@ -110,12 +115,11 @@ To configure review apps in your project: environment: name: review/$CI_COMMIT_REF_SLUG url: https://$CI_COMMIT_REF_SLUG.example.com - on_stop: stop_review_app rules: - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH ``` -1. Optional. Add a job to [manually stop](#manual-stop) the review app when it's no longer needed. +1. Optional. Add a job to [stop the review app](#manual-stop) when it's no longer needed. 1. Enter a commit message and select **Commit changes**. ### Use the review apps template -- GitLab From 9dac5255357300b441d4278aa730599df14cd0ac Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Tue, 25 Mar 2025 20:36:53 -0400 Subject: [PATCH 13/16] Adds review feedback pt.2 --- doc/ci/review_apps/_index.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index eebacf5ed9b43b..5526ec98c8f21a 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -95,7 +95,7 @@ Configure review apps when you want to provide a preview environment of your app Prerequisites: - You must have at least the Developer role for the project. -- You must have a GitLab Runner [installed](https://docs.gitlab.com/runner/install/) and [configured for deployment](https://docs.gitlab.com/runner/configuration/advanced-configuration/#the-runners-section). +- You must have CI/CD pipelines available in the project. - You must set up the infrastructure to host and deploy the review apps. To configure review apps in your project: @@ -119,7 +119,8 @@ To configure review apps in your project: - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH ``` -1. Optional. Add a job to [stop the review app](#manual-stop) when it's no longer needed. +1. Optional. Add `when: manual` to the job to only deploy review apps manually. +1. Optional. Add a job to [stop the review app](#stop-review-apps) when it's no longer needed. 1. Enter a commit message and select **Commit changes**. ### Use the review apps template @@ -179,11 +180,11 @@ To use and customize this template: ### Stop review apps -You can configure your review apps to be stopped either manually or automatically to conserve resources. +You can configure your review apps to be stopped in different ways to conserve resources. Review apps are automatically stopped when the associated merge request is merged or the branch is deleted. -For more information about stopping environments for review apps, see [Stopping an environment](../environments/_index.md#stopping-an-environment). +For more information about stopping environments for review apps, see [stopping an environment](../environments/_index.md#stopping-an-environment). #### Manual stop @@ -208,14 +209,13 @@ stop_review_app: action: stop rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - when: manual + when: manual # Makes this job manually triggerable ``` -For more information, see [Stop an environment by using the UI](../environments/_index.md#stop-an-environment-by-using-the-ui). +#### Time-based automatic stop -#### Automatic stop - -To configure automatic stopping after a period of time, add the `auto_stop_in` keyword to your deployment job: +To configure review apps to stop automatically after a period of time, +add the [`auto_stop_in`](../yaml/_index.md#environmentauto_stop_in) keyword to your deployment job: ```yaml # In your .gitlab-ci.yml file @@ -228,8 +228,6 @@ review_app: - if: $CI_MERGE_REQUEST_ID ``` -For more information, see [Stop an environment after a certain time period](../environments/_index.md#stop-an-environment-after-a-certain-time-period). - ## View review apps To deploy and access review apps: @@ -333,10 +331,8 @@ review app deployment. To view the mapped pages: -- In the merge request widget: - - - Select **View app** to go to the environment URL set in the `.gitlab-ci.yml` file. - The list shows up to 5 matched items from the route map (with filtering if more are available). +- In the merge request widget, select **View app** to go to the environment URL set in the `.gitlab-ci.yml` file. + The list shows up to 5 matched items from the route map (with filtering if more are available). ![Merge request widget with route maps showing matched items and filter bar](img/mr_widget_route_maps_v17_11.png) -- GitLab From 7158cbebc94db01b00c3a9938bd5e11fef4ebbc1 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Wed, 26 Mar 2025 16:53:29 -0400 Subject: [PATCH 14/16] Updates manual stop section per review feedback --- doc/ci/review_apps/_index.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 5526ec98c8f21a..f4ba1881b14570 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -180,15 +180,21 @@ To use and customize this template: ### Stop review apps -You can configure your review apps to be stopped in different ways to conserve resources. +You can configure your review apps to be stopped either manually or automatically to conserve resources. -Review apps are automatically stopped when the associated merge request is merged or the branch is deleted. +For more information about stopping environments for review apps, see [Stopping an environment](../environments/_index.md#stopping-an-environment). -For more information about stopping environments for review apps, see [stopping an environment](../environments/_index.md#stopping-an-environment). +#### Auto-stop review apps on merge -#### Manual stop +To configure review apps to automatically stop when the associated merge request is merged +or the branch is deleted: -To configure manual stopping, add the `on_stop` keyword to your deployment job and create a stop job with the `environment:action: stop` keyword: +- Add the [`on_stop`](../yaml/#environmenton_stop) keyword to your deployment job. +- Create a stop job with the [`environment:action: stop`](../yaml/#environmentaction). +- Optional. Add [`when: manual`](../yaml/#when) to the stop job to make it possible to + manually stop the review app at any time. + +For example: ```yaml # In your .gitlab-ci.yml file -- GitLab From 321215d766a888a4732849a46e4c2b2c244a18e7 Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Wed, 26 Mar 2025 16:56:25 -0400 Subject: [PATCH 15/16] Moves the when: manual config --- doc/ci/review_apps/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index f4ba1881b14570..5b1334e0589247 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -213,9 +213,9 @@ stop_review_app: environment: name: review/${CI_COMMIT_REF_NAME} action: stop + when: manual # Makes this job manually triggerable rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - when: manual # Makes this job manually triggerable ``` #### Time-based automatic stop -- GitLab From d6aab282d1d54be8e4c0431dacaeaededa405baf Mon Sep 17 00:00:00 2001 From: Lysanne Pinto Date: Wed, 26 Mar 2025 19:45:11 -0400 Subject: [PATCH 16/16] Improves view mapped pages topic --- doc/ci/review_apps/_index.md | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/doc/ci/review_apps/_index.md b/doc/ci/review_apps/_index.md index 5b1334e0589247..eb5112dd9a205d 100644 --- a/doc/ci/review_apps/_index.md +++ b/doc/ci/review_apps/_index.md @@ -189,10 +189,10 @@ For more information about stopping environments for review apps, see [Stopping To configure review apps to automatically stop when the associated merge request is merged or the branch is deleted: -- Add the [`on_stop`](../yaml/#environmenton_stop) keyword to your deployment job. -- Create a stop job with the [`environment:action: stop`](../yaml/#environmentaction). -- Optional. Add [`when: manual`](../yaml/#when) to the stop job to make it possible to - manually stop the review app at any time. +1. Add the [`on_stop`](../yaml/_index.md#environmenton_stop) keyword to your deployment job. +1. Create a stop job with the [`environment:action: stop`](../yaml/_index.md#environmentaction). +1. Optional. Add [`when: manual`](../yaml/_index.md#when) to the stop job to make it possible to + manually stop the review app at any time. For example: @@ -335,12 +335,22 @@ In this example: After you configure route maps, you can find links to the mapped pages after the next review app deployment. -To view the mapped pages: +To view mapped pages: -- In the merge request widget, select **View app** to go to the environment URL set in the `.gitlab-ci.yml` file. - The list shows up to 5 matched items from the route map (with filtering if more are available). +- In the merge request widget, select **View app** to go to the environment URL set in the + `.gitlab-ci.yml` file. The list shows up to 5 matched items from the route map (with + filtering if more are available). ![Merge request widget with route maps showing matched items and filter bar](img/mr_widget_route_maps_v17_11.png) -- In file diffs, select **View** ({{< icon name="external-link" >}}) next to the file. -- In the file browser, select **View** ({{< icon name="external-link" >}}) next to the file. +For files in your route map: + +1. In the **Changes** tab of your merge request, select **View file @ [commit]** next to a file. +1. On the file's page, look for **View on [deployment-URL]** ({{< icon name="external-link" >}}) in the upper-right corner. + +For merge requests using merged results pipelines: + +1. Go to the **Pipelines** tab in your merge request. +1. Select the commit for the latest pipeline. +1. Select **View file @ [commit]** next to a file. +1. On the file's page, select **View on [deployment-URL]** ({{< icon name="external-link" >}}) in the upper-right corner. -- GitLab