Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
58320d8e03
commit
0ca5c1a237
77 changed files with 826 additions and 440 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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?
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove status dropdown in merged tab
|
||||
merge_request: 37544
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove globe icon from explore projects dropdown
|
||||
merge_request: 21659
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix 500 error when unconfirmed OAuth2 user with 2FA logs in
|
||||
merge_request: 38104
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Add parenthesis support for if: conditions'
|
||||
merge_request: 37574
|
||||
author:
|
||||
type: added
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
35
lib/gitlab/ci/pipeline/expression/lexeme/logical_operator.rb
Normal file
35
lib/gitlab/ci/pipeline/expression/lexeme/logical_operator.rb
Normal 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
|
|
@ -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 = {})
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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
|
24
lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_open.rb
Normal file
24
lib/gitlab/ci/pipeline/expression/lexeme/parenthesis_open.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,6 +9,10 @@ module Gitlab
|
|||
def self.type
|
||||
:value
|
||||
end
|
||||
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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%');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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) }
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue