diff --git a/app/assets/javascripts/snippets/components/snippet_description_edit.vue b/app/assets/javascripts/snippets/components/snippet_description_edit.vue
index 737845d09b8..5e6caf27bdd 100644
--- a/app/assets/javascripts/snippets/components/snippet_description_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_description_edit.vue
@@ -49,6 +49,7 @@ export default {
:add-spacing-classes="false"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
+ :textarea-value="value"
>
+ * ```
+ *
+ * ... as opposed to this:
+ *
+ * ```html
+ *
+ *
+ * ```
+ *
+ * When using `
` as shown above in example #1,
+ * it's important to **always** provide a value to this prop.
+ * If `textareaValue` isn't provided, this component will not
+ * show a preview when the "Preview" tab is clicked - it
+ * will always show "Nothing to preview."
+ *
+ * For more info, see https://github.com/vuejs/vue/issues/10450.
+ */
textareaValue: {
type: String,
required: false,
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index f706b615e7e..965769d28f7 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -57,9 +57,7 @@
@import './pages/sherlock';
@import './pages/status';
@import './pages/storage_quota';
-@import './pages/tags';
@import './pages/tree';
@import './pages/trials';
-@import './pages/ui_dev_kit';
@import './pages/users';
@import './pages/wiki';
diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss
deleted file mode 100644
index a6d30522ff7..00000000000
--- a/app/assets/stylesheets/pages/tags.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.tag-release-link {
- color: $blue-600 !important;
-}
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
deleted file mode 100644
index 288da4da5c3..00000000000
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.gitlab-ui-dev-kit {
- > h2 {
- margin: 35px 0 20px;
- font-weight: $gl-font-weight-bold;
- }
-
- .example {
- padding: 15px;
- border: 1px dashed $gray-100;
- margin-bottom: 15px;
-
- &::before {
- content: 'Example';
- color: $ui-dev-kit-example-color;
- }
- }
-}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 9c666331c4f..9277269c730 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -156,3 +156,8 @@
display: none;
}
}
+
+// This utility is used to force the z-index to match that of dropdown menu's
+.gl-z-dropdown-menu\! {
+ z-index: 300 !important;
+}
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index d71e6b4c004..7df6bef7914 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -343,6 +343,18 @@ module GitlabRoutingHelper
Gitlab::UrlBuilder.wiki_page_url(wiki, page, only_path: true, **options)
end
+ def gitlab_ide_merge_request_path(merge_request)
+ target_project = merge_request.target_project
+ source_project = merge_request.source_project
+ params = {}
+
+ if target_project != source_project
+ params = { target_project: target_project.full_path }
+ end
+
+ ide_merge_request_path(source_project.namespace, source_project, merge_request, params)
+ end
+
private
def snippet_query_params(snippet, *args)
diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb
index eea0acdc11b..9872bbf80b5 100644
--- a/app/serializers/cluster_entity.rb
+++ b/app/serializers/cluster_entity.rb
@@ -6,6 +6,7 @@ class ClusterEntity < Grape::Entity
expose :cluster_type
expose :enabled
expose :environment_scope
+ expose :id
expose :name
expose :nodes
expose :provider_type
diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb
index 700a46040e3..f71591612a6 100644
--- a/app/serializers/cluster_serializer.rb
+++ b/app/serializers/cluster_serializer.rb
@@ -12,6 +12,7 @@ class ClusterSerializer < BaseSerializer
:environment_scope,
:gitlab_managed_apps_logs_path,
:enable_advanced_logs_querying,
+ :id,
:kubernetes_errors,
:name,
:nodes,
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index 9f27191c3c8..596f5d686da 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -34,7 +34,7 @@ class DiffFileBaseEntity < Grape::Entity
expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
- next unless merge_request.merged? || merge_request.source_branch_exists?
+ next unless has_edit_path?(merge_request)
target_project, target_branch = edit_project_branch_options(merge_request)
@@ -43,6 +43,14 @@ class DiffFileBaseEntity < Grape::Entity
project_edit_blob_path(target_project, tree_join(target_branch, diff_file.new_path), options)
end
+ expose :ide_edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
+ merge_request = options[:merge_request]
+
+ next unless has_edit_path?(merge_request)
+
+ gitlab_ide_merge_request_path(merge_request)
+ end
+
expose :old_path_html do |diff_file|
old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
old_path
@@ -125,4 +133,8 @@ class DiffFileBaseEntity < Grape::Entity
[merge_request.target_project, merge_request.target_branch]
end
end
+
+ def has_edit_path?(merge_request)
+ merge_request.merged? || merge_request.source_branch_exists?
+ end
end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index d7630dbdac9..9802f48ae7e 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -53,7 +53,6 @@ class AuditEventService
private
- attr_accessor :authentication_event
attr_reader :ip_address
def build_author(author)
@@ -99,11 +98,11 @@ class AuditEventService
end
def mark_as_authentication_event!
- self.authentication_event = true
+ @authentication_event = true
end
def authentication_event?
- authentication_event
+ @authentication_event
end
def log_security_event_to_database
diff --git a/app/views/notify/_failed_builds.html.haml b/app/views/notify/_failed_builds.html.haml
index cde0ac21d6d..11cbd700258 100644
--- a/app/views/notify/_failed_builds.html.haml
+++ b/app/views/notify/_failed_builds.html.haml
@@ -6,7 +6,7 @@
#{'build'.pluralize(failed.size)}.
%tr.table-warning
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" }
- Logs may contain sensitive data. Please consider before forwarding this email.
+ Failed builds
%tr.section
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" }
%table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" }
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index dba9b20fcff..4f14ee4d044 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -24,7 +24,7 @@
.text-secondary
= sprite_icon("rocket", size: 12)
= _("Release")
- = link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'tag-release-link'
+ = link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'gl-text-blue-600!'
- if release.description.present?
.md.gl-mt-3
= markdown_field(release, :description)
diff --git a/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml b/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml
new file mode 100644
index 00000000000..6c56b39bc0f
--- /dev/null
+++ b/changelogs/unreleased/21654-ide-button-in-mr-diff-files.yml
@@ -0,0 +1,5 @@
+---
+title: Add Web IDE as dropdown item to diff file edit
+merge_request: 42275
+author:
+type: changed
diff --git a/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml b/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml
new file mode 100644
index 00000000000..b6854e68776
--- /dev/null
+++ b/changelogs/unreleased/nfriend-fix-markdown-preview-on-new-release-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Markdown "Preview" tab on New/Edit Release and New Snippet pages
+merge_request: 42640
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml b/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml
new file mode 100644
index 00000000000..a9f5ddcef33
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-pipeline-notification-email-warning.yml
@@ -0,0 +1,5 @@
+---
+title: Update pipeline failed notification e-mail warning
+merge_request: 42736
+author:
+type: fixed
diff --git a/config/routes.rb b/config/routes.rb
index 9bd68bfeef6..5dbb24dcc7c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -122,6 +122,7 @@ Rails.application.routes.draw do
get 'ide' => 'ide#index'
get 'ide/*vueroute' => 'ide#index', format: false
+ get 'ide/project/:namespace/:project/merge_requests/:id' => 'ide#index', format: false, as: :ide_merge_request
draw :operations
draw :jira_connect
diff --git a/doc/README.md b/doc/README.md
index efae2cdd3ff..52123c1db66 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -295,7 +295,7 @@ The following documentation relates to the DevOps **Secure** stage:
| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [Group Security Dashboard](user/application_security/security_dashboard/index.md#group-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
-| [Instance Security Dashboard](user/application_security/security_dashboard/index.md#instance-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects you're interested in. |
+| [Instance Security Center](user/application_security/security_dashboard/index.md#instance-security-center) **(ULTIMATE)** | View vulnerabilities in all the projects you're interested in. |
| [License Compliance](user/compliance/license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
| [Pipeline Security](user/application_security/security_dashboard/index.md#pipeline-security) **(ULTIMATE)** | View the security reports for your project's pipelines. |
| [Project Security Dashboard](user/application_security/security_dashboard/index.md#project-security-dashboard) **(ULTIMATE)** | View the latest security reports for your project. |
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 099346b2b0b..7b6327838d3 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -183,6 +183,7 @@ the steps bellow.
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
+If available, you can enable it with a [feature flag](#enable-or-disable-audit-log-export-to-csv).
Export to CSV allows customers to export the current filter view of your audit log as a
CSV file,
diff --git a/doc/ci/img/gitlab_vault_workflow_v13_4.png b/doc/ci/img/gitlab_vault_workflow_v13_4.png
new file mode 100644
index 00000000000..80d07362bf4
Binary files /dev/null and b/doc/ci/img/gitlab_vault_workflow_v13_4.png differ
diff --git a/doc/ci/secrets/index.md b/doc/ci/secrets/index.md
index 6d561fe00a3..09aeebcc7cc 100644
--- a/doc/ci/secrets/index.md
+++ b/doc/ci/secrets/index.md
@@ -17,23 +17,36 @@ Unlike CI variables, which are always presented to a job, secrets must be explic
required by a job. Read [GitLab CI/CD pipeline configuration reference](../yaml/README.md#secrets)
for more information about the syntax.
-GitLab has selected [Vault by Hashicorp](https://www.vaultproject.io) as the
+GitLab has selected [Vault by HashiCorp](https://www.vaultproject.io) as the
first supported provider, and [KV-V2](https://www.vaultproject.io/docs/secrets/kv/kv-v2)
as the first supported secrets engine.
GitLab authenticates using Vault's
-[JWT Auth method](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication), using
+[JSON Web Token (JWT) authentication method](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication), using
the [JSON Web Token](https://gitlab.com/gitlab-org/gitlab/-/issues/207125) (`CI_JOB_JWT`)
introduced in GitLab 12.10.
You must [configure your Vault server](#configure-your-vault-server) before you
can use [use Vault secrets in a CI job](#use-vault-secrets-in-a-ci-job).
+The flow for using GitLab with HashiCorp Vault
+is summarized by this diagram:
+
+![Flow between GitLab and HashiCorp](../img/gitlab_vault_workflow_v13_4.png "How GitLab CI_JOB_JWT works with HashiCorp Vault")
+
+1. Configure your vault and secrets.
+1. Generate your JWT and provide it to your CI job.
+1. Runner contacts HashiCorp Vault and authenticates using the JWT.
+1. HashiCorp Vault verifies the JWT.
+1. HashiCorp Vault checks the bounded claims and attaches policies.
+1. HashiCorp Vault returns the token.
+1. Runner reads secrets from the HashiCoupr Vault.
+
NOTE: **Note:**
-Read the [Authenticating and Reading Secrets With Hashicorp Vault](../examples/authenticating-with-hashicorp-vault/index.md)
-tutorial for a version of this feature that is available to all
+Read the [Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md)
+tutorial for a version of this feature. It's available to all
subscription levels, supports writing secrets to and deleting secrets from Vault,
-and multiple secrets engines.
+and supports multiple secrets engines.
## Configure your Vault server
@@ -149,7 +162,7 @@ generated by this GitLab instance may be allowed to authenticate using this role
For a full list of `CI_JOB_JWT` claims, read the
[How it works](../examples/authenticating-with-hashicorp-vault/index.md#how-it-works) section of the
-[Authenticating and Reading Secrets With Hashicorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial.
+[Authenticating and Reading Secrets With HashiCorp Vault](../examples/authenticating-with-hashicorp-vault/index.md) tutorial.
You can also specify some attributes for the resulting Vault tokens, such as time-to-live,
IP address range, and number of uses. The full list of options is available in
diff --git a/doc/development/integrations/secure_partner_integration.md b/doc/development/integrations/secure_partner_integration.md
index 830cb84e257..36a40162184 100644
--- a/doc/development/integrations/secure_partner_integration.md
+++ b/doc/development/integrations/secure_partner_integration.md
@@ -44,7 +44,7 @@ best place to integrate your own product and its results into GitLab.
- If certain policies (such as [merge request approvals](../../user/project/merge_requests/merge_request_approvals.md))
are in place for a project, developers must resolve specific findings or get
an approval from a specific list of people.
-- The [security dashboard](../../user/application_security/security_dashboard/index.md#gitlab-security-dashboard)
+- The [security dashboard](../../user/application_security/security_dashboard/index.md)
also shows results which can developers can use to quickly see all the
vulnerabilities that need to be addressed in the code.
- When the developer reads the details about a vulnerability, they are
diff --git a/doc/development/redis.md b/doc/development/redis.md
index d205082b9c6..502bb656c22 100644
--- a/doc/development/redis.md
+++ b/doc/development/redis.md
@@ -96,10 +96,14 @@ requests that read the most data from the cache, we can just sort by
### The slow log
+TIP: **Tip:**
+There is a [video showing how to see the slow log](https://youtu.be/BBI68QuYRH8) (GitLab internal)
+on GitLab.com
+
On GitLab.com, entries from the [Redis
slow log](https://redis.io/commands/slowlog) are available in the
`pubsub-redis-inf-gprd*` index with the [`redis.slowlog`
-tag](https://log.gprd.gitlab.net/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&_a=(columns:!(json.type,json.command,json.exec_time),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:AWSQX_Vf93rHTYrsexmk,key:json.tag,negate:!f,params:(query:redis.slowlog),type:phrase),query:(match:(json.tag:(query:redis.slowlog,type:phrase))))),index:AWSQX_Vf93rHTYrsexmk)).
+tag](https://log.gprd.gitlab.net/app/kibana#/discover?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-1d,to:now))&_a=(columns:!(json.type,json.command,json.exec_time_s),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:AWSQX_Vf93rHTYrsexmk,key:json.tag,negate:!f,params:(query:redis.slowlog),type:phrase),query:(match:(json.tag:(query:redis.slowlog,type:phrase))))),index:AWSQX_Vf93rHTYrsexmk)).
This shows commands that have taken a long time and may be a performance
concern.
diff --git a/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png b/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png
new file mode 100644
index 00000000000..d7d5961087c
Binary files /dev/null and b/doc/user/application_security/security_dashboard/img/instance_security_center_settings_v13_4.png differ
diff --git a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png
index d010adcc90c..5e52bcc650a 100644
Binary files a/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png and b/doc/user/application_security/security_dashboard/img/instance_security_dashboard_v13_4.png differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 8c461e27e70..974131e7683 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -5,21 +5,26 @@ group: Threat Insights
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/#designated-technical-writers
---
-# GitLab Security Dashboard **(ULTIMATE)**
+# GitLab Security Dashboard, Security Center, and Vulnerability Reports **(ULTIMATE)**
-The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups, projects, and pipelines.
+GitLab provides a comprehensive set of features for viewing and managing vulnerabilities:
+
+- Security dashboards: An overview of the security status in your instance, groups, and projects.
+- Vulnerability reports: Detailed lists of all vulnerabilities for the instance, group, project, or
+ pipeline. This is where you triage and manage vulnerabilities.
+- Security Center: A dedicated area for vulnerability management at the instance level. This
+ includes a security dashboard, vulnerability report, and settings.
You can also drill down into a vulnerability and get extra information. This includes the project it
comes from, any related file(s), and metadata that helps you analyze the risk it poses. You can also
dismiss a vulnerability or create an issue for it.
-To benefit from the Security Dashboard you must first configure one of the
+To benefit from these features, you must first configure one of the
[security scanners](../index.md).
## Supported reports
-The Security Dashboard displays vulnerabilities detected by scanners such as:
+The vulnerability report displays vulnerabilities detected by scanners such as:
- [Container Scanning](../container_scanning/index.md)
- [Dynamic Application Security Testing](../dast/index.md)
@@ -29,7 +34,7 @@ The Security Dashboard displays vulnerabilities detected by scanners such as:
## Requirements
-To use the instance, group, project, or pipeline security dashboard:
+To use the security dashboards and vulnerability reports:
1. At least one project inside a group must be configured with at least one of
the [supported reports](#supported-reports).
@@ -112,38 +117,43 @@ Next to the timeline chart is a list of projects, grouped and sorted by the seve
Projects with no vulnerability tests configured will not appear in the list. Additionally, dismissed
vulnerabilities are excluded.
-Navigate to the group's [Vulnerability Report](#vulnerability-list) to view the vulnerabilities found.
+Navigate to the group's [vulnerability report](#vulnerability-report) to view the vulnerabilities found.
-## Instance Security Dashboard
+## Instance Security Center
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6953) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.8.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3426) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4.
-At the instance level, the Security Dashboard displays the vulnerabilities present in the default
-branches of all the projects you configure to display on the dashboard. It includes all the
-[group Security Dashboard's](#group-security-dashboard)
-features.
+The Security Center is where you manage vulnerabilities for your instance. It displays the
+vulnerabilities present in the default branches of all the projects you configure. It includes the
+following:
+
+- The [group security dashboard's](#group-security-dashboard) features.
+- A [vulnerability report](#vulnerability-report).
+- A dedicated settings area to configure which projects to display.
![Instance Security Dashboard with projects](img/instance_security_dashboard_v13_4.png)
-You can access the Instance Security Dashboard from the menu
+You can access the Instance Security Center from the menu
bar at the top of the page. Under **More**, select **Security**.
-![Instance Security Dashboard navigation link](img/instance_security_dashboard_link_v12_4.png)
+![Instance Security Center navigation link](img/instance_security_dashboard_link_v12_4.png)
-The dashboard is empty before you add projects to it.
+The dashboard and vulnerability report are empty before you add projects.
-![Uninitialized Instance Security Dashboard](img/instance_security_dashboard_empty_v13_4.png)
+![Uninitialized Instance Security Center](img/instance_security_dashboard_empty_v13_4.png)
-### Adding projects to the dashboard
+### Adding projects to the Security Center
-To add projects to the dashboard:
+To add projects to the Security Center:
1. Click **Settings** in the left navigation bar or click the **Add projects** button.
1. Search for and add one or more projects using the **Search your projects** field.
1. Click the **Add projects** button.
-After you add projects, the Security Dashboard displays the vulnerabilities found in those projects'
-default branches.
+![Adding projects to Instance Security Center](img/instance_security_center_settings_v13_4.png)
+
+After you add projects, the security dashboard and vulnerability report display the vulnerabilities
+found in those projects' default branches.
## Export vulnerabilities
@@ -192,14 +202,14 @@ When using [Auto DevOps](../../../topics/autodevops/index.md), use
[special environment variables](../../../topics/autodevops/customize.md#environment-variables)
to configure daily security scans.
-## Vulnerability list
+## Vulnerability report
-Each dashboard's vulnerability list contains vulnerabilities from the latest scans that were merged
+Each vulnerability report contains vulnerabilities from the latest scans that were merged
into the default branch.
![Vulnerability Report](img/group_vulnerability_report_v13_4.png)
-You can filter which vulnerabilities the Security Dashboard displays by:
+You can filter which vulnerabilities the vulnerability report displays by:
- Status
- Severity
diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md
index 7b745577cc4..98d76e51cc0 100644
--- a/doc/user/clusters/agent/index.md
+++ b/doc/user/clusters/agent/index.md
@@ -8,8 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
-## Goals
-
The [GitLab Kubernetes Agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent) is an active in-cluster component for solving GitLab and Kubernetes integration tasks in a secure and cloud native way.
Features:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index e2baac1a962..eeaa759b193 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -159,6 +159,7 @@ The following table depicts the various user permission levels in a project.
| Remove fork relationship | | | | | ✓ |
| Delete project | | | | | ✓ |
| Archive project | | | | | ✓ |
+| Export project | | | | ✓ | ✓ |
| Delete issues | | | | | ✓ |
| Delete pipelines | | | | | ✓ |
| Delete merge request | | | | | ✓ |
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d0d0606a34e..1a3525408c6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5374,6 +5374,9 @@ msgstr ""
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
msgstr ""
+msgid "ClusterIntegration|An unknown error occurred while attempting to connect to Kubernetes."
+msgstr ""
+
msgid "ClusterIntegration|Any project namespaces"
msgstr ""
@@ -5389,6 +5392,9 @@ msgstr ""
msgid "ClusterIntegration|Authenticate with Amazon Web Services"
msgstr ""
+msgid "ClusterIntegration|Authentication Error"
+msgstr ""
+
msgid "ClusterIntegration|Base domain"
msgstr ""
@@ -5407,6 +5413,15 @@ msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
+msgid "ClusterIntegration|Check your CA certificate"
+msgstr ""
+
+msgid "ClusterIntegration|Check your cluster status"
+msgstr ""
+
+msgid "ClusterIntegration|Check your token"
+msgstr ""
+
msgid "ClusterIntegration|Choose the %{startLink}security group %{externalLinkIcon} %{endLink} to apply to the EKS-managed Elastic Network Interfaces that are created in your worker node subnets."
msgstr ""
@@ -5449,6 +5464,9 @@ msgstr ""
msgid "ClusterIntegration|Connect existing cluster"
msgstr ""
+msgid "ClusterIntegration|Connection Error"
+msgstr ""
+
msgid "ClusterIntegration|Copy API URL"
msgstr ""
@@ -5605,6 +5623,12 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
+msgid "ClusterIntegration|GitLab failed to authenticate."
+msgstr ""
+
+msgid "ClusterIntegration|GitLab failed to connect to the cluster."
+msgstr ""
+
msgid "ClusterIntegration|GitLab-managed cluster"
msgstr ""
@@ -5626,6 +5650,9 @@ msgstr ""
msgid "ClusterIntegration|Group cluster"
msgstr ""
+msgid "ClusterIntegration|HTTP Error"
+msgstr ""
+
msgid "ClusterIntegration|Helm Tiller"
msgstr ""
@@ -5770,6 +5797,9 @@ msgstr ""
msgid "ClusterIntegration|Machine type"
msgstr ""
+msgid "ClusterIntegration|Make sure your API endpoint is correct"
+msgstr ""
+
msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters"
msgstr ""
@@ -5821,6 +5851,9 @@ msgstr ""
msgid "ClusterIntegration|No zones matched your search"
msgstr ""
+msgid "ClusterIntegration|Node calculations use the Kubernetes Metrics API. Make sure your cluster has metrics installed"
+msgstr ""
+
msgid "ClusterIntegration|Number of nodes"
msgstr ""
@@ -6088,6 +6121,9 @@ msgstr ""
msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
msgstr ""
+msgid "ClusterIntegration|There was an HTTP error when connecting to your cluster."
+msgstr ""
+
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr ""
@@ -6115,9 +6151,21 @@ msgstr ""
msgid "ClusterIntegration|To use a new project, first create one on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}."
msgstr ""
+msgid "ClusterIntegration|Troubleshooting tips:"
+msgstr ""
+
+msgid "ClusterIntegration|Unable to Authenticate"
+msgstr ""
+
+msgid "ClusterIntegration|Unable to Connect"
+msgstr ""
+
msgid "ClusterIntegration|Uninstall %{appTitle}"
msgstr ""
+msgid "ClusterIntegration|Unknown Error"
+msgstr ""
+
msgid "ClusterIntegration|Update %{appTitle}"
msgstr ""
@@ -9228,7 +9276,7 @@ msgstr ""
msgid "Edit environment"
msgstr ""
-msgid "Edit file"
+msgid "Edit file in..."
msgstr ""
msgid "Edit files in the editor and commit changes here"
@@ -9243,6 +9291,12 @@ msgstr ""
msgid "Edit identity for %{user_name}"
msgstr ""
+msgid "Edit in Web IDE"
+msgstr ""
+
+msgid "Edit in single-file editor"
+msgstr ""
+
msgid "Edit issues"
msgstr ""
diff --git a/package.json b/package.json
index ca76e4cab0b..307c24c80ce 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.164.0",
- "@gitlab/ui": "21.3.1",
+ "@gitlab/ui": "21.4.2",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.22.3",
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
index f96b424d233..5672060a953 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Push mirror a repository over HTTP' do
- it 'configures and syncs LFS objects for a (push) mirrored repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do
+ it 'configures and syncs LFS objects for a (push) mirrored repository', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do
Runtime::Feature.enable_and_verify('push_mirror_syncs_lfs')
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 326647b25f7..8de739f1559 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:max_wait) { 30 }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index a296d60b27c..9ce87f353d0 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let!(:runner) do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
index f4edaaa84a8..5bfc88e45f2 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Code coverage statistics' do
let(:simplecov) { '\(\d+.\d+\%\) covered' }
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
index a617f3b3e29..4ca356c9b65 100644
--- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :docker, :orchestrated, :packages do
+ RSpec.describe 'Package', :orchestrated, :packages do
describe 'Maven Repository' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
index e97ede35610..43c708093b3 100644
--- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :docker, :orchestrated, :packages do
+ RSpec.describe 'Package', :orchestrated, :packages do
describe 'NPM registry' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 18eb52830a2..abac4f2b91d 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- RSpec.describe 'Release', :docker, :runner do
+ RSpec.describe 'Release', :runner do
describe 'Git clone using a deploy key' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
index 47a1b3b5670..ece45d093a7 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :docker, :runner, :reliable do
+ RSpec.describe 'Release', :runner, :reliable do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
index 9eb81244aa4..38cee0e62ca 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :docker, :runner, :reliable do
+ RSpec.describe 'Release', :runner, :reliable do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index 0e65cb358da..2a3f7d62828 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -26,7 +26,10 @@ RSpec.describe 'a maintainer edits files on a source-branch of an MR from a fork
visit project_merge_request_path(target_project, merge_request)
click_link 'Changes'
wait_for_requests
- first('.js-file-title').find('.js-edit-blob').click
+ within first('.js-file-title') do
+ find('[data-testid="edit_file"]').click
+ click_link 'Edit in single-file editor'
+ end
wait_for_requests
end
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 7a3a14e61e3..b4c06535b68 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
- expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob")
+ expect(page).to have_selector("[id=\"#{changelog_id}\"] [data-testid='edit_file']")
end
end
@@ -73,7 +73,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
- find("[id=\"#{changelog_id}\"] .js-edit-blob").click
+ find("[id=\"#{changelog_id}\"] [data-testid=\"edit_file\"").click
expect(page).to have_selector('.js-fork-suggestion-button', count: 1)
expect(page).to have_selector('.js-cancel-fork-suggestion-button', count: 1)
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 5aca994f53e..3949c70e718 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -23,6 +23,19 @@ RSpec.describe 'Editing file blob', :js do
def edit_and_commit(commit_changes: true)
wait_for_requests
find('.js-edit-blob').click
+
+ fill_and_commit(commit_changes)
+ end
+
+ def mr_edit_and_commit(commit_changes: true)
+ wait_for_requests
+ find('[data-testid="edit_file"]').click
+ click_link 'Edit in single-file editor'
+
+ fill_and_commit(commit_changes)
+ end
+
+ def fill_and_commit(commit_changes)
fill_editor(content: 'class NextFeature\\nend\\n')
if commit_changes
@@ -38,7 +51,7 @@ RSpec.describe 'Editing file blob', :js do
context 'from MR diff' do
before do
visit diffs_project_merge_request_path(project, merge_request)
- edit_and_commit
+ mr_edit_and_commit
end
it 'returns me to the mr' do
diff --git a/spec/features/projects/releases/user_creates_release_spec.rb b/spec/features/projects/releases/user_creates_release_spec.rb
index 5d05a7e4c91..dd0d7338a26 100644
--- a/spec/features/projects/releases/user_creates_release_spec.rb
+++ b/spec/features/projects/releases/user_creates_release_spec.rb
@@ -108,6 +108,24 @@ RSpec.describe 'User creates release', :js do
end
end
+ context 'when the release notes "Preview" tab is clicked' do
+ before do
+ find_field('Release notes').click
+
+ fill_release_notes('**some** _markdown_ [content](https://example.com)')
+
+ click_on 'Preview'
+
+ wait_for_all_requests
+ end
+
+ it 'renders a preview of the release notes markdown' do
+ within('[data-testid="release-notes"]') do
+ expect(page).to have_text('some markdown content')
+ end
+ end
+ end
+
def fill_out_form_and_submit
fill_tag_name(tag_name)
diff --git a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
index 16e92bf505a..a65d1eae2e3 100644
--- a/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
+++ b/spec/frontend/alert_settings/__snapshots__/alert_settings_form_spec.js.snap
@@ -26,7 +26,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
- Reset key
+ Reset key
Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
@@ -35,13 +35,13 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
- Test alert payload
+ Test alert payload
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 628c35ae839..34d99473eb7 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -164,18 +164,18 @@ describe('Clusters', () => {
});
it.each`
- nodeSize | lineNumber
- ${'Unknown'} | ${0}
- ${'1'} | ${1}
- ${'2'} | ${2}
- ${'1'} | ${3}
- ${'1'} | ${4}
- ${'Unknown'} | ${5}
- `('renders node size for each cluster', ({ nodeSize, lineNumber }) => {
+ nodeText | lineNumber
+ ${'Unable to Authenticate'} | ${0}
+ ${'1'} | ${1}
+ ${'2'} | ${2}
+ ${'1'} | ${3}
+ ${'1'} | ${4}
+ ${'Unknown Error'} | ${5}
+ `('renders node size for each cluster', ({ nodeText, lineNumber }) => {
const sizes = findTable().findAll('td:nth-child(3)');
const size = sizes.at(lineNumber);
- expect(size.text()).toBe(nodeSize);
+ expect(size.text()).toContain(nodeText);
expect(size.find(GlSkeletonLoading).exists()).toBe(false);
});
});
diff --git a/spec/frontend/clusters_list/components/node_error_help_text_spec.js b/spec/frontend/clusters_list/components/node_error_help_text_spec.js
new file mode 100644
index 00000000000..4d157b3a8ab
--- /dev/null
+++ b/spec/frontend/clusters_list/components/node_error_help_text_spec.js
@@ -0,0 +1,33 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlPopover } from '@gitlab/ui';
+import NodeErrorHelpText from '~/clusters_list/components/node_error_help_text.vue';
+
+describe('NodeErrorHelpText', () => {
+ let wrapper;
+
+ const createWrapper = propsData => {
+ wrapper = shallowMount(NodeErrorHelpText, { propsData, stubs: { GlPopover } });
+ return wrapper.vm.$nextTick();
+ };
+
+ const findPopover = () => wrapper.find(GlPopover);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ errorType | wrapperText | popoverText
+ ${'authentication_error'} | ${'Unable to Authenticate'} | ${'GitLab failed to authenticate'}
+ ${'connection_error'} | ${'Unable to Connect'} | ${'GitLab failed to connect to the cluster'}
+ ${'http_error'} | ${'Unable to Connect'} | ${'There was an HTTP error when connecting to your cluster'}
+ ${'default'} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'}
+ ${'unknown_error_type'} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'}
+ ${null} | ${'Unknown Error'} | ${'An unknown error occurred while attempting to connect to Kubernetes.'}
+ `('displays error text', ({ errorType, wrapperText, popoverText }) => {
+ return createWrapper({ errorType, popoverId: 'id' }).then(() => {
+ expect(wrapper.text()).toContain(wrapperText);
+ expect(findPopover().text()).toContain(popoverText);
+ });
+ });
+});
diff --git a/spec/frontend/clusters_list/mock_data.js b/spec/frontend/clusters_list/mock_data.js
index 48af3b91c94..ed32655d10e 100644
--- a/spec/frontend/clusters_list/mock_data.js
+++ b/spec/frontend/clusters_list/mock_data.js
@@ -6,6 +6,11 @@ export const clusterList = [
provider_type: 'gcp',
status: 'creating',
nodes: null,
+ kubernetes_errors: {
+ connection_error: 'authentication_error',
+ node_connection_error: 'connection_error',
+ metrics_connection_error: 'http_error',
+ },
},
{
name: 'My Cluster 2',
@@ -19,6 +24,7 @@ export const clusterList = [
usage: { cpu: '246155922n', memory: '1255212Ki' },
},
],
+ kubernetes_errors: {},
},
{
name: 'My Cluster 3',
@@ -36,6 +42,7 @@ export const clusterList = [
usage: { cpu: '307051934n', memory: '1379136Ki' },
},
],
+ kubernetes_errors: {},
},
{
name: 'My Cluster 4',
@@ -48,6 +55,7 @@ export const clusterList = [
usage: { cpu: '1missingCpuUnit', memory: '1missingMemoryUnit' },
},
],
+ kubernetes_errors: {},
},
{
name: 'My Cluster 5',
@@ -59,12 +67,14 @@ export const clusterList = [
status: { allocatable: { cpu: '1missingCpuUnit', memory: '1missingMemoryUnit' } },
},
],
+ kubernetes_errors: {},
},
{
name: 'My Cluster 6',
environment_scope: '*',
cluster_type: 'project_type',
status: 'cleanup_ongoing',
+ kubernetes_errors: {},
},
];
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 745a163951a..62b751ec59b 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -56,6 +56,7 @@ exports[`Code navigation popover component renders popover 1`] = `
class="popover-body border-top"
>
{
const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' });
const findViewFileButton = () => wrapper.find({ ref: 'viewButton' });
const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' });
+ const hasZDropdownMenuClass = () => wrapper.classes('gl-z-dropdown-menu!');
const findIconByName = iconName => {
const icons = wrapper.findAll(GlIcon).filter(w => w.props('name') === iconName);
@@ -151,6 +152,10 @@ describe('DiffFileHeader component', () => {
expect(wrapper.find(ClipboardButton).exists()).toBe(true);
});
+ it('should not have z dropdown menu class', () => {
+ expect(hasZDropdownMenuClass()).toBe(false);
+ });
+
describe('for submodule', () => {
const submoduleDiffFile = {
...diffFile,
@@ -303,6 +308,27 @@ describe('DiffFileHeader component', () => {
expect(wrapper.find(EditButton).exists()).toBe(true);
});
+ describe('when edit button opens', () => {
+ beforeEach(async () => {
+ createComponent({ addMergeRequestButtons: true });
+ wrapper.find(EditButton).vm.$emit('open');
+
+ await wrapper.vm.$nextTick();
+ });
+
+ it('should add z dropdown menu class when edit button opens', async () => {
+ expect(hasZDropdownMenuClass()).toBe(true);
+ });
+
+ it('when closes again, should remove class', async () => {
+ wrapper.find(EditButton).vm.$emit('close');
+
+ await wrapper.vm.$nextTick();
+
+ expect(hasZDropdownMenuClass()).toBe(false);
+ });
+ });
+
describe('view on environment button', () => {
it('is displayed when external url is provided', () => {
const externalUrl = 'link://to/external';
diff --git a/spec/frontend/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js
index 71512c1c4af..cc425cc7301 100644
--- a/spec/frontend/diffs/components/edit_button_spec.js
+++ b/spec/frontend/diffs/components/edit_button_spec.js
@@ -1,15 +1,34 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlDeprecatedButton } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlDeprecatedDropdown, GlDeprecatedDropdownItem, GlIcon } from '@gitlab/ui';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import EditButton from '~/diffs/components/edit_button.vue';
-const editPath = 'test-path';
+jest.mock('lodash/uniqueId', () => (str = '') => `${str}fake`);
+
+const TOOLTIP_ID = 'edit_button_tooltip_fake';
+const EDIT_ITEM = {
+ href: 'test-path',
+ text: 'Edit in single-file editor',
+};
+const IDE_EDIT_ITEM = {
+ href: 'ide-test-path',
+ text: 'Edit in Web IDE',
+};
describe('EditButton', () => {
let wrapper;
- const createComponent = (props = {}) => {
- wrapper = shallowMount(EditButton, {
- propsData: { ...props },
+ const createComponent = (props = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(EditButton, {
+ propsData: {
+ editPath: EDIT_ITEM.href,
+ ideEditPath: IDE_EDIT_ITEM.href,
+ canCurrentUserFork: false,
+ ...props,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
});
};
@@ -17,59 +36,105 @@ describe('EditButton', () => {
wrapper.destroy();
});
- it('has correct href attribute', () => {
- createComponent({
- editPath,
- canCurrentUserFork: false,
- });
+ const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value;
+ const findDropdown = () => wrapper.find(GlDeprecatedDropdown);
+ const parseDropdownItems = () =>
+ wrapper.findAll(GlDeprecatedDropdownItem).wrappers.map(x => ({
+ text: x.text(),
+ href: x.attributes('href'),
+ }));
+ const triggerShow = () => {
+ const event = new Event('');
+ jest.spyOn(event, 'preventDefault');
- expect(wrapper.find(GlDeprecatedButton).attributes('href')).toBe(editPath);
+ findDropdown().vm.$emit('show', event);
+
+ return event;
+ };
+
+ it.each`
+ props | expectedItems
+ ${{}} | ${[EDIT_ITEM, IDE_EDIT_ITEM]}
+ ${{ editPath: '' }} | ${[IDE_EDIT_ITEM]}
+ ${{ ideEditPath: '' }} | ${[EDIT_ITEM]}
+ `('should render items with=$props', ({ props, expectedItems }) => {
+ createComponent(props);
+
+ expect(parseDropdownItems()).toEqual(expectedItems);
});
- it('emits a show fork message event if current user can fork', () => {
- createComponent({
- editPath,
- canCurrentUserFork: true,
+ describe('with default', () => {
+ beforeEach(() => {
+ createComponent({}, mount);
});
- wrapper.find(GlDeprecatedButton).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('showForkMessage')).toBeTruthy();
+ it('does not have tooltip', () => {
+ expect(getTooltip()).toEqual({ id: TOOLTIP_ID, title: 'Edit file in...' });
+ });
+
+ it('shows pencil dropdown', () => {
+ expect(wrapper.find(GlIcon).props('name')).toBe('pencil');
+ expect(wrapper.find('.gl-dropdown-caret').exists()).toBe(true);
+ });
+
+ describe.each`
+ event | expectedEmit | expectedRootEmit
+ ${'show'} | ${'open'} | ${[['bv::hide::tooltip', TOOLTIP_ID]]}
+ ${'hide'} | ${'close'} | ${[]}
+ `('when dropdown emits $event', ({ event, expectedEmit, expectedRootEmit }) => {
+ let rootEmitSpy;
+
+ beforeEach(() => {
+ rootEmitSpy = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ findDropdown().vm.$emit(event);
+ });
+
+ it(`emits ${expectedEmit}`, () => {
+ expect(wrapper.emitted(expectedEmit)).toEqual([[]]);
+ });
+
+ it(`emits root = ${JSON.stringify(expectedRootEmit)}`, () => {
+ expect(rootEmitSpy.mock.calls).toEqual(expectedRootEmit);
+ });
});
});
- it('doesnt emit a show fork message event if current user cannot fork', () => {
- createComponent({
- editPath,
- canCurrentUserFork: false,
+ describe('with cant modify blob and can fork', () => {
+ beforeEach(() => {
+ createComponent({
+ canModifyBlob: false,
+ canCurrentUserFork: true,
+ });
});
- wrapper.find(GlDeprecatedButton).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('showForkMessage')).toBeFalsy();
+ it('when try to open, emits showForkMessage', () => {
+ expect(wrapper.emitted('showForkMessage')).toBeUndefined();
+
+ const event = triggerShow();
+
+ expect(wrapper.emitted('showForkMessage')).toEqual([[]]);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(wrapper.emitted('open')).toBeUndefined();
});
});
- it('doesnt emit a show fork message event if current user can modify blob', () => {
- createComponent({
- editPath,
- canCurrentUserFork: true,
- canModifyBlob: true,
- });
- wrapper.find(GlDeprecatedButton).trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('showForkMessage')).toBeFalsy();
- });
- });
-
- it('disables button if editPath is empty', () => {
- createComponent({
- editPath: '',
- canCurrentUserFork: true,
- canModifyBlob: true,
+ describe('with editPath is falsey', () => {
+ beforeEach(() => {
+ createComponent({
+ editPath: '',
+ });
});
- expect(wrapper.find(GlDeprecatedButton).attributes('disabled')).toBe('true');
+ it('should disable dropdown', () => {
+ expect(findDropdown().attributes('disabled')).toBe('true');
+ });
+
+ it('should have tooltip', () => {
+ expect(getTooltip()).toEqual({
+ id: TOOLTIP_ID,
+ title: "Can't edit as source branch was deleted",
+ });
+ });
});
});
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
index 0befe1aa192..dd889e2ab6f 100644
--- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
+++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
@@ -17,6 +17,7 @@ exports[`grafana integration component default state to match the default snapsh
In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. More information
- Install Knative
+ Install Knative
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index dfd114a2d1c..ec4a81054db 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -39,6 +39,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>