Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-18 21:10:11 +00:00
parent 96c78a921f
commit f3352dd3f1
74 changed files with 693 additions and 171 deletions

View file

@ -10,6 +10,7 @@ import {
GlTable,
} from '@gitlab/ui';
import AncestorNotice from './ancestor_notice.vue';
import NodeErrorHelpText from './node_error_help_text.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
@ -26,6 +27,7 @@ export default {
GlSkeletonLoading,
GlSprintf,
GlTable,
NodeErrorHelpText,
},
directives: {
tooltip,
@ -231,9 +233,12 @@ export default {
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
<small v-else class="gl-font-sm gl-font-style-italic gl-text-gray-200">{{
__('Unknown')
}}</small>
<NodeErrorHelpText
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.connection_error"
:popover-id="`nodeSizeError${item.id}`"
/>
</template>
<template #cell(total_cpu)="{ item }">
@ -250,6 +255,13 @@ export default {
</span>
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
<NodeErrorHelpText
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.node_connection_error"
:popover-id="`nodeCpuError${item.id}`"
/>
</template>
<template #cell(total_memory)="{ item }">
@ -266,6 +278,13 @@ export default {
</span>
<gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />
<NodeErrorHelpText
v-else-if="item.kubernetes_errors"
:class="contentAlignClasses"
:error-type="item.kubernetes_errors.metrics_connection_error"
:popover-id="`nodeMemoryError${item.id}`"
/>
</template>
<template #cell(cluster_type)="{value}">

View file

@ -0,0 +1,53 @@
<script>
import { GlIcon, GlPopover } from '@gitlab/ui';
import { CLUSTER_ERRORS } from '../constants';
export default {
components: {
GlIcon,
GlPopover,
},
props: {
errorType: {
type: String,
required: false,
default: '',
},
popoverId: {
type: String,
required: true,
},
},
computed: {
errorContent() {
return CLUSTER_ERRORS[this.errorType] || CLUSTER_ERRORS.default;
},
},
};
</script>
<template>
<div :id="popoverId">
<span class="gl-font-style-italic">
{{ errorContent.tableText }}
</span>
<gl-icon name="status_warning" :size="24" class="gl-p-2" />
<gl-popover :container="popoverId" :target="popoverId" placement="top" triggers="hover focus">
<template #title>
<span class="gl-display-block gl-text-left">{{ errorContent.title }}</span>
</template>
<p class="gl-text-left">{{ errorContent.description }}</p>
<p class="gl-text-left">{{ s__('ClusterIntegration|Troubleshooting tips:') }}</p>
<ul class="gl-text-left">
<li v-for="tip in errorContent.troubleshootingTips" :key="tip">
{{ tip }}
</li>
</ul>
</gl-popover>
</div>
</template>

View file

@ -1,4 +1,45 @@
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
export const CLUSTER_ERRORS = {
default: {
tableText: s__('ClusterIntegration|Unknown Error'),
title: s__('ClusterIntegration|Unknown Error'),
description: s__(
'ClusterIntegration|An unknown error occurred while attempting to connect to Kubernetes.',
),
troubleshootingTips: [
s__('ClusterIntegration|Check your cluster status'),
s__('ClusterIntegration|Make sure your API endpoint is correct'),
s__(
'ClusterIntegration|Node calculations use the Kubernetes Metrics API. Make sure your cluster has metrics installed',
),
],
},
authentication_error: {
tableText: s__('ClusterIntegration|Unable to Authenticate'),
title: s__('ClusterIntegration|Authentication Error'),
description: s__('ClusterIntegration|GitLab failed to authenticate.'),
troubleshootingTips: [
s__('ClusterIntegration|Check your token'),
s__('ClusterIntegration|Check your CA certificate'),
],
},
connection_error: {
tableText: s__('ClusterIntegration|Unable to Connect'),
title: s__('ClusterIntegration|Connection Error'),
description: s__('ClusterIntegration|GitLab failed to connect to the cluster.'),
troubleshootingTips: [
s__('ClusterIntegration|Check your cluster status'),
s__('ClusterIntegration|Make sure your API endpoint is correct'),
],
},
http_error: {
tableText: s__('ClusterIntegration|Unable to Connect'),
title: s__('ClusterIntegration|HTTP Error'),
description: s__('ClusterIntegration|There was an HTTP error when connecting to your cluster.'),
troubleshootingTips: [s__('ClusterIntegration|Check your cluster status')],
},
};
export const CLUSTER_TYPES = {
project_type: __('Project'),

View file

@ -69,6 +69,11 @@ export default {
default: false,
},
},
data() {
return {
hasDropdownOpen: false,
};
},
computed: {
...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
diffContentIDSelector() {
@ -179,6 +184,9 @@ export default {
}
}
},
setDropdownOpen(val) {
this.hasDropdownOpen = val;
},
},
};
</script>
@ -187,6 +195,7 @@ export default {
<div
ref="header"
class="js-file-title file-title file-title-flex-parent"
:class="{ 'gl-z-dropdown-menu!': hasDropdownOpen }"
@click.self="handleToggleFile"
>
<div class="file-header-content">
@ -273,11 +282,14 @@ export default {
v-if="!diffFile.deleted_file"
:can-current-user-fork="canCurrentUserFork"
:edit-path="diffFile.edit_path"
:ide-edit-path="diffFile.ide_edit_path"
:can-modify-blob="diffFile.can_modify_blob"
data-track-event="click_toggle_edit_button"
data-track-label="diff_toggle_edit_button"
data-track-property="diff_toggle_edit"
@showForkMessage="showForkMessage"
@open="setDropdownOpen(true)"
@close="setDropdownOpen(false)"
/>
</template>

View file

@ -1,10 +1,17 @@
<script>
import { GlTooltipDirective, GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import {
GlTooltipDirective,
GlIcon,
GlDeprecatedDropdown as GlDropdown,
GlDeprecatedDropdownItem as GlDropdownItem,
} from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlDeprecatedButton,
GlDropdown,
GlDropdownItem,
GlIcon,
},
directives: {
@ -16,6 +23,11 @@ export default {
required: false,
default: '',
},
ideEditPath: {
type: String,
required: false,
default: '',
},
canCurrentUserFork: {
type: Boolean,
required: true,
@ -26,39 +38,62 @@ export default {
default: false,
},
},
data() {
return { tooltipId: uniqueId('edit_button_tooltip_') };
},
computed: {
tooltipTitle() {
if (this.isDisabled) {
return __("Can't edit as source branch was deleted");
}
return __('Edit file');
return __('Edit file in...');
},
isDisabled() {
return !this.editPath;
},
},
methods: {
handleEditClick(evt) {
handleShow(evt) {
// We must hide the tooltip because it is redundant and doesn't close itself
// when dropdown opens because we are still "focused".
this.$root.$emit('bv::hide::tooltip', this.tooltipId);
if (this.canCurrentUserFork && !this.canModifyBlob) {
evt.preventDefault();
this.$emit('showForkMessage');
} else {
this.$emit('open');
}
},
handleHide() {
this.$emit('close');
},
},
};
</script>
<template>
<span v-gl-tooltip.top :title="tooltipTitle">
<gl-deprecated-button
:href="editPath"
<div v-gl-tooltip.top="{ title: tooltipTitle, id: tooltipId }" class="gl-display-flex">
<gl-dropdown
toggle-class="rounded-0"
:disabled="isDisabled"
:class="{ 'cursor-not-allowed': isDisabled }"
class="rounded-0 js-edit-blob"
@click.native="handleEditClick"
right
data-testid="edit_file"
@show="handleShow"
@hide="handleHide"
>
<gl-icon name="pencil" />
</gl-deprecated-button>
</span>
<template #button-content>
<span class="gl-dropdown-toggle-text"><gl-icon name="pencil"/></span>
<gl-icon class="gl-dropdown-caret" name="chevron-down" aria-hidden="true" />
</template>
<gl-dropdown-item v-if="editPath" :href="editPath">{{
__('Edit in single-file editor')
}}</gl-dropdown-item>
<gl-dropdown-item v-if="ideEditPath" :href="ideEditPath">{{
__('Edit in Web IDE')
}}</gl-dropdown-item>
</gl-dropdown>
</div>
</template>

View file

@ -150,7 +150,7 @@ export default {
/>
</div>
</gl-form-group>
<gl-form-group>
<gl-form-group data-testid="release-notes">
<label for="release-notes">{{ __('Release notes') }}</label>
<div class="bordered-box pr-3 pl-3">
<markdown-field
@ -158,6 +158,7 @@ export default {
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:add-spacing-classes="false"
:textarea-value="releaseNotes"
class="gl-mt-3 gl-mb-3"
>
<template #textarea>

View file

@ -49,6 +49,7 @@ export default {
:add-spacing-classes="false"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:textarea-value="value"
>
<template #textarea>
<textarea

View file

@ -84,7 +84,37 @@ export default {
required: false,
default: false,
},
// This prop is used as a fallback in case if textarea.elm is undefined
/**
* This prop is used as a fallback if the value of the textarea can't be
* retreived using `this.$slots.textarea[0]?.elm?`.
*
* This happens when the `textarea` slot is defined like this:
*
* ```html
* <markdown-field>
* <template #textarea>
* <textarea></textarea>
* </template>
* </markdown-field>
* ```
*
* ... as opposed to this:
*
* ```html
* <markdown-field>
* <textarea slot="textarea">
* </markdown-field>
* ```
*
* When using `<template #textarea>` 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,

View file

@ -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';

View file

@ -1,3 +0,0 @@
.tag-release-link {
color: $blue-600 !important;
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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

View file

@ -12,6 +12,7 @@ class ClusterSerializer < BaseSerializer
:environment_scope,
:gitlab_managed_apps_logs_path,
:enable_advanced_logs_querying,
:id,
:kubernetes_errors,
:name,
:nodes,

View file

@ -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

View file

@ -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

View file

@ -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;" }

View file

@ -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)

View file

@ -0,0 +1,5 @@
---
title: Add Web IDE as dropdown item to diff file edit
merge_request: 42275
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix Markdown "Preview" tab on New/Edit Release and New Snippet pages
merge_request: 42640
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Update pipeline failed notification e-mail warning
merge_request: 42736
author:
type: fixed

View file

@ -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

View file

@ -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. |

View file

@ -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,

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -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

View file

@ -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

View file

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -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

View file

@ -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:

View file

@ -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 | | | | | ✓ |

View file

@ -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 ""

View file

@ -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",

View file

@ -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)

View file

@ -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 }

View file

@ -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

View file

@ -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}" }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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|

View file

@ -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|

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -26,7 +26,7 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
</gl-form-group-stub>
<gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\">
<gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
<gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
</gl-modal-stub>
@ -35,13 +35,13 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`]
<gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
</gl-form-group-stub>
<div class=\\"gl-display-flex gl-justify-content-end\\">
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
</div>
<div class=\\"footer-block row-content-block gl-display-flex gl-justify-content-space-between\\">
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">
Cancel
</gl-button-stub>
<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" disabled=\\"true\\">
<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">
Save changes
</gl-button-stub>
</div>

View file

@ -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);
});
});

View file

@ -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);
});
});
});

View file

@ -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: {},
},
];

View file

@ -56,6 +56,7 @@ exports[`Code navigation popover component renders popover 1`] = `
class="popover-body border-top"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="w-100"
data-testid="go-to-definition-btn"

View file

@ -13,6 +13,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
class="ml-3 mr-3"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-previous-design"
disabled="true"
@ -23,6 +24,7 @@ exports[`Design management pagination component renders navigation buttons 1`] =
/>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-next-design"
icon="angle-right"

View file

@ -41,6 +41,7 @@ exports[`Design management toolbar component renders design and updated data 1`]
/>
<gl-button-stub
buttontextclasses=""
category="primary"
href="/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d"
icon="download"

View file

@ -5,6 +5,7 @@ exports[`Design management upload button component renders inverted upload desig
isinverted="true"
>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
size="small"
@ -30,6 +31,7 @@ exports[`Design management upload button component renders inverted upload desig
exports[`Design management upload button component renders loading icon 1`] = `
<div>
<gl-button-stub
buttontextclasses=""
category="primary"
disabled="true"
icon=""
@ -62,6 +64,7 @@ exports[`Design management upload button component renders loading icon 1`] = `
exports[`Design management upload button component renders upload design button 1`] = `
<div>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
size="small"

View file

@ -110,6 +110,7 @@ exports[`Design management index page designs renders designs list and header wi
class="qa-selector-toolbar gl-display-flex gl-align-items-center"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="gl-mr-4 js-select-all"
icon=""

View file

@ -67,6 +67,7 @@ exports[`Design management design index page renders design index 1`] = `
/>
<gl-button-stub
buttontextclasses=""
category="primary"
class="link-inherit-color gl-text-body gl-text-decoration-none gl-font-weight-bold gl-mb-4"
data-testid="resolved-comments"

View file

@ -76,6 +76,7 @@ describe('DiffFileHeader component', () => {
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';

View file

@ -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",
});
});
});
});

View file

@ -17,6 +17,7 @@ exports[`grafana integration component default state to match the default snapsh
</h3>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-settings-toggle"
icon=""
@ -96,6 +97,7 @@ exports[`grafana integration component default state to match the default snapsh
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
size="medium"

View file

@ -97,6 +97,7 @@ exports[`Alert integration settings form default state should match the default
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-no-auto-disable"
data-qa-selector="save_changes_button"

View file

@ -18,6 +18,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
</h4>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-settings-toggle"
icon=""

View file

@ -46,6 +46,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="gl-mt-3"
data-testid="webhook-reset-btn"
@ -80,6 +81,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
class="gl-display-flex gl-justify-content-end"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-no-auto-disable"
icon=""

View file

@ -118,6 +118,7 @@ exports[`packages_list_row renders 1`] = `
>
<gl-button-stub
aria-label="Remove package"
buttontextclasses=""
category="primary"
data-testid="action-delete"
icon="remove"

View file

@ -39,6 +39,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
/>
</form>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
size="medium"
@ -48,6 +49,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
</gl-button-stub>
<gl-button-stub
buttontextclasses=""
category="primary"
disabled="true"
icon=""
@ -60,6 +62,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
</gl-button-stub>
<gl-button-stub
buttontextclasses=""
category="primary"
disabled="true"
icon=""

View file

@ -17,6 +17,7 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
/>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
role="button"

View file

@ -18,6 +18,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
/>
<gl-button-stub
buttontextclasses=""
category="primary"
icon=""
role="button"
@ -84,6 +85,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
<template>
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-modal-action-cancel"
icon=""
@ -98,6 +100,7 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
<!---->
<gl-button-stub
buttontextclasses=""
category="primary"
class="js-modal-action-primary"
disabled="true"

View file

@ -11,7 +11,7 @@ exports[`EmptyStateComponent should render content 1`] = `
<p>In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub>
</p>
<div>
<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
<gl-button-stub category=\\"primary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
<!---->
</div>
</div>

View file

@ -39,6 +39,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="d-inline-flex"
data-clipboard-text="ssh://foo.bar"
@ -80,6 +81,7 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
tag="div"
>
<gl-button-stub
buttontextclasses=""
category="primary"
class="d-inline-flex"
data-clipboard-text="http://foo.bar"

View file

@ -13,6 +13,7 @@ RSpec.describe ClusterSerializer do
:cluster_type,
:enabled,
:environment_scope,
:id,
:gitlab_managed_apps_logs_path,
:enable_advanced_logs_querying,
:kubernetes_errors,

View file

@ -3,10 +3,24 @@
require 'spec_helper'
RSpec.describe DiffFileBaseEntity do
let(:project) { create(:project, :repository) }
include ProjectForksHelper
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:repository) { project.repository }
let(:entity) { described_class.new(diff_file, options).as_json }
shared_examples 'nil if removed source branch' do |key|
before do
allow(merge_request).to receive(:source_branch_exists?).and_return(false)
end
specify do
expect(entity[key]).to eq(nil)
end
end
context 'submodule information for a' do
let(:commit_sha) { "" }
let(:commit) { project.commit(commit_sha) }
@ -67,7 +81,7 @@ RSpec.describe DiffFileBaseEntity do
context 'edit_path' do
let(:diff_file) { merge_request.diffs.diff_files.to_a.last }
let(:options) { { request: EntityRequest.new(current_user: create(:user)), merge_request: merge_request } }
let(:options) { { request: EntityRequest.new(current_user: user), merge_request: merge_request } }
let(:params) { {} }
shared_examples 'a diff file edit path to the source branch' do
@ -81,16 +95,7 @@ RSpec.describe DiffFileBaseEntity do
let(:params) { { from_merge_request_iid: merge_request.iid } }
it_behaves_like 'a diff file edit path to the source branch'
context 'removed source branch' do
before do
allow(merge_request).to receive(:source_branch_exists?).and_return(false)
end
it do
expect(entity[:edit_path]).to eq(nil)
end
end
it_behaves_like 'nil if removed source branch', :edit_path
end
context 'closed' do
@ -118,4 +123,30 @@ RSpec.describe DiffFileBaseEntity do
end
end
end
context 'ide_edit_path' do
let(:source_project) { project }
let(:merge_request) { create(:merge_request, iid: 123, target_project: target_project, source_project: source_project) }
let(:diff_file) { merge_request.diffs.diff_files.to_a.last }
let(:options) { { request: EntityRequest.new(current_user: user), merge_request: merge_request } }
let(:expected_merge_request_path) { "/-/ide/project/#{source_project.full_path}/merge_requests/#{merge_request.iid}" }
context 'when source_project and target_project are the same' do
let(:target_project) { source_project }
it_behaves_like 'nil if removed source branch', :ide_edit_path
it 'returns the merge_request ide route' do
expect(entity[:ide_edit_path]).to eq expected_merge_request_path
end
end
context 'when source_project and target_project are different' do
let(:target_project) { fork_project(source_project, source_project.owner, repository: true) }
it 'returns the merge_request ide route with the target_project as param' do
expect(entity[:ide_edit_path]).to eq("#{expected_merge_request_path}?target_project=#{ERB::Util.url_encode(target_project.full_path)}")
end
end
end
end

View file

@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.164.0.tgz#6cefad871c45f945ef92b99015d0f510b1d2de4a"
integrity sha512-a9e/cYUc1QQk7azjH4x/m6/p3icavwGEi5F9ipNlDqiJtUor5tqojxvMxPOhuVbN/mTwnC6lGsSZg4tqTsdJAQ==
"@gitlab/ui@21.3.1":
version "21.3.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.3.1.tgz#027b767804540539da73d4874370895d7398adea"
integrity sha512-ynyg8i8W8Ud+GoySr4hAjJoW55kWMwSEFLX5MEX8CbdqGurkTLqHYLLpXPBSSnVEcw4stR+bFbKSc35rmBkWPA==
"@gitlab/ui@21.4.2":
version "21.4.2"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.4.2.tgz#c3d36167ab4df49ce978e20bdd3790e716f5a2d1"
integrity sha512-p8ujeGvCG06Opn0eQlrwZyi9v9RK3T2V4TUcljTAUYDdm0p23qJjjIlFjfGHlQsNg0wRgnkbKFXfkZ/Oy8GyiQ==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"