Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 15:08:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-03 15:08:47 +0300
commit40024efc700a2ece0e30402ec5a9c512ed4d9b5b (patch)
treec263ccd45cd0b7a8ec600662ec392b39185231bf
parenta1aeaba23e388ac96d34c135c6c55e414f823487 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo.yml22
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue153
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue12
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue8
-rw-r--r--app/assets/javascripts/packages/details/components/app.vue32
-rw-r--r--app/assets/javascripts/packages/details/components/code_instruction.vue2
-rw-r--r--app/assets/javascripts/packages/details/components/dependency_row.vue2
-rw-r--r--app/assets/javascripts/packages/list/components/packages_list.vue4
-rw-r--r--app/assets/javascripts/packages/list/components/packages_list_app.vue4
-rw-r--r--app/assets/javascripts/packages/shared/components/package_tags.vue4
-rw-r--r--app/assets/javascripts/packages/shared/components/packages_list_loader.vue4
-rw-r--r--app/assets/javascripts/packages/shared/components/publish_method.vue20
-rw-r--r--app/assets/javascripts/reports/accessibility_report/store/index.js15
-rw-r--r--app/assets/javascripts/reports/codequality_report/store/index.js15
-rw-r--r--app/assets/javascripts/reports/store/index.js15
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/pages/packages.scss11
-rw-r--r--app/assets/stylesheets/utilities.scss17
-rw-r--r--app/controllers/projects/blob_controller.rb9
-rw-r--r--app/finders/issuable_finder.rb1
-rw-r--r--app/finders/labels_finder.rb9
-rw-r--r--app/graphql/resolvers/board_list_issues_resolver.rb10
-rw-r--r--app/graphql/resolvers/concerns/board_issue_filterable.rb24
-rw-r--r--app/graphql/types/board_list_type.rb2
-rw-r--r--app/graphql/types/boards/board_issue_input_base_type.rb35
-rw-r--r--app/graphql/types/boards/board_issue_input_type.rb24
-rw-r--r--app/models/concerns/optimized_issuable_label_filter.rb107
-rw-r--r--app/services/ci/create_downstream_pipeline_service.rb (renamed from app/services/ci/create_cross_project_pipeline_service.rb)6
-rw-r--r--app/services/packages/composer/create_package_service.rb7
-rw-r--r--app/services/packages/conan/create_package_service.rb5
-rw-r--r--app/services/packages/create_package_service.rb30
-rw-r--r--app/services/packages/maven/create_package_service.rb7
-rw-r--r--app/services/packages/npm/create_package_service.rb12
-rw-r--r--app/services/packages/nuget/create_package_service.rb4
-rw-r--r--app/services/packages/pypi/create_package_service.rb7
-rw-r--r--app/views/projects/find_file/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml4
-rw-r--r--app/views/shared/web_hooks/_form.html.haml52
-rw-r--r--app/workers/ci/create_cross_project_pipeline_worker.rb2
-rw-r--r--changelogs/unreleased/202270-migrate-spinner-for-app-assets-javascripts-notes-components-note_h.yml5
-rw-r--r--changelogs/unreleased/202582-migrate-spinner-for-app-views-projects-find_file.yml5
-rw-r--r--changelogs/unreleased/232840-fj-track-sfe-actions.yml5
-rw-r--r--changelogs/unreleased/232844-fj-add-ide-actions-usage-data.yml5
-rw-r--r--changelogs/unreleased/233475-source-branch-dropdown-empty.yml5
-rw-r--r--changelogs/unreleased/235699-graphql-board-issue-filters.yml5
-rw-r--r--changelogs/unreleased/i18n-webhook-form.yml5
-rw-r--r--changelogs/unreleased/jdb-refactor-inline-diff-table-row.yml5
-rw-r--r--changelogs/unreleased/let-before-examples-cop.yml5
-rw-r--r--changelogs/unreleased/optimized-issuable-label-search-pt1.yml5
-rw-r--r--changelogs/unreleased/single-line-cop.yml5
-rw-r--r--config/feature_flags/development/optimized_issuable_label_filter.yml7
-rw-r--r--db/migrate/20200629134747_add_extra_index_to_label_links.rb26
-rw-r--r--db/post_migrate/20200826220745_add_compound_index_on_vulnerabilities_for_background_migration.rb18
-rw-r--r--db/post_migrate/20200826220746_schedule_populate_resolved_on_default_branch_column.rb28
-rw-r--r--db/schema_migrations/202006291347471
-rw-r--r--db/schema_migrations/202008262207451
-rw-r--r--db/schema_migrations/202008262207461
-rw-r--r--db/structure.sql6
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql140
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json390
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb12
-rw-r--r--lib/gitlab/ci/ansi2html.rb200
-rw-r--r--lib/gitlab/usage_data.rb48
-rw-r--r--locale/gitlab.pot78
-rw-r--r--qa/qa/support/json_formatter.rb2
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb35
-rw-r--r--spec/controllers/search_controller_spec.rb6
-rw-r--r--spec/finders/issues_finder_spec.rb157
-rw-r--r--spec/finders/merge_requests_finder_spec.rb66
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js355
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/code_instruction_spec.js.snap2
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap2
-rw-r--r--spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap2
-rw-r--r--spec/frontend/packages/shared/components/__snapshots__/publish_method_spec.js.snap14
-rw-r--r--spec/frontend/packages/shared/components/publish_method_spec.js6
-rw-r--r--spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js11
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js33
-rw-r--r--spec/frontend/reports/components/grouped_test_reports_app_spec.js13
-rw-r--r--spec/graphql/resolvers/board_list_issues_resolver_spec.rb36
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb2
-rw-r--r--spec/graphql/types/boards/board_issue_input_type_spec.rb15
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb70
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb16
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb22
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb24
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb20
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb23
-rw-r--r--spec/models/commit_range_spec.rb23
-rw-r--r--spec/models/milestone_spec.rb10
-rw-r--r--spec/models/project_services/packagist_service_spec.rb28
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb97
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb4
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb8
-rw-r--r--spec/services/ci/create_downstream_pipeline_service_spec.rb (renamed from spec/services/ci/create_cross_project_pipeline_service_spec.rb)6
-rw-r--r--spec/support/shared_examples/controllers/unique_hll_events_examples.rb52
-rw-r--r--spec/workers/ci/create_cross_project_pipeline_worker_spec.rb4
100 files changed, 2207 insertions, 684 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 6609868689d..cafba1c6149 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -199,21 +199,6 @@ RSpec/ExpectChange:
RSpec/ExpectInHook:
Enabled: false
-# Offense count: 68
-# Cop supports --auto-correct.
-RSpec/LetBeforeExamples:
- Exclude:
- - 'spec/lib/banzai/filter/issue_reference_filter_spec.rb'
- - 'spec/lib/banzai/filter/user_reference_filter_spec.rb'
- - 'spec/lib/gitlab/email/handler/create_issue_handler_spec.rb'
- - 'spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb'
- - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
- - 'spec/models/commit_range_spec.rb'
- - 'spec/models/milestone_spec.rb'
- - 'spec/models/project_services/packagist_service_spec.rb'
- - 'spec/rubocop/cop/migration/update_column_in_batches_spec.rb'
- - 'spec/serializers/pipeline_details_entity_spec.rb'
-
# Offense count: 2188
# Cop supports --auto-correct.
# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers.
@@ -679,13 +664,6 @@ Style/RescueModifier:
Style/RescueStandardError:
Enabled: false
-# Offense count: 50
-# Cop supports --auto-correct.
-# Configuration parameters: AllowIfMethodIsEmpty.
-Style/SingleLineMethods:
- Exclude:
- - 'lib/gitlab/ci/ansi2html.rb'
-
# Offense count: 102
# Cop supports --auto-correct.
# Configuration parameters: .
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index e7ceb275bc4..edc9f9d47b2 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-a783958be5ae0797dac9041bdfb884440e0e6306
+02ae27efafdf367d991eac43df02b892be378a1b
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index e6ea441c95a..15a22d1767c 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -1,8 +1,7 @@
<script>
/* eslint-disable vue/no-v-html */
import { mapActions, mapGetters, mapState } from 'vuex';
-import { GlTooltipDirective } from '@gitlab/ui';
-import DiffTableCell from './diff_table_cell.vue';
+import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import {
MATCH_LINE_TYPE,
NEW_LINE_TYPE,
@@ -11,11 +10,19 @@ import {
CONTEXT_LINE_CLASS_NAME,
LINE_POSITION_LEFT,
LINE_POSITION_RIGHT,
+ LINE_HOVER_CLASS_NAME,
+ OLD_NO_NEW_LINE_TYPE,
+ NEW_NO_NEW_LINE_TYPE,
+ EMPTY_CELL_TYPE,
} from '../constants';
+import { __ } from '~/locale';
+import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
+import DiffGutterAvatars from './diff_gutter_avatars.vue';
export default {
components: {
- DiffTableCell,
+ DiffGutterAvatars,
+ GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -50,6 +57,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['isLoggedIn']),
...mapGetters('diffs', ['fileLineCoverage']),
...mapState({
isHighlighted(state) {
@@ -79,6 +87,70 @@ export default {
coverageState() {
return this.fileLineCoverage(this.filePath, this.line.new_line);
},
+ isMetaLine() {
+ const { type } = this.line;
+
+ return (
+ type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
+ );
+ },
+ classNameMapCell() {
+ const { type } = this.line;
+
+ return [
+ type,
+ {
+ hll: this.isHighlighted,
+ [LINE_HOVER_CLASS_NAME]:
+ this.isLoggedIn && this.isHover && !this.isContextLine && !this.isMetaLine,
+ },
+ ];
+ },
+ addCommentTooltip() {
+ const brokenSymlinks = this.line.commentsDisabled;
+ let tooltip = __('Add a comment to this line');
+
+ if (brokenSymlinks) {
+ if (brokenSymlinks.wasSymbolic || brokenSymlinks.isSymbolic) {
+ tooltip = __(
+ 'Commenting on symbolic links that replace or are replaced by files is currently not supported.',
+ );
+ } else if (brokenSymlinks.wasReal || brokenSymlinks.isReal) {
+ tooltip = __(
+ 'Commenting on files that replace or are replaced by symbolic links is currently not supported.',
+ );
+ }
+ }
+
+ return tooltip;
+ },
+ shouldRenderCommentButton() {
+ if (this.isLoggedIn) {
+ const isDiffHead = parseBoolean(getParameterByName('diff_head'));
+ return !isDiffHead || gon.features?.mergeRefHeadComments;
+ }
+
+ return false;
+ },
+ shouldShowCommentButton() {
+ return this.isHover && !this.isContextLine && !this.isMetaLine && !this.hasDiscussions;
+ },
+ hasDiscussions() {
+ return this.line.discussions && this.line.discussions.length > 0;
+ },
+ lineHref() {
+ return `#${this.line.line_code || ''}`;
+ },
+ lineCode() {
+ return (
+ this.line.line_code ||
+ (this.line.left && this.line.left.line_code) ||
+ (this.line.right && this.line.right.line_code)
+ );
+ },
+ shouldShowAvatarsOnGutter() {
+ return this.hasDiscussions;
+ },
},
created() {
this.newLineType = NEW_LINE_TYPE;
@@ -90,12 +162,20 @@ export default {
this.scrollToLineIfNeededInline(this.line);
},
methods: {
- ...mapActions('diffs', ['scrollToLineIfNeededInline']),
+ ...mapActions('diffs', [
+ 'scrollToLineIfNeededInline',
+ 'showCommentForm',
+ 'setHighlightedRow',
+ 'toggleLineDiscussions',
+ ]),
handleMouseMove(e) {
// To show the comment icon on the gutter we need to know if we hover the line.
// Current table structure doesn't allow us to do this with CSS in both of the diff view types
this.isHover = e.type === 'mouseover';
},
+ handleCommentButton() {
+ this.showCommentForm({ lineCode: this.line.line_code, fileHash: this.fileHash });
+ },
},
};
</script>
@@ -109,25 +189,52 @@ export default {
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
- <diff-table-cell
- :file-hash="fileHash"
- :line="line"
- :line-type="oldLineType"
- :is-bottom="isBottom"
- :is-hover="isHover"
- :show-comment-button="true"
- :is-highlighted="isHighlighted"
- class="diff-line-num old_line"
- />
- <diff-table-cell
- :file-hash="fileHash"
- :line="line"
- :line-type="newLineType"
- :is-bottom="isBottom"
- :is-hover="isHover"
- :is-highlighted="isHighlighted"
- class="diff-line-num new_line qa-new-diff-line"
- />
+ <td ref="oldTd" class="diff-line-num old_line" :class="classNameMapCell">
+ <span
+ v-if="shouldRenderCommentButton"
+ ref="addNoteTooltip"
+ v-gl-tooltip
+ class="add-diff-note tooltip-wrapper"
+ :title="addCommentTooltip"
+ >
+ <button
+ v-show="shouldShowCommentButton"
+ ref="addDiffNoteButton"
+ type="button"
+ class="add-diff-note note-button js-add-diff-note-button qa-diff-comment"
+ :disabled="line.commentsDisabled"
+ @click="handleCommentButton"
+ >
+ <gl-icon :size="12" name="comment" />
+ </button>
+ </span>
+ <a
+ v-if="line.old_line"
+ ref="lineNumberRefOld"
+ :data-linenumber="line.old_line"
+ :href="lineHref"
+ @click="setHighlightedRow(lineCode)"
+ >
+ </a>
+ <diff-gutter-avatars
+ v-if="shouldShowAvatarsOnGutter"
+ :discussions="line.discussions"
+ :discussions-expanded="line.discussionsExpanded"
+ @toggleLineDiscussions="
+ toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
+ "
+ />
+ </td>
+ <td ref="newTd" class="diff-line-num new_line qa-new-diff-line" :class="classNameMapCell">
+ <a
+ v-if="line.new_line"
+ ref="lineNumberRefNew"
+ :data-linenumber="line.new_line"
+ :href="lineHref"
+ @click="setHighlightedRow(lineCode)"
+ >
+ </a>
+ </td>
<td
v-gl-tooltip.hover
:title="coverageState.text"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 152819b4cb5..a13a0dbbf30 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -1,7 +1,7 @@
<script>
/* eslint-disable vue/no-v-html */
import { mapActions } from 'vuex';
-import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
@@ -10,6 +10,7 @@ export default {
GitlabTeamMemberBadge: () =>
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
GlIcon,
+ GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -195,13 +196,12 @@ export default {
class="gl-ml-1 gl-text-gray-700 align-middle"
/>
<slot name="extra-controls"></slot>
- <i
+ <gl-loading-icon
v-if="showSpinner"
ref="spinner"
- class="fa fa-spinner fa-spin editing-spinner"
- :aria-label="__('Comment is being updated')"
- aria-hidden="true"
- ></i>
+ class="editing-spinner"
+ :label="__('Comment is being updated')"
+ />
</span>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 7116a007ebe..e5f1fbff687 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,9 +1,8 @@
<script>
-/* eslint-disable vue/no-v-html */
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash';
-import { GlSprintf } from '@gitlab/ui';
+import { GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
@@ -35,6 +34,9 @@ export default {
NoteBody,
TimelineEntryItem,
},
+ directives: {
+ SafeHtml,
+ },
mixins: [noteable, resolvable, glFeatureFlagsMixin()],
props: {
note: {
@@ -379,7 +381,7 @@ export default {
:is-confidential="note.confidential"
>
<slot slot="note-header-info" name="note-header-info"></slot>
- <span v-if="commit" v-html="actionText"></span>
+ <span v-if="commit" v-safe-html="actionText"></span>
<span v-else-if="note.created_at" class="d-none d-sm-inline">&middot;</span>
</note-header>
<note-actions
diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue
index dbb5f7be0a0..56591fb6b15 100644
--- a/app/assets/javascripts/packages/details/components/app.vue
+++ b/app/assets/javascripts/packages/details/components/app.vue
@@ -268,22 +268,24 @@ export default {
</template>
</gl-sprintf>
- <div slot="modal-footer" class="w-100">
- <div class="float-right">
- <gl-button @click="cancelDelete()">{{ __('Cancel') }}</gl-button>
- <gl-button
- ref="modal-delete-button"
- data-method="delete"
- :to="destroyPath"
- variant="danger"
- category="primary"
- data-qa-selector="delete_modal_button"
- @click="track($options.trackingActions.DELETE_PACKAGE)"
- >
- {{ __('Delete') }}
- </gl-button>
+ <template #modal-footer>
+ <div class="gl-w-full">
+ <div class="float-right">
+ <gl-button @click="cancelDelete">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ ref="modal-delete-button"
+ data-method="delete"
+ :to="destroyPath"
+ variant="danger"
+ category="primary"
+ data-qa-selector="delete_modal_button"
+ @click="track($options.trackingActions.DELETE_PACKAGE)"
+ >
+ {{ __('Delete') }}
+ </gl-button>
+ </div>
</div>
- </div>
+ </template>
</gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/packages/details/components/code_instruction.vue b/app/assets/javascripts/packages/details/components/code_instruction.vue
index 0719ddfcd2b..5200857a9b9 100644
--- a/app/assets/javascripts/packages/details/components/code_instruction.vue
+++ b/app/assets/javascripts/packages/details/components/code_instruction.vue
@@ -48,7 +48,7 @@ export default {
<input
:value="instruction"
type="text"
- class="form-control monospace js-instruction-input"
+ class="form-control gl-font-monospace js-instruction-input"
readonly
@copy="trackCopy"
/>
diff --git a/app/assets/javascripts/packages/details/components/dependency_row.vue b/app/assets/javascripts/packages/details/components/dependency_row.vue
index 788673d2881..367ed6ca22a 100644
--- a/app/assets/javascripts/packages/details/components/dependency_row.vue
+++ b/app/assets/javascripts/packages/details/components/dependency_row.vue
@@ -26,7 +26,7 @@ export default {
<div
v-if="showVersion"
- class="table-section section-50 gl-display-flex justify-content-md-end"
+ class="table-section section-50 gl-display-flex gl-justify-content-md-end"
data-testid="version-pattern"
>
<span class="gl-text-body">{{ dependency.version_pattern }}</span>
diff --git a/app/assets/javascripts/packages/list/components/packages_list.vue b/app/assets/javascripts/packages/list/components/packages_list.vue
index b26c6087e14..19d0a914d41 100644
--- a/app/assets/javascripts/packages/list/components/packages_list.vue
+++ b/app/assets/javascripts/packages/list/components/packages_list.vue
@@ -82,7 +82,7 @@ export default {
</script>
<template>
- <div class="d-flex flex-column">
+ <div class="gl-display-flex gl-flex-direction-column">
<slot v-if="isListEmpty && !isLoading" name="empty-state"></slot>
<div v-else-if="isLoading">
@@ -106,7 +106,7 @@ export default {
:per-page="perPage"
:total-items="totalItems"
align="center"
- class="w-100 mt-2"
+ class="gl-w-full gl-mt-3"
/>
<gl-modal
diff --git a/app/assets/javascripts/packages/list/components/packages_list_app.vue b/app/assets/javascripts/packages/list/components/packages_list_app.vue
index ef242ea5f75..9c7725477eb 100644
--- a/app/assets/javascripts/packages/list/components/packages_list_app.vue
+++ b/app/assets/javascripts/packages/list/components/packages_list_app.vue
@@ -77,7 +77,9 @@ export default {
<template>
<gl-tabs @input="tabChanged">
<template #tabs-end>
- <div class="d-flex align-self-center ml-md-auto py-1 py-md-0">
+ <div
+ class="gl-display-flex gl-align-self-center gl-py-2 gl-flex-grow-1 gl-justify-content-end"
+ >
<package-filter class="mr-1" @filter="requestPackagesList" />
<package-sort @sort:changed="requestPackagesList" />
</div>
diff --git a/app/assets/javascripts/packages/shared/components/package_tags.vue b/app/assets/javascripts/packages/shared/components/package_tags.vue
index f51ca26abf2..3d7e233c1ba 100644
--- a/app/assets/javascripts/packages/shared/components/package_tags.vue
+++ b/app/assets/javascripts/packages/shared/components/package_tags.vue
@@ -91,7 +91,7 @@ export default {
variant="muted"
:title="moreTagsTooltip"
size="sm"
- class="gl-display-none d-md-flex gl-ml-2"
+ class="gl-display-none gl-display-md-flex gl-ml-2"
><gl-sprintf :message="__('+%{tags} more')">
<template #tags>
{{ moreTagsDisplay }}
@@ -103,7 +103,7 @@ export default {
v-if="moreTagsDisplay && hideLabel"
data-testid="moreBadge"
variant="muted"
- class="d-md-none gl-ml-2"
+ class="gl-display-md-none gl-ml-2"
>{{ tagsDisplay }}</gl-badge
>
</div>
diff --git a/app/assets/javascripts/packages/shared/components/packages_list_loader.vue b/app/assets/javascripts/packages/shared/components/packages_list_loader.vue
index cd9ef74d467..62038f13dd9 100644
--- a/app/assets/javascripts/packages/shared/components/packages_list_loader.vue
+++ b/app/assets/javascripts/packages/shared/components/packages_list_loader.vue
@@ -48,7 +48,7 @@ export default {
<template>
<div>
- <div class="d-xs-flex flex-column d-md-none">
+ <div class="gl-display-sm-flex gl-flex-direction-column gl-display-md-none">
<gl-skeleton-loader
v-for="index in $options.rowsToRender.mobile"
:key="index"
@@ -65,7 +65,7 @@ export default {
</gl-skeleton-loader>
</div>
- <div class="d-none d-md-flex flex-column">
+ <div class="gl-display-none gl-display-md-flex gl-flex-direction-column">
<gl-skeleton-loader
v-for="index in $options.rowsToRender.desktop"
:key="index"
diff --git a/app/assets/javascripts/packages/shared/components/publish_method.vue b/app/assets/javascripts/packages/shared/components/publish_method.vue
index 84e89d3b2d4..468cc458ed9 100644
--- a/app/assets/javascripts/packages/shared/components/publish_method.vue
+++ b/app/assets/javascripts/packages/shared/components/publish_method.vue
@@ -36,24 +36,28 @@ export default {
</script>
<template>
- <div class="d-flex align-items-center order-1 order-md-0 mb-md-1">
+ <div class="gl-display-flex gl-align-items-center gl-mb-2">
<template v-if="hasPipeline">
- <gl-icon name="git-merge" class="mr-1" />
- <span ref="pipeline-ref" class="mr-1">{{ packageEntity.pipeline.ref }}</span>
+ <gl-icon name="git-merge" class="gl-mr-2" />
+ <span data-testid="pipeline-ref" class="gl-mr-2">{{ packageEntity.pipeline.ref }}</span>
- <gl-icon name="commit" class="mr-1" />
- <gl-link ref="pipeline-sha" :href="linkToCommit" class="mr-1">{{ packageShaShort }}</gl-link>
+ <gl-icon name="commit" class="gl-mr-2" />
+ <gl-link data-testid="pipeline-sha" :href="linkToCommit" class="gl-mr-2">{{
+ packageShaShort
+ }}</gl-link>
<clipboard-button
:text="packageEntity.pipeline.sha"
:title="__('Copy commit SHA')"
- css-class="border-0 py-0 px-1"
+ css-class="gl-border-0 gl-py-0 gl-px-2"
/>
</template>
<template v-else>
- <gl-icon name="upload" class="mr-1" />
- <span ref="manual-ref">{{ s__('PackageRegistry|Manually Published') }}</span>
+ <gl-icon name="upload" class="gl-mr-2" />
+ <span data-testid="manually-published">
+ {{ s__('PackageRegistry|Manually Published') }}
+ </span>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/reports/accessibility_report/store/index.js b/app/assets/javascripts/reports/accessibility_report/store/index.js
index 047964260ad..9fa2c589324 100644
--- a/app/assets/javascripts/reports/accessibility_report/store/index.js
+++ b/app/assets/javascripts/reports/accessibility_report/store/index.js
@@ -7,10 +7,11 @@ import state from './state';
Vue.use(Vuex);
-export default initialState =>
- new Vuex.Store({
- actions,
- getters,
- mutations,
- state: state(initialState),
- });
+export const getStoreConfig = initialState => ({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+});
+
+export default initialState => new Vuex.Store(getStoreConfig(initialState));
diff --git a/app/assets/javascripts/reports/codequality_report/store/index.js b/app/assets/javascripts/reports/codequality_report/store/index.js
index 047964260ad..9fa2c589324 100644
--- a/app/assets/javascripts/reports/codequality_report/store/index.js
+++ b/app/assets/javascripts/reports/codequality_report/store/index.js
@@ -7,10 +7,11 @@ import state from './state';
Vue.use(Vuex);
-export default initialState =>
- new Vuex.Store({
- actions,
- getters,
- mutations,
- state: state(initialState),
- });
+export const getStoreConfig = initialState => ({
+ actions,
+ getters,
+ mutations,
+ state: state(initialState),
+});
+
+export default initialState => new Vuex.Store(getStoreConfig(initialState));
diff --git a/app/assets/javascripts/reports/store/index.js b/app/assets/javascripts/reports/store/index.js
index 467c692b438..a2edfa94a48 100644
--- a/app/assets/javascripts/reports/store/index.js
+++ b/app/assets/javascripts/reports/store/index.js
@@ -7,10 +7,11 @@ import state from './state';
Vue.use(Vuex);
-export default () =>
- new Vuex.Store({
- actions,
- mutations,
- getters,
- state: state(),
- });
+export const getStoreConfig = () => ({
+ actions,
+ mutations,
+ getters,
+ state: state(),
+});
+
+export default () => new Vuex.Store(getStoreConfig());
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index 6818c8df6db..b27602f6b4a 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -40,7 +40,6 @@
@import './pages/note_form';
@import './pages/notes';
@import './pages/notifications';
-@import './pages/packages';
@import './pages/pages';
@import './pages/pipeline_schedules';
@import './pages/pipelines';
diff --git a/app/assets/stylesheets/pages/packages.scss b/app/assets/stylesheets/pages/packages.scss
deleted file mode 100644
index 8f6eee524e5..00000000000
--- a/app/assets/stylesheets/pages/packages.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.commit-row-description {
- border: 0;
- border-left: 3px solid $white-dark;
-}
-
-.package-list-table[aria-busy='true'] {
- td {
- padding-bottom: 0;
- padding-top: 0;
- }
-}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 20482f30994..6792a27f2cd 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -160,3 +160,20 @@
min-height: $gl-spacing-scale-6;
}
+.gl-justify-content-md-end {
+ @media (min-width: $breakpoint-md) {
+ width: auto !important;
+ }
+}
+
+.gl-display-md-flex {
+ @media (min-width: $breakpoint-md) {
+ display: flex;
+ }
+}
+
+.gl-display-md-none {
+ @media (min-width: $breakpoint-md) {
+ display: none;
+ }
+}
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index d969e7bf771..cdcc069e1c4 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -10,6 +10,8 @@ class Projects::BlobController < Projects::ApplicationController
include RedirectsForMissingPathOnTree
include SourcegraphDecorator
include DiffHelper
+ include RedisTracking
+ extend ::Gitlab::Utils::Override
prepend_before_action :authenticate_user!, only: [:edit]
@@ -35,6 +37,8 @@ class Projects::BlobController < Projects::ApplicationController
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
end
+ track_redis_hll_event :create, :update, name: 'g_edit_by_sfe', feature: :track_editor_edit_actions
+
def new
commit unless @repository.empty?
end
@@ -256,4 +260,9 @@ class Projects::BlobController < Projects::ApplicationController
def diff_params
params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent)
end
+
+ override :visitor_id
+ def visitor_id
+ current_user&.id
+ end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 02e800eb86f..5f9a7fda02a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -37,6 +37,7 @@ class IssuableFinder
include FinderMethods
include CreatedAtFilter
include Gitlab::Utils::StrongMemoize
+ prepend OptimizedIssuableLabelFilter
requires_cross_project_access unless: -> { params.project? }
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index e726772fba4..4358cf249f7 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -172,7 +172,14 @@ class LabelsFinder < UnionFinder
ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute # rubocop: disable CodeReuse/Finder
end
- @projects = @projects.in_namespace(group.id) if group?
+ if group?
+ @projects = if params[:include_subgroups]
+ @projects.in_namespace(group.self_and_descendants.select(:id))
+ else
+ @projects.in_namespace(group.id)
+ end
+ end
+
@projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil)
diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb
index a7cc367379d..dba9f99edeb 100644
--- a/app/graphql/resolvers/board_list_issues_resolver.rb
+++ b/app/graphql/resolvers/board_list_issues_resolver.rb
@@ -2,12 +2,20 @@
module Resolvers
class BoardListIssuesResolver < BaseResolver
+ include BoardIssueFilterable
+
+ argument :filters, Types::Boards::BoardIssueInputType,
+ required: false,
+ description: 'Filters applied when selecting issues in the board list'
+
type Types::IssueType, null: true
alias_method :list, :object
def resolve(**args)
- service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], { board_id: list.board.id, id: list.id })
+ filter_params = issue_filters(args[:filters]).merge(board_id: list.board.id, id: list.id)
+ service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params)
+
Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute)
end
diff --git a/app/graphql/resolvers/concerns/board_issue_filterable.rb b/app/graphql/resolvers/concerns/board_issue_filterable.rb
new file mode 100644
index 00000000000..1541738f46c
--- /dev/null
+++ b/app/graphql/resolvers/concerns/board_issue_filterable.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module BoardIssueFilterable
+ extend ActiveSupport::Concern
+
+ private
+
+ def issue_filters(args)
+ filters = args.to_h
+ set_filter_values(filters)
+
+ if filters[:not]
+ filters[:not] = filters[:not].to_h
+ set_filter_values(filters[:not])
+ end
+
+ filters
+ end
+
+ def set_filter_values(filters)
+ end
+end
+
+::BoardIssueFilterable.prepend_if_ee('::EE::Resolvers::BoardIssueFilterable')
diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb
index 70c0794fc90..24faf1fe8bc 100644
--- a/app/graphql/types/board_list_type.rb
+++ b/app/graphql/types/board_list_type.rb
@@ -41,7 +41,7 @@ module Types
list = self.object
user = context[:current_user]
- Boards::Issues::ListService
+ ::Boards::Issues::ListService
.new(list.board.resource_parent, user, board_id: list.board_id, id: list.id)
.metadata
end
diff --git a/app/graphql/types/boards/board_issue_input_base_type.rb b/app/graphql/types/boards/board_issue_input_base_type.rb
new file mode 100644
index 00000000000..1187b3352cd
--- /dev/null
+++ b/app/graphql/types/boards/board_issue_input_base_type.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Types
+ module Boards
+ # rubocop: disable Graphql/AuthorizeTypes
+ class BoardIssueInputBaseType < BaseInputObject
+ argument :label_name, GraphQL::STRING_TYPE.to_list_type,
+ required: false,
+ description: 'Filter by label name'
+
+ argument :milestone_title, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter by milestone title'
+
+ argument :assignee_username, GraphQL::STRING_TYPE.to_list_type,
+ required: false,
+ description: 'Filter by assignee username'
+
+ argument :author_username, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter by author username'
+
+ argument :release_tag, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter by release tag'
+
+ argument :my_reaction_emoji, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter by reaction emoji'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
+
+Types::Boards::BoardIssueInputBaseType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputBaseType')
diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb
new file mode 100644
index 00000000000..40d065d8ea9
--- /dev/null
+++ b/app/graphql/types/boards/board_issue_input_type.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Types
+ module Boards
+ # rubocop: disable Graphql/AuthorizeTypes
+ class NegatedBoardIssueInputType < BoardIssueInputBaseType
+ end
+
+ class BoardIssueInputType < BoardIssueInputBaseType
+ graphql_name 'BoardIssueInput'
+
+ argument :not, NegatedBoardIssueInputType,
+ required: false,
+ description: 'List of negated params. Warning: this argument is experimental and a subject to change in future'
+
+ argument :search, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Search query for issue title or description'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
+
+Types::Boards::BoardIssueInputType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputType')
diff --git a/app/models/concerns/optimized_issuable_label_filter.rb b/app/models/concerns/optimized_issuable_label_filter.rb
new file mode 100644
index 00000000000..7be4a26d4fa
--- /dev/null
+++ b/app/models/concerns/optimized_issuable_label_filter.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+module OptimizedIssuableLabelFilter
+ def by_label(items)
+ return items unless params.labels?
+
+ return super if Feature.disabled?(:optimized_issuable_label_filter)
+
+ target_model = items.model
+
+ if params.filter_by_no_label?
+ items.where('NOT EXISTS (?)', optimized_any_label_query(target_model))
+ elsif params.filter_by_any_label?
+ items.where('EXISTS (?)', optimized_any_label_query(target_model))
+ else
+ issuables_with_selected_labels(items, target_model)
+ end
+ end
+
+ # Taken from IssuableFinder
+ def count_by_state
+ return super if root_namespace.nil?
+ return super if Feature.disabled?(:optimized_issuable_label_filter)
+
+ count_params = params.merge(state: nil, sort: nil, force_cte: true)
+ finder = self.class.new(current_user, count_params)
+
+ state_counts = finder
+ .execute
+ .reorder(nil)
+ .group(:state_id)
+ .count
+
+ counts = state_counts.transform_keys { |key| count_key(key) }
+
+ counts[:all] = counts.values.sum
+ counts.with_indifferent_access
+ end
+
+ private
+
+ def issuables_with_selected_labels(items, target_model)
+ if root_namespace
+ all_label_ids = find_label_ids(root_namespace)
+ # Found less labels in the DB than we were searching for. Return nothing.
+ return items.none if all_label_ids.size != params.label_names.size
+
+ all_label_ids.each do |label_ids|
+ items = items.where('EXISTS (?)', optimized_label_query_by_label_ids(target_model, label_ids))
+ end
+ else
+ params.label_names.each do |label_name|
+ items = items.where('EXISTS (?)', optimized_label_query_by_label_name(target_model, label_name))
+ end
+ end
+
+ items
+ end
+
+ def find_label_ids(root_namespace)
+ finder_params = {
+ include_subgroups: true,
+ include_ancestor_groups: true,
+ include_descendant_groups: true,
+ group: root_namespace,
+ title: params.label_names
+ }
+
+ LabelsFinder
+ .new(nil, finder_params)
+ .execute(skip_authorization: true)
+ .pluck(:title, :id)
+ .group_by(&:first)
+ .values
+ .map { |labels| labels.map(&:last) }
+ end
+
+ def root_namespace
+ strong_memoize(:root_namespace) do
+ (params.project || params.group)&.root_ancestor
+ end
+ end
+
+ def optimized_any_label_query(target_model)
+ LabelLink
+ .where(target_type: target_model.name)
+ .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
+ .limit(1)
+ end
+
+ def optimized_label_query_by_label_ids(target_model, label_ids)
+ LabelLink
+ .where(target_type: target_model.name)
+ .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
+ .where(label_id: label_ids)
+ .limit(1)
+ end
+
+ def optimized_label_query_by_label_name(target_model, label_name)
+ LabelLink
+ .joins(:label)
+ .where(target_type: target_model.name)
+ .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
+ .where(labels: { name: label_name })
+ .limit(1)
+ end
+end
diff --git a/app/services/ci/create_cross_project_pipeline_service.rb b/app/services/ci/create_downstream_pipeline_service.rb
index 23207d809d4..23cd06fbd4f 100644
--- a/app/services/ci/create_cross_project_pipeline_service.rb
+++ b/app/services/ci/create_downstream_pipeline_service.rb
@@ -1,8 +1,10 @@
# frozen_string_literal: true
module Ci
- # TODO: rename this (and worker) to CreateDownstreamPipelineService
- class CreateCrossProjectPipelineService < ::BaseService
+ # Takes in input a Ci::Bridge job and creates a downstream pipeline
+ # (either multi-project or child pipeline) according to the Ci::Bridge
+ # specifications.
+ class CreateDownstreamPipelineService < ::BaseService
include Gitlab::Utils::StrongMemoize
DuplicateDownstreamPipelineError = Class.new(StandardError)
diff --git a/app/services/packages/composer/create_package_service.rb b/app/services/packages/composer/create_package_service.rb
index ad5d267698b..7e16fc78599 100644
--- a/app/services/packages/composer/create_package_service.rb
+++ b/app/services/packages/composer/create_package_service.rb
@@ -2,7 +2,7 @@
module Packages
module Composer
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
include ::Gitlab::Utils::StrongMemoize
def execute
@@ -21,10 +21,7 @@ module Packages
private
def created_package
- project
- .packages
- .composer
- .safe_find_or_create_by!(name: package_name, version: package_version)
+ find_or_create_package!(:composer, name: package_name, version: package_version)
end
def composer_json
diff --git a/app/services/packages/conan/create_package_service.rb b/app/services/packages/conan/create_package_service.rb
index 22a0436c5fb..35046d8776e 100644
--- a/app/services/packages/conan/create_package_service.rb
+++ b/app/services/packages/conan/create_package_service.rb
@@ -2,12 +2,11 @@
module Packages
module Conan
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
def execute
- project.packages.create!(
+ create_package!(:conan,
name: params[:package_name],
version: params[:package_version],
- package_type: :conan,
conan_metadatum_attributes: {
package_username: params[:package_username],
package_channel: params[:package_channel]
diff --git a/app/services/packages/create_package_service.rb b/app/services/packages/create_package_service.rb
new file mode 100644
index 00000000000..05b0b8e9fa6
--- /dev/null
+++ b/app/services/packages/create_package_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Packages
+ class CreatePackageService < BaseService
+ protected
+
+ def find_or_create_package!(package_type, name: params[:name], version: params[:version])
+ project
+ .packages
+ .with_package_type(package_type)
+ .safe_find_or_create_by!(name: name, version: version)
+ end
+
+ def create_package!(package_type, attrs = {})
+ project
+ .packages
+ .with_package_type(package_type)
+ .create!(package_attrs(attrs))
+ end
+
+ private
+
+ def package_attrs(attrs)
+ {
+ name: params[:name],
+ version: params[:version]
+ }.merge(attrs)
+ end
+ end
+end
diff --git a/app/services/packages/maven/create_package_service.rb b/app/services/packages/maven/create_package_service.rb
index aca5d28ca98..3df17021499 100644
--- a/app/services/packages/maven/create_package_service.rb
+++ b/app/services/packages/maven/create_package_service.rb
@@ -1,15 +1,12 @@
# frozen_string_literal: true
module Packages
module Maven
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
def execute
app_group, _, app_name = params[:name].rpartition('/')
app_group.tr!('/', '.')
- package = project.packages.create!(
- name: params[:name],
- version: params[:version],
- package_type: :maven,
+ package = create_package!(:maven,
maven_metadatum_attributes: {
path: params[:path],
app_group: app_group,
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index 7eac45c00ca..7f868b71734 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Packages
module Npm
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
include Gitlab::Utils::StrongMemoize
def execute
@@ -9,17 +9,13 @@ module Packages
return error('Package already exists.', 403) if current_package_exists?
return error('File is too large.', 400) if file_size_exceeded?
- ActiveRecord::Base.transaction { create_package! }
+ ActiveRecord::Base.transaction { create_npm_package! }
end
private
- def create_package!
- package = project.packages.create!(
- name: name,
- version: version,
- package_type: 'npm'
- )
+ def create_npm_package!
+ package = create_package!(:npm, name: name, version: version)
if build.present?
package.create_build_info!(pipeline: build.pipeline)
diff --git a/app/services/packages/nuget/create_package_service.rb b/app/services/packages/nuget/create_package_service.rb
index 68ad7f028e4..3999ccd3347 100644
--- a/app/services/packages/nuget/create_package_service.rb
+++ b/app/services/packages/nuget/create_package_service.rb
@@ -2,12 +2,12 @@
module Packages
module Nuget
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
TEMPORARY_PACKAGE_NAME = 'NuGet.Temporary.Package'
PACKAGE_VERSION = '0.0.0'
def execute
- project.packages.nuget.create!(
+ create_package!(:nuget,
name: TEMPORARY_PACKAGE_NAME,
version: "#{PACKAGE_VERSION}-#{uuid}"
)
diff --git a/app/services/packages/pypi/create_package_service.rb b/app/services/packages/pypi/create_package_service.rb
index 1313fc80e33..e70552e8fa0 100644
--- a/app/services/packages/pypi/create_package_service.rb
+++ b/app/services/packages/pypi/create_package_service.rb
@@ -2,7 +2,7 @@
module Packages
module Pypi
- class CreatePackageService < BaseService
+ class CreatePackageService < ::Packages::CreatePackageService
include ::Gitlab::Utils::StrongMemoize
def execute
@@ -20,10 +20,7 @@ module Packages
def created_package
strong_memoize(:created_package) do
- project
- .packages
- .pypi
- .safe_find_or_create_by!(name: params[:name], version: params[:version])
+ find_or_create_package!(:pypi)
end
end
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 786af3714a6..194b10e9ef4 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -24,4 +24,4 @@
%p.text-secondary
= _('Try using a different search term to find the file you are looking for.')
.text-center.gl-mt-3.loading
- .spinner.spinner-md
+ = loading_icon(size: 'md')
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 874adb19734..f0a68512326 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -23,7 +23,7 @@
selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
- = dropdown_toggle f.object.source_branch || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch }, { toggle_class: "js-compare-dropdown js-source-branch monospace" }
+ = dropdown_toggle f.object.source_branch.presence || _("Select source branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[source_branch]", 'refs-url': refs_project_path(@source_project), selected: f.object.source_branch }, { toggle_class: "js-compare-dropdown js-source-branch monospace" }
.dropdown-menu.dropdown-menu-selectable.js-source-branch-dropdown.git-revision-dropdown
= dropdown_title(_("Select source branch"))
= dropdown_filter(_("Search branches"))
@@ -52,7 +52,7 @@
selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
- = dropdown_toggle f.object.target_branch || _("Select target branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch }, { toggle_class: "js-compare-dropdown js-target-branch monospace" }
+ = dropdown_toggle f.object.target_branch.presence || _("Select target branch"), { toggle: "dropdown", 'field-name': "#{f.object_name}[target_branch]", 'refs-url': refs_project_path(f.object.target_project), selected: f.object.target_branch }, { toggle_class: "js-compare-dropdown js-target-branch monospace" }
.dropdown-menu.dropdown-menu-selectable.js-target-branch-dropdown.git-revision-dropdown
= dropdown_title(_("Select target branch"))
= dropdown_filter(_("Search branches"))
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 0f6188fa334..96da5136908 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -1,77 +1,77 @@
= form_errors(hook)
.form-group
- = form.label :url, 'URL', class: 'label-bold'
+ = form.label :url, s_('Webhooks|URL'), class: 'label-bold'
= form.text_field :url, class: 'form-control', placeholder: 'http://example.com/trigger-ci.json'
.form-group
- = form.label :token, 'Secret Token', class: 'label-bold'
+ = form.label :token, s_('Webhooks|Secret Token'), class: 'label-bold'
= form.text_field :token, class: 'form-control', placeholder: ''
%p.form-text.text-muted
- Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
+ = s_('Webhooks|Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.')
.form-group
- = form.label :url, 'Trigger', class: 'label-bold'
+ = form.label :url, s_('Webhooks|Trigger'), class: 'label-bold'
%ul.list-unstyled.prepend-left-20
%li
= form.check_box :push_events, class: 'form-check-input'
= form.label :push_events, class: 'list-label form-check-label ml-1' do
- %strong Push events
+ %strong= s_('Webhooks|Push events')
= form.text_field :push_events_branch_filter, class: 'form-control', placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)'
%p.text-muted.ml-1
- This URL will be triggered by a push to the repository
+ = s_('Webhooks|This URL will be triggered by a push to the repository')
%li
= form.check_box :tag_push_events, class: 'form-check-input'
= form.label :tag_push_events, class: 'list-label form-check-label ml-1' do
- %strong Tag push events
+ %strong= s_('Webhooks|Tag push events')
%p.text-muted.ml-1
- This URL will be triggered when a new tag is pushed to the repository
+ = s_('Webhooks|This URL will be triggered when a new tag is pushed to the repository')
%li
= form.check_box :note_events, class: 'form-check-input'
= form.label :note_events, class: 'list-label form-check-label ml-1' do
- %strong Comments
+ %strong= s_('Webhooks|Comments')
%p.text-muted.ml-1
- This URL will be triggered when someone adds a comment
+ = s_('Webhooks|This URL will be triggered when someone adds a comment')
%li
= form.check_box :confidential_note_events, class: 'form-check-input'
= form.label :confidential_note_events, class: 'list-label form-check-label ml-1' do
- %strong Confidential Comments
+ %strong= s_('Webhooks|Confidential Comments')
%p.text-muted.ml-1
- This URL will be triggered when someone adds a comment on a confidential issue
+ = s_('Webhooks|This URL will be triggered when someone adds a comment on a confidential issue')
%li
= form.check_box :issues_events, class: 'form-check-input'
= form.label :issues_events, class: 'list-label form-check-label ml-1' do
- %strong Issues events
+ %strong= s_('Webhooks|Issues events')
%p.text-muted.ml-1
- This URL will be triggered when an issue is created/updated/merged
+ = s_('Webhooks|This URL will be triggered when an issue is created/updated/merged')
%li
= form.check_box :confidential_issues_events, class: 'form-check-input'
= form.label :confidential_issues_events, class: 'list-label form-check-label ml-1' do
- %strong Confidential Issues events
+ %strong= s_('Webhooks|Confidential Issues events')
%p.text-muted.ml-1
- This URL will be triggered when a confidential issue is created/updated/merged
+ = s_('Webhooks|This URL will be triggered when a confidential issue is created/updated/merged')
%li
= form.check_box :merge_requests_events, class: 'form-check-input'
= form.label :merge_requests_events, class: 'list-label form-check-label ml-1' do
- %strong Merge request events
+ %strong= s_('Webhooks|Merge request events')
%p.text-muted.ml-1
- This URL will be triggered when a merge request is created/updated/merged
+ = s_('Webhooks|This URL will be triggered when a merge request is created/updated/merged')
%li
= form.check_box :job_events, class: 'form-check-input'
= form.label :job_events, class: 'list-label form-check-label ml-1' do
- %strong Job events
+ %strong= s_('Webhooks|Job events')
%p.text-muted.ml-1
- This URL will be triggered when the job status changes
+ = s_('Webhooks|This URL will be triggered when the job status changes')
%li
= form.check_box :pipeline_events, class: 'form-check-input'
= form.label :pipeline_events, class: 'list-label form-check-label ml-1' do
- %strong Pipeline events
+ %strong= s_('Webhooks|Pipeline events')
%p.text-muted.ml-1
- This URL will be triggered when the pipeline status changes
+ = s_('Webhooks|This URL will be triggered when the pipeline status changes')
%li
= form.check_box :wiki_page_events, class: 'form-check-input'
= form.label :wiki_page_events, class: 'list-label form-check-label ml-1' do
- %strong Wiki Page events
+ %strong= s_('Webhooks|Wiki Page events')
%p.text-muted.ml-1
- This URL will be triggered when a wiki page is created/updated
+ = s_('Webhooks|This URL will be triggered when a wiki page is created/updated')
%li
= form.check_box :deployment_events, class: 'form-check-input'
= form.label :deployment_events, class: 'list-label form-check-label ml-1' do
@@ -79,8 +79,8 @@
%p.text-muted.ml-1
= s_('Webhooks|This URL will be triggered when a deployment is finished/failed/canceled')
.form-group
- = form.label :enable_ssl_verification, 'SSL verification', class: 'label-bold checkbox'
+ = form.label :enable_ssl_verification, s_('Webhooks|SSL verification'), class: 'label-bold checkbox'
.form-check
= form.check_box :enable_ssl_verification, class: 'form-check-input'
= form.label :enable_ssl_verification, class: 'form-check-label ml-1' do
- %strong Enable SSL verification
+ %strong= s_('Webhooks|Enable SSL verification')
diff --git a/app/workers/ci/create_cross_project_pipeline_worker.rb b/app/workers/ci/create_cross_project_pipeline_worker.rb
index 713d0092b32..679574d9f60 100644
--- a/app/workers/ci/create_cross_project_pipeline_worker.rb
+++ b/app/workers/ci/create_cross_project_pipeline_worker.rb
@@ -9,7 +9,7 @@ module Ci
def perform(bridge_id)
::Ci::Bridge.find_by_id(bridge_id).try do |bridge|
- ::Ci::CreateCrossProjectPipelineService
+ ::Ci::CreateDownstreamPipelineService
.new(bridge.project, bridge.user)
.execute(bridge)
end
diff --git a/changelogs/unreleased/202270-migrate-spinner-for-app-assets-javascripts-notes-components-note_h.yml b/changelogs/unreleased/202270-migrate-spinner-for-app-assets-javascripts-notes-components-note_h.yml
new file mode 100644
index 00000000000..c506a08a936
--- /dev/null
+++ b/changelogs/unreleased/202270-migrate-spinner-for-app-assets-javascripts-notes-components-note_h.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate '.fa-spinner' to '.spinner' for 'app/assets/javascripts/notes/components/note_header.vue'
+merge_request: 41140
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/202582-migrate-spinner-for-app-views-projects-find_file.yml b/changelogs/unreleased/202582-migrate-spinner-for-app-views-projects-find_file.yml
new file mode 100644
index 00000000000..0aa4855afe5
--- /dev/null
+++ b/changelogs/unreleased/202582-migrate-spinner-for-app-views-projects-find_file.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate '.fa-spinner' to '.spinner' for 'app/views/projects/find_file'
+merge_request: 41134
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/232840-fj-track-sfe-actions.yml b/changelogs/unreleased/232840-fj-track-sfe-actions.yml
new file mode 100644
index 00000000000..6e32187e941
--- /dev/null
+++ b/changelogs/unreleased/232840-fj-track-sfe-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Track SFE actions in BlobController
+merge_request: 40846
+author:
+type: changed
diff --git a/changelogs/unreleased/232844-fj-add-ide-actions-usage-data.yml b/changelogs/unreleased/232844-fj-add-ide-actions-usage-data.yml
new file mode 100644
index 00000000000..90878f07547
--- /dev/null
+++ b/changelogs/unreleased/232844-fj-add-ide-actions-usage-data.yml
@@ -0,0 +1,5 @@
+---
+title: Add IDE edit actions to Usage Data
+merge_request: 40939
+author:
+type: changed
diff --git a/changelogs/unreleased/233475-source-branch-dropdown-empty.yml b/changelogs/unreleased/233475-source-branch-dropdown-empty.yml
new file mode 100644
index 00000000000..5fed11ec057
--- /dev/null
+++ b/changelogs/unreleased/233475-source-branch-dropdown-empty.yml
@@ -0,0 +1,5 @@
+---
+title: Show default message in branch selection if none selected
+merge_request: 41211
+author: Jonston Chan
+type: fixed
diff --git a/changelogs/unreleased/235699-graphql-board-issue-filters.yml b/changelogs/unreleased/235699-graphql-board-issue-filters.yml
new file mode 100644
index 00000000000..55d1f3d4c6d
--- /dev/null
+++ b/changelogs/unreleased/235699-graphql-board-issue-filters.yml
@@ -0,0 +1,5 @@
+---
+title: Add issue filters when listing board issues in GraphQL
+merge_request: 40602
+author:
+type: added
diff --git a/changelogs/unreleased/i18n-webhook-form.yml b/changelogs/unreleased/i18n-webhook-form.yml
new file mode 100644
index 00000000000..5506b11d6f2
--- /dev/null
+++ b/changelogs/unreleased/i18n-webhook-form.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize i18n strings from app/views/shared/web_hooks/_form.html.haml
+merge_request: 41234
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/jdb-refactor-inline-diff-table-row.yml b/changelogs/unreleased/jdb-refactor-inline-diff-table-row.yml
new file mode 100644
index 00000000000..9d3d9ea0b0a
--- /dev/null
+++ b/changelogs/unreleased/jdb-refactor-inline-diff-table-row.yml
@@ -0,0 +1,5 @@
+---
+title: Jdb/refactor inline diff table row
+merge_request: 40906
+author:
+type: performance
diff --git a/changelogs/unreleased/let-before-examples-cop.yml b/changelogs/unreleased/let-before-examples-cop.yml
new file mode 100644
index 00000000000..fca5fd58f1d
--- /dev/null
+++ b/changelogs/unreleased/let-before-examples-cop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix RSpec/LetBeforeExamples cop
+merge_request: 41250
+author: Rajendra Kadam
+type: fixed
diff --git a/changelogs/unreleased/optimized-issuable-label-search-pt1.yml b/changelogs/unreleased/optimized-issuable-label-search-pt1.yml
new file mode 100644
index 00000000000..1c0943a9945
--- /dev/null
+++ b/changelogs/unreleased/optimized-issuable-label-search-pt1.yml
@@ -0,0 +1,5 @@
+---
+title: Add indexes to `label_links` database table
+merge_request: 34503
+author:
+type: other
diff --git a/changelogs/unreleased/single-line-cop.yml b/changelogs/unreleased/single-line-cop.yml
new file mode 100644
index 00000000000..76d5d98282a
--- /dev/null
+++ b/changelogs/unreleased/single-line-cop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Style/SingleLineMethods cop
+merge_request: 41247
+author: Rajendra Kadam
+type: fixed
diff --git a/config/feature_flags/development/optimized_issuable_label_filter.yml b/config/feature_flags/development/optimized_issuable_label_filter.yml
new file mode 100644
index 00000000000..12db63b03fb
--- /dev/null
+++ b/config/feature_flags/development/optimized_issuable_label_filter.yml
@@ -0,0 +1,7 @@
+---
+name: optimized_issuable_label_filter
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34503
+rollout_issue_url:
+group: group::analytics
+type: development
+default_enabled: false
diff --git a/db/migrate/20200629134747_add_extra_index_to_label_links.rb b/db/migrate/20200629134747_add_extra_index_to_label_links.rb
new file mode 100644
index 00000000000..e2066a1db42
--- /dev/null
+++ b/db/migrate/20200629134747_add_extra_index_to_label_links.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class AddExtraIndexToLabelLinks < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_COVERING_ALL_COLUMNS = 'index_on_label_links_all_columns'
+ INDEX_TO_REPLACE = 'index_label_links_on_label_id'
+ NEW_INDEX = 'index_label_links_on_label_id_and_target_type'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :label_links, [:target_id, :label_id, :target_type], name: INDEX_COVERING_ALL_COLUMNS
+
+ add_concurrent_index :label_links, [:label_id, :target_type], name: NEW_INDEX
+ remove_concurrent_index_by_name(:label_links, INDEX_TO_REPLACE)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:label_links, INDEX_COVERING_ALL_COLUMNS)
+
+ add_concurrent_index(:label_links, :label_id, name: INDEX_TO_REPLACE)
+ remove_concurrent_index_by_name(:label_links, NEW_INDEX)
+ end
+end
diff --git a/db/post_migrate/20200826220745_add_compound_index_on_vulnerabilities_for_background_migration.rb b/db/post_migrate/20200826220745_add_compound_index_on_vulnerabilities_for_background_migration.rb
new file mode 100644
index 00000000000..da95f708cf9
--- /dev/null
+++ b/db/post_migrate/20200826220745_add_compound_index_on_vulnerabilities_for_background_migration.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddCompoundIndexOnVulnerabilitiesForBackgroundMigration < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_vulnerabilities_on_project_id_and_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :vulnerabilities, [:project_id, :id], name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :vulnerabilities, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20200826220746_schedule_populate_resolved_on_default_branch_column.rb b/db/post_migrate/20200826220746_schedule_populate_resolved_on_default_branch_column.rb
new file mode 100644
index 00000000000..6faa4fc8101
--- /dev/null
+++ b/db/post_migrate/20200826220746_schedule_populate_resolved_on_default_branch_column.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class SchedulePopulateResolvedOnDefaultBranchColumn < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 100
+ DELAY_INTERVAL = 5.minutes.to_i
+ MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn'
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab.ee?
+
+ EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability.distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index|
+ project_ids = batch.pluck(:project_id)
+ migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids)
+ end
+ end
+
+ def down
+ # no-op
+ # This migration schedules background tasks to populate
+ # `resolved_on_default_branch` column of `vulnerabilities`
+ # table so there is no rollback operation needed for this.
+ end
+end
diff --git a/db/schema_migrations/20200629134747 b/db/schema_migrations/20200629134747
new file mode 100644
index 00000000000..04c2c1632b1
--- /dev/null
+++ b/db/schema_migrations/20200629134747
@@ -0,0 +1 @@
+9cd0e15dd2c5e70e53fc154a47a76ec066c741b5f6d148972b96d23888f0fcd4 \ No newline at end of file
diff --git a/db/schema_migrations/20200826220745 b/db/schema_migrations/20200826220745
new file mode 100644
index 00000000000..39134fb6223
--- /dev/null
+++ b/db/schema_migrations/20200826220745
@@ -0,0 +1 @@
+ee38dd60087a8879c4686214da1d25a60ab74306eb07b938efb1a8dfc46cc73a \ No newline at end of file
diff --git a/db/schema_migrations/20200826220746 b/db/schema_migrations/20200826220746
new file mode 100644
index 00000000000..1ff87e8df6d
--- /dev/null
+++ b/db/schema_migrations/20200826220746
@@ -0,0 +1 @@
+2564c387b727e557b2988996aa533ba5e4e6d7b01515407bd2692c09644ac2be \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b0837b8275a..893cb8d9e31 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20123,7 +20123,7 @@ CREATE INDEX index_keys_on_user_id ON public.keys USING btree (user_id);
CREATE UNIQUE INDEX index_kubernetes_namespaces_on_cluster_project_environment_id ON public.clusters_kubernetes_namespaces USING btree (cluster_id, project_id, environment_id);
-CREATE INDEX index_label_links_on_label_id ON public.label_links USING btree (label_id);
+CREATE INDEX index_label_links_on_label_id_and_target_type ON public.label_links USING btree (label_id, target_type);
CREATE INDEX index_label_links_on_target_id_and_target_type ON public.label_links USING btree (target_id, target_type);
@@ -20409,6 +20409,8 @@ CREATE INDEX index_on_identities_lower_extern_uid_and_provider ON public.identit
CREATE UNIQUE INDEX index_on_instance_statistics_recorded_at_and_identifier ON public.analytics_instance_statistics_measurements USING btree (identifier, recorded_at);
+CREATE INDEX index_on_label_links_all_columns ON public.label_links USING btree (target_id, label_id, target_type);
+
CREATE INDEX index_on_users_name_lower ON public.users USING btree (lower((name)::text));
CREATE INDEX index_open_project_tracker_data_on_service_id ON public.open_project_tracker_data USING btree (service_id);
@@ -21173,6 +21175,8 @@ CREATE INDEX index_vulnerabilities_on_milestone_id ON public.vulnerabilities USI
CREATE INDEX index_vulnerabilities_on_project_id ON public.vulnerabilities USING btree (project_id);
+CREATE INDEX index_vulnerabilities_on_project_id_and_id ON public.vulnerabilities USING btree (project_id, id);
+
CREATE INDEX index_vulnerabilities_on_resolved_by_id ON public.vulnerabilities USING btree (resolved_by_id);
CREATE INDEX index_vulnerabilities_on_start_date_sourcing_milestone_id ON public.vulnerabilities USING btree (start_date_sourcing_milestone_id);
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 7ae683ba705..34d26008972 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -1029,7 +1029,7 @@ type Board {
"""
Filters applied when selecting issues on the board
"""
- issueFilters: BoardEpicIssueInput
+ issueFilters: BoardIssueInput
"""
Returns the last _n_ elements from the list.
@@ -1133,7 +1133,12 @@ type BoardEdge {
node: Board
}
-input BoardEpicIssueInput {
+"""
+Identifier of Board
+"""
+scalar BoardID
+
+input BoardIssueInput {
"""
Filter by assignee username
"""
@@ -1145,9 +1150,14 @@ input BoardEpicIssueInput {
authorUsername: String
"""
- Filter by epic ID
+ Filter by epic ID. Incompatible with epicWildcardId
+ """
+ epicId: ID
+
+ """
+ Filter by epic ID wildcard. Incompatible with epicId
"""
- epicId: String
+ epicWildcardId: EpicWildcardId
"""
Filter by label name
@@ -1167,7 +1177,7 @@ input BoardEpicIssueInput {
"""
List of negated params. Warning: this argument is experimental and a subject to change in future
"""
- not: NegatedBoardEpicIssueInput
+ not: NegatedBoardIssueInput
"""
Filter by release tag
@@ -1186,11 +1196,6 @@ input BoardEpicIssueInput {
}
"""
-Identifier of Board
-"""
-scalar BoardID
-
-"""
Represents a list for an issue board
"""
type BoardList {
@@ -1224,6 +1229,11 @@ type BoardList {
before: String
"""
+ Filters applied when selecting issues in the board list
+ """
+ filters: BoardIssueInput
+
+ """
Returns the first _n_ elements from the list.
"""
first: Int
@@ -5884,6 +5894,21 @@ type EpicTreeReorderPayload {
errors: [String!]!
}
+"""
+Epic ID wildcard values
+"""
+enum EpicWildcardId {
+ """
+ Any epic is assigned
+ """
+ ANY
+
+ """
+ No epic is assigned
+ """
+ NONE
+}
+
type GeoNode {
"""
The maximum concurrency of container repository sync for this secondary node
@@ -6864,6 +6889,36 @@ type Group {
): VulnerabilityScannerConnection
"""
+ Counts for each vulnerability severity in the group and its subgroups
+ """
+ vulnerabilitySeveritiesCount(
+ """
+ Filter vulnerabilities by project
+ """
+ projectId: [ID!]
+
+ """
+ Filter vulnerabilities by report type
+ """
+ reportType: [VulnerabilityReportType!]
+
+ """
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
+ Filter vulnerabilities by severity
+ """
+ severity: [VulnerabilitySeverity!]
+
+ """
+ Filter vulnerabilities by state
+ """
+ state: [VulnerabilityState!]
+ ): VulnerabilitySeveritiesCount
+
+ """
Web URL of the group
"""
webUrl: String!
@@ -7030,6 +7085,36 @@ type InstanceSecurityDashboard {
"""
last: Int
): VulnerabilityScannerConnection
+
+ """
+ Counts for each vulnerability severity from projects selected in Instance Security Dashboard
+ """
+ vulnerabilitySeveritiesCount(
+ """
+ Filter vulnerabilities by project
+ """
+ projectId: [ID!]
+
+ """
+ Filter vulnerabilities by report type
+ """
+ reportType: [VulnerabilityReportType!]
+
+ """
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
+ Filter vulnerabilities by severity
+ """
+ severity: [VulnerabilitySeverity!]
+
+ """
+ Filter vulnerabilities by state
+ """
+ state: [VulnerabilityState!]
+ ): VulnerabilitySeveritiesCount
}
"""
@@ -10236,7 +10321,7 @@ type NamespaceIncreaseStorageTemporarilyPayload {
namespace: Namespace
}
-input NegatedBoardEpicIssueInput {
+input NegatedBoardIssueInput {
"""
Filter by assignee username
"""
@@ -10248,9 +10333,9 @@ input NegatedBoardEpicIssueInput {
authorUsername: String
"""
- Filter by epic ID
+ Filter by epic ID. Incompatible with epicWildcardId
"""
- epicId: String
+ epicId: ID
"""
Filter by label name
@@ -12428,9 +12513,34 @@ type Project {
): VulnerabilityScannerConnection
"""
- Counts for each severity of vulnerability of the project
+ Counts for each vulnerability severity in the project
"""
- vulnerabilitySeveritiesCount: VulnerabilitySeveritiesCount
+ vulnerabilitySeveritiesCount(
+ """
+ Filter vulnerabilities by project
+ """
+ projectId: [ID!]
+
+ """
+ Filter vulnerabilities by report type
+ """
+ reportType: [VulnerabilityReportType!]
+
+ """
+ Filter vulnerabilities by scanner
+ """
+ scanner: [String!]
+
+ """
+ Filter vulnerabilities by severity
+ """
+ severity: [VulnerabilitySeverity!]
+
+ """
+ Filter vulnerabilities by state
+ """
+ state: [VulnerabilityState!]
+ ): VulnerabilitySeveritiesCount
"""
Web URL of the project
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 7dab2b15dca..fdffa810044 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -2716,7 +2716,7 @@
"description": "Filters applied when selecting issues on the board",
"type": {
"kind": "INPUT_OBJECT",
- "name": "BoardEpicIssueInput",
+ "name": "BoardIssueInput",
"ofType": null
},
"defaultValue": null
@@ -3042,8 +3042,18 @@
"possibleTypes": null
},
{
+ "kind": "SCALAR",
+ "name": "BoardID",
+ "description": "Identifier of Board",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "INPUT_OBJECT",
- "name": "BoardEpicIssueInput",
+ "name": "BoardIssueInput",
"description": null,
"fields": null,
"inputFields": [
@@ -3106,8 +3116,8 @@
"defaultValue": null
},
{
- "name": "epicId",
- "description": "Filter by epic ID",
+ "name": "myReactionEmoji",
+ "description": "Filter by reaction emoji",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -3116,11 +3126,11 @@
"defaultValue": null
},
{
- "name": "myReactionEmoji",
- "description": "Filter by reaction emoji",
+ "name": "epicId",
+ "description": "Filter by epic ID. Incompatible with epicWildcardId",
"type": {
"kind": "SCALAR",
- "name": "String",
+ "name": "ID",
"ofType": null
},
"defaultValue": null
@@ -3140,7 +3150,7 @@
"description": "List of negated params. Warning: this argument is experimental and a subject to change in future",
"type": {
"kind": "INPUT_OBJECT",
- "name": "NegatedBoardEpicIssueInput",
+ "name": "NegatedBoardIssueInput",
"ofType": null
},
"defaultValue": null
@@ -3154,6 +3164,16 @@
"ofType": null
},
"defaultValue": null
+ },
+ {
+ "name": "epicWildcardId",
+ "description": "Filter by epic ID wildcard. Incompatible with epicId",
+ "type": {
+ "kind": "ENUM",
+ "name": "EpicWildcardId",
+ "ofType": null
+ },
+ "defaultValue": null
}
],
"interfaces": null,
@@ -3161,16 +3181,6 @@
"possibleTypes": null
},
{
- "kind": "SCALAR",
- "name": "BoardID",
- "description": "Identifier of Board",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": null,
- "possibleTypes": null
- },
- {
"kind": "OBJECT",
"name": "BoardList",
"description": "Represents a list for an issue board",
@@ -3226,6 +3236,16 @@
"description": "Board issues",
"args": [
{
+ "name": "filters",
+ "description": "Filters applied when selecting issues in the board list",
+ "type": {
+ "kind": "INPUT_OBJECT",
+ "name": "BoardIssueInput",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
@@ -16458,6 +16478,29 @@
"possibleTypes": null
},
{
+ "kind": "ENUM",
+ "name": "EpicWildcardId",
+ "description": "Epic ID wildcard values",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": [
+ {
+ "name": "NONE",
+ "description": "No epic is assigned",
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "ANY",
+ "description": "Any epic is assigned",
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "possibleTypes": null
+ },
+ {
"kind": "SCALAR",
"name": "Float",
"description": "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
@@ -18892,6 +18935,109 @@
"deprecationReason": null
},
{
+ "name": "vulnerabilitySeveritiesCount",
+ "description": "Counts for each vulnerability severity in the group and its subgroups",
+ "args": [
+ {
+ "name": "projectId",
+ "description": "Filter vulnerabilities by project",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "reportType",
+ "description": "Filter vulnerabilities by report type",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityReportType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "severity",
+ "description": "Filter vulnerabilities by severity",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilitySeverity",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "state",
+ "description": "Filter vulnerabilities by state",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityState",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitySeveritiesCount",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "webUrl",
"description": "Web URL of the group",
"args": [
@@ -19404,6 +19550,109 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "vulnerabilitySeveritiesCount",
+ "description": "Counts for each vulnerability severity from projects selected in Instance Security Dashboard",
+ "args": [
+ {
+ "name": "projectId",
+ "description": "Filter vulnerabilities by project",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "reportType",
+ "description": "Filter vulnerabilities by report type",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityReportType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "severity",
+ "description": "Filter vulnerabilities by severity",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilitySeverity",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "state",
+ "description": "Filter vulnerabilities by state",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityState",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitySeveritiesCount",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -30628,7 +30877,7 @@
},
{
"kind": "INPUT_OBJECT",
- "name": "NegatedBoardEpicIssueInput",
+ "name": "NegatedBoardIssueInput",
"description": null,
"fields": null,
"inputFields": [
@@ -30691,8 +30940,8 @@
"defaultValue": null
},
{
- "name": "epicId",
- "description": "Filter by epic ID",
+ "name": "myReactionEmoji",
+ "description": "Filter by reaction emoji",
"type": {
"kind": "SCALAR",
"name": "String",
@@ -30701,11 +30950,11 @@
"defaultValue": null
},
{
- "name": "myReactionEmoji",
- "description": "Filter by reaction emoji",
+ "name": "epicId",
+ "description": "Filter by epic ID. Incompatible with epicWildcardId",
"type": {
"kind": "SCALAR",
- "name": "String",
+ "name": "ID",
"ofType": null
},
"defaultValue": null
@@ -36407,9 +36656,98 @@
},
{
"name": "vulnerabilitySeveritiesCount",
- "description": "Counts for each severity of vulnerability of the project",
+ "description": "Counts for each vulnerability severity in the project",
"args": [
-
+ {
+ "name": "projectId",
+ "description": "Filter vulnerabilities by project",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "reportType",
+ "description": "Filter vulnerabilities by report type",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityReportType",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "severity",
+ "description": "Filter vulnerabilities by severity",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilitySeverity",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "state",
+ "description": "Filter vulnerabilities by state",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "VulnerabilityState",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "scanner",
+ "description": "Filter vulnerabilities by scanner",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ }
],
"type": {
"kind": "OBJECT",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7ead6d397a2..9c88d802987 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1048,6 +1048,7 @@ Autogenerated return type of EpicTreeReorder
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the namespace |
| `vulnerabilityGrades` | VulnerableProjectsByGrade! => Array | Represents vulnerable project counts for each grade |
+| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity in the group and its subgroups |
| `webUrl` | String! | Web URL of the group |
## GroupMember
@@ -1077,6 +1078,7 @@ Represents a Group Membership
| Name | Type | Description |
| --- | ---- | ---------- |
| `vulnerabilityGrades` | VulnerableProjectsByGrade! => Array | Represents vulnerable project counts for each grade |
+| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity from projects selected in Instance Security Dashboard |
## Issue
@@ -1770,7 +1772,7 @@ Autogenerated return type of PipelineRetry
| `tagList` | String | List of project topics (not Git tags) |
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
| `visibility` | String | Visibility of the project |
-| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each severity of vulnerability of the project |
+| `vulnerabilitySeveritiesCount` | VulnerabilitySeveritiesCount | Counts for each vulnerability severity in the project |
| `webUrl` | String | Web URL of the project |
| `wikiEnabled` | Boolean | Indicates if Wikis are enabled for the current user |
diff --git a/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb b/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb
new file mode 100644
index 00000000000..eb72ef1de33
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_resolved_on_default_branch_column.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # rubocop:disable Style/Documentation
+ class PopulateResolvedOnDefaultBranchColumn
+ def perform(*); end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn.prepend_if_ee('EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn')
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index e145bd2e9df..1fac00337a3 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -31,105 +31,205 @@ module Gitlab
end
class Converter
- def on_0(_) reset end
+ def on_0(_)
+ reset
+ end
- def on_1(_) enable(STYLE_SWITCHES[:bold]) end
+ def on_1(_)
+ enable(STYLE_SWITCHES[:bold])
+ end
- def on_3(_) enable(STYLE_SWITCHES[:italic]) end
+ def on_3(_)
+ enable(STYLE_SWITCHES[:italic])
+ end
- def on_4(_) enable(STYLE_SWITCHES[:underline]) end
+ def on_4(_)
+ enable(STYLE_SWITCHES[:underline])
+ end
- def on_8(_) enable(STYLE_SWITCHES[:conceal]) end
+ def on_8(_)
+ enable(STYLE_SWITCHES[:conceal])
+ end
- def on_9(_) enable(STYLE_SWITCHES[:cross]) end
+ def on_9(_)
+ enable(STYLE_SWITCHES[:cross])
+ end
- def on_21(_) disable(STYLE_SWITCHES[:bold]) end
+ def on_21(_)
+ disable(STYLE_SWITCHES[:bold])
+ end
- def on_22(_) disable(STYLE_SWITCHES[:bold]) end
+ def on_22(_)
+ disable(STYLE_SWITCHES[:bold])
+ end
- def on_23(_) disable(STYLE_SWITCHES[:italic]) end
+ def on_23(_)
+ disable(STYLE_SWITCHES[:italic])
+ end
- def on_24(_) disable(STYLE_SWITCHES[:underline]) end
+ def on_24(_)
+ disable(STYLE_SWITCHES[:underline])
+ end
- def on_28(_) disable(STYLE_SWITCHES[:conceal]) end
+ def on_28(_)
+ disable(STYLE_SWITCHES[:conceal])
+ end
- def on_29(_) disable(STYLE_SWITCHES[:cross]) end
+ def on_29(_)
+ disable(STYLE_SWITCHES[:cross])
+ end
- def on_30(_) set_fg_color(0) end
+ def on_30(_)
+ set_fg_color(0)
+ end
- def on_31(_) set_fg_color(1) end
+ def on_31(_)
+ set_fg_color(1)
+ end
- def on_32(_) set_fg_color(2) end
+ def on_32(_)
+ set_fg_color(2)
+ end
- def on_33(_) set_fg_color(3) end
+ def on_33(_)
+ set_fg_color(3)
+ end
- def on_34(_) set_fg_color(4) end
+ def on_34(_)
+ set_fg_color(4)
+ end
- def on_35(_) set_fg_color(5) end
+ def on_35(_)
+ set_fg_color(5)
+ end
- def on_36(_) set_fg_color(6) end
+ def on_36(_)
+ set_fg_color(6)
+ end
- def on_37(_) set_fg_color(7) end
+ def on_37(_)
+ set_fg_color(7)
+ end
- def on_38(stack) set_fg_color_256(stack) end
+ def on_38(stack)
+ set_fg_color_256(stack)
+ end
- def on_39(_) set_fg_color(9) end
+ def on_39(_)
+ set_fg_color(9)
+ end
- def on_40(_) set_bg_color(0) end
+ def on_40(_)
+ set_bg_color(0)
+ end
- def on_41(_) set_bg_color(1) end
+ def on_41(_)
+ set_bg_color(1)
+ end
- def on_42(_) set_bg_color(2) end
+ def on_42(_)
+ set_bg_color(2)
+ end
- def on_43(_) set_bg_color(3) end
+ def on_43(_)
+ set_bg_color(3)
+ end
- def on_44(_) set_bg_color(4) end
+ def on_44(_)
+ set_bg_color(4)
+ end
- def on_45(_) set_bg_color(5) end
+ def on_45(_)
+ set_bg_color(5)
+ end
- def on_46(_) set_bg_color(6) end
+ def on_46(_)
+ set_bg_color(6)
+ end
- def on_47(_) set_bg_color(7) end
+ def on_47(_)
+ set_bg_color(7)
+ end
- def on_48(stack) set_bg_color_256(stack) end
+ def on_48(stack)
+ set_bg_color_256(stack)
+ end
- def on_49(_) set_bg_color(9) end
+ def on_49(_)
+ set_bg_color(9)
+ end
- def on_90(_) set_fg_color(0, 'l') end
+ def on_90(_)
+ set_fg_color(0, 'l')
+ end
- def on_91(_) set_fg_color(1, 'l') end
+ def on_91(_)
+ set_fg_color(1, 'l')
+ end
- def on_92(_) set_fg_color(2, 'l') end
+ def on_92(_)
+ set_fg_color(2, 'l')
+ end
- def on_93(_) set_fg_color(3, 'l') end
+ def on_93(_)
+ set_fg_color(3, 'l')
+ end
- def on_94(_) set_fg_color(4, 'l') end
+ def on_94(_)
+ set_fg_color(4, 'l')
+ end
- def on_95(_) set_fg_color(5, 'l') end
+ def on_95(_)
+ set_fg_color(5, 'l')
+ end
- def on_96(_) set_fg_color(6, 'l') end
+ def on_96(_)
+ set_fg_color(6, 'l')
+ end
- def on_97(_) set_fg_color(7, 'l') end
+ def on_97(_)
+ set_fg_color(7, 'l')
+ end
- def on_99(_) set_fg_color(9, 'l') end
+ def on_99(_)
+ set_fg_color(9, 'l')
+ end
- def on_100(_) set_bg_color(0, 'l') end
+ def on_100(_)
+ set_bg_color(0, 'l')
+ end
- def on_101(_) set_bg_color(1, 'l') end
+ def on_101(_)
+ set_bg_color(1, 'l')
+ end
- def on_102(_) set_bg_color(2, 'l') end
+ def on_102(_)
+ set_bg_color(2, 'l')
+ end
- def on_103(_) set_bg_color(3, 'l') end
+ def on_103(_)
+ set_bg_color(3, 'l')
+ end
- def on_104(_) set_bg_color(4, 'l') end
+ def on_104(_)
+ set_bg_color(4, 'l')
+ end
- def on_105(_) set_bg_color(5, 'l') end
+ def on_105(_)
+ set_bg_color(5, 'l')
+ end
- def on_106(_) set_bg_color(6, 'l') end
+ def on_106(_)
+ set_bg_color(6, 'l')
+ end
- def on_107(_) set_bg_color(7, 'l') end
+ def on_107(_)
+ set_bg_color(7, 'l')
+ end
- def on_109(_) set_bg_color(9, 'l') end
+ def on_109(_)
+ set_bg_color(9, 'l')
+ end
attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask, :sections, :lineno_in_section
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index b2c296cc3d5..22c344a27f7 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -650,41 +650,37 @@ module Gitlab
end
def action_monthly_active_users(time_period)
- counter = Gitlab::UsageDataCounters::TrackUniqueEvents
+ date_range = { date_from: time_period[:created_at].first, date_to: time_period[:created_at].last }
- project_count = redis_usage_data do
- counter.count_unique_events(
- event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION,
- date_from: time_period[:created_at].first,
- date_to: time_period[:created_at].last
- )
- end
+ event_monthly_active_users(date_range)
+ .merge!(ide_monthly_active_users(date_range))
+ end
- design_count = redis_usage_data do
- counter.count_unique_events(
- event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION,
- date_from: time_period[:created_at].first,
- date_to: time_period[:created_at].last
- )
- end
+ private
- wiki_count = redis_usage_data do
- counter.count_unique_events(
- event_action: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION,
- date_from: time_period[:created_at].first,
- date_to: time_period[:created_at].last
- )
+ def event_monthly_active_users(date_range)
+ data = {
+ action_monthly_active_users_project_repo: Gitlab::UsageDataCounters::TrackUniqueEvents::PUSH_ACTION,
+ action_monthly_active_users_design_management: Gitlab::UsageDataCounters::TrackUniqueEvents::DESIGN_ACTION,
+ action_monthly_active_users_wiki_repo: Gitlab::UsageDataCounters::TrackUniqueEvents::WIKI_ACTION
+ }
+
+ data.each do |key, event|
+ data[key] = redis_usage_data { Gitlab::UsageDataCounters::TrackUniqueEvents.count_unique_events(event_action: event, **date_range) }
end
+ end
+
+ def ide_monthly_active_users(date_range)
+ counter = Gitlab::UsageDataCounters::EditorUniqueCounter
{
- action_monthly_active_users_project_repo: project_count,
- action_monthly_active_users_design_management: design_count,
- action_monthly_active_users_wiki_repo: wiki_count
+ action_monthly_active_users_web_ide_edit: redis_usage_data { counter.count_web_ide_edit_actions(date_range) },
+ action_monthly_active_users_sfe_edit: redis_usage_data { counter.count_sfe_edit_actions(date_range) },
+ action_monthly_active_users_snippet_editor_edit: redis_usage_data { counter.count_snippet_editor_edit_actions(date_range) },
+ action_monthly_active_users_ide_edit: redis_usage_data { counter.count_edit_using_editor(date_range) }
}
end
- private
-
def distinct_count_service_desk_enabled_projects(time_period)
project_creator_id_start = user_minimum_id
project_creator_id_finish = user_maximum_id
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index df6345cba62..01dd4e7b62e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -27754,12 +27754,90 @@ msgstr ""
msgid "Webhooks have moved. They can now be found under the Settings menu."
msgstr ""
+msgid "Webhooks|Comments"
+msgstr ""
+
+msgid "Webhooks|Confidential Comments"
+msgstr ""
+
+msgid "Webhooks|Confidential Issues events"
+msgstr ""
+
msgid "Webhooks|Deployment events"
msgstr ""
+msgid "Webhooks|Enable SSL verification"
+msgstr ""
+
+msgid "Webhooks|Issues events"
+msgstr ""
+
+msgid "Webhooks|Job events"
+msgstr ""
+
+msgid "Webhooks|Merge request events"
+msgstr ""
+
+msgid "Webhooks|Pipeline events"
+msgstr ""
+
+msgid "Webhooks|Push events"
+msgstr ""
+
+msgid "Webhooks|SSL verification"
+msgstr ""
+
+msgid "Webhooks|Secret Token"
+msgstr ""
+
+msgid "Webhooks|Tag push events"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered by a push to the repository"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when a confidential issue is created/updated/merged"
+msgstr ""
+
msgid "Webhooks|This URL will be triggered when a deployment is finished/failed/canceled"
msgstr ""
+msgid "Webhooks|This URL will be triggered when a merge request is created/updated/merged"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when a new tag is pushed to the repository"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when a wiki page is created/updated"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when an issue is created/updated/merged"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when someone adds a comment"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when someone adds a comment on a confidential issue"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when the job status changes"
+msgstr ""
+
+msgid "Webhooks|This URL will be triggered when the pipeline status changes"
+msgstr ""
+
+msgid "Webhooks|Trigger"
+msgstr ""
+
+msgid "Webhooks|URL"
+msgstr ""
+
+msgid "Webhooks|Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header."
+msgstr ""
+
+msgid "Webhooks|Wiki Page events"
+msgstr ""
+
msgid "Wednesday"
msgstr ""
diff --git a/qa/qa/support/json_formatter.rb b/qa/qa/support/json_formatter.rb
index ceafa34b1c2..f6e40436ec8 100644
--- a/qa/qa/support/json_formatter.rb
+++ b/qa/qa/support/json_formatter.rb
@@ -48,7 +48,7 @@ module QA
line_number: line_number.to_i,
run_time: example.execution_result.run_time,
pending_message: example.execution_result.pending_message,
- status_issue: example.metadata[:status_issue],
+ testcase: example.metadata[:testcase],
quarantine: example.metadata[:quarantine],
screenshot: example.metadata[:screenshot]
}
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 9fee97f938c..b998dee09b2 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -347,6 +347,13 @@ RSpec.describe Projects::BlobController do
end
end
end
+
+ it_behaves_like 'tracking unique hll events', :track_editor_edit_actions do
+ subject { put :update, params: default_params, format: format }
+
+ let(:target_id) { 'g_edit_by_sfe' }
+ let(:expected_type) { instance_of(Integer) }
+ end
end
describe 'DELETE destroy' do
@@ -436,4 +443,32 @@ RSpec.describe Projects::BlobController do
end
end
end
+
+ describe 'POST create' do
+ let(:user) { create(:user) }
+ let(:default_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'master',
+ branch_name: 'master',
+ file_name: 'docs/EXAMPLE_FILE',
+ content: 'Added changes',
+ commit_message: 'Create CHANGELOG'
+ }
+ end
+
+ before do
+ project.add_developer(user)
+
+ sign_in(user)
+ end
+
+ it_behaves_like 'tracking unique hll events', :track_editor_edit_actions do
+ subject { post :create, params: default_params, format: format }
+
+ let(:target_id) { 'g_edit_by_sfe' }
+ let(:expected_type) { instance_of(Integer) }
+ end
+ end
end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 4d325f55fb0..e85330b08f6 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -154,9 +154,11 @@ RSpec.describe SearchController do
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
end
- it_behaves_like 'tracking unique hll events', :show do
- let(:request_params) { { scope: 'projects', search: 'term' } }
+ it_behaves_like 'tracking unique hll events', :search_track_unique_users do
+ subject { get :show, params: { scope: 'projects', search: 'term' }, format: format }
+
let(:target_id) { 'i_search_total' }
+ let(:expected_type) { instance_of(String) }
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 9cb4f8c9fc1..303bfc59f61 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -330,100 +330,139 @@ RSpec.describe IssuesFinder do
end
end
- context 'filtering by label' do
- let(:params) { { label_name: label.title } }
+ shared_examples ':label_name parameter' do
+ context 'filtering by label' do
+ let(:params) { { label_name: label.title } }
- it 'returns issues with that label' do
- expect(issues).to contain_exactly(issue2)
- end
+ it 'returns issues with that label' do
+ expect(issues).to contain_exactly(issue2)
+ end
- context 'using NOT' do
- let(:params) { { not: { label_name: label.title } } }
+ context 'using NOT' do
+ let(:params) { { not: { label_name: label.title } } }
- it 'returns issues that do not have that label' do
- expect(issues).to contain_exactly(issue1, issue3, issue4)
- end
+ it 'returns issues that do not have that label' do
+ expect(issues).to contain_exactly(issue1, issue3, issue4)
+ end
- # IssuableFinder first filters using the outer params (the ones not inside the `not` key.)
- # Afterwards, it applies the `not` params to that resultset. This means that things inside the `not` param
- # do not take precedence over the outer params with the same name.
- context 'shadowing the same outside param' do
- let(:params) { { label_name: label2.title, not: { label_name: label.title } } }
+ # IssuableFinder first filters using the outer params (the ones not inside the `not` key.)
+ # Afterwards, it applies the `not` params to that resultset. This means that things inside the `not` param
+ # do not take precedence over the outer params with the same name.
+ context 'shadowing the same outside param' do
+ let(:params) { { label_name: label2.title, not: { label_name: label.title } } }
- it 'does not take precedence over labels outside NOT' do
- expect(issues).to contain_exactly(issue3)
+ it 'does not take precedence over labels outside NOT' do
+ expect(issues).to contain_exactly(issue3)
+ end
end
- end
- context 'further filtering outside params' do
- let(:params) { { label_name: label2.title, not: { assignee_username: user2.username } } }
+ context 'further filtering outside params' do
+ let(:params) { { label_name: label2.title, not: { assignee_username: user2.username } } }
- it 'further filters on the returned resultset' do
- expect(issues).to be_empty
+ it 'further filters on the returned resultset' do
+ expect(issues).to be_empty
+ end
end
end
end
- end
- context 'filtering by multiple labels' do
- let(:params) { { label_name: [label.title, label2.title].join(',') } }
- let(:label2) { create(:label, project: project2) }
+ context 'filtering by multiple labels' do
+ let(:params) { { label_name: [label.title, label2.title].join(',') } }
+ let(:label2) { create(:label, project: project2) }
- before do
- create(:label_link, label: label2, target: issue2)
- end
+ before do
+ create(:label_link, label: label2, target: issue2)
+ end
- it 'returns the unique issues with all those labels' do
- expect(issues).to contain_exactly(issue2)
- end
+ it 'returns the unique issues with all those labels' do
+ expect(issues).to contain_exactly(issue2)
+ end
- context 'using NOT' do
- let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
+ context 'using NOT' do
+ let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
- it 'returns issues that do not have any of the labels provided' do
- expect(issues).to contain_exactly(issue1, issue4)
+ it 'returns issues that do not have any of the labels provided' do
+ expect(issues).to contain_exactly(issue1, issue4)
+ end
end
end
- end
- context 'filtering by a label that includes any or none in the title' do
- let(:params) { { label_name: [label.title, label2.title].join(',') } }
- let(:label) { create(:label, title: 'any foo', project: project2) }
- let(:label2) { create(:label, title: 'bar none', project: project2) }
+ context 'filtering by a label that includes any or none in the title' do
+ let(:params) { { label_name: [label.title, label2.title].join(',') } }
+ let(:label) { create(:label, title: 'any foo', project: project2) }
+ let(:label2) { create(:label, title: 'bar none', project: project2) }
- before do
- create(:label_link, label: label2, target: issue2)
- end
+ before do
+ create(:label_link, label: label2, target: issue2)
+ end
- it 'returns the unique issues with all those labels' do
- expect(issues).to contain_exactly(issue2)
+ it 'returns the unique issues with all those labels' do
+ expect(issues).to contain_exactly(issue2)
+ end
+
+ context 'using NOT' do
+ let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
+
+ it 'returns issues that do not have ANY ONE of the labels provided' do
+ expect(issues).to contain_exactly(issue1, issue4)
+ end
+ end
end
- context 'using NOT' do
- let(:params) { { not: { label_name: [label.title, label2.title].join(',') } } }
+ context 'filtering by no label' do
+ let(:params) { { label_name: described_class::Params::FILTER_NONE } }
- it 'returns issues that do not have ANY ONE of the labels provided' do
+ it 'returns issues with no labels' do
expect(issues).to contain_exactly(issue1, issue4)
end
end
- end
- context 'filtering by no label' do
- let(:params) { { label_name: described_class::Params::FILTER_NONE } }
+ context 'filtering by any label' do
+ let(:params) { { label_name: described_class::Params::FILTER_ANY } }
- it 'returns issues with no labels' do
- expect(issues).to contain_exactly(issue1, issue4)
+ it 'returns issues that have one or more label' do
+ create_list(:label_link, 2, label: create(:label, project: project2), target: issue3)
+
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+
+ context 'when the same label exists on project and group levels' do
+ let(:issue1) { create(:issue, project: project1) }
+ let(:issue2) { create(:issue, project: project1) }
+
+ # Skipping validation to reproduce a "real-word" scenario.
+ # We still have legacy labels on PRD that have the same title on the group and project levels, example: `bug`
+ let(:project_label) { build(:label, title: 'somelabel', project: project1).tap { |r| r.save!(validate: false) } }
+ let(:group_label) { create(:group_label, title: 'somelabel', group: project1.group) }
+
+ let(:params) { { label_name: 'somelabel' } }
+
+ before do
+ create(:label_link, label: group_label, target: issue1)
+ create(:label_link, label: project_label, target: issue2)
+ end
+
+ it 'finds both issue records' do
+ expect(issues).to contain_exactly(issue1, issue2)
+ end
end
end
- context 'filtering by any label' do
- let(:params) { { label_name: described_class::Params::FILTER_ANY } }
+ context 'when `optimized_issuable_label_filter` feature flag is off' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: false)
+ end
- it 'returns issues that have one or more label' do
- create_list(:label_link, 2, label: create(:label, project: project2), target: issue3)
+ it_behaves_like ':label_name parameter'
+ end
- expect(issues).to contain_exactly(issue2, issue3)
+ context 'when `optimized_issuable_label_filter` feature flag is on' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: true)
end
+
+ it_behaves_like ':label_name parameter'
end
context 'filtering by issue term' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 5b86c891e47..4f86323c7c6 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -167,38 +167,56 @@ RSpec.describe MergeRequestsFinder do
end
end
- describe ':label_name parameter' do
- let(:common_labels) { create_list(:label, 3) }
- let(:distinct_labels) { create_list(:label, 3) }
- let(:merge_requests) do
- common_attrs = {
- source_project: project1, target_project: project1, author: user
- }
- distinct_labels.map do |label|
- labels = [label, *common_labels]
- create(:labeled_merge_request, :closed, labels: labels, **common_attrs)
+ shared_examples ':label_name parameter' do
+ describe ':label_name parameter' do
+ let(:common_labels) { create_list(:label, 3) }
+ let(:distinct_labels) { create_list(:label, 3) }
+ let(:merge_requests) do
+ common_attrs = {
+ source_project: project1, target_project: project1, author: user
+ }
+ distinct_labels.map do |label|
+ labels = [label, *common_labels]
+ create(:labeled_merge_request, :closed, labels: labels, **common_attrs)
+ end
end
- end
- def find(label_name)
- described_class.new(user, label_name: label_name).execute
- end
+ def find(label_name)
+ described_class.new(user, label_name: label_name).execute
+ end
+
+ it 'accepts a single label' do
+ found = find(distinct_labels.first.title)
+ common = find(common_labels.first.title)
+
+ expect(found).to contain_exactly(merge_requests.first)
+ expect(common).to match_array(merge_requests)
+ end
- it 'accepts a single label' do
- found = find(distinct_labels.first.title)
- common = find(common_labels.first.title)
+ it 'accepts an array of labels, all of which must match' do
+ all_distinct = find(distinct_labels.pluck(:title))
+ all_common = find(common_labels.pluck(:title))
- expect(found).to contain_exactly(merge_requests.first)
- expect(common).to match_array(merge_requests)
+ expect(all_distinct).to be_empty
+ expect(all_common).to match_array(merge_requests)
+ end
+ end
+ end
+
+ context 'when `optimized_issuable_label_filter` feature flag is off' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: false)
end
- it 'accepts an array of labels, all of which must match' do
- all_distinct = find(distinct_labels.pluck(:title))
- all_common = find(common_labels.pluck(:title))
+ it_behaves_like ':label_name parameter'
+ end
- expect(all_distinct).to be_empty
- expect(all_common).to match_array(merge_requests)
+ context 'when `optimized_issuable_label_filter` feature flag is on' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: true)
end
+
+ it_behaves_like ':label_name parameter'
end
it 'filters by source project id' do
diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
index f929f97b598..951b3f6258b 100644
--- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
@@ -1,114 +1,317 @@
import { shallowMount } from '@vue/test-utils';
+import { TEST_HOST } from 'helpers/test_constants';
import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
+import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import diffFileMockData from '../mock_data/diff_file';
+import discussionsMockData from '../mock_data/diff_discussions';
+
+const TEST_USER_ID = 'abc123';
+const TEST_USER = { id: TEST_USER_ID };
describe('InlineDiffTableRow', () => {
let wrapper;
- let vm;
+ let store;
const thisLine = diffFileMockData.highlighted_diff_lines[0];
- beforeEach(() => {
+ const createComponent = (props = {}, propsStore = store) => {
wrapper = shallowMount(InlineDiffTableRow, {
- store: createStore(),
+ store: propsStore,
propsData: {
line: thisLine,
fileHash: diffFileMockData.file_hash,
filePath: diffFileMockData.file_path,
contextLinesPath: 'contextLinesPath',
isHighlighted: false,
+ ...props,
},
});
- vm = wrapper.vm;
+ };
+
+ const setWindowLocation = value => {
+ Object.defineProperty(window, 'location', {
+ writable: true,
+ value,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ store.state.notes.userData = TEST_USER;
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
});
- it('does not add hll class to line content when line does not match highlighted row', done => {
- vm.$nextTick()
- .then(() => {
- expect(wrapper.find('.line_content').classes('hll')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ it('does not add hll class to line content when line does not match highlighted row', () => {
+ createComponent();
+ expect(wrapper.find('.line_content').classes('hll')).toBe(false);
});
- it('adds hll class to lineContent when line is the highlighted row', done => {
- vm.$nextTick()
- .then(() => {
- vm.$store.state.diffs.highlightedRow = thisLine.line_code;
-
- return vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.find('.line_content').classes('hll')).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('adds hll class to lineContent when line is the highlighted row', () => {
+ store.state.diffs.highlightedRow = thisLine.line_code;
+ createComponent({}, store);
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
});
it('adds hll class to lineContent when line is part of a multiline comment', () => {
- wrapper.setProps({ isCommented: true });
- return vm.$nextTick().then(() => {
- expect(wrapper.find('.line_content').classes('hll')).toBe(true);
- });
+ createComponent({ isCommented: true });
+ expect(wrapper.find('.line_content').classes('hll')).toBe(true);
});
describe('sets coverage title and class', () => {
- it('for lines with coverage', done => {
- vm.$nextTick()
- .then(() => {
- const name = diffFileMockData.file_path;
- const line = thisLine.new_line;
-
- vm.$store.state.diffs.coverageFiles = { files: { [name]: { [line]: 5 } } };
-
- return vm.$nextTick();
- })
- .then(() => {
- const coverage = wrapper.find('.line-coverage');
-
- expect(coverage.attributes('title')).toContain('Test coverage: 5 hits');
- expect(coverage.classes('coverage')).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('for lines with coverage', () => {
+ const name = diffFileMockData.file_path;
+ const line = thisLine.new_line;
+
+ store.state.diffs.coverageFiles = { files: { [name]: { [line]: 5 } } };
+ createComponent({}, store);
+ const coverage = wrapper.find('.line-coverage');
+
+ expect(coverage.attributes('title')).toContain('Test coverage: 5 hits');
+ expect(coverage.classes('coverage')).toBe(true);
+ });
+
+ it('for lines without coverage', () => {
+ const name = diffFileMockData.file_path;
+ const line = thisLine.new_line;
+
+ store.state.diffs.coverageFiles = { files: { [name]: { [line]: 0 } } };
+ createComponent({}, store);
+ const coverage = wrapper.find('.line-coverage');
+
+ expect(coverage.attributes('title')).toContain('No test coverage');
+ expect(coverage.classes('no-coverage')).toBe(true);
+ });
+
+ it('for unknown lines', () => {
+ store.state.diffs.coverageFiles = {};
+ createComponent({}, store);
+
+ const coverage = wrapper.find('.line-coverage');
+
+ expect(coverage.attributes('title')).toBeUndefined();
+ expect(coverage.classes('coverage')).toBe(false);
+ expect(coverage.classes('no-coverage')).toBe(false);
+ });
+ });
+
+ describe('Table Cells', () => {
+ const findNewTd = () => wrapper.find({ ref: 'newTd' });
+ const findOldTd = () => wrapper.find({ ref: 'oldTd' });
+
+ describe('td', () => {
+ it('highlights when isHighlighted true', () => {
+ store.state.diffs.highlightedRow = thisLine.line_code;
+ createComponent({}, store);
+
+ expect(findNewTd().classes()).toContain('hll');
+ expect(findOldTd().classes()).toContain('hll');
+ });
+
+ it('does not highlight when isHighlighted false', () => {
+ createComponent();
+
+ expect(findNewTd().classes()).not.toContain('hll');
+ expect(findOldTd().classes()).not.toContain('hll');
+ });
+ });
+
+ describe('comment button', () => {
+ const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' });
+
+ it.each`
+ userData | query | mergeRefHeadComments | expectation
+ ${TEST_USER} | ${'diff_head=false'} | ${false} | ${true}
+ ${TEST_USER} | ${'diff_head=true'} | ${true} | ${true}
+ ${TEST_USER} | ${'diff_head=true'} | ${false} | ${false}
+ ${null} | ${''} | ${true} | ${false}
+ `(
+ 'exists is $expectation - with userData ($userData) query ($query)',
+ ({ userData, query, mergeRefHeadComments, expectation }) => {
+ store.state.notes.userData = userData;
+ gon.features = { mergeRefHeadComments };
+ setWindowLocation({ href: `${TEST_HOST}?${query}` });
+ createComponent({}, store);
+
+ expect(findNoteButton().exists()).toBe(expectation);
+ },
+ );
+
+ it.each`
+ isHover | line | expectation
+ ${true} | ${{ ...thisLine, discussions: [] }} | ${true}
+ ${false} | ${{ ...thisLine, discussions: [] }} | ${false}
+ ${true} | ${{ ...thisLine, type: 'context', discussions: [] }} | ${false}
+ ${true} | ${{ ...thisLine, type: 'old-nonewline', discussions: [] }} | ${false}
+ ${true} | ${{ ...thisLine, discussions: [{}] }} | ${false}
+ `('visible is $expectation - line ($line)', ({ isHover, line, expectation }) => {
+ createComponent({ line });
+ wrapper.setData({ isHover });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().isVisible()).toBe(expectation);
+ });
+ });
+
+ it.each`
+ disabled | commentsDisabled
+ ${'disabled'} | ${true}
+ ${undefined} | ${false}
+ `(
+ 'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
+ ({ disabled, commentsDisabled }) => {
+ createComponent({
+ line: { ...thisLine, commentsDisabled },
+ });
+
+ wrapper.setData({ isHover: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findNoteButton().attributes('disabled')).toBe(disabled);
+ });
+ },
+ );
+
+ const symlinkishFileTooltip =
+ 'Commenting on symbolic links that replace or are replaced by files is currently not supported.';
+ const realishFileTooltip =
+ 'Commenting on files that replace or are replaced by symbolic links is currently not supported.';
+ const otherFileTooltip = 'Add a comment to this line';
+ const findTooltip = () => wrapper.find({ ref: 'addNoteTooltip' });
+
+ it.each`
+ tooltip | commentsDisabled
+ ${symlinkishFileTooltip} | ${{ wasSymbolic: true }}
+ ${symlinkishFileTooltip} | ${{ isSymbolic: true }}
+ ${realishFileTooltip} | ${{ wasReal: true }}
+ ${realishFileTooltip} | ${{ isReal: true }}
+ ${otherFileTooltip} | ${false}
+ `(
+ 'has the correct tooltip when commentsDisabled=$commentsDisabled',
+ ({ tooltip, commentsDisabled }) => {
+ createComponent({
+ line: { ...thisLine, commentsDisabled },
+ });
+
+ wrapper.setData({ isHover: true });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findTooltip().attributes('title')).toBe(tooltip);
+ });
+ },
+ );
});
- it('for lines without coverage', done => {
- vm.$nextTick()
- .then(() => {
- const name = diffFileMockData.file_path;
- const line = thisLine.new_line;
+ describe('line number', () => {
+ const findLineNumberOld = () => wrapper.find({ ref: 'lineNumberRefOld' });
+ const findLineNumberNew = () => wrapper.find({ ref: 'lineNumberRefNew' });
+
+ it('renders line numbers in correct cells', () => {
+ createComponent();
+
+ expect(findLineNumberOld().exists()).toBe(false);
+ expect(findLineNumberNew().exists()).toBe(true);
+ });
- vm.$store.state.diffs.coverageFiles = { files: { [name]: { [line]: 0 } } };
+ describe('with lineNumber prop', () => {
+ const TEST_LINE_CODE = 'LC_42';
+ const TEST_LINE_NUMBER = 1;
- return vm.$nextTick();
- })
- .then(() => {
- const coverage = wrapper.find('.line-coverage');
+ describe.each`
+ lineProps | findLineNumber | expectedHref | expectedClickArg
+ ${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE}
+ ${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined}
+ ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE }, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${TEST_LINE_CODE}
+ ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE }, new_line: TEST_LINE_NUMBER }} | ${findLineNumberNew} | ${'#'} | ${TEST_LINE_CODE}
+ `(
+ 'with line ($lineProps)',
+ ({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => {
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+ createComponent({
+ line: { ...thisLine, ...lineProps },
+ });
+ });
- expect(coverage.attributes('title')).toContain('No test coverage');
- expect(coverage.classes('no-coverage')).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ it('renders', () => {
+ expect(findLineNumber().exists()).toBe(true);
+ expect(findLineNumber().attributes()).toEqual({
+ href: expectedHref,
+ 'data-linenumber': TEST_LINE_NUMBER.toString(),
+ });
+ });
+
+ it('on click, dispatches setHighlightedRow', () => {
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+
+ findLineNumber().trigger('click');
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'diffs/setHighlightedRow',
+ expectedClickArg,
+ );
+ expect(store.dispatch).toHaveBeenCalledTimes(2);
+ });
+ },
+ );
+ });
});
- it('for unknown lines', done => {
- vm.$nextTick()
- .then(() => {
- vm.$store.state.diffs.coverageFiles = {};
-
- return vm.$nextTick();
- })
- .then(() => {
- const coverage = wrapper.find('.line-coverage');
-
- expect(coverage.attributes('title')).toBeUndefined();
- expect(coverage.classes('coverage')).toBe(false);
- expect(coverage.classes('no-coverage')).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ describe('diff-gutter-avatars', () => {
+ const TEST_LINE_CODE = 'LC_42';
+ const TEST_FILE_HASH = diffFileMockData.file_hash;
+ const findAvatars = () => wrapper.find(DiffGutterAvatars);
+ let line;
+
+ beforeEach(() => {
+ jest.spyOn(store, 'dispatch').mockImplementation();
+
+ line = {
+ line_code: TEST_LINE_CODE,
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ discussions: [{ ...discussionsMockData }],
+ discussionsExpanded: true,
+ text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ rich_text: '+<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n',
+ meta_data: null,
+ };
+ });
+
+ describe('with showCommentButton', () => {
+ it('renders if line has discussions', () => {
+ createComponent({ line });
+
+ expect(findAvatars().props()).toEqual({
+ discussions: line.discussions,
+ discussionsExpanded: line.discussionsExpanded,
+ });
+ });
+
+ it('does notrender if line has no discussions', () => {
+ line.discussions = [];
+ createComponent({ line });
+
+ expect(findAvatars().exists()).toEqual(false);
+ });
+
+ it('toggles line discussion', () => {
+ createComponent({ line });
+
+ expect(store.dispatch).toHaveBeenCalledTimes(1);
+
+ findAvatars().vm.$emit('toggleLineDiscussions');
+
+ expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', {
+ lineCode: TEST_LINE_CODE,
+ fileHash: TEST_FILE_HASH,
+ expanded: !line.discussionsExpanded,
+ });
+ });
+ });
});
});
});
diff --git a/spec/frontend/packages/details/components/__snapshots__/code_instruction_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/code_instruction_spec.js.snap
index 172b8919673..4f376a5d58f 100644
--- a/spec/frontend/packages/details/components/__snapshots__/code_instruction_spec.js.snap
+++ b/spec/frontend/packages/details/components/__snapshots__/code_instruction_spec.js.snap
@@ -16,7 +16,7 @@ exports[`Package code instruction single line to match the default snapshot 1`]
class="input-group gl-mb-3"
>
<input
- class="form-control monospace js-instruction-input"
+ class="form-control gl-font-monospace js-instruction-input"
readonly="readonly"
type="text"
/>
diff --git a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap
index 28b7ca442eb..df2844eaa3c 100644
--- a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap
+++ b/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap
@@ -21,7 +21,7 @@ exports[`DependencyRow renders full dependency 1`] = `
</div>
<div
- class="table-section section-50 gl-display-flex justify-content-md-end"
+ class="table-section section-50 gl-display-flex gl-justify-content-md-end"
data-testid="version-pattern"
>
<span
diff --git a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
index 2b7a4c83bed..6ff9376565a 100644
--- a/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -444,7 +444,7 @@ exports[`packages_list_app renders 1`] = `
</template>
<template>
<div
- class="d-flex align-self-center ml-md-auto py-1 py-md-0"
+ class="gl-display-flex gl-align-self-center gl-py-2 gl-flex-grow-1 gl-justify-content-end"
>
<package-filter-stub
class="mr-1"
diff --git a/spec/frontend/packages/shared/components/__snapshots__/publish_method_spec.js.snap b/spec/frontend/packages/shared/components/__snapshots__/publish_method_spec.js.snap
index 490aa34766c..55ac86f9c49 100644
--- a/spec/frontend/packages/shared/components/__snapshots__/publish_method_spec.js.snap
+++ b/spec/frontend/packages/shared/components/__snapshots__/publish_method_spec.js.snap
@@ -2,35 +2,37 @@
exports[`publish_method renders 1`] = `
<div
- class="d-flex align-items-center order-1 order-md-0 mb-md-1"
+ class="gl-display-flex gl-align-items-center gl-mb-2"
>
<gl-icon-stub
- class="mr-1"
+ class="gl-mr-2"
name="git-merge"
size="16"
/>
<span
- class="mr-1"
+ class="gl-mr-2"
+ data-testid="pipeline-ref"
>
branch-name
</span>
<gl-icon-stub
- class="mr-1"
+ class="gl-mr-2"
name="commit"
size="16"
/>
<gl-link-stub
- class="mr-1"
+ class="gl-mr-2"
+ data-testid="pipeline-sha"
href="../commit/sha-baz"
>
sha-baz
</gl-link-stub>
<clipboard-button-stub
- cssclass="border-0 py-0 px-1"
+ cssclass="gl-border-0 gl-py-0 gl-px-2"
text="sha-baz"
title="Copy commit SHA"
tooltipplacement="top"
diff --git a/spec/frontend/packages/shared/components/publish_method_spec.js b/spec/frontend/packages/shared/components/publish_method_spec.js
index bb9287c1204..6014774990c 100644
--- a/spec/frontend/packages/shared/components/publish_method_spec.js
+++ b/spec/frontend/packages/shared/components/publish_method_spec.js
@@ -7,9 +7,9 @@ describe('publish_method', () => {
const [packageWithoutPipeline, packageWithPipeline] = packageList;
- const findPipelineRef = () => wrapper.find({ ref: 'pipeline-ref' });
- const findPipelineSha = () => wrapper.find({ ref: 'pipeline-sha' });
- const findManualPublish = () => wrapper.find({ ref: 'manual-ref' });
+ const findPipelineRef = () => wrapper.find('[data-testid="pipeline-ref"]');
+ const findPipelineSha = () => wrapper.find('[data-testid="pipeline-sha"]');
+ const findManualPublish = () => wrapper.find('[data-testid="manually-published"]');
const mountComponent = (packageEntity = {}, isGroup = false) => {
wrapper = shallowMount(PublishMethod, {
diff --git a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
index a036588596a..ccceb78f2d1 100644
--- a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
+++ b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
@@ -2,7 +2,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue';
import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue';
-import store from '~/reports/accessibility_report/store';
+import { getStoreConfig } from '~/reports/accessibility_report/store';
import { mockReport } from './mock_data';
const localVue = createLocalVue();
@@ -20,16 +20,17 @@ describe('Grouped accessibility reports app', () => {
propsData: {
endpoint: 'endpoint.json',
},
- methods: {
- fetchReport: () => {},
- },
});
};
const findHeader = () => wrapper.find('[data-testid="report-section-code-text"]');
beforeEach(() => {
- mockStore = store();
+ mockStore = new Vuex.Store({
+ ...getStoreConfig(),
+ actions: { fetchReport: () => {}, setEndpoint: () => {} },
+ });
+
mountComponent();
});
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
index c9bd040aaf5..77d7c6f8678 100644
--- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -2,7 +2,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
-import store from '~/reports/codequality_report/store';
+import { getStoreConfig } from '~/reports/codequality_report/store';
import { mockParsedHeadIssues, mockParsedBaseIssues } from './mock_data';
const localVue = createLocalVue();
@@ -13,21 +13,22 @@ describe('Grouped code quality reports app', () => {
let wrapper;
let mockStore;
+ const PATHS = {
+ codequalityHelpPath: 'codequality_help.html',
+ basePath: 'base.json',
+ headPath: 'head.json',
+ baseBlobPath: 'base/blob/path/',
+ headBlobPath: 'head/blob/path/',
+ };
+
const mountComponent = (props = {}) => {
wrapper = mount(Component, {
store: mockStore,
localVue,
propsData: {
- basePath: 'base.json',
- headPath: 'head.json',
- baseBlobPath: 'base/blob/path/',
- headBlobPath: 'head/blob/path/',
- codequalityHelpPath: 'codequality_help.html',
+ ...PATHS,
...props,
},
- methods: {
- fetchReports: () => {},
- },
});
};
@@ -35,7 +36,19 @@ describe('Grouped code quality reports app', () => {
const findIssueBody = () => wrapper.find(CodequalityIssueBody);
beforeEach(() => {
- mockStore = store();
+ const { state, ...storeConfig } = getStoreConfig();
+ mockStore = new Vuex.Store({
+ ...storeConfig,
+ actions: {
+ setPaths: () => {},
+ fetchReports: () => {},
+ },
+ state: {
+ ...state,
+ ...PATHS,
+ },
+ });
+
mountComponent();
});
diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
index c26e2fbc19a..556904b7da5 100644
--- a/spec/frontend/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js
@@ -1,7 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import GroupedTestReportsApp from '~/reports/components/grouped_test_reports_app.vue';
-import store from '~/reports/store';
+import { getStoreConfig } from '~/reports/store';
import { failedReport } from '../mock_data/mock_data';
import successTestReports from '../mock_data/no_failures_report.json';
@@ -29,9 +29,6 @@ describe('Grouped test reports app', () => {
pipelinePath,
...props,
},
- methods: {
- fetchReports: () => {},
- },
});
};
@@ -49,7 +46,13 @@ describe('Grouped test reports app', () => {
wrapper.findAll('[data-testid="test-issue-body-description"]');
beforeEach(() => {
- mockStore = store();
+ mockStore = new Vuex.Store({
+ ...getStoreConfig(),
+ actions: {
+ fetchReports: () => {},
+ setEndpoint: () => {},
+ },
+ });
mountComponent();
});
diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
index e23a37b3d69..4ccf194522f 100644
--- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb
@@ -11,41 +11,59 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
let_it_be(:group) { create(:group, :private) }
shared_examples_for 'group and project board list issues resolver' do
- let!(:board) { create(:board, resource_parent: board_parent) }
-
before do
board_parent.add_developer(user)
end
# auth is handled by the parent object
context 'when authorized' do
- let!(:list) { create(:list, board: board, label: label) }
+ let!(:issue1) { create(:issue, project: project, labels: [label], relative_position: 10) }
+ let!(:issue2) { create(:issue, project: project, labels: [label, label2], relative_position: 12) }
+ let!(:issue3) { create(:issue, project: project, labels: [label, label3], relative_position: 10) }
it 'returns the issues in the correct order' do
- issue1 = create(:issue, project: project, labels: [label], relative_position: 10)
- issue2 = create(:issue, project: project, labels: [label], relative_position: 12)
- issue3 = create(:issue, project: project, labels: [label], relative_position: 10)
-
# by relative_position and then ID
issues = resolve_board_list_issues.items
expect(issues.map(&:id)).to eq [issue3.id, issue1.id, issue2.id]
end
+
+ it 'finds only issues matching filters' do
+ result = resolve_board_list_issues(args: { filters: { label_name: label.title, not: { label_name: label2.title } } }).items
+
+ expect(result).to match_array([issue1, issue3])
+ end
+
+ it 'finds only issues matching search param' do
+ result = resolve_board_list_issues(args: { filters: { search: issue1.title } }).items
+
+ expect(result).to match_array([issue1])
+ end
end
end
describe '#resolve' do
context 'when project boards' do
+ let_it_be(:label) { create(:label, project: user_project) }
+ let_it_be(:label2) { create(:label, project: user_project) }
+ let_it_be(:label3) { create(:label, project: user_project) }
+ let_it_be(:board) { create(:board, resource_parent: user_project) }
+ let_it_be(:list) { create(:list, board: board, label: label) }
+
let(:board_parent) { user_project }
- let!(:label) { create(:label, project: project, name: 'project label') }
let(:project) { user_project }
it_behaves_like 'group and project board list issues resolver'
end
context 'when group boards' do
+ let_it_be(:label) { create(:group_label, group: group) }
+ let_it_be(:label2) { create(:group_label, group: group) }
+ let_it_be(:label3) { create(:group_label, group: group) }
+ let_it_be(:board) { create(:board, resource_parent: group) }
+ let_it_be(:list) { create(:list, board: board, label: label) }
+
let(:board_parent) { group }
- let!(:label) { create(:group_label, group: group, name: 'group label') }
let!(:project) { create(:project, :private, group: group) }
it_behaves_like 'group and project board list issues resolver'
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index a548589d269..aecffc487aa 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
let_it_be(:merge_request_3) { create(:merge_request, :unique_branches, **common_attrs) }
let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) }
let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) }
- let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2), **common_attrs) }
+ let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2, project: project), **common_attrs) }
let_it_be(:merge_request_with_milestone) { create(:merge_request, :unique_branches, **common_attrs, milestone: milestone) }
let_it_be(:other_project) { create(:project, :repository) }
let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) }
diff --git a/spec/graphql/types/boards/board_issue_input_type_spec.rb b/spec/graphql/types/boards/board_issue_input_type_spec.rb
new file mode 100644
index 00000000000..6319ff9a88e
--- /dev/null
+++ b/spec/graphql/types/boards/board_issue_input_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['BoardIssueInput'] do
+ it { expect(described_class.graphql_name).to eq('BoardIssueInput') }
+
+ it 'exposes negated issue arguments' do
+ allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername
+ releaseTag myReactionEmoji not search)
+
+ expect(described_class.arguments.keys).to include(*allowed_args)
+ expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType)
+ end
+end
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 0ad058675fe..447802d18a7 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -40,10 +40,10 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'internal reference' do
- it_behaves_like 'a reference containing an element node'
-
let(:reference) { "##{issue.iid}" }
+ it_behaves_like 'a reference containing an element node'
+
it 'links to a valid reference' do
doc = reference_filter("Fixed #{reference}")
@@ -134,11 +134,11 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project / cross-namespace complete reference' do
- it_behaves_like 'a reference containing an element node'
-
- let(:project2) { create(:project, :public) }
- let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.full_path}##{issue.iid}" }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public) }
+
+ it_behaves_like 'a reference containing an element node'
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
@@ -182,13 +182,13 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project / same-namespace complete reference' do
- it_behaves_like 'a reference containing an element node'
-
- let(:namespace) { create(:namespace) }
- let(:project) { create(:project, :public, namespace: namespace) }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.full_path}##{issue.iid}" }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:project) { create(:project, :public, namespace: namespace) }
+ let(:namespace) { create(:namespace) }
+
+ it_behaves_like 'a reference containing an element node'
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
@@ -232,13 +232,13 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project shorthand reference' do
- it_behaves_like 'a reference containing an element node'
-
- let(:namespace) { create(:namespace) }
- let(:project) { create(:project, :public, namespace: namespace) }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.path}##{issue.iid}" }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:project) { create(:project, :public, namespace: namespace) }
+ let(:namespace) { create(:namespace) }
+
+ it_behaves_like 'a reference containing an element node'
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object)
@@ -282,12 +282,12 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project URL reference' do
- it_behaves_like 'a reference containing an element node'
-
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
let(:reference) { issue_url + "#note_123" }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+
+ it_behaves_like 'a reference containing an element node'
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
@@ -310,13 +310,13 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project reference in link href' do
- it_behaves_like 'a reference containing an element node'
-
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
- let(:reference) { issue.to_reference(project) }
let(:reference_link) { %{<a href="#{reference}">Reference</a>} }
+ let(:reference) { issue.to_reference(project) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+
+ it_behaves_like 'a reference containing an element node'
it 'links to a valid reference' do
doc = reference_filter("See #{reference_link}")
@@ -339,13 +339,13 @@ RSpec.describe Banzai::Filter::IssueReferenceFilter do
end
context 'cross-project URL in link href' do
- it_behaves_like 'a reference containing an element node'
-
- let(:namespace) { create(:namespace, name: 'cross-reference') }
- let(:project2) { create(:project, :public, namespace: namespace) }
- let(:issue) { create(:issue, project: project2) }
- let(:reference) { "#{issue_url + "#note_123"}" }
let(:reference_link) { %{<a href="#{reference}">Reference</a>} }
+ let(:reference) { "#{issue_url + "#note_123"}" }
+ let(:issue) { create(:issue, project: project2) }
+ let(:project2) { create(:project, :public, namespace: namespace) }
+ let(:namespace) { create(:namespace, name: 'cross-reference') }
+
+ it_behaves_like 'a reference containing an element node'
it 'links to a valid reference' do
doc = reference_filter("See #{reference_link}")
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index d8de3e5cc11..b8baccf6658 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -40,10 +40,10 @@ RSpec.describe Banzai::Filter::UserReferenceFilter do
end
context 'mentioning @all' do
- it_behaves_like 'a reference containing an element node'
-
let(:reference) { User.reference_prefix + 'all' }
+ it_behaves_like 'a reference containing an element node'
+
before do
project.add_developer(project.creator)
end
@@ -78,10 +78,10 @@ RSpec.describe Banzai::Filter::UserReferenceFilter do
end
context 'mentioning a group' do
- it_behaves_like 'a reference containing an element node'
-
- let(:group) { create(:group) }
let(:reference) { group.to_reference }
+ let(:group) { create(:group) }
+
+ it_behaves_like 'a reference containing an element node'
it 'links to the Group' do
doc = reference_filter("Hey #{reference}")
@@ -98,10 +98,10 @@ RSpec.describe Banzai::Filter::UserReferenceFilter do
end
context 'mentioning a nested group' do
- it_behaves_like 'a reference containing an element node'
-
- let(:group) { create(:group, :nested) }
let(:reference) { group.to_reference }
+ let(:group) { create(:group, :nested) }
+
+ it_behaves_like 'a reference containing an element node'
it 'links to the nested group' do
doc = reference_filter("Hey #{reference}")
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index ee2173a9c8d..1a7d837af73 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -4,17 +4,6 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Handler::CreateIssueHandler do
include_context :email_shared_context
- it_behaves_like :reply_processing_shared_examples
-
- before do
- stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
- stub_config_setting(host: 'localhost')
- end
-
- let(:email_raw) { email_fixture('emails/valid_new_issue.eml') }
- let(:namespace) { create(:namespace, path: 'gitlabhq') }
-
- let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
let!(:user) do
create(
:user,
@@ -23,6 +12,17 @@ RSpec.describe Gitlab::Email::Handler::CreateIssueHandler do
)
end
+ let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') }
+ let(:namespace) { create(:namespace, path: 'gitlabhq') }
+ let(:email_raw) { email_fixture('emails/valid_new_issue.eml') }
+
+ it_behaves_like :reply_processing_shared_examples
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
context "when email key" do
let(:mail) { Mail::Message.new(email_raw) }
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index 75d5fc040cb..37ee4591db0 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -4,6 +4,18 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Handler::CreateMergeRequestHandler do
include_context :email_shared_context
+ let!(:user) do
+ create(
+ :user,
+ email: 'jake@adventuretime.ooo',
+ incoming_email_token: 'auth_token'
+ )
+ end
+
+ let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') }
+ let(:namespace) { create(:namespace, path: 'gitlabhq') }
+ let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') }
+
it_behaves_like :reply_processing_shared_examples
before do
@@ -15,18 +27,6 @@ RSpec.describe Gitlab::Email::Handler::CreateMergeRequestHandler do
TestEnv.clean_test_path
end
- let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') }
- let(:namespace) { create(:namespace, path: 'gitlabhq') }
-
- let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') }
- let!(:user) do
- create(
- :user,
- email: 'jake@adventuretime.ooo',
- incoming_email_token: 'auth_token'
- )
- end
-
context "when email key" do
let(:mail) { Mail::Message.new(email_raw) }
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index e5598bbd10f..07b8070be30 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -4,6 +4,16 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
include_context :email_shared_context
+ let!(:sent_notification) do
+ SentNotification.record_note(note, user.id, mail_key)
+ end
+
+ let(:noteable) { note.noteable }
+ let(:note) { create(:diff_note_on_merge_request, project: project) }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:email_raw) { fixture_file('emails/valid_reply.eml') }
+
it_behaves_like :reply_processing_shared_examples
before do
@@ -11,16 +21,6 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
stub_config_setting(host: 'localhost')
end
- let(:email_raw) { fixture_file('emails/valid_reply.eml') }
- let(:project) { create(:project, :public, :repository) }
- let(:user) { create(:user) }
- let(:note) { create(:diff_note_on_merge_request, project: project) }
- let(:noteable) { note.noteable }
-
- let!(:sent_notification) do
- SentNotification.record_note(note, user.id, mail_key)
- end
-
context "when the recipient address doesn't include a mail key" do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index c80e15055c6..a119c85401d 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -999,6 +999,9 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe '#action_monthly_active_users', :clean_gitlab_redis_shared_state do
let(:time_period) { { created_at: 2.days.ago..time } }
let(:time) { Time.zone.now }
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:user3) { build(:user, id: 3) }
before do
counter = Gitlab::UsageDataCounters::TrackUniqueEvents
@@ -1014,6 +1017,20 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
counter.track_event(event_action: :created, event_target: project, author_id: 5, time: time - 3.days)
counter.track_event(event_action: :created, event_target: wiki, author_id: 3)
counter.track_event(event_action: :created, event_target: design, author_id: 3)
+
+ counter = Gitlab::UsageDataCounters::EditorUniqueCounter
+
+ counter.track_web_ide_edit_action(author: user1)
+ counter.track_web_ide_edit_action(author: user1)
+ counter.track_sfe_edit_action(author: user1)
+ counter.track_snippet_editor_edit_action(author: user1)
+ counter.track_snippet_editor_edit_action(author: user1, time: time - 3.days)
+
+ counter.track_web_ide_edit_action(author: user2)
+ counter.track_sfe_edit_action(author: user2)
+
+ counter.track_web_ide_edit_action(author: user3, time: time - 3.days)
+ counter.track_snippet_editor_edit_action(author: user3)
end
it 'returns the distinct count of user actions within the specified time period' do
@@ -1021,7 +1038,11 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
{
action_monthly_active_users_design_management: 1,
action_monthly_active_users_project_repo: 3,
- action_monthly_active_users_wiki_repo: 1
+ action_monthly_active_users_wiki_repo: 1,
+ action_monthly_active_users_web_ide_edit: 2,
+ action_monthly_active_users_sfe_edit: 2,
+ action_monthly_active_users_snippet_editor_edit: 2,
+ action_monthly_active_users_ide_edit: 3
}
)
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 3fb8708c884..334833e884b 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -3,25 +3,22 @@
require 'spec_helper'
RSpec.describe CommitRange do
+ let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
+ let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) }
+ let(:full_sha_to) { commit2.id }
+ let(:full_sha_from) { commit1.id }
+ let(:sha_to) { commit2.short_id }
+ let(:sha_from) { commit1.short_id }
+ let!(:commit2) { project.commit }
+ let!(:commit1) { project.commit("HEAD~2") }
+ let!(:project) { create(:project, :public, :repository) }
+
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
- let!(:project) { create(:project, :public, :repository) }
- let!(:commit1) { project.commit("HEAD~2") }
- let!(:commit2) { project.commit }
-
- let(:sha_from) { commit1.short_id }
- let(:sha_to) { commit2.short_id }
-
- let(:full_sha_from) { commit1.id }
- let(:full_sha_to) { commit2.id }
-
- let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) }
- let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
-
it 'raises ArgumentError when given an invalid range string' do
expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index b52b035e130..e611484f5ee 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -3,6 +3,11 @@
require 'spec_helper'
RSpec.describe Milestone do
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:project) { create(:project, :public) }
+
it_behaves_like 'a timebox', :milestone
describe 'MilestoneStruct#serializable_hash' do
@@ -47,11 +52,6 @@ RSpec.describe Milestone do
it { is_expected.to have_many(:milestone_releases) }
end
- let(:project) { create(:project, :public) }
- let(:milestone) { create(:milestone, project: project) }
- let(:issue) { create(:issue, project: project) }
- let(:user) { create(:user) }
-
describe '.predefined_id?' do
it 'returns true for a predefined Milestone ID' do
expect(Milestone.predefined_id?(described_class::Upcoming.id)).to be true
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index f710385b6e2..35f282b638b 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -3,20 +3,6 @@
require 'spec_helper'
RSpec.describe PackagistService do
- describe "Associations" do
- it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
- end
-
- let(:project) { create(:project) }
-
- let(:packagist_server) { 'https://packagist.example.com' }
- let(:packagist_username) { 'theUser' }
- let(:packagist_token) { 'verySecret' }
- let(:packagist_hook_url) do
- "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}"
- end
-
let(:packagist_params) do
{
active: true,
@@ -29,6 +15,20 @@ RSpec.describe PackagistService do
}
end
+ let(:packagist_hook_url) do
+ "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}"
+ end
+
+ let(:packagist_token) { 'verySecret' }
+ let(:packagist_username) { 'theUser' }
+ let(:packagist_server) { 'https://packagist.example.com' }
+ let(:project) { create(:project) }
+
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
describe '#execute' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
index ae1abb50a40..3628171fcc1 100644
--- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do
nodes {
lists {
nodes {
- issues {
+ issues(filters: {labelName: "#{label2.title}"}) {
count
nodes {
#{all_graphql_fields_for('issues'.classify)}
@@ -51,8 +51,8 @@ RSpec.describe 'get board lists' do
shared_examples 'group and project board list issues query' do
let!(:board) { create(:board, resource_parent: board_parent) }
let!(:label_list) { create(:list, board: board, label: label, position: 10) }
- let!(:issue1) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
- let!(:issue2) { create(:issue, project: issue_project, labels: [label], relative_position: 2) }
+ let!(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
+ let!(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
let!(:issue3) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
let!(:issue4) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
@@ -72,7 +72,7 @@ RSpec.describe 'get board lists' do
it 'can access the issues' do
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
- expect(issue_titles).to eq([issue2.title, issue3.title, issue1.title])
+ expect(issue_titles).to eq([issue2.title, issue1.title])
end
end
end
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 9446e10c01d..22b003501a1 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:current_user) { create(:user) }
- let_it_be(:label) { create(:label) }
+ let_it_be(:label) { create(:label, project: project) }
let_it_be(:merge_request_a) { create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) }
let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) }
let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) }
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index b0fbf3bf66d..3870c78deee 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -402,30 +402,76 @@ RSpec.describe API::Issues do
expect_paginated_array_response([group_closed_issue.id, group_issue.id])
end
- it 'returns an array of labeled group issues' do
- get api(base_url, user), params: { labels: group_label.title }
+ shared_examples 'labels parameter' do
+ it 'returns an array of labeled group issues' do
+ get api(base_url, user), params: { labels: group_label.title }
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['labels']).to eq([group_label.title])
- end
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['labels']).to eq([group_label.title])
+ end
- it 'returns an array of labeled group issues with labels param as array' do
- get api(base_url, user), params: { labels: [group_label.title] }
+ it 'returns an array of labeled group issues' do
+ get api(base_url, user), params: { labels: group_label.title }
- expect_paginated_array_response(group_issue.id)
- expect(json_response.first['labels']).to eq([group_label.title])
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['labels']).to eq([group_label.title])
+ end
+
+ it 'returns an array of labeled group issues with labels param as array' do
+ get api(base_url, user), params: { labels: [group_label.title] }
+
+ expect_paginated_array_response(group_issue.id)
+ expect(json_response.first['labels']).to eq([group_label.title])
+ end
+
+ it 'returns an array of labeled group issues where all labels match' do
+ get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
+
+ expect_paginated_array_response([])
+ end
+
+ it 'returns an array of labeled group issues where all labels match with labels param as array' do
+ get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] }
+
+ expect_paginated_array_response([])
+ end
+
+ context 'with labeled issues' do
+ let(:group_issue2) { create :issue, project: group_project }
+ let(:label_b) { create(:label, title: 'foo', project: group_project) }
+ let(:label_c) { create(:label, title: 'bar', project: group_project) }
+
+ before do
+ create(:label_link, label: group_label, target: group_issue2)
+ create(:label_link, label: label_b, target: group_issue)
+ create(:label_link, label: label_b, target: group_issue2)
+ create(:label_link, label: label_c, target: group_issue)
+
+ get api(base_url, user), params: params
+ end
+
+ let(:issue) { group_issue }
+ let(:issue2) { group_issue2 }
+ let(:label) { group_label }
+
+ it_behaves_like 'labeled issues with labels and label_name params'
+ end
end
- it 'returns an array of labeled group issues where all labels match' do
- get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
+ context 'when `optimized_issuable_label_filter` feature flag is off' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: false)
+ end
- expect_paginated_array_response([])
+ it_behaves_like 'labels parameter'
end
- it 'returns an array of labeled group issues where all labels match with labels param as array' do
- get api(base_url, user), params: { labels: [group_label.title, 'foo', 'bar'] }
+ context 'when `optimized_issuable_label_filter` feature flag is on' do
+ before do
+ stub_feature_flags(optimized_issuable_label_filter: true)
+ end
- expect_paginated_array_response([])
+ it_behaves_like 'labels parameter'
end
it 'returns issues matching given search string for title' do
@@ -440,27 +486,6 @@ RSpec.describe API::Issues do
expect_paginated_array_response(group_issue.id)
end
- context 'with labeled issues' do
- let(:group_issue2) { create :issue, project: group_project }
- let(:label_b) { create(:label, title: 'foo', project: group_project) }
- let(:label_c) { create(:label, title: 'bar', project: group_project) }
-
- before do
- create(:label_link, label: group_label, target: group_issue2)
- create(:label_link, label: label_b, target: group_issue)
- create(:label_link, label: label_b, target: group_issue2)
- create(:label_link, label: label_c, target: group_issue)
-
- get api(base_url, user), params: params
- end
-
- let(:issue) { group_issue }
- let(:issue2) { group_issue2 }
- let(:label) { group_label }
-
- it_behaves_like 'labeled issues with labels and label_name params'
- end
-
context 'with archived projects' do
let_it_be(:archived_issue) do
create(
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 5d96e8048bf..212d39caf74 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -27,6 +27,8 @@ RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
FileUtils.rm_rf(tmp_rails_root)
end
+ let(:spec_filepath) { tmp_rails_root.join('spec', 'migrations', 'my_super_migration_spec.rb') }
+
context 'outside of a migration' do
it 'does not register any offenses' do
inspect_source(migration_code)
@@ -35,8 +37,6 @@ RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
end
end
- let(:spec_filepath) { tmp_rails_root.join('spec', 'migrations', 'my_super_migration_spec.rb') }
-
shared_context 'with a migration file' do
before do
FileUtils.mkdir_p(File.dirname(migration_filepath))
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index 35ce7c7175c..1357836cb89 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe PipelineDetailsEntity do
let_it_be(:user) { create(:user) }
let(:request) { double('request') }
+ let(:entity) do
+ described_class.represent(pipeline, request: request)
+ end
+
it 'inherrits from PipelineEntity' do
expect(described_class).to be < PipelineEntity
end
@@ -16,10 +20,6 @@ RSpec.describe PipelineDetailsEntity do
allow(request).to receive(:current_user).and_return(user)
end
- let(:entity) do
- described_class.represent(pipeline, request: request)
- end
-
describe '#as_json' do
subject { entity.as_json }
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb
index 1aabdb85afd..f26f9eeb6f0 100644
--- a/spec/services/ci/create_cross_project_pipeline_service_spec.rb
+++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Ci::CreateCrossProjectPipelineService, '#execute' do
+RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
let_it_be(:user) { create(:user) }
let(:upstream_project) { create(:project, :repository) }
let_it_be(:downstream_project) { create(:project, :repository) }
@@ -130,7 +130,7 @@ RSpec.describe Ci::CreateCrossProjectPipelineService, '#execute' do
expect(Gitlab::ErrorTracking)
.to receive(:track_exception)
.with(
- instance_of(Ci::CreateCrossProjectPipelineService::DuplicateDownstreamPipelineError),
+ instance_of(described_class::DuplicateDownstreamPipelineError),
bridge_id: bridge.id, project_id: bridge.project.id)
.and_call_original
expect(Ci::CreatePipelineService).not_to receive(:new)
@@ -397,7 +397,7 @@ RSpec.describe Ci::CreateCrossProjectPipelineService, '#execute' do
context 'when pipeline variables are defined' do
before do
- upstream_pipeline.variables.create(key: 'PIPELINE_VARIABLE', value: 'my-value')
+ upstream_pipeline.variables.create!(key: 'PIPELINE_VARIABLE', value: 'my-value')
end
it 'does not pass pipeline variables directly downstream' do
diff --git a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
index ff86d44f5e8..7e5a225f020 100644
--- a/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
+++ b/spec/support/shared_examples/controllers/unique_hll_events_examples.rb
@@ -1,29 +1,47 @@
# frozen_string_literal: true
-RSpec.shared_examples 'tracking unique hll events' do |method|
- it 'tracks unique event if the format is HTML' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(instance_of(String), target_id)
+RSpec.shared_examples 'tracking unique hll events' do |feature_flag|
+ context 'when format is HTML' do
+ let(:format) { :html }
- get method, params: request_params, format: :html
- end
+ it 'tracks unique event' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(expected_type, target_id)
- it 'tracks unique event if DNT is not enabled' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(instance_of(String), target_id)
- request.headers['DNT'] = '0'
+ subject
+ end
- get method, params: request_params, format: :html
- end
+ it 'tracks unique event if DNT is not enabled' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(expected_type, target_id)
+ request.headers['DNT'] = '0'
+
+ subject
+ end
+
+ it 'does not track unique event if DNT is enabled' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with(expected_type, target_id)
+ request.headers['DNT'] = '1'
- it 'does not track unique event if DNT is enabled' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with(instance_of(String), target_id)
- request.headers['DNT'] = '1'
+ subject
+ end
- get method, params: request_params, format: :html
+ context 'when feature flag is disabled' do
+ it 'does not track unique event' do
+ stub_feature_flags(feature_flag => false)
+
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with(expected_type, target_id)
+
+ subject
+ end
+ end
end
- it 'does not track unique event if the format is JSON' do
- expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with(instance_of(String), target_id)
+ context 'when format is JSON' do
+ let(:format) { :json }
+
+ it 'does not track unique event if the format is JSON' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with(expected_type, target_id)
- get method, params: request_params, format: :json
+ subject
+ end
end
end
diff --git a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
index 95dcf5624cc..116e6878281 100644
--- a/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
+++ b/spec/workers/ci/create_cross_project_pipeline_worker_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Ci::CreateCrossProjectPipelineWorker do
describe '#perform' do
context 'when bridge exists' do
it 'calls cross project pipeline creation service' do
- expect(Ci::CreateCrossProjectPipelineService)
+ expect(Ci::CreateDownstreamPipelineService)
.to receive(:new)
.with(project, user)
.and_return(service)
@@ -26,7 +26,7 @@ RSpec.describe Ci::CreateCrossProjectPipelineWorker do
context 'when bridge does not exist' do
it 'does nothing' do
- expect(Ci::CreateCrossProjectPipelineService)
+ expect(Ci::CreateDownstreamPipelineService)
.not_to receive(:new)
described_class.new.perform(non_existing_record_id)