Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d6df46f3c3
commit
55cd0a88bb
8 changed files with 244 additions and 20 deletions
|
@ -20,4 +20,9 @@ module NotifyHelper
|
||||||
|
|
||||||
(source.description || default_description).truncate(200, separator: ' ')
|
(source.description || default_description).truncate(200, separator: ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def merge_request_approved_description(merge_request, approved_by)
|
||||||
|
format(s_('Notify|%{mr_highlight}Merge request%{highlight_end} %{mr_link} %{approved_highlight}was approved by%{highlight_end} %{approver_avatar} %{approver_link}').html_safe, mr_highlight: '<span style="font-weight: 600;color:#333333;">'.html_safe, highlight_end: '</span>'.html_safe, mr_link: link_to(merge_request.to_reference, merge_request_url(merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none").html_safe, approved_highlight: '<span>'.html_safe, approver_avatar: content_tag(:img, nil, height: "24", src: avatar_icon_for_user(approved_by, 24, only_path: false),
|
||||||
|
style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar", class: "avatar").html_safe, approver_link: link_to(approved_by.name, user_url(approved_by), style: "color:#333333;text-decoration:none;", class: "muted").html_safe)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -78,10 +78,11 @@
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
|
||||||
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
|
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
|
||||||
- if @merge_request.respond_to? :approvals_required
|
%span
|
||||||
%span Merge request was approved (#{@merge_request.approvals.count}/#{@merge_request.approvals_required})
|
- if @merge_request.respond_to? :approvals_required
|
||||||
- else
|
= s_('Notify|Merge request was approved (%{approvals}/%{required_approvals})') % { approvals: @merge_request.approvals.count, required_approvals: @merge_request.approvals_required }
|
||||||
%span Merge request was approved
|
- else
|
||||||
|
= s_('Notify|Merge request was approved')
|
||||||
%tr.spacer
|
%tr.spacer
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
|
||||||
|
|
||||||
|
@ -92,12 +93,7 @@
|
||||||
%tr{ style: 'width:100%;' }
|
%tr{ style: 'width:100%;' }
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
|
||||||
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:-4px;", alt: "Merge request icon" }
|
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:-4px;", alt: "Merge request icon" }
|
||||||
%span{ style: "font-weight: 600;color:#333333;" } Merge request
|
= merge_request_approved_description(@merge_request, @approved_by)
|
||||||
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
|
|
||||||
%span was approved by
|
|
||||||
%img.avatar{ height: "24", src: avatar_icon_for_user(@approved_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }/
|
|
||||||
%a.muted{ href: user_url(@approved_by), style: "color:#333333;text-decoration:none;" }
|
|
||||||
= @approved_by.name
|
|
||||||
%tr.spacer
|
%tr.spacer
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
|
||||||
|
|
||||||
|
@ -106,7 +102,7 @@
|
||||||
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
|
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
|
||||||
%tbody
|
%tbody
|
||||||
%tr
|
%tr
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _("Project")
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
|
||||||
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
|
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
|
||||||
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
|
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
|
||||||
|
@ -116,7 +112,7 @@
|
||||||
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
|
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
|
||||||
= @project.name
|
= @project.name
|
||||||
%tr
|
%tr
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Branch")
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
|
||||||
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
|
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
|
||||||
%tbody
|
%tbody
|
||||||
|
@ -127,7 +123,7 @@
|
||||||
%span.muted{ style: "color:#333333;text-decoration:none;" }
|
%span.muted{ style: "color:#333333;text-decoration:none;" }
|
||||||
= @merge_request.source_branch
|
= @merge_request.source_branch
|
||||||
%tr
|
%tr
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _("Author")
|
||||||
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
|
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
|
||||||
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
|
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
|
||||||
%tbody
|
%tbody
|
||||||
|
|
157
doc/ci/cloud_services/azure/index.md
Normal file
157
doc/ci/cloud_services/azure/index.md
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
---
|
||||||
|
stage: Verify
|
||||||
|
group: Pipeline Authoring
|
||||||
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||||
|
---
|
||||||
|
|
||||||
|
# Configure OpenID Connect in Azure to retrieve temporary credentials
|
||||||
|
|
||||||
|
This tutorial demonstrates how to use a JSON web token (JWT) in a GitLab CI/CD job
|
||||||
|
to retrieve temporary credentials from Azure without needing to store secrets.
|
||||||
|
|
||||||
|
To get started, configure OpenID Connect (OIDC) for identity federation between GitLab and Azure.
|
||||||
|
For more information on using OIDC with GitLab, read [Connect to cloud services](../index.md).
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Access to an existing Azure Subscription with `Owner` access level.
|
||||||
|
- Access to the corresponding Azure Active Directory Tenant with at least the `Application Developer` access level.
|
||||||
|
- A local installation of the [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli).
|
||||||
|
Alternatively, you can follow all the steps below with the [Azure Cloud Shell](https://shell.azure.com/).
|
||||||
|
- A GitLab project.
|
||||||
|
|
||||||
|
To complete this tutorial:
|
||||||
|
|
||||||
|
1. [Create Azure AD application and service principal](#create-azure-ad-application-and-service-principal).
|
||||||
|
1. [Create Azure AD federated identity credentials](#create-azure-ad-federated-identity-credentials).
|
||||||
|
1. [Grant permissions for the service principal](#grant-permissions-for-the-service-principal).
|
||||||
|
1. [Retrieve a temporary credential](#retrieve-a-temporary-credential).
|
||||||
|
|
||||||
|
For more information, review Azure's documentation on [Workload identity federation](https://docs.microsoft.com/azure/active-directory/develop/workload-identity-federation).
|
||||||
|
|
||||||
|
## Create Azure AD application and service principal
|
||||||
|
|
||||||
|
To create an [Azure AD application](https://docs.microsoft.com/cli/azure/ad/app?view=azure-cli-latest#az-ad-app-create)
|
||||||
|
and service principal:
|
||||||
|
|
||||||
|
1. In the Azure CLI, create the AD application:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
appId=$(az ad app create --display-name gitlab-oidc --query appId -otsv)
|
||||||
|
```
|
||||||
|
|
||||||
|
Save the `appId` (Application client ID) output, as you need it later
|
||||||
|
to configure your GitLab CI/CD pipeline.
|
||||||
|
|
||||||
|
1. Create a corresponding [Service Principal](https://docs.microsoft.com/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
az ad sp create --id $appId --query appId -otsv
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of the Azure CLI, you can [use the Azure Portal to create these resources](https://docs.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal).
|
||||||
|
|
||||||
|
## Create Azure AD federated identity credentials
|
||||||
|
|
||||||
|
To create the federated identity credentials for the above Azure AD application:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
objectId=$(az ad app show --id $appId --query id -otsv)
|
||||||
|
|
||||||
|
cat <<EOF > body.json
|
||||||
|
{
|
||||||
|
"name": "gitlab-federated-identity",
|
||||||
|
"issuer": "https://gitlab.example.com",
|
||||||
|
"subject": "project_path:<mygroup>/<myproject>:ref_type:branch:ref:<branch>",
|
||||||
|
"description": "GitLab service account federated identity",
|
||||||
|
"audiences": [
|
||||||
|
"https://gitlab.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
az rest --method POST --uri "https://graph.microsoft.com/beta/applications/$objectId/federatedIdentityCredentials" --body @body.json
|
||||||
|
```
|
||||||
|
|
||||||
|
For issues related to the values of `issuer`, `subject` or `audiences`, see the
|
||||||
|
[troubleshooting](#troubleshooting) details.
|
||||||
|
|
||||||
|
Optionally, you can now verify the Azure AD application and the Azure AD federated
|
||||||
|
identity credentials from the Azure Portal:
|
||||||
|
|
||||||
|
1. Open the [Azure Active Directory App Registration](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps)
|
||||||
|
view and select the appropriate app registration by searching for the display name `gitlab-oidc`.
|
||||||
|
1. On the overview page you can verify details like the `Application (client) ID`,
|
||||||
|
`Object ID`, and `Tenant ID`.
|
||||||
|
1. Under `Certificates & secrets`, go to `Federated credentials` to review your
|
||||||
|
Azure AD federated identity credentials.
|
||||||
|
|
||||||
|
## Grant permissions for the service principal
|
||||||
|
|
||||||
|
After you create the credentials, use [`role assignment`](https://docs.microsoft.com/cli/azure/role/assignment?view=azure-cli-latest#az-role-assignment-create)
|
||||||
|
to grant permissions to the above service principal to access to Azure resources:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
az role assignment create --assignee $appId --role Reader --scope /subscriptions/<subscription-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find your subscription ID in:
|
||||||
|
|
||||||
|
- The [Azure Portal](https://docs.microsoft.com/azure/azure-portal/get-subscription-tenant-id#find-your-azure-subscription).
|
||||||
|
- The [Azure CLI](https://docs.microsoft.com/cli/azure/manage-azure-subscriptions-azure-cli#get-the-active-subscription).
|
||||||
|
|
||||||
|
## Retrieve a temporary credential
|
||||||
|
|
||||||
|
After you configure the Azure AD application and federated identity credentials,
|
||||||
|
the CI/CD job can retrieve a temporary credential by using the [Azure CLI](https://docs.microsoft.com/cli/azure/reference-index?view=azure-cli-latest#az-login):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
default:
|
||||||
|
image: mcr.microsoft.com/azure-cli:latest
|
||||||
|
|
||||||
|
variables:
|
||||||
|
AZURE_CLIENT_ID: "<client-id>"
|
||||||
|
AZURE_TENANT_ID: "<tenant-id>"
|
||||||
|
|
||||||
|
auth:
|
||||||
|
script:
|
||||||
|
- az login --service-principal -u $AZURE_CLIENT_ID -t $AZURE_TENANT_ID --federated-token $CI_JOB_JWT_V2
|
||||||
|
- az account show
|
||||||
|
```
|
||||||
|
|
||||||
|
The CI/CD variables are:
|
||||||
|
|
||||||
|
- `AZURE_CLIENT_ID`: The [application client ID you saved earlier](#create-azure-ad-application-and-service-principal).
|
||||||
|
- `AZURE_TENANT_ID`: Your Azure Active Directory. You can
|
||||||
|
[find it by using the Azure CLI or Azure Portal](https://docs.microsoft.com/azure/active-directory/fundamentals/active-directory-how-to-find-tenant).
|
||||||
|
- `CI_JOB_JWT_V2`: The JSON web token is a [predefined CI/CD variable](../../variables/predefined_variables.md).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "No matching federated identity record found"
|
||||||
|
|
||||||
|
If you receive the error `ERROR: AADSTS70021: No matching federated identity record found for presented assertion.`
|
||||||
|
you should verify:
|
||||||
|
|
||||||
|
- The `Issuer` defined in the Azure AD federated identity credentials, for example
|
||||||
|
`https://gitlab.com` or your own GitLab URL.
|
||||||
|
- The `Subject identifier` defined in the Azure AD federated identity credentials,
|
||||||
|
for example `project_path:<mygroup>/<myproject>:ref_type:branch:ref:<branch>`.
|
||||||
|
- For the `gitlab-group/gitlab-project` project and `main` branch it would be:
|
||||||
|
`project_path:gitlab-group/gitlab-project:ref_type:branch:ref:main`.
|
||||||
|
- The correct values of `mygroup` and `myproject` can be retrieved by checking the URL
|
||||||
|
when accessing your GitLab project or by selecting the **Clone** option in the project.
|
||||||
|
- The `Audience` defined in the Azure AD federated identity credentials, for example `https://gitlab.com`
|
||||||
|
or your own GitLab URL.
|
||||||
|
|
||||||
|
You can review these settings, as well as your `AZURE_CLIENT_ID` and `AZURE_TENANT_ID`
|
||||||
|
CI/CD variables, from the Azure Portal:
|
||||||
|
|
||||||
|
1. Open the [Azure Active Directory App Registration](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps)
|
||||||
|
view and select the appropriate app registration by searching for the display name `gitlab-oidc`.
|
||||||
|
1. On the overview page you can verify details like the `Application (client) ID`,
|
||||||
|
`Object ID`, and `Tenant ID`.
|
||||||
|
1. Under `Certificates & secrets`, go to `Federated credentials` to review your
|
||||||
|
Azure AD federated identity credentials.
|
||||||
|
|
||||||
|
Review [Connect to cloud services](../index.md) for further details.
|
|
@ -16,7 +16,7 @@ GitLab CI/CD supports [OpenID Connect (OIDC)](https://openid.net/connect/faq/) t
|
||||||
- Account on GitLab.
|
- Account on GitLab.
|
||||||
- Access to a cloud provider that supports OIDC to configure authorization and create roles.
|
- Access to a cloud provider that supports OIDC to configure authorization and create roles.
|
||||||
|
|
||||||
The original implementation of `CI_JOB_JWT` supports [HashiCorp Vault integration](../examples/authenticating-with-hashicorp-vault/). The updated implementation of `CI_JOB_JWT_V2` supports additional cloud providers with OIDC including AWS, GCP, and Vault.
|
The original implementation of `CI_JOB_JWT` supports [HashiCorp Vault integration](../examples/authenticating-with-hashicorp-vault/). The updated implementation of `CI_JOB_JWT_V2` supports additional cloud providers with OIDC including AWS, Azure, GCP, and Vault.
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
Configuring OIDC enables JWT token access to the target environments for all pipelines.
|
Configuring OIDC enables JWT token access to the target environments for all pipelines.
|
||||||
|
@ -38,7 +38,7 @@ The `CI_JOB_JWT_V2` variable is under development [(alpha)](../../policy/alpha-b
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
Each job has a JSON web token (JWT) provided as a CI/CD [predefined variable](../variables/predefined_variables.md) named `CI_JOB_JWT` or `CI_JOB_JWT_V2`. This JWT can be used to authenticate with the OIDC-supported cloud provider such as AWS, GCP, or Vault.
|
Each job has a JSON web token (JWT) provided as a CI/CD [predefined variable](../variables/predefined_variables.md) named `CI_JOB_JWT` or `CI_JOB_JWT_V2`. This JWT can be used to authenticate with the OIDC-supported cloud provider such as AWS, Azure, GCP, or Vault.
|
||||||
|
|
||||||
The following fields are included in the JWT:
|
The following fields are included in the JWT:
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ sequenceDiagram
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Create an OIDC identity provider in the cloud (for example, AWS, GCP, Vault).
|
1. Create an OIDC identity provider in the cloud (for example, AWS, Azure, GCP, Vault).
|
||||||
1. Create a conditional role in the cloud service that filters to a group, project, branch, or tag.
|
1. Create a conditional role in the cloud service that filters to a group, project, branch, or tag.
|
||||||
1. The CI/CD job includes a predefined variable `CI_JOB_JWT_V2` that is a JWT token. You can use this token for authorization with your cloud API.
|
1. The CI/CD job includes a predefined variable `CI_JOB_JWT_V2` that is a JWT token. You can use this token for authorization with your cloud API.
|
||||||
1. The cloud verifies the token, validates the conditional role from the payload, and returns a temporary credential.
|
1. The cloud verifies the token, validates the conditional role from the payload, and returns a temporary credential.
|
||||||
|
@ -138,4 +138,5 @@ To configure the trust between GitLab and OIDC, you must create a conditional ro
|
||||||
To connect with your cloud provider, see the following tutorials:
|
To connect with your cloud provider, see the following tutorials:
|
||||||
|
|
||||||
- [Configure OpenID Connect in AWS](aws/index.md)
|
- [Configure OpenID Connect in AWS](aws/index.md)
|
||||||
|
- [Configure OpenID Connect in Azure](azure/index.md)
|
||||||
- [Configure OpenID Connect in Google Cloud](google_cloud/index.md)
|
- [Configure OpenID Connect in Google Cloud](google_cloud/index.md)
|
||||||
|
|
|
@ -26857,6 +26857,9 @@ msgstr ""
|
||||||
msgid "Notify|%{member_link} requested %{member_role} access to the %{target_source_link} %{target_type}."
|
msgid "Notify|%{member_link} requested %{member_role} access to the %{target_source_link} %{target_type}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify|%{mr_highlight}Merge request%{highlight_end} %{mr_link} %{approved_highlight}was approved by%{highlight_end} %{approver_avatar} %{approver_link}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Notify|Assignee changed from %{fromNames} to %{toNames}"
|
msgid "Notify|Assignee changed from %{fromNames} to %{toNames}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26902,6 +26905,12 @@ msgstr ""
|
||||||
msgid "Notify|Merge request URL: %{merge_request_url}"
|
msgid "Notify|Merge request URL: %{merge_request_url}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify|Merge request was approved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Notify|Merge request was approved (%{approvals}/%{required_approvals})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Notify|Milestone changed to %{milestone}"
|
msgid "Notify|Milestone changed to %{milestone}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
|
||||||
import SidebarService from '~/sidebar/services/sidebar_service';
|
import SidebarService from '~/sidebar/services/sidebar_service';
|
||||||
import SidebarMediator from '~/sidebar/sidebar_mediator';
|
import SidebarMediator from '~/sidebar/sidebar_mediator';
|
||||||
import SidebarStore from '~/sidebar/stores/sidebar_store';
|
import SidebarStore from '~/sidebar/stores/sidebar_store';
|
||||||
|
import { GitLabDropdown } from '~/deprecated_jquery_dropdown/gl_dropdown';
|
||||||
import Mock from './mock_data';
|
import Mock from './mock_data';
|
||||||
|
|
||||||
jest.mock('~/flash');
|
jest.mock('~/flash');
|
||||||
|
@ -75,7 +76,9 @@ describe('SidebarMoveIssue', () => {
|
||||||
it('should initialize the deprecatedJQueryDropdown', () => {
|
it('should initialize the deprecatedJQueryDropdown', () => {
|
||||||
test.sidebarMoveIssue.initDropdown();
|
test.sidebarMoveIssue.initDropdown();
|
||||||
|
|
||||||
expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy();
|
expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeInstanceOf(
|
||||||
|
GitLabDropdown,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('escapes html from project name', async () => {
|
it('escapes html from project name', async () => {
|
||||||
|
@ -97,7 +100,7 @@ describe('SidebarMoveIssue', () => {
|
||||||
test.sidebarMoveIssue.onConfirmClicked();
|
test.sidebarMoveIssue.onConfirmClicked();
|
||||||
|
|
||||||
expect(test.mediator.moveIssue).toHaveBeenCalled();
|
expect(test.mediator.moveIssue).toHaveBeenCalled();
|
||||||
expect(test.$confirmButton.prop('disabled')).toBeTruthy();
|
expect(test.$confirmButton.prop('disabled')).toBe(true);
|
||||||
expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
|
expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,7 +116,7 @@ describe('SidebarMoveIssue', () => {
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
expect(createFlash).toHaveBeenCalled();
|
expect(createFlash).toHaveBeenCalled();
|
||||||
expect(test.$confirmButton.prop('disabled')).toBeFalsy();
|
expect(test.$confirmButton.prop('disabled')).toBe(false);
|
||||||
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
|
expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,7 +142,7 @@ describe('SidebarMoveIssue', () => {
|
||||||
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
|
test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
|
||||||
|
|
||||||
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
|
expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
|
||||||
expect(test.$confirmButton.prop('disabled')).toBeTruthy();
|
expect(test.$confirmButton.prop('disabled')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set moveToProjectId on dropdown item click', async () => {
|
it('should set moveToProjectId on dropdown item click', async () => {
|
||||||
|
|
|
@ -51,6 +51,33 @@ RSpec.describe NotifyHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#merge_request_approved_description' do
|
||||||
|
let(:merge_request) { create(:merge_request) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:avatar_icon_for_user) { 'avatar_icon_for_user' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(helper).to receive(:avatar_icon_for_user).and_return(avatar_icon_for_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns MR approved description' do
|
||||||
|
result = helper.merge_request_approved_description(merge_request, user)
|
||||||
|
expect(result).to eq "<span style=\"font-weight: 600;color:#333333;\">Merge request</span> " \
|
||||||
|
"#{
|
||||||
|
link_to(merge_request.to_reference, merge_request_url(merge_request),
|
||||||
|
style: "font-weight: 600;color:#3777b0;text-decoration:none")
|
||||||
|
} " \
|
||||||
|
"<span>was approved by</span> " \
|
||||||
|
"#{
|
||||||
|
content_tag(:img, nil, height: "24", src: avatar_icon_for_user,
|
||||||
|
style: "border-radius:12px;margin:-7px 0 -7px 3px;",
|
||||||
|
width: "24", alt: "Avatar", class: "avatar"
|
||||||
|
)
|
||||||
|
} " \
|
||||||
|
"#{link_to(user.name, user_url(user), style: "color:#333333;text-decoration:none;", class: "muted")}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reference_link(entity, url)
|
def reference_link(entity, url)
|
||||||
"<a href=\"#{url}\">#{entity.to_reference}</a>"
|
"<a href=\"#{url}\">#{entity.to_reference}</a>"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
require 'email_spec'
|
||||||
|
|
||||||
|
RSpec.describe 'notify/approved_merge_request_email.html.haml' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:merge_request) { create(:merge_request) }
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:project) { create(:project, group: group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(view).to receive(:message) { instance_double(Mail::Message, subject: 'Subject') }
|
||||||
|
assign(:project, project)
|
||||||
|
assign(:approved_by, user)
|
||||||
|
assign(:merge_request, merge_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'contains approval information' do
|
||||||
|
render
|
||||||
|
|
||||||
|
expect(rendered).to have_content(merge_request.to_reference.to_s)
|
||||||
|
expect(rendered).to have_content("was approved by")
|
||||||
|
expect(rendered).to have_content(user.name.to_s)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue