Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-29 18:09:50 +00:00
parent 58320d8e03
commit 0ca5c1a237
77 changed files with 826 additions and 440 deletions

View file

@ -19,6 +19,9 @@ export default {
handleButtonClick(e) {
if (this.isDropdownVariantStandalone || this.isDropdownVariantEmbedded) {
this.toggleDropdownContents();
}
if (this.isDropdownVariantStandalone) {
e.stopPropagation();
}
},
@ -31,9 +34,9 @@ export default {
class="labels-select-dropdown-button js-dropdown-button w-100 text-left"
@click="handleButtonClick"
>
<span class="dropdown-toggle-text flex-fill">
<span class="dropdown-toggle-text gl-pointer-events-none flex-fill">
{{ dropdownButtonText }}
</span>
<gl-icon name="chevron-down" class="float-right" />
<gl-icon name="chevron-down" class="gl-pointer-events-none float-right" />
</gl-button>
</template>

View file

@ -9,6 +9,13 @@ export default {
DropdownContentsLabelsView,
DropdownContentsCreateView,
},
props: {
renderOnTop: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState(['showDropdownContentsCreateView']),
dropdownContentsView() {
@ -17,6 +24,13 @@ export default {
}
return 'dropdown-contents-labels-view';
},
directionStyle() {
if (this.renderOnTop) {
return { bottom: '100%' };
}
return {};
},
},
};
</script>
@ -24,6 +38,7 @@ export default {
<template>
<div
class="labels-select-dropdown-contents w-100 mt-1 mb-3 py-2 rounded-top rounded-bottom position-absolute"
:style="directionStyle"
>
<component :is="dropdownContentsView" />
</div>

View file

@ -45,6 +45,16 @@ export default {
}
return this.labels;
},
showListContainer() {
if (this.isDropdownVariantSidebar) {
return !this.labelsFetchInProgress;
}
return true;
},
showNoMatchingResultsMessage() {
return !this.labelsFetchInProgress && !this.visibleLabels.length;
},
},
watch: {
searchKey(value) {
@ -132,6 +142,7 @@ export default {
<div
v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3!"
data-testid="dropdown-title"
>
<span class="flex-grow-1">{{ labelsListTitle }}</span>
<gl-button
@ -146,7 +157,12 @@ export default {
<div class="dropdown-input" @click.stop="() => {}">
<gl-search-box-by-type v-model="searchKey" :autofocus="true" />
</div>
<div v-show="!labelsFetchInProgress" ref="labelsListContainer" class="dropdown-content">
<div
v-show="showListContainer"
ref="labelsListContainer"
class="dropdown-content"
data-testid="dropdown-content"
>
<smart-virtual-list
:length="visibleLabels.length"
:remain="$options.LIST_BUFFER_SIZE"
@ -163,12 +179,16 @@ export default {
@clickLabel="handleLabelClick(label)"
/>
</li>
<li v-show="!visibleLabels.length" class="p-2 text-center">
<li v-show="showNoMatchingResultsMessage" class="gl-p-3 gl-text-center">
{{ __('No matching results') }}
</li>
</smart-virtual-list>
</div>
<div v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded" class="dropdown-footer">
<div
v-if="isDropdownVariantSidebar || isDropdownVariantEmbedded"
class="dropdown-footer"
data-testid="dropdown-footer"
>
<ul class="list-unstyled">
<li v-if="allowLabelCreate">
<gl-link

View file

@ -2,6 +2,7 @@
import $ from 'jquery';
import Vue from 'vue';
import Vuex, { mapState, mapActions, mapGetters } from 'vuex';
import { isInViewport } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
@ -100,6 +101,11 @@ export default {
default: __('Manage group labels'),
},
},
data() {
return {
contentIsOnViewport: true,
};
},
computed: {
...mapState(['showDropdownButton', 'showDropdownContents']),
...mapGetters([
@ -117,6 +123,9 @@ export default {
selectedLabels,
});
},
showDropdownContents(showDropdownContents) {
this.setContentIsOnViewport(showDropdownContents);
},
},
mounted() {
this.setInitialState({
@ -203,6 +212,20 @@ export default {
handleCollapsedValueClick() {
this.$emit('toggleCollapse');
},
setContentIsOnViewport(showDropdownContents) {
if (!this.isDropdownVariantEmbedded || !showDropdownContents) {
this.contentIsOnViewport = true;
return;
}
this.$nextTick(() => {
if (this.$refs.dropdownContents) {
const offset = { top: 100 };
this.contentIsOnViewport = isInViewport(this.$refs.dropdownContents.$el, offset);
}
});
},
},
};
</script>
@ -239,6 +262,7 @@ export default {
<dropdown-contents
v-if="dropdownButtonVisible && showDropdownContents"
ref="dropdownContents"
:render-on-top="!contentIsOnViewport"
/>
</template>
</div>

View file

@ -9,7 +9,7 @@
// scss-lint:disable MergeableSelector
@font-face {
font-family: 'FontAwesome';
src: url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('fontawesome-webfont.woff?v=4.7.0') format('woff');
src: asset-url('fontawesome-webfont.woff2?v=4.7.0') format('woff2'), asset-url('fontawesome-webfont.woff?v=4.7.0') format('woff');
font-weight: normal;
font-style: normal;
}

View file

@ -33,9 +33,7 @@ module AuthenticatesWithTwoFactor
end
def locked_user_redirect(user)
flash.now[:alert] = locked_user_redirect_alert(user)
render 'devise/sessions/new'
redirect_to new_user_session_path, alert: locked_user_redirect_alert(user)
end
def authenticate_with_two_factor
@ -54,7 +52,13 @@ module AuthenticatesWithTwoFactor
private
def locked_user_redirect_alert(user)
user.access_locked? ? _('Your account is locked.') : _('Invalid Login or password')
if user.access_locked?
_('Your account is locked.')
elsif !user.confirmed?
I18n.t('devise.failure.unconfirmed')
else
_('Invalid Login or password')
end
end
def clear_two_factor_attempt!

View file

@ -167,7 +167,6 @@ class Project < ApplicationRecord
has_one :youtrack_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
has_one :confluence_service
has_one :external_wiki_service
has_one :prometheus_service, inverse_of: :project

View file

@ -1,45 +0,0 @@
# frozen_string_literal: true
class GitlabIssueTrackerService < IssueTrackerService
include Gitlab::Routing
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
default_value_for :default, true
def title
'GitLab'
end
def description
s_('IssueTracker|GitLab issue tracker')
end
def self.to_param
'gitlab'
end
def project_url
project_issues_url(project)
end
def new_issue_url
new_project_issue_url(project)
end
def issue_url(iid)
project_issue_url(project, id: iid)
end
def issue_tracker_path
project_issues_path(project)
end
def new_issue_path
new_project_issue_path(project)
end
def issue_path(iid)
project_issue_path(project, id: iid)
end
end

View file

@ -55,11 +55,9 @@ class Service < ApplicationRecord
validates :instance, uniqueness: { scope: :type }, if: -> { instance? }
validate :validate_is_instance_or_template
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
scope :external_wikis, -> { where(type: 'ExternalWikiService').active }
scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) }
scope :by_type, -> (type) { where(type: type) }
scope :by_active_flag, -> (flag) { where(active: flag) }
scope :templates, -> { where(template: true, type: available_services_types) }
@ -77,7 +75,7 @@ class Service < ApplicationRecord
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
scope :alert_hooks, -> { where(alert_events: true, active: true) }
scope :external_issue_trackers, -> { issue_trackers.active.without_defaults }
scope :external_issue_trackers, -> { issue_trackers.active }
scope :deployment, -> { where(category: 'deployment') }
default_value_for :category, 'common'

View file

@ -17,14 +17,6 @@ class CommitPresenter < Gitlab::View::Presenter::Delegated
commit.pipelines.any?
end
def web_url
url_builder.build(commit)
end
def web_path
url_builder.build(commit, only_path: true)
end
def signature_html
return unless commit.has_signature?

View file

@ -3,10 +3,6 @@
class IssuePresenter < Gitlab::View::Presenter::Delegated
presents :issue
def web_url
url_builder.build(issue)
end
def issue_path
url_builder.build(issue, only_path: true)
end

View file

@ -202,10 +202,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
def web_url
Gitlab::UrlBuilder.build(merge_request)
end
def subscribed?
merge_request.subscribed?(current_user, merge_request.target_project)
end

View file

@ -3,12 +3,8 @@
class SnippetPresenter < Gitlab::View::Presenter::Delegated
presents :snippet
def web_url
Gitlab::UrlBuilder.build(snippet)
end
def raw_url
Gitlab::UrlBuilder.build(snippet, raw: true)
url_builder.build(snippet, raw: true)
end
def ssh_url_to_repo

View file

@ -2,12 +2,4 @@
class UserPresenter < Gitlab::View::Presenter::Delegated
presents :user
def web_url
Gitlab::Routing.url_helpers.user_url(user)
end
def web_path
Gitlab::Routing.url_helpers.user_path(user)
end
end

View file

@ -11,14 +11,14 @@ module Projects
end
def execute
if file_equals?(pages_config_file, pages_config_json)
return success(reload: false)
unless file_equals?(pages_config_file, pages_config_json)
update_file(pages_config_file, pages_config_json)
reload_daemon
end
update_file(pages_config_file, pages_config_json)
reload_daemon
success(reload: true)
success
rescue => e
Gitlab::ErrorTracking.track_exception(e)
error(e.message)
end

View file

@ -5,8 +5,7 @@
.dropdown.js-project-filter-dropdown-wrap{ class: ('d-flex flex-grow-1 flex-shrink-1' if feature_project_list_filter_bar) }
%button.dropdown-menu-toggle{ href: '#', "data-toggle" => "dropdown", 'data-display' => 'static' }
- unless has_label
= icon('globe', class: 'mt-1')
%span.light.ml-3= _("Visibility:")
%span= _("Visibility:")
- if params[:visibility_level].present?
= visibility_level_label(params[:visibility_level].to_i)
- else

View file

@ -9,18 +9,19 @@
.filter-item.inline.update-issues-btn.float-left
= button_tag _('Update all'), class: "btn update-selected-issues btn-info", disabled: true
= button_tag _('Cancel'), class: "btn btn-default js-bulk-update-menu-hide float-right"
.block
.title
= _('Status')
.filter-item
= dropdown_tag(_("Select status"), options: { toggle_class: "js-issue-status", title: _("Change status"), dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: _("Status") } } ) do
%ul
%li
%a{ href: "#", data: { id: "reopen" } }
= _('Open')
%li
%a{ href: "#", data: { id: "close" } }
= _('Closed')
- if params[:state] != 'merged'
.block
.title
= _('Status')
.filter-item
= dropdown_tag(_("Select status"), options: { toggle_class: "js-issue-status", title: _("Change status"), dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]", default_label: _("Status") } } ) do
%ul
%li
%a{ href: "#", data: { id: "reopen" } }
= _('Open')
%li
%a{ href: "#", data: { id: "close" } }
= _('Closed')
.block
.title
= _('Assignee')

View file

@ -14,12 +14,10 @@ class PagesWorker # rubocop:disable Scalability/IdempotentWorker
# rubocop: disable CodeReuse/ActiveRecord
def deploy(build_id)
build = Ci::Build.find_by(id: build_id)
result = Projects::UpdatePagesService.new(build.project, build).execute
if result[:status] == :success
result = Projects::UpdatePagesConfigurationService.new(build.project).execute
update_contents = Projects::UpdatePagesService.new(build.project, build).execute
if update_contents[:status] == :success
Projects::UpdatePagesConfigurationService.new(build.project).execute
end
result
end
# rubocop: enable CodeReuse/ActiveRecord

View file

@ -0,0 +1,5 @@
---
title: Remove status dropdown in merged tab
merge_request: 37544
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Remove globe icon from explore projects dropdown
merge_request: 21659
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Fix 500 error when unconfirmed OAuth2 user with 2FA logs in
merge_request: 38104
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: 'Add parenthesis support for if: conditions'
merge_request: 37574
author:
type: added

View file

@ -114,7 +114,7 @@ since that is needed in all configurations.
---
URL scheme: `http://page.example.io`
URL scheme: `http://<namespace>.example.io/<project_slug>`
This is the minimum setup that you can use Pages with. It is the base for all
other setups as described below. NGINX will proxy all requests to the daemon.
@ -139,7 +139,7 @@ Watch the [video tutorial](https://youtu.be/dD8c7WNcc6s) for this configuration.
---
URL scheme: `https://page.example.io`
URL scheme: `https://<namespace>.example.io/<project_slug>`
NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the
outside world.
@ -254,7 +254,7 @@ you have IPv6 as well as IPv4 addresses, you can use them both.
---
URL scheme: `http://page.example.io` and `http://domain.com`
URL scheme: `http://<namespace>.example.io/<project_slug>` and `http://custom-domain.com`
In that case, the Pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
@ -285,7 +285,7 @@ world. Custom domains are supported, but no TLS.
---
URL scheme: `https://page.example.io` and `https://domain.com`
URL scheme: `https://<namespace>.example.io/<project_slug>` and `https://custom-domain.com`
In that case, the Pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside

View file

@ -94,7 +94,7 @@ since that is needed in all configurations.
- [Wildcard DNS setup](#dns-configuration)
URL scheme: `http://page.example.io`
URL scheme: `http://<namespace>.example.io/<project_slug>`
This is the minimum setup that you can use Pages with. It is the base for all
other setups as described below. NGINX will proxy all requests to the daemon.
@ -157,7 +157,7 @@ The Pages daemon doesn't listen to the outside world.
- [Wildcard DNS setup](#dns-configuration)
- Wildcard TLS certificate
URL scheme: `https://page.example.io`
URL scheme: `https://<namespace>.example.io/<project_slug>`
NGINX will proxy all requests to the daemon. Pages daemon doesn't listen to the
outside world.
@ -221,7 +221,7 @@ that without TLS certificates.
- [Wildcard DNS setup](#dns-configuration)
- Secondary IP
URL scheme: `http://page.example.io` and `http://domain.com`
URL scheme: `http://<namespace>.example.io/<project_slug>` and `http://custom-domain.com`
In that case, the pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
@ -286,7 +286,7 @@ world. Custom domains are supported, but no TLS.
- Wildcard TLS certificate
- Secondary IP
URL scheme: `https://page.example.io` and `https://domain.com`
URL scheme: `https://<namespace>.example.io/<project_slug>` and `https://custom-domain.com`
In that case, the pages daemon is running, NGINX still proxies requests to
the daemon but the daemon is also able to receive requests from the outside

View file

@ -742,6 +742,40 @@ Precedence of operators follows the
[Ruby 2.5 standard](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html),
so `&&` is evaluated before `||`.
#### Parentheses
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230938) in GitLab 13.3
It is possible to use parentheses to group conditions. Parentheses have the highest
precedence of all operators. Expressions enclosed in parentheses are evaluated first,
and the result is used for the rest of the expression.
Many nested parentheses can be used to create complex conditions, and the inner-most
expressions in parentheses are evaluated first. For an expression to be valid an equal
number of `(` and `)` need to be used.
Examples:
- `($VARIABLE1 =~ /^content.*/ || $VARIABLE2) && ($VARIABLE3 =~ /thing$/ || $VARIABLE4)`
- `($VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/) && $VARIABLE3`
- `$CI_COMMIT_BRANCH == "my-branch" || (($VARIABLE1 == "thing" || $VARIABLE2 == "thing") && $VARIABLE3)`
The feature is currently deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to enable it for your instance.
To enable it:
```ruby
Feature.enable(:ci_if_parenthesis_enabled)
```
To disable it:
```ruby
Feature.disable(:ci_if_parenthesis_enabled)
```
### Storing regular expressions in variables
It is possible to store a regular expression in a variable, to be used for pattern matching:

View file

@ -136,10 +136,47 @@ target users. See the [Ruby example](#ruby-application-example) below.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35930) in GitLab 13.1.
Enables the feature for lists of users created with the [Feature Flag User List API](../api/feature_flag_user_lists.md).
Enables the feature for lists of users created [in the Feature Flags UI](#create-a-user-list), or with the [Feature Flag User List API](../api/feature_flag_user_lists.md).
Similar to [User IDs](#user-ids), it uses the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
activation strategy.
#### Create a user list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3.
To create a user list:
1. In your project, navigate to **Operations > Feature Flags**.
1. Click on **New list**.
1. Enter a name for the list.
1. Click **Create**.
You can view a list's User IDs by clicking the **{pencil}** (edit) button next to it.
When viewing a list, you can rename it by clicking the **Edit** button.
#### Add users to a user list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3.
To add users to a user list:
1. In your project, navigate to **Operations > Feature Flags**.
1. Click on the **{pencil}** (edit) button next to the list you want to add users to.
1. Click on **Add Users**.
1. Enter the user IDs as a comma-separated list of values. For example,
`user@example.com, user2@example.com`, or `username1,username2,username3`, and so on.
1. Click on **Add**.
#### Remove users from a user list
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13308) in GitLab 13.3.
To remove users from a user list:
1. In your project, navigate to **Operations > Feature Flags**.
1. Click on the **{pencil}** (edit) button next to the list you want to change.
1. Click on the **{remove}** (remove) button next to the ID you want to remove.
### Enable or disable feature flag strategies
This feature is under development, but is ready for production use. It's

View file

@ -90,6 +90,12 @@ Check the [currently supported languages](#currently-supported-languages).
Auto Test uses tests you already have in your application. If there are no
tests, it's up to you to add them.
NOTE: **Note:**
Not all buildpacks supported by [Auto Build](#auto-build) are supported by Auto Test.
Auto Test uses [Herokuish](https://gitlab.com/gitlab-org/gitlab/-/issues/212689), *not*
Cloud Native Buildpacks, and only buildpacks that implement the
[Testpack API](https://devcenter.heroku.com/articles/testpack-api) are supported.
### Currently supported languages
Note that not all buildpacks support Auto Test yet, as it's a relatively new

View file

@ -49,6 +49,14 @@ targets. Each fuzz target **must** have a separate job. For example, the
[go-fuzzing-example project](https://gitlab.com/gitlab-org/security-products/demos/go-fuzzing-example)
contains one job that extends `.fuzz_base` for its single fuzz target.
Note that the hidden job `.fuzz_base` uses several YAML keys that you must not override in your own
job. If you include these keys in your own job, you must copy their original content. These keys
are:
- `before_script`
- `artifacts`
- `rules`
The `my_fuzz_target` job (the separate job for your fuzz target) does the following:
- Extends `.fuzz_base`.

View file

@ -61,9 +61,9 @@ GitLab uses the following tools to scan and report known vulnerabilities found i
| [Dependency List](dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [Secret Detection](secret_detection/index.md) **(ULTIMATE)** | Analyze Git history for leaked secrets. |
| [Secret Detection](secret_detection/index.md) **(ULTIMATE)** | Analyze Git history for leaked secrets. |
| [Security Dashboard](security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
| [Static Application Security Testing (SAST)](sast/index.md) | Analyze source code for known vulnerabilities. |
| [Coverage fuzzing](coverage_fuzzing/index.md) **(ULTIMATE)** | Find unknown bugs and vulnerabilities with coverage-guided fuzzing. |
## Security Scanning with Auto DevOps

View file

@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference, howto
---
# Static Application Security Testing (SAST) **(ULTIMATE)**
# Static Application Security Testing (SAST)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3775) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.3.
@ -26,7 +26,9 @@ You can take advantage of SAST by doing one of the following:
[Auto DevOps](../../../topics/autodevops/index.md).
GitLab checks the SAST report, compares the found vulnerabilities between the
source and target branches, and shows the information right on the merge request.
source and target branches.
Details of the vulnerabilities found are included in the merge request. **(ULTIMATE)**
![SAST Widget](img/sast_v13_2.png)
@ -56,7 +58,7 @@ To run SAST jobs, by default, you need a GitLab Runner with the
[`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor.
If you're using the shared Runners on GitLab.com, this is enabled by default.
Beginning with GitLab 13.0, Docker privileged mode is necessary only if you've [enabled Docker-in-Docker for SAST](#enabling-docker-in-docker).
Beginning with GitLab 13.0, Docker privileged mode is necessary only if you've [enabled Docker-in-Docker for SAST](#enabling-docker-in-docker-ultimate).
CAUTION: **Caution:**
Our SAST jobs currently expect a Linux container type. Windows containers are not yet supported.
@ -69,27 +71,27 @@ is **not** `19.03.0`. See [troubleshooting information](#error-response-from-dae
The following table shows which languages, package managers and frameworks are supported and which tools are used.
| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------|
| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11., [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Go | [Gosec](https://github.com/securego/gosec) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 |
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 |
| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11., [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Go | [Gosec](https://github.com/securego/gosec) | 10.7, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 |
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven), [moved](https://gitlab.com/groups/gitlab-org/-/epics/2098) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.3 |
| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 |
NOTE: **Note:**
The Java analyzers can also be used for variants like the
@ -102,7 +104,7 @@ All open source (OSS) analyzers have been moved to the GitLab Core tier. Progres
tracked in the corresponding
[epic](https://gitlab.com/groups/gitlab-org/-/epics/2098).
Please note that support for [Docker-in-Docker](#enabling-docker-in-docker)
Please note that support for [Docker-in-Docker](#enabling-docker-in-docker-ultimate)
will not be extended to the GitLab Core tier.
#### Summary of features per tier
@ -110,14 +112,14 @@ will not be extended to the GitLab Core tier.
Different features are available in different [GitLab tiers](https://about.gitlab.com/pricing/),
as shown in the following table:
| Capability | In Core | In Ultimate |
|:--------------------------------------------------------------------------|:--------------------|:-------------------|
| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** |
| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities) | **{dotted-circle}** | **{check-circle}** |
| [Access to Security Dashboard](#security-dashboard) | **{dotted-circle}** | **{check-circle}** |
| Capability | In Core | In Ultimate |
|:-----------------------------------------------------------------------------------|:--------------------|:-------------------|
| [Configure SAST Scanners](#configuration) | **{check-circle}** | **{check-circle}** |
| [Customize SAST Settings](#customizing-the-sast-settings) | **{check-circle}** | **{check-circle}** |
| View [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |
| [Presentation of JSON Report in Merge Request](#overview) | **{dotted-circle}** | **{check-circle}** |
| [Interaction with Vulnerabilities](#interacting-with-the-vulnerabilities-ultimate) | **{dotted-circle}** | **{check-circle}** |
| [Access to Security Dashboard](#security-dashboard-ultimate) | **{dotted-circle}** | **{check-circle}** |
## Contribute your scanner
@ -203,7 +205,7 @@ you can use the `MAVEN_CLI_OPTS` environment variable.
Read more on [how to use private Maven repositories](../index.md#using-private-maven-repos).
### Enabling Docker-in-Docker
### Enabling Docker-in-Docker **(ULTIMATE)**
If needed, you can enable Docker-in-Docker to restore the SAST behavior that existed prior to GitLab
13.0. Follow these steps to do so:
@ -310,12 +312,12 @@ of CA certs that you want to trust within the SAST environment.
The following are Docker image-related variables.
| Environment variable | Description |
|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker). This variable is `true` by default. |
| Environment variable | Description |
|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). |
| `SAST_DISABLE_DIND` | Disable Docker-in-Docker and run analyzers [individually](#enabling-docker-in-docker-ultimate). This variable is `true` by default. |
#### Vulnerability filters
@ -324,7 +326,7 @@ Some analyzers make it possible to filter out vulnerabilities under a given thre
| Environment variable | Default value | Description |
|-------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SAST_EXCLUDED_PATHS` | `spec, test, tests, tmp` | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec` ). Parent directories will also match patterns. |
| `SAST_BANDIT_EXCLUDED_PATHS` | | Comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*, */venv/*'` |
| `SAST_BANDIT_EXCLUDED_PATHS` | | Comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html); For example: `'*/tests/*, */venv/*'` |
| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
| `SAST_DISABLE_BABEL` | `false` | Disable Babel processing for the NodeJsScan scanner. Set to `true` to disable Babel processing. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33065) in GitLab 13.2. |
| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
@ -336,40 +338,40 @@ Some analyzers make it possible to filter out vulnerabilities under a given thre
#### Docker-in-Docker orchestrator
The following variables configure the Docker-in-Docker orchestrator, and therefore are only used when the Docker-in-Docker mode is [enabled](#enabling-docker-in-docker).
The following variables configure the Docker-in-Docker orchestrator, and therefore are only used when the Docker-in-Docker mode is [enabled](#enabling-docker-in-docker-ultimate).
| Environment variable | Default value | Description |
|------------------------------------------|---------------|-------------|
| `SAST_ANALYZER_IMAGES` | | Comma-separated list of custom images. Default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
| `SAST_PULL_ANALYZER_IMAGES` | 1 | Pull the images from the Docker registry (set to 0 to disable). Read more about [customizing analyzers](analyzers.md). |
| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. |
| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. |
| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`.|
| Environment variable | Default value | Description |
|------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SAST_ANALYZER_IMAGES` | | Comma-separated list of custom images. Default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
| `SAST_PULL_ANALYZER_IMAGES` | 1 | Pull the images from the Docker registry (set to 0 to disable). Read more about [customizing analyzers](analyzers.md). |
| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. |
| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. |
| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h` or `2h45m`. |
#### Analyzer settings
Some analyzers can be customized with environment variables.
| Environment variable | Analyzer | Description |
|---------------------------------------|----------------------|-------------|
| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195252) in GitLab 13.1. |
| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. |
| `JAVA_PATH` | SpotBugs | Path to the `java` executable. |
| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. |
| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. |
| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. |
| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. |
| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. |
| `SAST_GOSEC_CONFIG` | Gosec | Path to configuration for Gosec (optional). |
| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. |
| `SEARCH_MAX_DEPTH` | any | Maximum number of directories traversed when searching for source code files. Default: `4`. |
| Environment variable | Analyzer | Description |
|---------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SCAN_KUBERNETES_MANIFESTS` | Kubesec | Set to `"true"` to scan Kubernetes manifests. |
| `KUBESEC_HELM_CHARTS_PATH` | Kubesec | Optional path to Helm charts that `helm` will use to generate a Kubernetes manifest that `kubesec` will scan. If dependencies are defined, `helm dependency build` should be ran in a `before_script` to fetch the necessary dependencies. |
| `KUBESEC_HELM_OPTIONS` | Kubesec | Additional arguments for the `helm` executable. |
| `COMPILE` | SpotBugs | Set to `false` to disable project compilation and dependency fetching. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/195252) in GitLab 13.1. |
| `ANT_HOME` | SpotBugs | The `ANT_HOME` environment variable. |
| `ANT_PATH` | SpotBugs | Path to the `ant` executable. |
| `GRADLE_PATH` | SpotBugs | Path to the `gradle` executable. |
| `JAVA_OPTS` | SpotBugs | Additional arguments for the `java` executable. |
| `JAVA_PATH` | SpotBugs | Path to the `java` executable. |
| `SAST_JAVA_VERSION` | SpotBugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. |
| `MAVEN_CLI_OPTS` | SpotBugs | Additional arguments for the `mvn` or `mvnw` executable. |
| `MAVEN_PATH` | SpotBugs | Path to the `mvn` executable. |
| `MAVEN_REPO_PATH` | SpotBugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
| `SBT_PATH` | SpotBugs | Path to the `sbt` executable. |
| `FAIL_NEVER` | SpotBugs | Set to `1` to ignore compilation failure. |
| `SAST_GOSEC_CONFIG` | Gosec | Path to configuration for Gosec (optional). |
| `PHPCS_SECURITY_AUDIT_PHP_EXTENSIONS` | phpcs-security-audit | Comma separated list of additional PHP Extensions. |
| `SEARCH_MAX_DEPTH` | any | Maximum number of directories traversed when searching for source code files. Default: `4`. |
#### Custom environment variables
@ -471,13 +473,13 @@ Here's an example SAST report:
Learn more about [Secret Detection](../secret_detection).
## Security Dashboard
## Security Dashboard **(ULTIMATE)**
The Security Dashboard is a good place to get an overview of all the security
vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md).
## Interacting with the vulnerabilities
## Interacting with the vulnerabilities **(ULTIMATE)**
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).

View file

@ -20,7 +20,7 @@ above the log file data, depending on your configuration:
- **Namespace** - Select the environment to display. Users with Maintainer or
greater [permissions](../../permissions.md) can also select Managed Apps.
- **Search** - Only available if the Elastic Stack managed application is installed.
- **Time picker** - Select the range of time to display. Only available if the
- **Select time range** - Select the range of time to display. Only available if the
Elastic Stack managed application is installed.
- **Scroll to bottom** **{scroll_down}** - Scroll to the end of the displayed logs.
- **Refresh** **{retry}** - Reload the displayed logs.

View file

@ -23,10 +23,6 @@ module Banzai
issue_url(issue, project)
end
def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
def parent_records(parent, ids)
parent.issues.where(iid: ids.to_a)
end

View file

@ -70,6 +70,10 @@ module Gitlab
::Feature.enabled?(:ci_bulk_insert_on_create, project, default_enabled: true)
end
def self.ci_if_parenthesis_enabled?
::Feature.enabled?(:ci_if_parenthesis_enabled)
end
def self.allow_to_create_merge_request_pipelines_in_target_project?(target_project)
::Feature.enabled?(:ci_allow_to_create_merge_request_pipelines_in_target_project, target_project, default_enabled: true)
end

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class And < Lexeme::Operator
class And < Lexeme::LogicalOperator
PATTERN = /&&/.freeze
def evaluate(variables = {})

View file

@ -10,6 +10,10 @@ module Gitlab
raise NotImplementedError
end
def name
self.class.name.demodulize.underscore
end
def self.build(token)
raise NotImplementedError
end
@ -23,6 +27,10 @@ module Gitlab
def self.pattern
self::PATTERN
end
def self.consume?(lexeme)
lexeme && precedence >= lexeme.precedence
end
end
end
end

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class Equals < Lexeme::Operator
class Equals < Lexeme::LogicalOperator
PATTERN = /==/.freeze
def evaluate(variables = {})

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class LogicalOperator < Lexeme::Operator
# This operator class is design to handle single operators that take two
# arguments. Expression::Parser was originally designed to read infix operators,
# and so the two operands are called "left" and "right" here. If we wish to
# implement an Operator that takes a greater or lesser number of arguments, a
# structural change or additional Operator superclass will likely be needed.
def initialize(left, right)
raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate
raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate
@left = left
@right = right
end
def inspect
"#{name}(#{@left.inspect}, #{@right.inspect})"
end
def self.type
:logical_operator
end
end
end
end
end
end
end

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class Matches < Lexeme::Operator
class Matches < Lexeme::LogicalOperator
PATTERN = /=~/.freeze
def evaluate(variables = {})

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class NotEquals < Lexeme::Operator
class NotEquals < Lexeme::LogicalOperator
PATTERN = /!=/.freeze
def evaluate(variables = {})

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class NotMatches < Lexeme::Operator
class NotMatches < Lexeme::LogicalOperator
PATTERN = /\!~/.freeze
def evaluate(variables = {})

View file

@ -9,13 +9,17 @@ module Gitlab
PATTERN = /null/.freeze
def initialize(value = nil)
@value = nil
super
end
def evaluate(variables = {})
nil
end
def inspect
'null'
end
def self.build(_value)
self.new
end

View file

@ -6,24 +6,10 @@ module Gitlab
module Expression
module Lexeme
class Operator < Lexeme::Base
# This operator class is design to handle single operators that take two
# arguments. Expression::Parser was originally designed to read infix operators,
# and so the two operands are called "left" and "right" here. If we wish to
# implement an Operator that takes a greater or lesser number of arguments, a
# structural change or additional Operator superclass will likely be needed.
OperatorError = Class.new(Expression::ExpressionError)
def initialize(left, right)
raise OperatorError, 'Invalid left operand' unless left.respond_to? :evaluate
raise OperatorError, 'Invalid right operand' unless right.respond_to? :evaluate
@left = left
@right = right
end
def self.type
:operator
raise NotImplementedError
end
def self.precedence

View file

@ -5,7 +5,7 @@ module Gitlab
module Pipeline
module Expression
module Lexeme
class Or < Lexeme::Operator
class Or < Lexeme::LogicalOperator
PATTERN = /\|\|/.freeze
def evaluate(variables = {})

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class ParenthesisClose < Lexeme::Operator
PATTERN = /\)/.freeze
def self.type
:parenthesis_close
end
def self.precedence
900
end
end
end
end
end
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Expression
module Lexeme
class ParenthesisOpen < Lexeme::Operator
PATTERN = /\(/.freeze
def self.type
:parenthesis_open
end
def self.precedence
# Needs to be higher than `ParenthesisClose` and all other Lexemes
901
end
end
end
end
end
end
end

View file

@ -11,7 +11,7 @@ module Gitlab
PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
@value = regexp.gsub(/\\\//, '/')
super(regexp.gsub(/\\\//, '/'))
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
@ -24,6 +24,10 @@ module Gitlab
raise Expression::RuntimeError, 'Invalid regular expression!'
end
def inspect
"/#{value}/"
end
def self.pattern
PATTERN
end

View file

@ -9,13 +9,17 @@ module Gitlab
PATTERN = /("(?<string>.*?)")|('(?<string>.*?)')/.freeze
def initialize(value)
@value = value
super(value)
end
def evaluate(variables = {})
@value.to_s
end
def inspect
@value.inspect
end
def self.build(string)
new(string.match(PATTERN)[:string])
end

View file

@ -9,6 +9,10 @@ module Gitlab
def self.type
:value
end
def initialize(value)
@value = value
end
end
end
end

View file

@ -8,12 +8,12 @@ module Gitlab
class Variable < Lexeme::Value
PATTERN = /\$(?<name>\w+)/.freeze
def initialize(name)
@name = name
def evaluate(variables = {})
variables.with_indifferent_access.fetch(@value, nil)
end
def evaluate(variables = {})
variables.with_indifferent_access.fetch(@name, nil)
def inspect
"$#{@value}"
end
def self.build(string)

View file

@ -10,6 +10,8 @@ module Gitlab
SyntaxError = Class.new(Expression::ExpressionError)
LEXEMES = [
Expression::Lexeme::ParenthesisOpen,
Expression::Lexeme::ParenthesisClose,
Expression::Lexeme::Variable,
Expression::Lexeme::String,
Expression::Lexeme::Pattern,
@ -22,6 +24,28 @@ module Gitlab
Expression::Lexeme::Or
].freeze
# To be removed with `ci_if_parenthesis_enabled`
LEGACY_LEXEMES = [
Expression::Lexeme::Variable,
Expression::Lexeme::String,
Expression::Lexeme::Pattern,
Expression::Lexeme::Null,
Expression::Lexeme::Equals,
Expression::Lexeme::Matches,
Expression::Lexeme::NotEquals,
Expression::Lexeme::NotMatches,
Expression::Lexeme::And,
Expression::Lexeme::Or
].freeze
def self.lexemes
if ::Gitlab::Ci::Features.ci_if_parenthesis_enabled?
LEXEMES
else
LEGACY_LEXEMES
end
end
MAX_TOKENS = 100
def initialize(statement, max_tokens: MAX_TOKENS)
@ -47,7 +71,7 @@ module Gitlab
return tokens if @scanner.eos?
lexeme = LEXEMES.find do |type|
lexeme = self.class.lexemes.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end

View file

@ -15,11 +15,18 @@ module Gitlab
def tree
results = []
tokens_rpn.each do |token|
tokens =
if ::Gitlab::Ci::Features.ci_if_parenthesis_enabled?
tokens_rpn
else
legacy_tokens_rpn
end
tokens.each do |token|
case token.type
when :value
results.push(token.build)
when :operator
when :logical_operator
right_operand = results.pop
left_operand = results.pop
@ -27,7 +34,7 @@ module Gitlab
results.push(res)
end
else
raise ParseError, 'Unprocessable token found in parse tree'
raise ParseError, "Unprocessable token found in parse tree: #{token.type}"
end
end
@ -45,6 +52,7 @@ module Gitlab
# Parse the expression into Reverse Polish Notation
# (See: Shunting-yard algorithm)
# Taken from: https://en.wikipedia.org/wiki/Shunting-yard_algorithm#The_algorithm_in_detail
def tokens_rpn
output = []
operators = []
@ -53,7 +61,34 @@ module Gitlab
case token.type
when :value
output.push(token)
when :operator
when :logical_operator
output.push(operators.pop) while token.lexeme.consume?(operators.last&.lexeme)
operators.push(token)
when :parenthesis_open
operators.push(token)
when :parenthesis_close
output.push(operators.pop) while token.lexeme.consume?(operators.last&.lexeme)
raise ParseError, 'Unmatched parenthesis' unless operators.last
operators.pop if operators.last.lexeme.type == :parenthesis_open
end
end
output.concat(operators.reverse)
end
# To be removed with `ci_if_parenthesis_enabled`
def legacy_tokens_rpn
output = []
operators = []
@tokens.each do |token|
case token.type
when :value
output.push(token)
when :logical_operator
if operators.any? && token.lexeme.precedence >= operators.last.lexeme.precedence
output.push(operators.pop)
end

View file

@ -34,6 +34,14 @@ module Gitlab
super || subject.is_a?(type)
end
def web_url
url_builder.build(subject)
end
def web_path
url_builder.build(subject, only_path: true)
end
class_methods do
def presenter?
true

View file

@ -10219,10 +10219,7 @@ msgstr ""
msgid "FeatureFlags|Edit Feature Flag"
msgstr ""
msgid "FeatureFlags|Edit Feature Flag User List"
msgstr ""
msgid "FeatureFlags|Edit list"
msgid "FeatureFlags|Edit User List"
msgstr ""
msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies."
@ -10303,15 +10300,12 @@ msgstr ""
msgid "FeatureFlags|New Feature Flag"
msgstr ""
msgid "FeatureFlags|New Feature Flag User List"
msgid "FeatureFlags|New User List"
msgstr ""
msgid "FeatureFlags|New feature flag"
msgstr ""
msgid "FeatureFlags|New list"
msgstr ""
msgid "FeatureFlags|Percent rollout (logged in users)"
msgstr ""
@ -13195,9 +13189,6 @@ msgstr ""
msgid "IssueTracker|Custom issue tracker"
msgstr ""
msgid "IssueTracker|GitLab issue tracker"
msgstr ""
msgid "IssueTracker|Redmine issue tracker"
msgstr ""

View file

@ -181,6 +181,23 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
end
end
context 'when user with 2FA is unconfirmed' do
render_views
let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider) }
before do
user.update_column(:confirmed_at, nil)
end
it 'redirects to login page' do
post provider
expect(response).to redirect_to(new_user_session_path)
expect(flash[:alert]).to match(/You have to confirm your email address before continuing./)
end
end
context 'sign up' do
include_context 'sign_up'

View file

@ -399,7 +399,7 @@ RSpec.describe SessionsController do
end
it 'warns about invalid login' do
expect(response).to set_flash.now[:alert].to /Your account is locked./
expect(flash[:alert]).to eq('Your account is locked.')
end
it 'locks the user' do
@ -409,7 +409,7 @@ RSpec.describe SessionsController do
it 'keeps the user locked on future login attempts' do
post(:create, params: { user: { login: user.username, password: user.password } })
expect(response).to set_flash.now[:alert].to /Your account is locked./
expect(flash[:alert]).to eq('Your account is locked.')
end
end
end

View file

@ -116,12 +116,6 @@ FactoryBot.define do
issue_tracker
end
factory :gitlab_issue_tracker_service do
project
active { true }
issue_tracker
end
trait :issue_tracker do
transient do
create_data { true }

View file

@ -10,10 +10,6 @@ RSpec.describe "User views issues" do
let_it_be(:user) { create(:user) }
before do
stub_feature_flags(vue_issuables_list: false)
end
shared_examples "opens issue from list" do
it "opens issue" do
click_link(issue.title)
@ -112,7 +108,7 @@ RSpec.describe "User views issues" do
end
end
context "when signed in as developer" do
context "when signed in as developer", :js do
before do
project.add_developer(user)
sign_in(user)
@ -122,27 +118,7 @@ RSpec.describe "User views issues" do
include_examples "internal project"
end
context "when not signed in" do
context "when not signed in", :js do
include_examples "public project"
end
context 'when vue_issuables_list feature is enabled', :js do
before do
stub_feature_flags(vue_issuables_list: true)
end
context 'when signed in' do
before do
project.add_developer(user)
sign_in(user)
end
include_examples "public project"
include_examples "internal project"
end
context 'when not signed in' do
include_examples "public project"
end
end
end

View file

@ -37,6 +37,15 @@ RSpec.describe 'Merge requests > User mass updates', :js do
expect(page).to have_selector('.merge-request', count: 0)
end
end
it 'does not exist in merged state' do
merge_request.close
visit project_merge_requests_path(project, state: 'merged')
click_button 'Edit merge requests'
expect(page).not_to have_css('.js-issue-status')
end
end
context 'assignee' do

View file

@ -41,23 +41,20 @@ describe('DropdownButton', () => {
describe('methods', () => {
describe('handleButtonClick', () => {
it.each`
variant
${'standalone'}
${'embedded'}
variant | expectPropagationStopped
${'standalone'} | ${true}
${'embedded'} | ${false}
`(
'toggles dropdown content and stops event propagation when `state.variant` is "$variant"',
({ variant }) => {
'toggles dropdown content and handles event propagation when `state.variant` is "$variant"',
({ variant, expectPropagationStopped }) => {
const event = { stopPropagation: jest.fn() };
wrapper = createComponent({
...mockConfig,
variant,
});
wrapper = createComponent({ ...mockConfig, variant });
findDropdownButton().vm.$emit('click', event);
expect(store.state.showDropdownContents).toBe(true);
expect(event.stopPropagation).toHaveBeenCalled();
expect(event.stopPropagation).toHaveBeenCalledTimes(expectPropagationStopped ? 1 : 0);
},
);
});

View file

@ -17,53 +17,47 @@ import { mockConfig, mockLabels, mockRegularLabel } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = (initialState = mockConfig) => {
const store = new Vuex.Store({
getters,
mutations,
state: {
...defaultState(),
footerCreateLabelTitle: 'Create label',
footerManageLabelTitle: 'Manage labels',
},
actions: {
...actions,
fetchLabels: jest.fn(),
},
});
store.dispatch('setInitialState', initialState);
store.dispatch('receiveLabelsSuccess', mockLabels);
return shallowMount(DropdownContentsLabelsView, {
localVue,
store,
});
};
describe('DropdownContentsLabelsView', () => {
let wrapper;
let wrapperStandalone;
let wrapperEmbedded;
const createComponent = (initialState = mockConfig) => {
const store = new Vuex.Store({
getters,
mutations,
state: {
...defaultState(),
footerCreateLabelTitle: 'Create label',
footerManageLabelTitle: 'Manage labels',
},
actions: {
...actions,
fetchLabels: jest.fn(),
},
});
store.dispatch('setInitialState', initialState);
store.dispatch('receiveLabelsSuccess', mockLabels);
wrapper = shallowMount(DropdownContentsLabelsView, {
localVue,
store,
});
};
beforeEach(() => {
wrapper = createComponent();
wrapperStandalone = createComponent({
...mockConfig,
variant: 'standalone',
});
wrapperEmbedded = createComponent({
...mockConfig,
variant: 'embedded',
});
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapperStandalone.destroy();
wrapperEmbedded.destroy();
wrapper = null;
});
const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]');
const findDropdownTitle = () => wrapper.find('[data-testid="dropdown-title"]');
const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
describe('computed', () => {
describe('visibleLabels', () => {
it('returns matching labels filtered with `searchKey`', () => {
@ -83,6 +77,24 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.vm.visibleLabels.length).toBe(mockLabels.length);
});
});
describe('showListContainer', () => {
it.each`
variant | loading | showList
${'sidebar'} | ${false} | ${true}
${'sidebar'} | ${true} | ${false}
${'not-sidebar'} | ${true} | ${true}
${'not-sidebar'} | ${false} | ${true}
`(
'returns $showList if `state.variant` is "$variant" and `labelsFetchInProgress` is $loading',
({ variant, loading, showList }) => {
createComponent({ ...mockConfig, variant });
wrapper.vm.$store.state.labelsFetchInProgress = loading;
expect(wrapper.vm.showListContainer).toBe(showList);
},
);
});
});
describe('methods', () => {
@ -199,7 +211,7 @@ describe('DropdownContentsLabelsView', () => {
wrapper.vm.$store.dispatch('requestLabels');
return wrapper.vm.$nextTick(() => {
const loadingIconEl = wrapper.find(GlLoadingIcon);
const loadingIconEl = findLoadingIcon();
expect(loadingIconEl.exists()).toBe(true);
expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
@ -207,22 +219,24 @@ describe('DropdownContentsLabelsView', () => {
});
it('renders dropdown title element', () => {
const titleEl = wrapper.find('.dropdown-title > span');
const titleEl = findDropdownTitle();
expect(titleEl.exists()).toBe(true);
expect(titleEl.text()).toBe('Assign labels');
});
it('does not render dropdown title element when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-title').exists()).toBe(false);
createComponent({ ...mockConfig, variant: 'standalone' });
expect(findDropdownTitle().exists()).toBe(false);
});
it('renders dropdown title element when `state.variant` is "embedded"', () => {
expect(wrapperEmbedded.find('.dropdown-title').exists()).toBe(true);
createComponent({ ...mockConfig, variant: 'embedded' });
expect(findDropdownTitle().exists()).toBe(true);
});
it('renders dropdown close button element', () => {
const closeButtonEl = wrapper.find('.dropdown-title').find(GlButton);
const closeButtonEl = findDropdownTitle().find(GlButton);
expect(closeButtonEl.exists()).toBe(true);
expect(closeButtonEl.props('icon')).toBe('close');
@ -249,8 +263,7 @@ describe('DropdownContentsLabelsView', () => {
});
return wrapper.vm.$nextTick(() => {
const labelsEl = wrapper.findAll('.dropdown-content li');
const labelItemEl = labelsEl.at(0).find(LabelItem);
const labelItemEl = findDropdownContent().find(LabelItem);
expect(labelItemEl.props('highlight')).toBe(true);
});
@ -262,22 +275,28 @@ describe('DropdownContentsLabelsView', () => {
});
return wrapper.vm.$nextTick(() => {
const noMatchEl = wrapper.find('.dropdown-content li');
const noMatchEl = findDropdownContent().find('li');
expect(noMatchEl.isVisible()).toBe(true);
expect(noMatchEl.text()).toContain('No matching results');
});
});
it('renders empty content while loading', () => {
wrapper.vm.$store.state.labelsFetchInProgress = true;
return wrapper.vm.$nextTick(() => {
const dropdownContent = findDropdownContent();
expect(dropdownContent.exists()).toBe(true);
expect(dropdownContent.isVisible()).toBe(false);
});
});
it('renders footer list items', () => {
const createLabelLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(0);
const manageLabelsLink = wrapper
.find('.dropdown-footer')
.findAll(GlLink)
.at(1);
const footerLinks = findDropdownFooter().findAll(GlLink);
const createLabelLink = footerLinks.at(0);
const manageLabelsLink = footerLinks.at(1);
expect(createLabelLink.exists()).toBe(true);
expect(createLabelLink.text()).toBe('Create label');
@ -289,8 +308,7 @@ describe('DropdownContentsLabelsView', () => {
wrapper.vm.$store.state.allowLabelCreate = false;
return wrapper.vm.$nextTick(() => {
const createLabelLink = wrapper
.find('.dropdown-footer')
const createLabelLink = findDropdownFooter()
.findAll(GlLink)
.at(0);
@ -299,11 +317,12 @@ describe('DropdownContentsLabelsView', () => {
});
it('does not render footer list items when `state.variant` is "standalone"', () => {
expect(wrapperStandalone.find('.dropdown-footer').exists()).toBe(false);
createComponent({ ...mockConfig, variant: 'standalone' });
expect(findDropdownFooter().exists()).toBe(false);
});
it('renders footer list items when `state.variant` is "embedded"', () => {
expect(wrapperEmbedded.find('.dropdown-footer').exists()).toBe(true);
expect(findDropdownFooter().exists()).toBe(true);
});
});
});

View file

@ -10,12 +10,13 @@ import { mockConfig } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = (initialState = mockConfig) => {
const createComponent = (initialState = mockConfig, propsData = {}) => {
const store = new Vuex.Store(labelsSelectModule());
store.dispatch('setInitialState', initialState);
return shallowMount(DropdownContents, {
propsData,
localVue,
store,
});
@ -47,8 +48,15 @@ describe('DropdownContent', () => {
});
describe('template', () => {
it('renders component container element with class `labels-select-dropdown-contents`', () => {
it('renders component container element with class `labels-select-dropdown-contents` and no styles', () => {
expect(wrapper.attributes('class')).toContain('labels-select-dropdown-contents');
expect(wrapper.attributes('style')).toBe(undefined);
});
it('renders component container element with styles when `renderOnTop` is true', () => {
wrapper = createComponent(mockConfig, { renderOnTop: true });
expect(wrapper.attributes('style')).toContain('bottom: 100%');
});
});
});

View file

@ -9,9 +9,14 @@ import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dr
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
import { isInViewport } from '~/lib/utils/common_utils';
import { mockConfig } from './mock_data';
jest.mock('~/lib/utils/common_utils', () => ({
isInViewport: jest.fn().mockReturnValue(true),
}));
const localVue = createLocalVue();
localVue.use(Vuex);
@ -21,6 +26,9 @@ const createComponent = (config = mockConfig, slots = {}) =>
slots,
store: new Vuex.Store(labelsSelectModule()),
propsData: config,
stubs: {
'dropdown-contents': DropdownContents,
},
});
describe('LabelsSelectRoot', () => {
@ -144,5 +152,42 @@ describe('LabelsSelectRoot', () => {
expect(wrapper.find(DropdownContents).exists()).toBe(true);
});
});
describe('sets content direction based on viewport', () => {
it('does not set direction when `state.variant` is not "embedded"', () => {
wrapper.vm.$store.dispatch('toggleDropdownContents');
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
});
});
describe('when `state.variant` is "embedded"', () => {
beforeEach(() => {
wrapper = createComponent({ ...mockConfig, variant: 'embedded' });
wrapper.vm.$store.dispatch('toggleDropdownContents');
});
it('set direction when out of viewport', () => {
isInViewport.mockImplementation(() => false);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
});
});
it('does not set direction when inside of viewport', () => {
isInViewport.mockImplementation(() => true);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
});
});
});
});
});
});

View file

@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::And do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -27,7 +27,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -28,7 +28,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -24,7 +24,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexeme::Or do
describe '.type' do
it 'is an operator' do
expect(described_class.type).to eq :operator
expect(described_class.type).to eq :logical_operator
end
end

View file

@ -81,6 +81,35 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Lexer do
with_them do
it { is_expected.to eq(tokens) }
end
context 'with parentheses are used' do
where(:expression, :tokens) do
'($PRESENT_VARIABLE =~ /my var/) && $EMPTY_VARIABLE =~ /nope/' | ['(', '$PRESENT_VARIABLE', '=~', '/my var/', ')', '&&', '$EMPTY_VARIABLE', '=~', '/nope/']
'$PRESENT_VARIABLE =~ /my var/ || ($EMPTY_VARIABLE =~ /nope/)' | ['$PRESENT_VARIABLE', '=~', '/my var/', '||', '(', '$EMPTY_VARIABLE', '=~', '/nope/', ')']
'($PRESENT_VARIABLE && (null || $EMPTY_VARIABLE == ""))' | ['(', '$PRESENT_VARIABLE', '&&', '(', 'null', '||', '$EMPTY_VARIABLE', '==', '""', ')', ')']
end
with_them do
context 'when ci_if_parenthesis_enabled is enabled' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: true)
end
it { is_expected.to eq(tokens) }
end
context 'when ci_if_parenthesis_enabled is disabled' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: false)
end
it do
expect { subject }
.to raise_error described_class::SyntaxError
end
end
end
end
end
end

View file

@ -1,51 +1,79 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do
before do
stub_feature_flags(ci_if_parenthesis_enabled: true)
end
describe '#tree' do
context 'when using two operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123"').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
context 'validates simple operators' do
using RSpec::Parameterized::TableSyntax
where(:expression, :result_tree) do
'$VAR1 == "123"' | 'equals($VAR1, "123")'
'$VAR1 == "123" == $VAR2' | 'equals(equals($VAR1, "123"), $VAR2)'
'$VAR' | '$VAR'
'"some value"' | '"some value"'
'null' | 'null'
'$VAR1 || $VAR2 && $VAR3' | 'or($VAR1, and($VAR2, $VAR3))'
'$VAR1 && $VAR2 || $VAR3' | 'or(and($VAR1, $VAR2), $VAR3)'
'$VAR1 && $VAR2 || $VAR3 && $VAR4' | 'or(and($VAR1, $VAR2), and($VAR3, $VAR4))'
'$VAR1 && ($VAR2 || $VAR3) && $VAR4' | 'and(and($VAR1, or($VAR2, $VAR3)), $VAR4)'
end
with_them do
it { expect(described_class.seed(expression).tree.inspect).to eq(result_tree) }
end
end
context 'when using three operators' do
it 'returns a reverse descent parse tree' do
expect(described_class.seed('$VAR1 == "123" == $VAR2').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Equals
context 'when combining && and OR operators' do
subject { described_class.seed('$VAR1 == "a" || $VAR2 == "b" && $VAR3 == "c" || $VAR4 == "d" && $VAR5 == "e"').tree }
context 'when parenthesis engine is enabled' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: true)
end
it 'returns operations in a correct order' do
expect(subject.inspect)
.to eq('or(or(equals($VAR1, "a"), and(equals($VAR2, "b"), equals($VAR3, "c"))), and(equals($VAR4, "d"), equals($VAR5, "e")))')
end
end
context 'when parenthesis engine is disabled (legacy)' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: false)
end
it 'returns operations in a invalid order' do
expect(subject.inspect)
.to eq('or(equals($VAR1, "a"), and(equals($VAR2, "b"), or(equals($VAR3, "c"), and(equals($VAR4, "d"), equals($VAR5, "e")))))')
end
end
end
context 'when using a single variable token' do
it 'returns a single token instance' do
expect(described_class.seed('$VAR').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Variable
end
end
context 'when using parenthesis' do
subject { described_class.seed('(($VAR1 == "a" || $VAR2 == "b") && $VAR3 == "c" || $VAR4 == "d") && $VAR5 == "e"').tree }
context 'when using a single string token' do
it 'returns a single token instance' do
expect(described_class.seed('"some value"').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::String
before do
stub_feature_flags(ci_if_parenthesis_enabled: true)
end
it 'returns operations in a correct order' do
expect(subject.inspect)
.to eq('and(or(and(or(equals($VAR1, "a"), equals($VAR2, "b")), equals($VAR3, "c")), equals($VAR4, "d")), equals($VAR5, "e"))')
end
end
context 'when expression is empty' do
it 'returns a null token' do
it 'raises a parsing error' do
expect { described_class.seed('').tree }
.to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
end
end
context 'when expression is null' do
it 'returns a null token' do
expect(described_class.seed('null').tree)
.to be_a Gitlab::Ci::Pipeline::Expression::Lexeme::Null
end
end
context 'when two value tokens have no operator' do
it 'raises a parsing error' do
expect { described_class.seed('$VAR "text"').tree }
@ -66,5 +94,42 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Parser do
.to raise_error Gitlab::Ci::Pipeline::Expression::Lexeme::Operator::OperatorError
end
end
context 'when parenthesis are unmatched' do
context 'when parenthesis engine is enabled' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: true)
end
where(:expression) do
[
'$VAR == (',
'$VAR2 == ("aa"',
'$VAR2 == ("aa"))',
'$VAR2 == "aa")',
'(($VAR2 == "aa")',
'($VAR2 == "aa"))'
]
end
with_them do
it 'raises a ParseError' do
expect { described_class.seed(expression).tree }
.to raise_error Gitlab::Ci::Pipeline::Expression::Parser::ParseError
end
end
end
context 'when parenthesis engine is disabled' do
before do
stub_feature_flags(ci_if_parenthesis_enabled: false)
end
it 'raises an SyntaxError' do
expect { described_class.seed('$VAR == (').tree }
.to raise_error Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError
end
end
end
end
end

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
subject do
@ -109,6 +108,17 @@ RSpec.describe Gitlab::Ci::Pipeline::Expression::Statement do
'$UNDEFINED_VARIABLE || $PRESENT_VARIABLE' | 'my variable'
'$UNDEFINED_VARIABLE == null || $PRESENT_VARIABLE' | true
'$PRESENT_VARIABLE || $UNDEFINED_VARIABLE == null' | 'my variable'
'($PRESENT_VARIABLE)' | 'my variable'
'(($PRESENT_VARIABLE))' | 'my variable'
'(($PRESENT_VARIABLE && null) || $EMPTY_VARIABLE == "")' | true
'($PRESENT_VARIABLE) && (null || $EMPTY_VARIABLE == "")' | true
'("string" || "test") == "string"' | true
'(null || ("test" == "string"))' | false
'("string" == ("test" && "string"))' | true
'("string" == ("test" || "string"))' | false
'("string" == "test" || "string")' | "string"
'("string" == ("string" || (("1" == "1") && ("2" == "3"))))' | true
end
with_them do

View file

@ -353,7 +353,6 @@ project:
- youtrack_service
- custom_issue_tracker_service
- bugzilla_service
- gitlab_issue_tracker_service
- external_wiki_service
- mock_ci_service
- mock_deployment_service

View file

@ -57,4 +57,32 @@ RSpec.describe Gitlab::View::Presenter::Base do
expect(presenter.present).to eq(presenter)
end
end
describe '#url_builder' do
it 'returns the UrlBuilder instance' do
presenter = presenter_class.new(project)
expect(presenter.url_builder).to eq(Gitlab::UrlBuilder.instance)
end
end
describe '#web_url' do
it 'delegates to the UrlBuilder' do
presenter = presenter_class.new(project)
expect(presenter.url_builder).to receive(:build).with(project)
presenter.web_url
end
end
describe '#web_path' do
it 'delegates to the UrlBuilder' do
presenter = presenter_class.new(project)
expect(presenter.url_builder).to receive(:build).with(project, only_path: true)
presenter.web_path
end
end
end

View file

@ -1,54 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabIssueTrackerService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
context 'when service is active' do
subject { described_class.new(project: create(:project), active: true) }
it { is_expected.to validate_presence_of(:issues_url) }
it_behaves_like 'issue tracker service URL attribute', :issues_url
end
context 'when service is inactive' do
subject { described_class.new(project: create(:project), active: false) }
it { is_expected.not_to validate_presence_of(:issues_url) }
end
end
describe 'project and issue urls' do
let(:project) { create(:project) }
let(:service) { project.create_gitlab_issue_tracker_service(active: true) }
context 'with absolute urls' do
before do
allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
end
it 'gives the correct path' do
expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues")
expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/new")
expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.full_path}/-/issues/432")
end
end
context 'with relative urls' do
before do
allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
end
it 'gives the correct path' do
expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.full_path}/-/issues")
expect(service.new_issue_path).to eq("/gitlab/root/#{project.full_path}/-/issues/new")
expect(service.issue_path(432)).to eq("/gitlab/root/#{project.full_path}/-/issues/432")
end
end
end
end

View file

@ -61,7 +61,6 @@ RSpec.describe Project do
it { is_expected.to have_one(:youtrack_service) }
it { is_expected.to have_one(:custom_issue_tracker_service) }
it { is_expected.to have_one(:bugzilla_service) }
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
it { is_expected.to have_one(:external_wiki_service) }
it { is_expected.to have_one(:confluence_service) }
it { is_expected.to have_one(:project_feature) }

View file

@ -535,7 +535,7 @@ RSpec.describe Service do
describe 'initialize service with no properties' do
let(:service) do
GitlabIssueTrackerService.create(
BugzillaService.create(
project: create(:project),
project_url: 'http://gitlab.example.com'
)

View file

@ -24,7 +24,7 @@ RSpec.describe Projects::UpdatePagesConfigurationService do
it 'updates the .update file' do
expect(service).to receive(:reload_daemon).and_call_original
expect(subject).to include(status: :success, reload: true)
expect(subject).to include(status: :success)
end
end
@ -37,7 +37,7 @@ RSpec.describe Projects::UpdatePagesConfigurationService do
it 'does not update the .update file' do
expect(service).not_to receive(:reload_daemon)
expect(subject).to include(status: :success, reload: false)
expect(subject).to include(status: :success)
end
end
end