diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 6609868689d115e3a3c45999622c41e29062304f..cafba1c61493ca04d16d27bc930fbfcae93695c7 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 e7ceb275bc47876ef10f6fd4bb6978cc5a082478..edc9f9d47b28aa4410fee31a3d900ccbb1c04b9c 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 e6ea441c95a62e457890931992fd5e19c682209b..15a22d1767c17e6912830f5872572b34f0ab6f1a 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 @@
@@ -109,25 +189,52 @@ export default {
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
-
-
+
+
+
+
+
+
+
+ |
+
+
+
+ |
/* 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"
/>
-
+ class="editing-spinner"
+ :label="__('Comment is being updated')"
+ />
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 7116a007ebeda9010aefc2ba41f3784fe82c2eab..e5f1fbff68781dbd2a567fe8b80c7bc0b53bd03d 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -1,9 +1,8 @@
-
+
@@ -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"
/>
-
+
diff --git a/app/assets/javascripts/packages/shared/components/package_tags.vue b/app/assets/javascripts/packages/shared/components/package_tags.vue
index f51ca26abf26c9e87df39ebfb951f83c03ee5954..3d7e233c1ba08bdd53b408f55c11868b497a9345 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"
>
{{ 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 }}
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 cd9ef74d46766b703db522ce5011a596a3a6f01a..62038f13dd99724a67c7853c270489612dd81494 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 {
-
+
-
+
-
+
-
- {{ packageEntity.pipeline.ref }}
+
+ {{ packageEntity.pipeline.ref }}
-
- {{ packageShaShort }}
+
+ {{
+ packageShaShort
+ }}
-
- {{ s__('PackageRegistry|Manually Published') }}
+
+
+ {{ s__('PackageRegistry|Manually Published') }}
+
diff --git a/app/assets/javascripts/reports/accessibility_report/store/index.js b/app/assets/javascripts/reports/accessibility_report/store/index.js
index 047964260adccf9f747b68cd8fa4896a58905ebd..9fa2c5893247ceb2f69f2014704a1ddc98502672 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 047964260adccf9f747b68cd8fa4896a58905ebd..9fa2c5893247ceb2f69f2014704a1ddc98502672 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 467c692b438b91f7aab292cbb9b6d65ecb5465c9..a2edfa94a48f07dfed2d059d19111fa16508c048 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 6818c8df6db8d38e712a7002e0cb1f3fa81b8ec2..b27602f6b4ae16a5d614d8e7a6bd8c6dbda42a01 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 8f6eee524e5e100a0ff4fdc5d87894ca5f498522..0000000000000000000000000000000000000000
--- 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 20482f309946ee4ab36f0af15098520ffdd57722..6792a27f2cd4c3fdbed65df6f952b219c20ca538 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 d969e7bf771c5bcada5fec5c3fa2a282a813e13d..cdcc069e1c4c35f38447ce6316ab7a81671f24af 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 02e800eb86fb3b714212ef9e022e5c122dec6edb..5f9a7fda02ad28634c5d2fd3a30217c02e8998fb 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 e726772fba4fca4c6dd19af4dcf3a082cfeec3e4..4358cf249f7fcab9b21aaa7f6ba58e9580a32944 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 a7cc367379daf445894e9a8ea2ada66298063503..dba9f99edeb7d9e8f08666962db7247395df3874 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 0000000000000000000000000000000000000000..1541738f46cb907b6ead7449d71e3d8d8696c495
--- /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 70c0794fc90b5f47d2c5c179501045288634daba..24faf1fe8bc823e9b9b408ba76eeb0543f401b6c 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 0000000000000000000000000000000000000000..1187b3352cdb17b22d06cee7d303e16ca57be112
--- /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 0000000000000000000000000000000000000000..40d065d8ea9c9cc0560dcfc863b78f08e76504b7
--- /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 0000000000000000000000000000000000000000..7be4a26d4facc206a3d5afe7d32d0d2c8a25f60b
--- /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
similarity index 94%
rename from app/services/ci/create_cross_project_pipeline_service.rb
rename to app/services/ci/create_downstream_pipeline_service.rb
index 23207d809d44e466e1969212654a4aee1204b7e7..23cd06fbd4f15f43587d112bd9663d4974a10d21 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 ad5d267698b1de30f41f37977c26ec353e692f80..7e16fc78599a4d5782627ae270da759827581391 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 22a0436c5fbd2f824cf4edae6f280686e78c73e0..35046d8776e0beef95bf920808b9997f93065626 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 0000000000000000000000000000000000000000..05b0b8e9fa6bf43011d83ff00c0292995c1b85f0
--- /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 aca5d28ca9879935e1f7ef3786e00131c2edce33..3df17021499454a58511b6d75df9a4c9b7503756 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 7eac45c00ca8891c021475e36cfcba49f521d44c..7f868b71734dff5db02097b1e83b718861f4d86b 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 68ad7f028e49bcdba0ab91842ff56def8531ce82..3999ccd33473e029e0dc9d6bf7bdf3114ca97771 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 1313fc80e3309c18010c0659990e70537011470b..e70552e8fa030bc07f77a8087c21f7aae5e029fd 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 786af3714a64f1d758e9264f8be17c8e3c09885d..194b10e9ef4f7c655976c0df977b2f2ed80dddf0 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 874adb197345e8c5bae696c6f606bc4a7e2effb0..f0a68512326f603d7f85c202554281c62af4416f 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 0f6188fa334d7c171530225275517e0e9eded134..96da513690824d8f5fa143ae36547e5eae35c157 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 713d0092b32ebca46d08a7e57013b3d8e730323f..679574d9f608dd4a1c01e10c4206e8f5ad46d6c6 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 0000000000000000000000000000000000000000..c506a08a936affd26e3de596bb02d4cee6b7f2c1
--- /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 0000000000000000000000000000000000000000..0aa4855afe5a8294fb1bcbe52944f5e371d19f05
--- /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 0000000000000000000000000000000000000000..6e32187e941af2329855e0adac40bf1bb261a05c
--- /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 0000000000000000000000000000000000000000..90878f07547dee6d34194fcb9d57beca817744c6
--- /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 0000000000000000000000000000000000000000..5fed11ec0576572116a6ed88c9c99130d65cd313
--- /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 0000000000000000000000000000000000000000..55d1f3d4c6d3c202c4302ba41f03d0d1e41b4098
--- /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 0000000000000000000000000000000000000000..5506b11d6f277abf2a36f7698c90c7824f31c087
--- /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 0000000000000000000000000000000000000000..9d3d9ea0b0a688b808a01af822b4665ee9431136
--- /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 0000000000000000000000000000000000000000..fca5fd58f1d41044c5724b781554c7671f51d67a
--- /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 0000000000000000000000000000000000000000..1c0943a9945f3285434c5c6214461e3a636293ee
--- /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 0000000000000000000000000000000000000000..76d5d98282afa1775d51fef61f185f11a97b3f8d
--- /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 0000000000000000000000000000000000000000..12db63b03fb895f31f33f41dabcfeb3425e34db2
--- /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 0000000000000000000000000000000000000000..e2066a1db42b289fbbd5a03e95bb323536893eff
--- /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 0000000000000000000000000000000000000000..da95f708cf9b888427f5a726f8c66c1771234d1d
--- /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 0000000000000000000000000000000000000000..6faa4fc810128d4948c2de81d174694bc0991c34
--- /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 0000000000000000000000000000000000000000..04c2c1632b125cac0b6cd5b3bb9ef259f3e6a2a3
--- /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 0000000000000000000000000000000000000000..39134fb6223210bdbbb9a7d743b5ffc8e2dd59dd
--- /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 0000000000000000000000000000000000000000..1ff87e8df6d6107f1cf4c63dc5a923027c1a8715
--- /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 b0837b8275a1effa34e210af07f7bbb6cf7491e4..893cb8d9e311e519e4b675f277d4b0f6b509a6ba 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 7ae683ba70558bb83f4788e064637b638680b7a0..34d26008972541fc15030a80107bf0d5d1705b34 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
@@ -1185,11 +1195,6 @@ input BoardEpicIssueInput {
weight: String
}
-"""
-Identifier of Board
-"""
-scalar BoardID
-
"""
Represents a list for an issue board
"""
@@ -1223,6 +1228,11 @@ type BoardList {
"""
before: String
+ """
+ Filters applied when selecting issues in the board list
+ """
+ filters: BoardIssueInput
+
"""
Returns the first _n_ elements from the list.
"""
@@ -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
@@ -6863,6 +6888,36 @@ type Group {
last: Int
): 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
"""
@@ -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 7dab2b15dcac248c127584687c7016c4bc01d900..fdffa81004458a8b9b88b276e61c5743f9cdd988 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
@@ -3041,9 +3041,19 @@
"enumValues": null,
"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,22 +3164,22 @@
"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,
"enumValues": null,
"possibleTypes": null
},
- {
- "kind": "SCALAR",
- "name": "BoardID",
- "description": "Identifier of Board",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": null,
- "possibleTypes": null
- },
{
"kind": "OBJECT",
"name": "BoardList",
@@ -3225,6 +3235,16 @@
"name": "issues",
"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.",
@@ -16457,6 +16477,29 @@
"enumValues": null,
"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",
@@ -18891,6 +18934,109 @@
"isDeprecated": false,
"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",
@@ -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 7ead6d397a233bd21360e8212b1be19566230786..9c88d8029870536443b84d85319c731d4e69f939 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 0000000000000000000000000000000000000000..eb72ef1de33fc76c17772fb8f516dc8dbd32a728
--- /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 e145bd2e9df95196b1e4fc6545005e087175db69..1fac00337a31945795a9735578d3de611de988c5 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 b2c296cc3d5585d32e1a09b5601781f4ffe99b8c..22c344a27f7d1abc7dbe1ee70e164a39a01eafaa 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 df6345cba625fd4887bb6a488692bb4e297f22bc..01dd4e7b62e8b463dfa1a720fd77d26ee13870e9 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 ceafa34b1c2f72d0853c881ae8241580ca4521bb..f6e40436ec8b8145eea1ea309fcbb767c0f09366 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 9fee97f938ce2ecda32a4dc2a85460bb8283ef31..b998dee09b285788e35b05ec39aca5abd9423c12 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 4d325f55fb0771b3398ab7e980e9e9ecf3476e69..e85330b08f6c6698b9b1540ee1d7a6c9aaaab5f0 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 9cb4f8c9fc168e07c3f159ef0ca0455683e89fe9..303bfc59f61f515eb0d8d67835dd36b912149c8a 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 5b86c891e477eb393d96b86e9650fc26f1f4029f..4f86323c7c647f4b98f3cfb99f1949615278d9ca 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 f929f97b59831d50060ae79fee0bdeb7b2c9de4f..951b3f6258b9d77369e1e72a75b174452eab39cc 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: '+ - Bad dates\n',
+ rich_text: '+ - Bad dates\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 172b8919673a48e3fa14b2dc0d340908e66aa538..4f376a5d58f3315063f650b164854026f83adb54 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"
>
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 28b7ca442ebe8f7fff0eb8312073ce37c1289f79..df2844eaa3cd8911b82434276aea6f2e7df02b9c 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`] = `
branch-name
sha-baz
{
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 a036588596a59b185b1fb521bce96d67d8df99f2..ccceb78f2d1caccdb7cfc06aa3791e129741a3fa 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 c9bd040aaf53aeb8e30f429190951710b9e5b2c4..77d7c6f8678211b1e36f1c55f1d7e46d76a31ecb 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 c26e2fbc19a9db2612a5aa6673a475c2f73c6dd1..556904b7da59d5194eb5f8153fee0ef1fdad4aa3 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 e23a37b3d696bbdc3dfc4983ab5b15066b84af17..4ccf194522fc378a98850ad8f0790c51cfa7e741 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 a548589d269ad45c4280398c4a895b7cc22fe71d..aecffc487aaf61d7456eaf63c86f22019bc2daa2 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 0000000000000000000000000000000000000000..6319ff9a88e2d6a025d8fb72be1779783489ff72
--- /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 0ad058675fea5337754de0b1abfa06d245c0f76c..447802d18a719e8a9e05eeca819472a7d327fa52 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) { %{Reference} }
+ 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) { %{Reference} }
+ 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 d8de3e5cc11157631953617eb719e5a9ed9806b4..b8baccf665839d75786e83937e311a78d74672fc 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 ee2173a9c8d42f490419a1124df013317ee49b4d..1a7d837af73bf359f1906b2f000be9ea3b175594 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 75d5fc040cb1bacea19a321d36f6b6a0c80033c0..37ee4591db086f60f207ed90df407739d76e9991 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 e5598bbd10f1f75a6958e0cc8833ca71b08f773e..07b8070be306c0c66abebd4c4bff1b582585ed25 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 c80e15055c6012a43b601330c0a544adf967d6b0..a119c85401d66f8bf9889f8079732a03a66df411 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 3fb8708c884ad19bfb257270b543d16ca83d7f3d..334833e884b51e1ab8a34b9472726ca1b65d1ec4 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 b52b035e1302e30acffb6685f967124f0a03d5c2..e611484f5eea8891a2fa2584545d96ef4071bce3 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 f710385b6e239c3bc754912ba740bfce2f698be4..35f282b638b4f8bbcf1d8c3da8c3f75002c7d33c 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 ae1abb50a40e9e77b9a333d3b1d9c1ea0cc3b270..3628171fcc1dea1611dc52146e2fb64ec83a90d4 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 9446e10c01da770d8fc31a9644b0dc763441c986..22b003501a19895e05842cd8b9c6e0dd9574cfa5 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 b0fbf3bf66d5eda968ebe9907d1de426b1188750..3870c78deee95c14420b8160d23fe4226db32de3 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 5d96e8048bf17baa9f20332715371133f00851fc..212d39caf748c125f04ebb8c196fbded64914b9c 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 35ce7c7175c2be13b75d12f26b533e2c1fdcd351..1357836cb894b75be9b4aea7f318e3133b099c8d 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
similarity index 98%
rename from spec/services/ci/create_cross_project_pipeline_service_spec.rb
rename to spec/services/ci/create_downstream_pipeline_service_spec.rb
index 1aabdb85afda300ad70cc1e3fe3c6ac4d4e8cc02..f26f9eeb6f0f0775dadb6a15aa8126e335c600d5 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 ff86d44f5e8d53d959e5c3ab834ec579ca584296..7e5a225f02096e82035fc92c71e8313cfcede253 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 95dcf5624cced7e1bf046647bb2075d7a1de22a6..116e6878281e27b91f63c582d23b356709765d6e 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)
|