Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-24 18:10:19 +00:00
parent 27622f7417
commit 9b14160725
84 changed files with 600 additions and 223 deletions

View File

@ -7,6 +7,7 @@ stages:
- post-test
- review-prepare
- review
- dast
- qa
- post-qa
- pages
@ -104,3 +105,4 @@ include:
- local: .gitlab/ci/yaml.gitlab-ci.yml
- local: .gitlab/ci/releases.gitlab-ci.yml
- local: .gitlab/ci/notify.gitlab-ci.yml
- local: .gitlab/ci/dast.gitlab-ci.yml

View File

@ -0,0 +1,201 @@
.dast_conf:
tags:
- prm
# For scheduling dast job
extends:
- .reports:schedule-dast
image:
name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
resource_group: dast_scan
variables:
DAST_USERNAME_FIELD: "user[login]"
DAST_PASSWORD_FIELD: "user[password]"
DAST_FULL_SCAN_ENABLED: "true"
DAST_SPIDER_MINS: 0
# TBD pin to a version
DAST_VERSION: 1.22.1
# -Xmx is used to set the JVM memory to 6GB to prevent DAST OutOfMemoryError.
DAST_ZAP_CLI_OPTIONS: "-Xmx6144m"
DAST_RULES: "41,42,43,10027,10032,10041,10042,10045,10047,10052,10053,10057,10061,10096,10097,10104,10106,20012,20014,20015,20016,20017,20018,40019,40020,40021,40024,40025,40027,40029,40032,90001,90019,10109,10026,10028,10029,10030,10031,10033,10034,10035,10036,10038,10039,10043,10044,10048,10050,10051,10058,10062,10095,10107,10108,30003,40013,40022,40023,40028,90021,90023,90024,90025,90027,90028,10003,50003,0,2,3,6,7,10010,10011,10015,10017,10019,10020,10021,10023,10024,10025,10037,10040,10054,10055,10056,10098,10105,10202,20019,30001,30002,40003,40008,40009,40012,40014,40016,40017,40018,50000,50001,90011,90020,90022,90033"
before_script:
- 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
- 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
- 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# Below three lines can be removed once https://gitlab.com/gitlab-org/gitlab/-/issues/230687 is fixed
- mkdir -p /zap/xml
- 'sed -i "84 s/true/false/" /zap/xml/config.xml'
- cat /zap/xml/config.xml
# Help pages are excluded from scan as they are static pages.
# profile/two_factor_auth is excluded from scan to prevent 2FA from being turned on from user profile, which will reduce coverage.
- 'export DAST_AUTH_EXCLUDE_URLS="${DAST_WEBSITE}/help/.*,${DAST_WEBSITE}/profile/two_factor_auth,${DAST_WEBSITE}/users/sign_out"'
- enable_rule () { read all_rules; rule=$1; echo $all_rules | sed -r "s/(,)?$rule(,)?/\1-1\2/" ; }
# Sort ids in DAST_RULES ascendingly, which is required when using DAST_RULES as argument to enable_rule
- 'DAST_RULES=$(echo $DAST_RULES | tr "," "\n" | sort -n | paste -sd ",")'
needs: ["review-deploy"]
stage: dast
# Default job timeout set to 90m and dast rules needs 2h to so that it won't timeout.
timeout: 2h
artifacts:
paths:
- gl-dast-report.json # GitLab-specific
reports:
dast: gl-dast-report.json
expire_in: 1 week # GitLab-specific
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset1:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user1"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10019 | enable_rule 10020 | enable_rule 10021 | enable_rule 10023 | enable_rule 10024 | enable_rule 10025 | enable_rule 10037 | enable_rule 10040 | enable_rule 10054 | enable_rule 10055 | enable_rule 10056)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset2:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user2"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 90011 | enable_rule 90020 | enable_rule 90022 | enable_rule 90033)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset3:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user3"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40016 | enable_rule 40017 | enable_rule 50000 | enable_rule 50001)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset4:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user4"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 0 | enable_rule 2 | enable_rule 3 | enable_rule 7 )
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset5:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user5"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10010 | enable_rule 10011 | enable_rule 10015 | enable_rule 10017 | enable_rule 10019)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with a subset of Release scan rules.
DAST-fullscan-ruleset6:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user6"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 30001 | enable_rule 40009)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
# DAST scan with a subset of Beta scan rules.
# DAST-fullscan-ruleset7:
# extends:
# - .dast_conf
# variables:
# DAST_USERNAME: "user7"
# script:
# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10098 | enable_rule 10105 | enable_rule 10202 | enable_rule 30002 | enable_rule 40003 | enable_rule 40008 | enable_rule 40009)
# - echo $DAST_EXCLUDE_RULES
# - /analyze -t $DAST_WEBSITE -d
# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
# Below jobs runs DAST scans with one time consuming scan rule. These scan rules are disabled in above jobs so that those jobs won't timeout.
# DAST scan with rule - 20019 External Redirect
# DAST-fullscan-rule-20019:
# extends:
# - .dast_conf
# variables:
# DAST_USERNAME: "user8"
# script:
# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 20019)
# - echo $DAST_EXCLUDE_RULES
# - /analyze -t $DAST_WEBSITE -d
# Enable when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39749 is fixed
# DAST scan with rule - 10107 Httpoxy - Proxy Header Misuse - Active/beta
# DAST-fullscan-rule-10107:
# extends:
# - .dast_conf
# variables:
# DAST_USERNAME: "user9"
# script:
# - export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 10107)
# - echo $DAST_EXCLUDE_RULES
# - /analyze -t $DAST_WEBSITE -d
# DAST scan with rule - 90020 Remote OS Command Injection
DAST-fullscan-rule-90020:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user10"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 90020)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with rule - 40018 SQL Injection - Active/release
DAST-fullscan-rule-40018:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user11"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40018)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with rule - 40014 Cross Site Scripting (Persistent) - Active/release
DAST-fullscan-rule-40014:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user12"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40014)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with rule - 6 Path travesal
DAST-fullscan-rule-6:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user13"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 6)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d
# DAST scan with rule - 40012 Cross Site Scripting (Reflected)
DAST-fullscan-rule-40012:
extends:
- .dast_conf
variables:
DAST_USERNAME: "user14"
script:
- export DAST_EXCLUDE_RULES=$(echo $DAST_RULES | enable_rule 40012)
- echo $DAST_EXCLUDE_RULES
- /analyze -t $DAST_WEBSITE -d

View File

@ -145,45 +145,3 @@ dependency_scanning:
reports:
dependency_scanning: gl-dependency-scanning-report.json
expire_in: 1 week # GitLab-specific
# Temporarily disabling review apps
## We need to duplicate this job's definition because it seems it's impossible to
## override an included `only.refs`.
## See https://gitlab.com/gitlab-org/gitlab/issues/31371.
# dast:
# extends:
# - .default-retry
# - .reports:rules:dast
# # This is needed so that manual jobs with needs don't block the pipeline.
# # See https://gitlab.com/gitlab-org/gitlab/-/issues/199979.
# dependencies: ["review-deploy"]
# stage: qa # GitLab-specific
# image:
# name: "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION"
# variables:
# # To be done in a later iteration
# # DAST_USERNAME: "root"
# # DAST_USERNAME_FIELD: "user[login]"
# # DAST_PASSWORD_FIELD: "user[passowrd]"
# DAST_VERSION: 1
# script:
# - 'export DAST_WEBSITE="${DAST_WEBSITE:-$(cat environment_url.txt)}"'
# # To be done in a later iteration
# # - 'export DAST_AUTH_URL="${DAST_WEBSITE}/users/sign_in"'
# # - 'export DAST_PASSWORD="${REVIEW_APPS_ROOT_PASSWORD}"'
# - /analyze -t $DAST_WEBSITE
# timeout: 4h
# artifacts:
# paths:
# - gl-dast-report.json # GitLab-specific
# reports:
# dast: gl-dast-report.json
# expire_in: 1 week # GitLab-specific
# To be done in a later iteration: https://gitlab.com/gitlab-org/gitlab/issues/31160#note_278188255
# schedule:dast:
# extends:
# - dast
# - .reports:schedule-dast
# variables:
# DAST_FULL_SCAN_ENABLED: "true"

View File

@ -77,6 +77,11 @@ review-deploy:
# to have to manually start the jobs in sequence, so we do it for them.
- '[ -z $CI_JOB_MANUAL ] || play_job "review-qa-smoke"'
- '[ -z $CI_JOB_MANUAL ] || play_job "review-performance"'
after_script:
# Run seed-dast-test-data.sh only when DAST_RUN is set to true. This is to pupulate review app with data for DAST scan.
# Set DAST_RUN to true when jobs are manually scheduled.
- if [ "$DAST_RUN" == "true" ]; then source scripts/review_apps/seed-dast-test-data.sh; TRACE=1 trigger_proj_user_creation; fi
artifacts:
paths: [environment_url.txt]
expire_in: 2 days

View File

@ -645,6 +645,7 @@
- if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/'
when: never
- <<: *if-dot-com-gitlab-org-schedule
allow_failure: true
################
# Review rules #
@ -665,6 +666,8 @@
.review:rules:mr-and-schedule-auto-if-frontend-manual-otherwise:
rules:
- if: '$DAST_RUN == "true"' # Skip this job when DAST is run
when: never
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request

View File

@ -68,10 +68,10 @@ a nightly pipeline, select ~"found:nightly".
<!--
https://about.gitlab.com/handbook/engineering/quality/guidelines/#priorities:
- ~P::1: Tests that are needed to verify fundamental GitLab functionality.
- ~P::2: Tests that deal with external integrations which may take a longer time to debug and fix.
- ~"priority::1": Tests that are needed to verify fundamental GitLab functionality.
- ~"priority::2": Tests that deal with external integrations which may take a longer time to debug and fix.
-->
/label ~P::
/label ~priority::
<!-- Select the current milestone if ~P::1 or the next milestone if ~P::2. -->
<!-- Select the current milestone if ~"priority::1" or the next milestone if ~"priority::2". -->
/milestone %

View File

@ -23,7 +23,6 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
- [ ] Ensure it's approved by an AppSec engineer.
- If you're unsure who should approve, find the AppSec engineer associated to the issue in the [Canonical repository], or ask #sec-appsec on Slack.
- Trigger the [`package-and-qa` build]. The docker image generated will be used by the AppSec engineer to validate the security vulnerability has been remediated.
- [ ] Merge request _must_ close the corresponding security issue.
- [ ] For a backport MR targeting a versioned stable branch (`X-Y-stable-ee`)
- [ ] Ensure it's approved by a maintainer.

View File

@ -1,6 +1,6 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlButton, GlIcon } from '@gitlab/ui';
import { sprintf, n__ } from '~/locale';
import DraftsCount from './drafts_count.vue';
import PublishButton from './publish_button.vue';
@ -9,6 +9,7 @@ import PreviewItem from './preview_item.vue';
export default {
components: {
GlLoadingIcon,
GlButton,
GlIcon,
DraftsCount,
PublishButton,
@ -28,7 +29,7 @@ export default {
watch: {
showPreviewDropdown() {
if (this.showPreviewDropdown && this.$refs.dropdown) {
this.$nextTick(() => this.$refs.dropdown.focus());
this.$nextTick(() => this.$refs.dropdown.$el.focus());
}
},
},
@ -62,16 +63,18 @@ export default {
show: showPreviewDropdown,
}"
>
<button
<gl-button
ref="dropdown"
type="button"
class="btn btn-success review-preview-dropdown-toggle qa-review-preview-toggle"
category="primary"
variant="success"
class="review-preview-dropdown-toggle qa-review-preview-toggle"
@click="toggleReviewDropdown"
>
{{ __('Finish review') }}
<drafts-count />
<gl-icon name="angle-up" />
</button>
</gl-button>
<div
class="dropdown-menu dropdown-menu-large dropdown-menu-right dropdown-open-top"
:class="{

View File

@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
import $ from 'jquery';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
@ -19,7 +19,7 @@ export default class TemplateSelector {
}
initDropdown(dropdown, data) {
return $(dropdown).glDropdown({
return initDeprecatedJQueryDropdown($(dropdown), {
data,
filterable: true,
selectable: true,

View File

@ -1,4 +1,5 @@
import FileTemplateSelector from '../file_template_selector';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class BlobCiYamlSelector extends FileTemplateSelector {
constructor({ mediator }) {
@ -15,7 +16,7 @@ export default class BlobCiYamlSelector extends FileTemplateSelector {
initDropdown() {
// maybe move to super class as well
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,

View File

@ -1,5 +1,6 @@
import FileTemplateSelector from '../file_template_selector';
import { __ } from '~/locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class DockerfileSelector extends FileTemplateSelector {
constructor({ mediator }) {
@ -16,7 +17,7 @@ export default class DockerfileSelector extends FileTemplateSelector {
initDropdown() {
// maybe move to super class as well
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,

View File

@ -1,4 +1,5 @@
import FileTemplateSelector from '../file_template_selector';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class BlobGitignoreSelector extends FileTemplateSelector {
constructor({ mediator }) {
@ -14,7 +15,7 @@ export default class BlobGitignoreSelector extends FileTemplateSelector {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,

View File

@ -1,4 +1,5 @@
import FileTemplateSelector from '../file_template_selector';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class BlobLicenseSelector extends FileTemplateSelector {
constructor({ mediator }) {
@ -14,7 +15,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,

View File

@ -1,4 +1,5 @@
import FileTemplateSelector from '../file_template_selector';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class MetricsDashboardSelector extends FileTemplateSelector {
constructor({ mediator }) {
@ -14,7 +15,7 @@ export default class MetricsDashboardSelector extends FileTemplateSelector {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,

View File

@ -1,4 +1,5 @@
import FileTemplateSelector from '../file_template_selector';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class FileTemplateTypeSelector extends FileTemplateSelector {
constructor({ mediator, dropdownData }) {
@ -12,7 +13,7 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.config.dropdownData,
filterable: false,
selectable: true,

View File

@ -83,7 +83,7 @@ export default Vue.extend({
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el)
.data('glDropdown')
.data('deprecatedJQueryDropdown')
.clearMenu();
});
}
@ -95,7 +95,7 @@ export default Vue.extend({
},
},
created() {
// Get events from glDropdown
// Get events from deprecatedJQueryDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);

View File

@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
$(document)
.off('created.label')
@ -36,7 +37,7 @@ export default function initNewListDropdown() {
$dropdownToggle.data('projectPath'),
);
$dropdownToggle.glDropdown({
initDeprecatedJQueryDropdown($dropdownToggle, {
data(term, callback) {
axios
.get($dropdownToggle.attr('data-list-labels-path'))

View File

@ -6,6 +6,7 @@ import { __ } from '~/locale';
import eventHub from '../eventhub';
import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default {
name: 'BoardProjectSelect',
@ -36,7 +37,7 @@ export default {
},
},
mounted() {
$(this.$refs.projectsDropdown).glDropdown({
initDeprecatedJQueryDropdown($(this.$refs.projectsDropdown), {
filterable: true,
filterRemote: true,
search: {

View File

@ -53,7 +53,7 @@ export default class VariableList {
},
environment_scope: {
// We can't use a `.js-` class here because
// gl_dropdown replaces the <input> and doesn't copy over the class
// deprecated_jquery_dropdown replaces the <input> and doesn't copy over the class
// See https://gitlab.com/gitlab-org/gitlab-foss/issues/42458
selector: `input[name="${this.formField}[variables_attributes][][environment_scope]"]`,
default: '*',

View File

@ -5,6 +5,7 @@ import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from './flash';
import { capitalizeFirstCharacter } from './lib/utils/text_utility';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default function initCompareAutocomplete(limitTo = null, clickHandler = () => {}) {
$('.js-compare-dropdown').each(function() {
@ -13,7 +14,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
const $dropdownContainer = $dropdown.closest('.dropdown');
const $fieldInput = $(`input[name="${$dropdown.data('fieldName')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
$dropdown.glDropdown({
initDeprecatedJQueryDropdown($dropdown, {
data(term, callback) {
const params = {
ref: $dropdown.data('ref'),

View File

@ -1,5 +1,5 @@
import { escape } from 'lodash';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class CreateItemDropdown {
/**
@ -28,7 +28,7 @@ export default class CreateItemDropdown {
}
buildDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.getData.bind(this),
filterable: true,
filterRemote: this.getDataRemote,
@ -67,12 +67,12 @@ export default class CreateItemDropdown {
e.preventDefault();
this.refreshData();
this.$dropdown.data('glDropdown').selectRowAtIndex();
this.$dropdown.data('deprecatedJQueryDropdown').selectRowAtIndex();
}
refreshData() {
// Refresh the dropdown's data, which ends up calling `getData`
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('deprecatedJQueryDropdown').remote.execute();
}
getData(term, callback) {

View File

@ -3,10 +3,10 @@
import $ from 'jquery';
import { escape } from 'lodash';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import axios from './lib/utils/axios_utils';
import axios from '../lib/utils/axios_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { isObject } from './lib/utils/type_utility';
import renderItem from './gl_dropdown/render';
import { isObject } from '~/lib/utils/type_utility';
import renderItem from './render';
const BLUR_KEYCODES = [27, 40];
@ -890,12 +890,11 @@ class GitLabDropdown {
}
}
// eslint-disable-next-line func-names
$.fn.glDropdown = function(opts) {
export default function initDeprecatedJQueryDropdown($el, opts) {
// eslint-disable-next-line func-names
return this.each(function() {
if (!$.data(this, 'glDropdown')) {
return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
return $el.each(function() {
if (!$.data(this, 'deprecatedJQueryDropdown')) {
$.data(this, 'deprecatedJQueryDropdown', new GitLabDropdown(this, opts));
}
});
};
}

View File

@ -6,6 +6,7 @@ import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { timeFor, parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
import boardsStore from './boards/stores/boards_store';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
@ -35,7 +36,7 @@ class DueDateSelect {
}
initGlDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
opened: () => {
const calendar = this.$datePicker.data('pikaday');
calendar.show();

View File

@ -1,5 +1,6 @@
import $ from 'jquery';
import { __ } from '~/locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class TransferDropdown {
constructor() {
@ -16,7 +17,7 @@ export default class TransferDropdown {
buildDropdown() {
const extraOptions = [{ id: '-1', text: __('No parent group') }, { type: 'divider' }];
this.groupDropdown.glDropdown({
initDeprecatedJQueryDropdown(this.groupDropdown, {
selectable: true,
filterable: true,
toggleLabel: item => item.text,

View File

@ -1,7 +1,6 @@
<script>
import { mapActions, mapState } from 'vuex';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import IdeSidebarNav from '../ide_sidebar_nav.vue';
export default {
@ -10,7 +9,6 @@ export default {
tooltip,
},
components: {
Icon,
IdeSidebarNav,
},
props: {

View File

@ -1,10 +1,11 @@
import $ from 'jquery';
import { stickyMonitor } from './lib/utils/sticky';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default stickyTop => {
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
$('.js-diff-stats-dropdown').glDropdown({
initDeprecatedJQueryDropdown($('.js-diff-stats-dropdown'), {
filterable: true,
remoteFilter: false,
});

View File

@ -1,10 +1,11 @@
import $ from 'jquery';
import { __ } from './locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default function issueStatusSelect() {
$('.js-issue-status').each((i, el) => {
const fieldName = $(el).data('fieldName');
return $(el).glDropdown({
initDeprecatedJQueryDropdown($(el), {
selectable: true,
fieldName,
toggleLabel(selected, element, instance) {

View File

@ -12,6 +12,7 @@ import { deprecatedCreateFlash as flash } from './flash';
import ModalStore from './boards/stores/modal_store';
import boardsStore from './boards/stores/boards_store';
import { isScopedLabel } from '~/lib/utils/common_utils';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class LabelsSelect {
constructor(els, options = {}) {
@ -173,7 +174,7 @@ export default class LabelsSelect {
})
.catch(() => flash(__('Error saving label update.')));
};
$dropdown.glDropdown({
initDeprecatedJQueryDropdown($dropdown, {
showMenuAbove,
data(term, callback) {
const labelUrl = $dropdown.attr('data-labels');
@ -203,7 +204,7 @@ export default class LabelsSelect {
callback(data);
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
$dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
}
})
.catch(() => flash(__('Error fetching labels.')));
@ -348,7 +349,7 @@ export default class LabelsSelect {
} else {
if (!$dropdown.hasClass('js-filter-bulk-update')) {
saveLabelData();
$dropdown.data('glDropdown').clearMenu();
$dropdown.data('deprecatedJQueryDropdown').clearMenu();
}
}
}
@ -455,7 +456,7 @@ export default class LabelsSelect {
if ($dropdown.hasClass('js-issue-board-sidebar')) {
const previousSelection = $dropdown.attr('data-selected');
this.selected = previousSelection ? previousSelection.split(',') : [];
$dropdown.data('glDropdown').updateLabel();
$dropdown.data('deprecatedJQueryDropdown').updateLabel();
}
},
preserveContext: true,

View File

@ -22,7 +22,6 @@ import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// everything else
import loadAwardsHandler from './awards_handler';
import { deprecatedCreateFlash as Flash, removeFlashClickListener } from './flash';
import './gl_dropdown';
import initTodoToggle from './header';
import initImporterStatus from './importer_status';
import initLayoutNav from './layout_nav';

View File

@ -1,5 +1,6 @@
import $ from 'jquery';
import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class Members {
constructor() {
@ -37,7 +38,7 @@ export default class Members {
$('.js-member-permissions-dropdown').each((i, btn) => {
const $btn = $(btn);
$btn.glDropdown({
initDeprecatedJQueryDropdown($btn, {
selectable: true,
isSelectable: (selected, $el) => this.dropdownIsSelectable(selected, $el),
fieldName: $btn.data('fieldName'),

View File

@ -5,7 +5,7 @@
import $ from 'jquery';
import { template, escape } from 'lodash';
import { __, sprintf } from '~/locale';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import Api from '~/api';
import axios from './lib/utils/axios_utils';
import { timeFor, parsePikadayDate, dateInWords } from './lib/utils/datetime_utility';
@ -69,7 +69,7 @@ export default class MilestoneSelect {
);
milestoneLinkNoneTemplate = `<span class="no-value">${__('None')}</span>`;
}
return $dropdown.glDropdown({
return initDeprecatedJQueryDropdown($dropdown, {
showMenuAbove,
data: (term, callback) => {
let contextId = $dropdown.get(0).dataset.projectId;
@ -138,7 +138,7 @@ export default class MilestoneSelect {
callback(extraOptions.concat(data));
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
$dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
}
$(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
});

View File

@ -1,16 +1,16 @@
import $ from 'jquery';
import '~/gl_dropdown';
import Api from './api';
import { mergeUrlParams } from './lib/utils/url_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
import { __ } from './locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class NamespaceSelect {
constructor(opts) {
const isFilter = parseBoolean(opts.dropdown.dataset.isFilter);
const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
$(opts.dropdown).glDropdown({
initDeprecatedJQueryDropdown($(opts.dropdown), {
filterable: true,
selectable: true,
filterRemote: true,

View File

@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this, no-unneeded-ternary */
import $ from 'jquery';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { visitUrl } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import { isMetaClick } from '~/lib/utils/common_utils';
@ -50,7 +50,7 @@ export default class Todos {
}
initFilterDropdown($dropdown, fieldName, searchFields) {
$dropdown.glDropdown({
initDeprecatedJQueryDropdown($dropdown, {
fieldName,
selectable: true,
filterable: searchFields ? true : false,

View File

@ -1,8 +1,9 @@
import $ from 'jquery';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default () => {
const $targetProjectDropdown = $('.js-target-project');
$targetProjectDropdown.glDropdown({
initDeprecatedJQueryDropdown($targetProjectDropdown, {
selectable: true,
fieldName: $targetProjectDropdown.data('fieldName'),
filterable: true,
@ -16,7 +17,7 @@ export default () => {
$('.mr_target_commit').empty();
const $targetBranchDropdown = $('.js-target-branch');
$targetBranchDropdown.data('refsUrl', $el.data('refsUrl'));
$targetBranchDropdown.data('glDropdown').clearMenu();
$targetBranchDropdown.data('deprecatedJQueryDropdown').clearMenu();
},
});
};

View File

@ -1,4 +1,5 @@
import $ from 'jquery';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class TargetBranchDropdown {
constructor() {
@ -10,7 +11,7 @@ export default class TargetBranchDropdown {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.formatBranchesList(),
filterable: true,
selectable: true,

View File

@ -1,3 +1,5 @@
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
const defaultTimezone = { name: 'UTC', offset: 0 };
const defaults = {
$inputEl: null,
@ -42,7 +44,7 @@ export default class TimezoneDropdown {
}
initDropdown() {
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.timezoneData,
filterable: true,
selectable: true,

View File

@ -8,6 +8,7 @@ import { serializeForm } from '~/lib/utils/forms';
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from '~/flash';
import projectSelect from '../../project_select';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class Project {
constructor() {
@ -104,7 +105,7 @@ export default class Project {
const action = $form.attr('action');
const linkTarget = mergeUrlParams(serializeForm($form[0]), action);
return $dropdown.glDropdown({
return initDeprecatedJQueryDropdown($dropdown, {
data(term, callback) {
axios
.get($dropdown.data('refsUrl'), {

View File

@ -1,5 +1,5 @@
import $ from 'jquery';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { deprecatedCreateFlash as Flash } from '~/flash';
import Api from '~/api';
import { __ } from '~/locale';
@ -20,7 +20,7 @@ export default class Search {
this.eventListeners();
refreshCounts();
$groupDropdown.glDropdown({
initDeprecatedJQueryDropdown($groupDropdown, {
selectable: true,
filterable: true,
filterRemote: true,
@ -46,7 +46,7 @@ export default class Search {
clicked: () => Search.submitSearch(),
});
$projectDropdown.glDropdown({
initDeprecatedJQueryDropdown($projectDropdown, {
selectable: true,
filterable: true,
filterRemote: true,

View File

@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as Flash } from '~/flash';
import { n__, s__, __ } from '~/locale';
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class AccessDropdown {
constructor(options) {
@ -29,7 +30,7 @@ export default class AccessDropdown {
initDropdown() {
const { onSelect, onHide } = this.options;
this.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdown, {
data: this.getData.bind(this),
selectable: true,
filterable: true,

View File

@ -1,4 +1,5 @@
import { __ } from '~/locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class ProtectedTagAccessDropdown {
constructor(options) {
@ -8,7 +9,7 @@ export default class ProtectedTagAccessDropdown {
initDropdown() {
const { onSelect } = this.options;
this.options.$dropdown.glDropdown({
initDeprecatedJQueryDropdown(this.options.$dropdown, {
data: this.options.data,
selectable: true,
inputId: this.options.$dropdown.data('inputId'),

View File

@ -23,7 +23,7 @@ export default class ProtectedTagCreate {
});
// Select default
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
$allowedToCreateDropdown.data('deprecatedJQueryDropdown').selectRowAtIndex(0);
// Protected tag dropdown
this.createItemDropdown = new CreateItemDropdown({

View File

@ -1,11 +1,11 @@
import $ from 'jquery';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
class RefSelectDropdown {
constructor($dropdownButton, availableRefs) {
const availableRefsValue =
availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({
initDeprecatedJQueryDropdown($dropdownButton, {
data: availableRefsValue,
filterable: true,
filterByText: true,

View File

@ -13,6 +13,7 @@ import {
spriteIcon,
} from './lib/utils/common_utils';
import Tracking from '~/tracking';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
/**
* Search input in top navigation bar.
@ -119,7 +120,7 @@ export class SearchAutocomplete {
}
createAutocomplete() {
return this.searchInput.glDropdown({
return initDeprecatedJQueryDropdown(this.searchInput, {
filterInputBlur: false,
filterable: true,
filterRemote: true,
@ -145,10 +146,10 @@ export class SearchAutocomplete {
if (!term) {
const contents = this.getCategoryContents();
if (contents) {
const glDropdownInstance = this.searchInput.data('glDropdown');
const deprecatedJQueryDropdownInstance = this.searchInput.data('deprecatedJQueryDropdown');
if (glDropdownInstance) {
glDropdownInstance.filter.options.callback(contents);
if (deprecatedJQueryDropdownInstance) {
deprecatedJQueryDropdownInstance.filter.options.callback(contents);
}
this.enableAutocomplete();
}
@ -463,7 +464,7 @@ export class SearchAutocomplete {
}
highlightFirstRow() {
this.searchInput.data('glDropdown').highlightRowAtIndex(null, 0);
this.searchInput.data('deprecatedJQueryDropdown').highlightRowAtIndex(null, 0);
}
getAvatar(item) {

View File

@ -62,7 +62,7 @@ export default {
this.addAssignee = this.store.addAssignee.bind(this.store);
this.removeAllAssignees = this.store.removeAllAssignees.bind(this.store);
// Get events from glDropdown
// Get events from deprecatedJQueryDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);

View File

@ -1,7 +1,7 @@
import $ from 'jquery';
import '~/gl_dropdown';
import { escape } from 'lodash';
import { __ } from '~/locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
function isValidProjectId(id) {
return id > 0;
@ -27,7 +27,7 @@ class SidebarMoveIssue {
}
initDropdown() {
this.$dropdownToggle.glDropdown({
initDeprecatedJQueryDropdown(this.$dropdownToggle, {
search: {
fields: ['name_with_namespace'],
},

View File

@ -1,11 +1,12 @@
import $ from 'jquery';
import { __ } from './locale';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default function subscriptionSelect() {
$('.js-subscription-event').each((i, element) => {
const fieldName = $(element).data('fieldName');
return $(element).glDropdown({
return initDeprecatedJQueryDropdown($(element), {
selectable: true,
fieldName,
toggleLabel(selected, el, instance) {

View File

@ -33,7 +33,7 @@ export default class IssuableTemplateSelector extends TemplateSelector {
this.templateWarningEl.find('.js-close-btn').on('click', () => {
// Explicitly check against 0 value
if (this.previousSelectedIndex !== undefined) {
this.dropdown.data('glDropdown').selectRowAtIndex(this.previousSelectedIndex);
this.dropdown.data('deprecatedJQueryDropdown').selectRowAtIndex(this.previousSelectedIndex);
} else {
this.reset();
}
@ -61,7 +61,7 @@ export default class IssuableTemplateSelector extends TemplateSelector {
}
setSelectedIndex() {
this.previousSelectedIndex = this.dropdown.data('glDropdown').selectedIndex;
this.previousSelectedIndex = this.dropdown.data('deprecatedJQueryDropdown').selectedIndex;
}
onDropdownClicked(query) {

View File

@ -1,8 +1,9 @@
import $ from 'jquery';
import Api from './api';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default () => {
$('#js-project-dropdown').glDropdown({
initDeprecatedJQueryDropdown($('#js-project-dropdown'), {
data: (term, callback) => {
Api.projects(
term,

View File

@ -13,6 +13,7 @@ import { s__, __, sprintf } from '../locale';
import ModalStore from '../boards/stores/modal_store';
import { parseBoolean } from '../lib/utils/common_utils';
import { getAjaxUsersSelectOptions, getAjaxUsersSelectParams } from './utils';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
@ -233,14 +234,14 @@ function UsersSelect(currentUser, els, options = {}) {
closingTag: '</a>',
})}</span> <% } %>`,
);
return $dropdown.glDropdown({
return initDeprecatedJQueryDropdown($dropdown, {
showMenuAbove,
data(term, callback) {
return userSelect.users(term, options, users => {
// GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
const glDropdown = this.instance || this.options.instance;
glDropdown.options.processData(term, users, callback);
const deprecatedJQueryDropdown = this.instance || this.options.instance;
deprecatedJQueryDropdown.options.processData(term, users, callback);
});
},
processData(term, data, callback) {
@ -349,7 +350,7 @@ function UsersSelect(currentUser, els, options = {}) {
callback(users);
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
$dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
}
},
filterable: true,
@ -359,13 +360,13 @@ function UsersSelect(currentUser, els, options = {}) {
},
selectable: true,
fieldName: $dropdown.data('fieldName'),
toggleLabel(selected, el, glDropdown) {
const inputValue = glDropdown.filterInput.val();
toggleLabel(selected, el, deprecatedJQueryDropdown) {
const inputValue = deprecatedJQueryDropdown.filterInput.val();
if (this.multiSelect && inputValue === '') {
// Remove non-users from the fullData array
const users = glDropdown.filteredFullData();
const callback = glDropdown.parseData.bind(glDropdown);
const users = deprecatedJQueryDropdown.filteredFullData();
const callback = deprecatedJQueryDropdown.parseData.bind(deprecatedJQueryDropdown);
// Update the data model
this.processData(inputValue, users, callback);

View File

@ -1,5 +0,0 @@
<script>
import { GlIcon } from '@gitlab/ui';
export default GlIcon;
</script>

View File

@ -2,7 +2,6 @@ import { isEmpty } from 'lodash';
import { sprintf, __ } from '~/locale';
import { formatDate } from '~/lib/utils/datetime_utility';
import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
const mixins = {
@ -100,9 +99,6 @@ const mixins = {
default: () => ({}),
},
},
components: {
icon,
},
directives: {
tooltip,
},

View File

@ -105,7 +105,7 @@ input[type='checkbox']:hover {
}
.dropdown-header {
// Necessary because glDropdown doesn't support a second style of headers
// Necessary because deprecatedJQueryDropdown doesn't support a second style of headers
font-weight: $gl-font-weight-bold;
color: $gl-text-color;
font-size: $gl-font-size;

View File

@ -80,11 +80,15 @@ class ProjectStatistics < ApplicationRecord
end
def update_storage_size
storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size + pipeline_artifacts_size
storage_size = repository_size + wiki_size + lfs_objects_size + build_artifacts_size + packages_size
# The `snippets_size` column was added on 20200622095419 but db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
# might try to update project statistics before the `snippets_size` column has been created.
storage_size += snippets_size if self.class.column_names.include?('snippets_size')
# The `pipeline_artifacts_size` column was added on 20200817142800 but db/post_migrate/20190527194900_schedule_calculate_wiki_sizes.rb
# might try to update project statistics before the `pipeline_artifacts_size` column has been created.
storage_size += pipeline_artifacts_size if self.class.column_names.include?('pipeline_artifacts_size')
self.storage_size = storage_size
end

View File

@ -0,0 +1,5 @@
---
title: Make highlighting limits stricter
merge_request: 39934
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Fix missing pipeline e-mails when job logs moved to object storage
merge_request: 40075
author:
type: fixed

View File

@ -522,7 +522,7 @@ More information can be found in the [Push event activities limit and bulk push
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218017) in GitLab 13.4.
On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md)
is 50 megabytes.
is 5 gigabytes.
Limits are set per package type.

View File

@ -49,8 +49,8 @@ Most issues will have labels for at least one of the following:
- Team: `~"Technical Writing"`, `~Delivery`
- Specialization: `~frontend`, `~backend`, `~documentation`
- Release Scoping: `~Deliverable`, `~Stretch`, `~"Next Patch Release"`
- Priority: `~P::1`, `~P::2`, `~P::3`, `~P::4`
- Severity: ~`S::1`, `~S::2`, `~S::3`, `~S::4`
- Priority: `~"priority::1"`, `~"priority::2"`, `~"priority::3"`, `~"priority::4"`
- Severity: ~`"severity::1"`, `~"severity::2"`, `~"severity::3"`, `~"severity::4"`
All labels, their meaning and priority are defined on the
[labels page](https://gitlab.com/gitlab-org/gitlab/-/labels).
@ -275,10 +275,10 @@ or ~"Stretch". Any open issue for a previous milestone should be labeled
We have the following priority labels:
- ~P::1
- ~P::2
- ~P::3
- ~P::4
- ~"priority::1"
- ~"priority::2"
- ~"priority::3"
- ~"priority::4"
Please refer to the issue triage [priority label](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#priority) section in our handbook to see how it's used.
@ -286,10 +286,10 @@ Please refer to the issue triage [priority label](https://about.gitlab.com/handb
We have the following severity labels:
- ~S::1
- ~S::2
- ~S::3
- ~S::4
- ~"severity::1"
- ~"severity::2"
- ~"severity::3"
- ~"severity::4"
Please refer to the issue triage [severity label](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#severity) section in our handbook to see how it's used.

View File

@ -211,7 +211,7 @@ For keeping transaction as minimal as possible, please consider using `AfterComm
module or `after_commit` AR hook.
Here is [an example](https://gitlab.com/gitlab-org/gitlab/-/issues/36154#note_247228859)
that one request to Gitaly instance during transaction triggered a P::1 issue.
that one request to Gitaly instance during transaction triggered a ~"priority::1" issue.
## Eager Loading

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -129,6 +129,14 @@ characters to begin your search. For example, if you want to search for
issues that have the assignee "Simone Presley", you'll need to type at
least "Sim" before autocomplete gives any relevant results.
## Code search
To search through code or other documents in a single project, you can use
the search field on the top-right of your screen while the project page is open.
![code search dropdown](img/project_search_dropdown.png)
![code search results](img/project_code_search.png)
## Search history
You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser.

View File

@ -79,22 +79,15 @@ module Gitlab
job.trace_chunks.any? || current_path.present? || old_trace.present?
end
def read
stream = Gitlab::Ci::Trace::Stream.new do
if trace_artifact
trace_artifact.open
elsif job.trace_chunks.any?
Gitlab::Ci::Trace::ChunkedIO.new(job)
elsif current_path
File.open(current_path, "rb")
elsif old_trace
StringIO.new(old_trace)
end
end
def read(&block)
should_retry = true if lock_taken?(lock_key)
yield stream
ensure
stream&.close
read_stream(&block)
rescue Errno::ENOENT
raise unless should_retry
job.reset
read_stream(&block)
end
def write(mode, &blk)
@ -141,6 +134,24 @@ module Gitlab
private
def read_stream
stream = Gitlab::Ci::Trace::Stream.new do
if trace_artifact
trace_artifact.open
elsif job.trace_chunks.any?
Gitlab::Ci::Trace::ChunkedIO.new(job)
elsif current_path
File.open(current_path, "rb")
elsif old_trace
StringIO.new(old_trace)
end
end
yield stream
ensure
stream&.close
end
def unsafe_write!(mode, &blk)
stream = Gitlab::Ci::Trace::Stream.new do
if trace_artifact
@ -184,10 +195,13 @@ module Gitlab
end
def in_write_lock(&blk)
lock_key = "trace:write:lock:#{job.id}"
in_lock(lock_key, ttl: LOCK_TTL, retries: LOCK_RETRIES, sleep_sec: LOCK_SLEEP, &blk)
end
def lock_key
"trace:write:lock:#{job.id}"
end
def archive_stream!(stream)
clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path|
create_build_trace!(job, clone_path)

View File

@ -39,5 +39,10 @@ module Gitlab
ensure
lease&.cancel
end
def lock_taken?(key)
lease = Gitlab::ExclusiveLease.new(key, timeout: 0)
lease.exists?
end
end
end

View File

@ -3,8 +3,8 @@
module Gitlab
class Highlight
TIMEOUT_BACKGROUND = 30.seconds
TIMEOUT_FOREGROUND = 3.seconds
MAXIMUM_TEXT_HIGHLIGHT_SIZE = 1.megabyte
TIMEOUT_FOREGROUND = 1.5.seconds
MAXIMUM_TEXT_HIGHLIGHT_SIZE = 512.kilobytes
def self.highlight(blob_name, blob_content, language: nil, plain: false)
new(blob_name, blob_content, language: language)

View File

@ -28,6 +28,25 @@ module Gitlab
end
# rubocop:enable Rails/Output
module Workhorse
extend Gitlab::SetupHelper
class << self
def configuration_toml(dir, _)
config = { redis: { URL: redis_url } }
TomlRB.dump(config)
end
def redis_url
Gitlab::Redis::SharedState.url
end
def get_config_path(dir)
File.join(dir, 'config.toml')
end
end
end
module Gitaly
extend Gitlab::SetupHelper
class << self

View File

@ -27066,6 +27066,9 @@ msgstr ""
msgid "ValueStreamAnalytics|Median time from issue created to issue closed."
msgstr ""
msgid "ValueStream|The Default Value Stream cannot be deleted"
msgstr ""
msgid "Variable"
msgstr ""

View File

@ -0,0 +1,69 @@
[[ "$TRACE" ]] && set -x
function create_user() {
local user="${1}"
# API details at https://docs.gitlab.com/ee/api/users.html#user-creation
#
# We set "can_create_group=false" because we don't want the DAST user to create groups.
# Otherwise, the DAST user likely creates a group and enables 2FA for all group members,
# which leads to the DAST scan getting "stuck" on the 2FA set up page.
# Once https://gitlab.com/gitlab-org/gitlab/-/issues/231447 is resolved, we can use
# DAST_AUTH_EXCLUDE_URLS instead to prevent DAST from enabling 2FA.
curl --silent --show-error --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" \
--data "email=${user}@example.com" \
--data "name=${user}" \
--data "username=${user}" \
--data "password=${REVIEW_APPS_ROOT_PASSWORD}" \
--data "skip_confirmation=true" \
--data "can_create_group=false" \
"${CI_ENVIRONMENT_URL}/api/v4/users" > /tmp/user.json
[[ "$TRACE" ]] && cat /tmp/user.json >&2
jq .id /tmp/user.json
}
function create_project_for_user() {
local userid="${1}"
# API details at https://docs.gitlab.com/ee/api/projects.html#create-project-for-user
curl --silent --show-error --header "PRIVATE-TOKEN: ${REVIEW_APPS_ROOT_TOKEN}" \
--data "user_id=${userid}" \
--data "name=awesome-test-project-${userid}" \
--data "visibility=private" \
"${CI_ENVIRONMENT_URL}/api/v4/projects/user/${userid}" > /tmp/project.json
[[ "$TRACE" ]] && cat /tmp/project.json >&2
}
function trigger_proj_user_creation(){
local u1=$(create_user "user1")
create_project_for_user $u1
local u2=$(create_user "user2")
create_project_for_user $u2
local u3=$(create_user "user3")
create_project_for_user $u3
local u4=$(create_user "user4")
create_project_for_user $u4
local u5=$(create_user "user5")
create_project_for_user $u5
local u6=$(create_user "user6")
create_project_for_user $u6
local u7=$(create_user "user7")
create_project_for_user $u7
local u8=$(create_user "user8")
create_project_for_user $u8
local u9=$(create_user "user9")
create_project_for_user $u9
local u10=$(create_user "user10")
create_project_for_user $u10
local u11=$(create_user "user11")
create_project_for_user $u11
local u12=$(create_user "user12")
create_project_for_user $u12
local u13=$(create_user "user13")
create_project_for_user $u13
local u14=$(create_user "user14")
create_project_for_user $u14
}

View File

@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */
import $ from 'jquery';
import '~/gl_dropdown';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import '~/lib/utils/common_utils';
import { visitUrl } from '~/lib/utils/url_utility';
@ -9,8 +9,8 @@ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn().mockName('visitUrl'),
}));
describe('glDropdown', () => {
preloadFixtures('static/gl_dropdown.html');
describe('deprecatedJQueryDropdown', () => {
preloadFixtures('static/deprecated_jquery_dropdown.html');
const NON_SELECTABLE_CLASSES =
'.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';
@ -60,14 +60,12 @@ describe('glDropdown', () => {
id: project => project.id,
...extraOpts,
};
test.dropdownButtonElement = $(
'#js-project-dropdown',
test.dropdownContainerElement,
).glDropdown(options);
test.dropdownButtonElement = $('#js-project-dropdown', test.dropdownContainerElement);
initDeprecatedJQueryDropdown(test.dropdownButtonElement, options);
}
beforeEach(() => {
loadFixtures('static/gl_dropdown.html');
loadFixtures('static/deprecated_jquery_dropdown.html');
test.dropdownContainerElement = $('.dropdown.inline');
test.$dropdownMenuElement = $('.dropdown-menu', test.dropdownContainerElement);
test.projectsData = getJSONFixture('static/projects.json');
@ -248,9 +246,9 @@ describe('glDropdown', () => {
function dropdownWithOptions(options) {
const $dropdownDiv = $('<div />');
$dropdownDiv.glDropdown(options);
initDeprecatedJQueryDropdown($dropdownDiv, options);
return $dropdownDiv.data('glDropdown');
return $dropdownDiv.data('deprecatedJQueryDropdown');
}
function basicDropdown() {

View File

@ -7,7 +7,6 @@ import axios from '~/lib/utils/axios_utils';
import IssuableContext from '~/issuable_context';
import LabelsSelect from '~/labels_select';
import '~/gl_dropdown';
import 'select2';
import '~/api';
import '~/create_label';

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { GlBarChart } from '@gitlab/ui/dist/charts';
import Bar from '~/monitoring/components/charts/bar.vue';
import { barMockData } from '../../mock_data';
import { barGraphData } from '../../graph_data';
jest.mock('~/lib/utils/icon_utils', () => ({
getSvgIconPathContent: jest.fn().mockResolvedValue('mockSvgPathContent'),
@ -10,11 +10,14 @@ jest.mock('~/lib/utils/icon_utils', () => ({
describe('Bar component', () => {
let barChart;
let store;
let graphData;
beforeEach(() => {
graphData = barGraphData();
barChart = shallowMount(Bar, {
propsData: {
graphData: barMockData,
graphData,
},
store,
});
@ -31,7 +34,7 @@ describe('Bar component', () => {
beforeEach(() => {
glbarChart = barChart.find(GlBarChart);
chartData = barChart.vm.chartData[barMockData.metrics[0].label];
chartData = barChart.vm.chartData[graphData.metrics[0].label];
});
it('is a Vue instance', () => {
@ -39,7 +42,7 @@ describe('Bar component', () => {
});
it('should display a label on the x axis', () => {
expect(glbarChart.vm.xAxisTitle).toBe(barMockData.xLabel);
expect(glbarChart.props('xAxisTitle')).toBe(graphData.xLabel);
});
it('should return chartData as array of arrays', () => {

View File

@ -15,10 +15,14 @@ import {
mockNamespace,
mockNamespacedData,
mockTimeRange,
barMockData,
} from '../mock_data';
import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
import { anomalyGraphData, singleStatGraphData, heatmapGraphData } from '../graph_data';
import {
anomalyGraphData,
singleStatGraphData,
heatmapGraphData,
barGraphData,
} from '../graph_data';
import { panelTypes } from '~/monitoring/constants';
@ -240,7 +244,7 @@ describe('Dashboard Panel', () => {
${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} | ${false}
${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} | ${false}
${heatmapGraphData()} | ${MonitorHeatmapChart} | ${false}
${barMockData} | ${MonitorBarChart} | ${false}
${barGraphData()} | ${MonitorBarChart} | ${false}
`('when $data.type data is provided', ({ data, component, hasCtxMenu }) => {
const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' };

View File

@ -259,3 +259,16 @@ export const stackedColumnGraphData = (panelOptions = {}, dataOptions = {}) => {
type: panelTypes.STACKED_COLUMN,
};
};
/**
* Generates bar mock graph data according to options
*
* @param {Object} panelOptions - Panel options as in YML.
* @param {Object} dataOptions
*/
export const barGraphData = (panelOptions = {}, dataOptions = {}) => {
return {
...timeSeriesGraphData(panelOptions, dataOptions),
type: panelTypes.BAR,
};
};

View File

@ -1,56 +1,55 @@
import $ from 'jquery';
import NamespaceSelect from '~/namespace_select';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
jest.mock('~/deprecated_jquery_dropdown');
describe('NamespaceSelect', () => {
beforeEach(() => {
jest.spyOn($.fn, 'glDropdown').mockImplementation(() => {});
});
it('initializes glDropdown', () => {
it('initializes deprecatedJQueryDropdown', () => {
const dropdown = document.createElement('div');
// eslint-disable-next-line no-new
new NamespaceSelect({ dropdown });
expect($.fn.glDropdown).toHaveBeenCalled();
expect(initDeprecatedJQueryDropdown).toHaveBeenCalled();
});
describe('as input', () => {
let glDropdownOptions;
let deprecatedJQueryDropdownOptions;
beforeEach(() => {
const dropdown = document.createElement('div');
// eslint-disable-next-line no-new
new NamespaceSelect({ dropdown });
[[glDropdownOptions]] = $.fn.glDropdown.mock.calls;
[[, deprecatedJQueryDropdownOptions]] = initDeprecatedJQueryDropdown.mock.calls;
});
it('prevents click events', () => {
const dummyEvent = new Event('dummy');
jest.spyOn(dummyEvent, 'preventDefault').mockImplementation(() => {});
glDropdownOptions.clicked({ e: dummyEvent });
// expect(foo).toContain('test');
deprecatedJQueryDropdownOptions.clicked({ e: dummyEvent });
expect(dummyEvent.preventDefault).toHaveBeenCalled();
});
});
describe('as filter', () => {
let glDropdownOptions;
let deprecatedJQueryDropdownOptions;
beforeEach(() => {
const dropdown = document.createElement('div');
dropdown.dataset.isFilter = 'true';
// eslint-disable-next-line no-new
new NamespaceSelect({ dropdown });
[[glDropdownOptions]] = $.fn.glDropdown.mock.calls;
[[, deprecatedJQueryDropdownOptions]] = initDeprecatedJQueryDropdown.mock.calls;
});
it('does not prevent click events', () => {
const dummyEvent = new Event('dummy');
jest.spyOn(dummyEvent, 'preventDefault').mockImplementation(() => {});
glDropdownOptions.clicked({ e: dummyEvent });
deprecatedJQueryDropdownOptions.clicked({ e: dummyEvent });
expect(dummyEvent.preventDefault).not.toHaveBeenCalled();
});
@ -58,7 +57,7 @@ describe('NamespaceSelect', () => {
it('sets URL of dropdown items', () => {
const dummyNamespace = { id: 'eal' };
const itemUrl = glDropdownOptions.url(dummyNamespace);
const itemUrl = deprecatedJQueryDropdownOptions.url(dummyNamespace);
expect(itemUrl).toContain(`namespace_id=${dummyNamespace.id}`);
});

View File

@ -2,7 +2,6 @@ import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
import '~/gl_dropdown';
import axios from '~/lib/utils/axios_utils';
import { addDelimiter } from '~/lib/utils/text_utility';
import { visitUrl } from '~/lib/utils/url_utility';

View File

@ -1,5 +1,4 @@
import $ from 'jquery';
import '~/gl_dropdown';
import TimezoneDropdown, {
formatUtcOffset,
formatTimezone,

View File

@ -1,5 +1,4 @@
import $ from 'jquery';
import '~/gl_dropdown';
import AccessDropdown from '~/projects/settings/access_dropdown';
import { LEVEL_TYPES } from '~/projects/settings/constants';

View File

@ -1,7 +1,6 @@
/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
import $ from 'jquery';
import '~/gl_dropdown';
import AxiosMockAdapter from 'axios-mock-adapter';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import initSearchAutocomplete from '~/search_autocomplete';
@ -215,7 +214,7 @@ describe('Search autocomplete dropdown', () => {
function triggerAutocomplete() {
return new Promise(resolve => {
const dropdown = widget.searchInput.data('glDropdown');
const dropdown = widget.searchInput.data('deprecatedJQueryDropdown');
const filterCallback = dropdown.filter.options.callback;
dropdown.filter.options.callback = jest.fn(data => {
filterCallback(data);

View File

@ -68,12 +68,10 @@ describe('SidebarMoveIssue', () => {
});
describe('initDropdown', () => {
it('should initialize the gl_dropdown', () => {
jest.spyOn($.fn, 'glDropdown').mockImplementation(() => {});
it('should initialize the deprecatedJQueryDropdown', () => {
test.sidebarMoveIssue.initDropdown();
expect($.fn.glDropdown).toHaveBeenCalled();
expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy();
});
it('escapes html from project name', done => {

View File

@ -11,6 +11,41 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
it { expect(trace).to delegate_method(:old_trace).to(:job) }
end
context 'when trace is migrated to object storage' do
let!(:job) { create(:ci_build, :trace_artifact) }
let!(:artifact1) { job.job_artifacts_trace }
let!(:artifact2) { job.reload.job_artifacts_trace }
let(:test_data) { "hello world" }
before do
stub_artifacts_object_storage
artifact1.file.migrate!(ObjectStorage::Store::REMOTE)
end
context 'when write lock is not present' do
it 'raises an exception' do
expect { artifact2.job.trace.raw }.to raise_error(Errno::ENOENT)
end
end
context 'when write lock is present', :clean_gitlab_redis_shared_state do
before do
Gitlab::ExclusiveLease.new("trace:write:lock:#{job.id}", timeout: 10.seconds).try_obtain
end
it 'reloads the trace after is it migrated' do
stub_const('Gitlab::HttpIO::BUFFER_SIZE', test_data.length)
expect_next_instance_of(Gitlab::HttpIO) do |http_io|
expect(http_io).to receive(:get_chunk).and_return(test_data, "")
end
expect(artifact2.job.trace.raw).to eq(test_data)
end
end
end
context 'when live trace feature is disabled' do
before do
stub_feature_flags(ci_enable_live_trace: false)

View File

@ -111,4 +111,14 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state d
end
end
end
describe '#lock_taken?' do
it 'returns true when lock has been taken' do
expect(class_instance.lock_taken?(unique_key)).to be false
class_instance.in_lock(unique_key) do
expect(class_instance.lock_taken?(unique_key)).to be true
end
end
end
end

View File

@ -16,9 +16,9 @@ RSpec.describe ScheduleCalculateWikiSizes do
let(:project3) { projects.create!(name: 'wiki-project-3', path: 'wiki-project-3', namespace_id: namespace.id) }
context 'when missing wiki sizes exist' do
let!(:project_statistic1) { project_statistics.create!(id: 1, project_id: project1.id, namespace_id: namespace.id, wiki_size: 1000) }
let!(:project_statistic2) { project_statistics.create!(id: 2, project_id: project2.id, namespace_id: namespace.id, wiki_size: nil) }
let!(:project_statistic3) { project_statistics.create!(id: 3, project_id: project3.id, namespace_id: namespace.id, wiki_size: nil) }
let!(:project_statistic1) { project_statistics.create!(project_id: project1.id, namespace_id: namespace.id, wiki_size: 1000) }
let!(:project_statistic2) { project_statistics.create!(project_id: project2.id, namespace_id: namespace.id, wiki_size: nil) }
let!(:project_statistic3) { project_statistics.create!(project_id: project3.id, namespace_id: namespace.id, wiki_size: nil) }
it 'schedules a background migration' do
Timecop.freeze do
@ -44,7 +44,7 @@ RSpec.describe ScheduleCalculateWikiSizes do
before do
namespace = namespaces.create!(name: 'wiki-migration', path: 'wiki-migration')
project = projects.create!(name: 'wiki-project-1', path: 'wiki-project-1', namespace_id: namespace.id)
project_statistics.create!(project_id: project.id, namespace_id: 1, wiki_size: 1000)
project_statistics.create!(project_id: project.id, namespace_id: namespace.id, wiki_size: 1000)
end
it 'does not schedule a background migration' do

View File

@ -247,8 +247,9 @@ module TestEnv
'GitLab Workhorse',
install_dir: workhorse_dir,
version: Gitlab::Workhorse.version,
task: "gitlab:workhorse:install[#{install_workhorse_args}]"
)
task: "gitlab:workhorse:install[#{install_workhorse_args}]") do
Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil)
end
end
def workhorse_dir
@ -259,6 +260,14 @@ module TestEnv
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')
config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir)
# This should be set up in setup_workhorse, but since
# component_needs_update? only checks that versions are consistent,
# we need to ensure the config file exists. This line can be removed
# later after a new Workhorse version is updated.
Gitlab::SetupHelper::Workhorse.create_configuration(workhorse_dir, nil) unless File.exist?(config_path)
workhorse_pid = spawn(
{ 'PATH' => "#{ENV['PATH']}:#{workhorse_dir}" },
File.join(workhorse_dir, 'gitlab-workhorse'),
@ -266,10 +275,7 @@ module TestEnv
'-documentRoot', Rails.root.join('public').to_s,
'-listenAddr', listen_addr,
'-secretPath', Gitlab::Workhorse.secret_path.to_s,
# TODO: Needed for workhorse + redis features.
# https://gitlab.com/gitlab-org/gitlab/-/issues/209245
#
# '-config', '',
'-config', config_path,
'-logFile', 'log/workhorse-test.log',
'-logFormat', 'structured',
'-developmentMode' # to serve assets and rich error messages