Commit 072787ba authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents ebc95134 1949b6bb
......@@ -818,14 +818,6 @@ Style/RescueModifier:
Style/RescueStandardError:
Enabled: false
# Offense count: 4
# Cop supports --auto-correct.
Style/SelfAssignment:
Exclude:
- 'app/models/concerns/bulk_member_access_load.rb'
- 'app/serializers/base_serializer.rb'
- 'spec/support/import_export/configuration_helper.rb'
# Offense count: 50
# Cop supports --auto-correct.
# Configuration parameters: AllowIfMethodIsEmpty.
......
......@@ -37,6 +37,11 @@ export default {
required: false,
default: null,
},
gitlabIssuesEnabled: {
type: Boolean,
required: false,
default: true,
},
upgradePlanPath: {
type: String,
required: false,
......@@ -133,7 +138,7 @@ export default {
:disabled="!enableJiraIssues"
/>
</gl-form-group>
<p>
<p v-if="gitlabIssuesEnabled">
<gl-sprintf
:message="
s__(
......
......@@ -34,6 +34,7 @@ function parseDatasetToProps(data) {
enableComments,
showJiraIssuesIntegration,
enableJiraIssues,
gitlabIssuesEnabled,
} = parseBooleanInData(booleanAttributes);
return {
......@@ -50,6 +51,7 @@ function parseDatasetToProps(data) {
showJiraIssuesIntegration,
initialEnableJiraIssues: enableJiraIssues,
initialProjectKey: projectKey,
gitlabIssuesEnabled,
upgradePlanPath,
editProjectPath,
},
......
......@@ -158,7 +158,7 @@ export default {
value: this.issuable.merge_requests_count,
title: __('Related merge requests'),
dataTestId: 'merge-requests',
class: 'js-merge-requests icon-merge-request-unmerged',
class: 'js-merge-requests',
icon: 'merge-request',
},
{
......
......@@ -22,7 +22,7 @@ module BulkMemberAccessLoad
end
# Look up only the IDs we need
resource_ids = resource_ids - access.keys
resource_ids -= access.keys
return access if resource_ids.empty?
......
# frozen_string_literal: true
module FromExcept
extend ActiveSupport::Concern
class_methods do
# Produces a query that uses a FROM to select data using an EXCEPT.
#
# Example:
# groups = Group.from_except([group1.self_and_hierarchy, group2.self_and_hierarchy])
#
# This would produce the following SQL query:
#
# SELECT *
# FROM (
# SELECT "namespaces". *
# ...
#
# EXCEPT
#
# SELECT "namespaces". *
# ...
# ) groups;
#
# members - An Array of ActiveRecord::Relation objects to use in the except.
#
# remove_duplicates - A boolean indicating if duplicate entries should be
# removed. Defaults to true.
#
# alias_as - The alias to use for the sub query. Defaults to the name of the
# table of the current model.
# rubocop: disable Gitlab/Except
extend FromSetOperator
define_set_operator Gitlab::SQL::Except
# rubocop: enable Gitlab/Except
end
end
# frozen_string_literal: true
module FromIntersect
extend ActiveSupport::Concern
class_methods do
# Produces a query that uses a FROM to select data using an INTERSECT.
#
# Example:
# groups = Group.from_intersect([group1.self_and_hierarchy, group2.self_and_hierarchy])
#
# This would produce the following SQL query:
#
# SELECT *
# FROM (
# SELECT "namespaces". *
# ...
#
# INTERSECT
#
# SELECT "namespaces". *
# ...
# ) groups;
#
# members - An Array of ActiveRecord::Relation objects to use in the intersect.
#
# remove_duplicates - A boolean indicating if duplicate entries should be
# removed. Defaults to true.
#
# alias_as - The alias to use for the sub query. Defaults to the name of the
# table of the current model.
# rubocop: disable Gitlab/Intersect
extend FromSetOperator
define_set_operator Gitlab::SQL::Intersect
# rubocop: enable Gitlab/Intersect
end
end
# frozen_string_literal: true
module FromSetOperator
# Define a high level method to more easily work with the SQL set operations
# of UNION, INTERSECT, and EXCEPT as defined by Gitlab::SQL::Union,
# Gitlab::SQL::Intersect, and Gitlab::SQL::Except respectively.
def define_set_operator(operator)
method_name = 'from_' + operator.name.demodulize.downcase
method_name = method_name.to_sym
raise "Trying to redefine method '#{method(method_name)}'" if methods.include?(method_name)
define_method(method_name) do |members, remove_duplicates: true, alias_as: table_name|
operator_sql = operator.new(members, remove_duplicates: remove_duplicates).to_sql
from(Arel.sql("(#{operator_sql}) #{alias_as}"))
end
end
end
......@@ -35,13 +35,29 @@ module FromUnion
# alias_as - The alias to use for the sub query. Defaults to the name of the
# table of the current model.
# rubocop: disable Gitlab/Union
extend FromSetOperator
define_set_operator Gitlab::SQL::Union
alias_method :from_union_set_operator, :from_union
def from_union(members, remove_duplicates: true, alias_as: table_name)
if Feature.enabled?(:sql_set_operators)
from_union_set_operator(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
else
# The original from_union method.
standard_from_union(members, remove_duplicates: remove_duplicates, alias_as: alias_as)
end
end
private
def standard_from_union(members, remove_duplicates: true, alias_as: table_name)
union = Gitlab::SQL::Union
.new(members, remove_duplicates: remove_duplicates)
.to_sql
from(Arel.sql("(#{union}) #{alias_as}"))
end
# rubocop: enable Gitlab/Union
end
end
......@@ -9,7 +9,7 @@ class BaseSerializer
end
def represent(resource, opts = {}, entity_class = nil)
entity_class = entity_class || self.class.entity_class
entity_class ||= self.class.entity_class
entity_class
.represent(resource, opts.merge(request: @request))
......
---
title: Adds creator_id field to packages_packages table
merge_request: 40562
author:
type: other
---
title: Fix Style/SelfAssignment cop
merge_request: 41079
author: Rajendra Kadam
type: fixed
---
name: sql-set-operators
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39786
group: group::access
type: development
default_enabled: false
# frozen_string_literal: true
class AddCreatorIdToPackages < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column(:packages_packages, :creator_id, :integer)
end
end
# frozen_string_literal: true
class AddIndexToPackageCreator < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
INDEX_NAME = 'index_packages_packages_on_creator_id'
def up
add_concurrent_index :packages_packages, :creator_id, name: INDEX_NAME
add_concurrent_foreign_key(:packages_packages, :users, column: :creator_id, on_delete: :nullify)
end
def down
remove_foreign_key_if_exists(:packages_packages, :users, column: :creator_id)
remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
end
end
f4f1efcc93476a1d70add93e166f4c702ad7dfc97ad29c3455722fd98824498f
\ No newline at end of file
1e8dd4542b13009b748d352933a4a59fcabb31e916226fcbf87043396f94e09f
\ No newline at end of file
......@@ -14025,7 +14025,8 @@ CREATE TABLE public.packages_packages (
updated_at timestamp with time zone NOT NULL,
name character varying NOT NULL,
version character varying,
package_type smallint NOT NULL
package_type smallint NOT NULL,
creator_id integer
);
CREATE SEQUENCE public.packages_packages_id_seq
......@@ -20457,6 +20458,8 @@ CREATE INDEX index_packages_package_files_on_file_store ON public.packages_packa
CREATE INDEX index_packages_package_files_on_package_id_and_file_name ON public.packages_package_files USING btree (package_id, file_name);
CREATE INDEX index_packages_packages_on_creator_id ON public.packages_packages USING btree (creator_id);
CREATE INDEX index_packages_packages_on_name_trigram ON public.packages_packages USING gin (name public.gin_trgm_ops);
CREATE INDEX index_packages_packages_on_project_id_and_created_at ON public.packages_packages USING btree (project_id, created_at);
......@@ -22039,6 +22042,9 @@ ALTER TABLE ONLY public.ci_builds
ALTER TABLE ONLY public.design_management_versions
ADD CONSTRAINT fk_c1440b4896 FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE SET NULL;
ALTER TABLE ONLY public.packages_packages
ADD CONSTRAINT fk_c188f0dba4 FOREIGN KEY (creator_id) REFERENCES public.users(id) ON DELETE SET NULL;
ALTER TABLE ONLY public.geo_event_log
ADD CONSTRAINT fk_c1f241c70d FOREIGN KEY (upload_deleted_event_id) REFERENCES public.geo_upload_deleted_events(id) ON DELETE CASCADE;
......
......@@ -17410,6 +17410,11 @@ type Vulnerability {
"""
description: String
"""
Timestamp of when the vulnerability was first detected
"""
detectedAt: Time!
"""
GraphQL ID of the vulnerability
"""
......
......@@ -51106,6 +51106,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "detectedAt",
"description": "Timestamp of when the vulnerability was first detected",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "GraphQL ID of the vulnerability",
......@@ -2580,6 +2580,7 @@ Represents a vulnerability.
| Name | Type | Description |
| --- | ---- | ---------- |
| `description` | String | Description of the vulnerability |
| `detectedAt` | Time! | Timestamp of when the vulnerability was first detected |
| `id` | ID! | GraphQL ID of the vulnerability |
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability |
......
......@@ -66,5 +66,9 @@ module Types
description: 'The project on which the vulnerability was found',
authorize: :read_project,
resolve: -> (obj, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.project_id).find }
field :detected_at, Types::TimeType, null: false,
description: 'Timestamp of when the vulnerability was first detected',
method: :created_at
end
end
......@@ -18,6 +18,7 @@ module EE
show_jira_issues_integration: @project&.feature_available?(:jira_issues_integration).to_s,
enable_jira_issues: integration.issues_enabled.to_s,
project_key: integration.project_key,
gitlab_issues_enabled: @project&.issues_enabled?.to_s,
upgrade_plan_path: @project && ::Gitlab::CurrentSettings.should_check_namespace_plan? ? upgrade_plan_path(@project.group) : nil,
edit_project_path: @project ? edit_project_path(@project, anchor: 'js-shared-permissions') : nil
)
......
---
title: Hide warning to disable GitLab issues in Jira integration form
merge_request: 40248
author:
type: changed
---
title: Introduce `detectedAt` field for VulnerabilityType on GraphQL API
merge_request: 41000
author:
type: added
......@@ -6,9 +6,24 @@ RSpec.describe GitlabSchema.types['Vulnerability'] do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
let(:fields) do
%i[userPermissions id title description user_notes_count state severity report_type resolved_on_default_branch vulnerability_path location scanner primary_identifier identifiers project issueLinks]
let_it_be(:fields) do
%i[userPermissions
id
title
description
user_notes_count
state
severity
report_type
resolved_on_default_branch
vulnerability_path
location
scanner
primary_identifier
identifiers
project
issueLinks
detected_at]
end
before do
......
......@@ -20,19 +20,31 @@ RSpec.describe EE::ServicesHelper do
describe '#integration_form_data' do
subject { helper.integration_form_data(integration) }
before do
assign(:project, project)
end
context 'Slack service' do
let(:integration) { build(:slack_service) }
it 'does not include Jira specific fields' do
is_expected.not_to include(:show_jira_issues_integration, :enable_jira_issues, :project_key, :edit_project_path)
is_expected.not_to include(:show_jira_issues_integration, :enable_jira_issues, :project_key, :gitlab_issues_enabled, :edit_project_path)
end
end
context 'Jira service' do
let(:integration) { build(:jira_service) }
let(:integration) { build(:jira_service, issues_enabled: true, project_key: 'FE') }
it 'includes Jira specific fields' do
is_expected.to include(:show_jira_issues_integration, :enable_jira_issues, :project_key, :edit_project_path)
stub_licensed_features(jira_issues_integration: true)
is_expected.to include(
show_jira_issues_integration: 'true',
enable_jira_issues: 'true',
project_key: 'FE',
gitlab_issues_enabled: 'true',
edit_project_path: edit_project_path(project, anchor: 'js-shared-permissions')
)
end
end
end
......
# frozen_string_literal: true
module Gitlab
module SQL
# Class for building SQL EXCEPT statements.
#
# ORDER BYs are dropped from the relations as the final sort order is not
# guaranteed any way.
#
# Example usage:
#
# except = Gitlab::SQL::Except.new([user.projects, user.personal_projects])
# sql = except.to_sql
#
# Project.where("id IN (#{sql})")
class Except < SetOperator
def self.operator_keyword
'EXCEPT'
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SQL
# Class for building SQL INTERSECT statements.
#
# ORDER BYs are dropped from the relations as the final sort order is not
# guaranteed any way.
#
# Example usage:
#
# hierarchies = [group1.self_and_hierarchy, group2.self_and_hierarchy]
# intersect = Gitlab::SQL::Intersect.new(hierarchies)
# sql = intersect.to_sql
#
# Project.where("id IN (#{sql})")
class Intersect < SetOperator
def self.operator_keyword
'INTERSECT'
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SQL
# Class for building SQL set operator statements (UNION, INTERSECT, and
# EXCEPT).
#
# ORDER BYs are dropped from the relations as the final sort order is not
# guaranteed any way.
#
# Example usage:
#
# union = Gitlab::SQL::Union.new([user.personal_projects, user.projects])
# sql = union.to_sql
#
# Project.where("id IN (#{sql})")
class SetOperator
def initialize(relations, remove_duplicates: true)
@relations = relations
@remove_duplicates = remove_duplicates
end
def self.operator_keyword
raise NotImplementedError
end
def to_sql
# Some relations may include placeholders for prepared statements, these
# aren't incremented properly when joining relations together this way.
# By using "unprepared_statements" we remove the usage of placeholders
# (thus fixing this problem), at a slight performance cost.
fragments = ActiveRecord::Base.connection.unprepared_statement do
relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
end
if fragments.any?
"(" + fragments.join(")\n#{operator_keyword_fragment}\n(") + ")"
else
'NULL'
end
end
# UNION [ALL] | INTERSECT [ALL] | EXCEPT [ALL]
def operator_keyword_fragment
remove_duplicates ? self.class.operator_keyword : "#{self.class.operator_keyword} ALL"
end
private
attr_reader :relations, :remove_duplicates
end
end
end
......@@ -13,30 +13,9 @@ module Gitlab
# sql = union.to_sql
#
# Project.where("id IN (#{sql})")
class Union
def initialize(relations, remove_duplicates: true)
@relations = relations
@remove_duplicates = remove_duplicates
end
def to_sql
# Some relations may include placeholders for prepared statements, these
# aren't incremented properly when joining relations together this way.
# By using "unprepared_statements" we remove the usage of placeholders
# (thus fixing this problem), at a slight performance cost.
fragments = ActiveRecord::Base.connection.unprepared_statement do
@relations.map { |rel| rel.reorder(nil).to_sql }.reject(&:blank?)
end
if fragments.any?
"(" + fragments.join(")\n#{union_keyword}\n(") + ")"
else
'NULL'
end
end
def union_keyword
@remove_duplicates ? 'UNION' : 'UNION ALL'
class Union < SetOperator
def self.operator_keyword
'UNION'
end
end
end
......
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# Cop that disallows the use of `Gitlab::SQL::Except`, in favour of using
# the `FromExcept` module.
class Except < RuboCop::Cop::Cop
MSG = 'Use the `FromExcept` concern, instead of using `Gitlab::SQL::Except` directly'
def_node_matcher :raw_except?, <<~PATTERN
(send (const (const (const nil? :Gitlab) :SQL) :Except) :new ...)
PATTERN
def on_send(node)
return unless raw_except?(node)
add_offense(node, location: :expression)
end
end
end
end
end
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# Cop that disallows the use of `Gitlab::SQL::Intersect`, in favour of using
# the `FromIntersect` module.
class Intersect < RuboCop::Cop::Cop
MSG = 'Use the `FromIntersect` concern, instead of using `Gitlab::SQL::Intersect` directly'
def_node_matcher :raw_intersect?, <<~PATTERN
(send (const (const (const nil? :Gitlab) :SQL) :Intersect) :new ...)
PATTERN
def on_send(node)
return unless raw_intersect?(node)
add_offense(node, location: :expression)
end
end
end
end
end
......@@ -51,8 +51,8 @@ RSpec.describe 'issuable list', :js do
it "counts merge requests closing issues icons for each issue" do
visit_issuable_list(:issue)
expect(page).to have_selector('.icon-merge-request-unmerged', count: 1)
expect(first('.icon-merge-request-unmerged').find(:xpath, '..')).to have_content(1)
expect(page).to have_selector('[data-testid="merge-requests"]', count: 1)
expect(first('[data-testid="merge-requests"]').find(:xpath, '..')).to have_content(1)
end
def visit_issuable_list(issuable_type)
......
......@@ -92,5 +92,21 @@ describe('JiraIssuesFields', () => {
expect(wrapper.find(`a[href="${defaultProps.editProjectPath}"]`).exists()).toBe(true);
});
describe('GitLab issues warning', () => {
const expectedText = 'Consider disabling GitLab issues';
it('contains warning when GitLab issues is enabled', () => {
createComponent();
expect(wrapper.text()).toContain(expectedText);
});
it('does not contain warning when GitLab issues is disabled', () => {
createComponent({ gitlabIssuesEnabled: false });
expect(wrapper.text()).not.toContain(expectedText);
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::SQL::Except do
it_behaves_like 'SQL set operator', 'EXCEPT'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::SQL::Intersect do
it_behaves_like 'SQL set operator', 'INTERSECT'
end
......@@ -3,40 +3,5 @@
require 'spec_helper'
RSpec.describe Gitlab::SQL::Union do
let(:relation_1) { User.where(email: 'alice@example.com').select(:id) }
let(:relation_2) { User.where(email: 'bob@example.com').select(:id) }
def to_sql(relation)
relation.reorder(nil).to_sql
end
describe '#to_sql' do
it 'returns a String joining relations together using a UNION' do
union = described_class.new([relation_1, relation_2])
expect(union.to_sql).to eq("(#{to_sql(relation_1)})\nUNION\n(#{to_sql(relation_2)})")
end
it 'skips Model.none segements' do
empty_relation = User.none
union = described_class.new([empty_relation, relation_1, relation_2])
expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
expect(union.to_sql).to eq("(#{to_sql(relation_1)})\nUNION\n(#{to_sql(relation_2)})")
end
it 'uses UNION ALL when removing duplicates is disabled' do
union = described_class
.new([relation_1, relation_2], remove_duplicates: false)
expect(union.to_sql).to include('UNION ALL')
end
it 'returns `NULL` if all relations are empty' do
empty_relation = User.none
union = described_class.new([empty_relation, empty_relation])
expect(union.to_sql).to eq('NULL')
end
end
it_behaves_like 'SQL set operator', 'UNION'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe FromExcept do
it_behaves_like 'from set operator', Gitlab::SQL::Except
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe FromIntersect do
it_behaves_like 'from set operator', Gitlab::SQL::Intersect
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe FromSetOperator do
describe 'when set operator method already exists' do
let(:redefine_method) do
Class.new do
def self.from_union
# This method intentionally left blank.
end
extend FromSetOperator
define_set_operator Gitlab::SQL::Union
end
end
it { expect { redefine_method }.to raise_exception(RuntimeError) }
end
end
......@@ -3,38 +3,13 @@
require 'spec_helper'
RSpec.describe FromUnion do
describe '.from_union' do
let(:model) do
Class.new(ActiveRecord::Base) do
self.table_name = 'users'
include FromUnion
[true, false].each do |sql_set_operator|
context "when sql-set-operators feature flag is #{sql_set_operator}" do
before do
stub_feature_flags(sql_set_operators: sql_set_operator)
end
end
it 'selects from the results of the UNION' do
query = model.from_union([model.where(id: 1), model.where(id: 2)])
expect(query.to_sql).to match(/FROM \(\(SELECT.+\)\nUNION\n\(SELECT.+\)\) users/m)
end
it 'supports the use of a custom alias for the sub query' do
query = model.from_union(
[model.where(id: 1), model.where(id: 2)],
alias_as: 'kittens'
)
expect(query.to_sql).to match(/FROM \(\(SELECT.+\)\nUNION\n\(SELECT.+\)\) kittens/m)
end
it 'supports keeping duplicate rows' do
query = model.from_union(
[model.where(id: 1), model.where(id: 2)],
remove_duplicates: false
)
expect(query.to_sql)
.to match(/FROM \(\(SELECT.+\)\nUNION ALL\n\(SELECT.+\)\) users/m)
it_behaves_like 'from set operator', Gitlab::SQL::Union
end
end
end
......@@ -261,7 +261,7 @@ RSpec.describe 'getting an issue list for a project' do
<<~QUERY
edges {
node {
id
iid
alertManagementAlert {
title
}
......@@ -270,9 +270,9 @@ RSpec.describe 'getting an issue list for a project' do
QUERY
end
# Alerts need to reporter and above
# Alerts need to have developer permission and above
before do
project.add_reporter(current_user)
project.add_developer(current_user)
end
it 'avoids N+1 queries' do
......@@ -286,7 +286,10 @@ RSpec.describe 'getting an issue list for a project' do
it 'returns the alert data' do
post_graphql(query, current_user: current_user)
issues_data
alert_titles = issues_data.map { |issue| issue.dig('node', 'alertManagementAlert', 'title') }
expected_titles = issues.map { |issue| issue.alert_management_alert&.title }
expect(alert_titles).to contain_exactly(*expected_titles)
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/except'
RSpec.describe RuboCop::Cop::Gitlab::Except, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
it 'flags the use of Gitlab::SQL::Except.new' do
expect_offense(<<~SOURCE)
Gitlab::SQL::Except.new([foo])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `FromExcept` concern, instead of using `Gitlab::SQL::Except` directly
SOURCE
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/intersect'
RSpec.describe RuboCop::Cop::Gitlab::Intersect, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
it 'flags the use of Gitlab::SQL::Intersect.new' do
expect_offense(<<~SOURCE)
Gitlab::SQL::Intersect.new([foo])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `FromIntersect` concern, instead of using `Gitlab::SQL::Intersect` directly
SOURCE
end
end
......@@ -44,8 +44,8 @@ module ConfigurationHelper
import_export_config = config_hash(config)
excluded_attributes = import_export_config[:excluded_attributes][relation_name.to_sym]
included_attributes = import_export_config[:included_attributes][relation_name.to_sym]
attributes = attributes - Gitlab::Json.parse(excluded_attributes.to_json) if excluded_attributes
attributes = attributes & Gitlab::Json.parse(included_attributes.to_json) if included_attributes
attributes -= Gitlab::Json.parse(excluded_attributes.to_json) if excluded_attributes
attributes &= Gitlab::Json.parse(included_attributes.to_json) if included_attributes
attributes
end
......
# frozen_string_literal: true
RSpec.shared_examples 'SQL set operator' do |operator_keyword|
operator_keyword = operator_keyword.upcase
let(:relation_1) { User.where(email: 'alice@example.com').select(:id) }
let(:relation_2) { User.where(email: 'bob@example.com').select(:id) }
def to_sql(relation)
relation.reorder(nil).to_sql
end
describe '.operator_keyword' do
it { expect(described_class.operator_keyword).to eq operator_keyword }
end
describe '#to_sql' do
it "returns a String joining relations together using a #{operator_keyword}" do
set_operator = described_class.new([relation_1, relation_2])
expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})")
end
it 'skips Model.none segements' do
empty_relation = User.none
set_operator = described_class.new([empty_relation, relation_1, relation_2])
expect {User.where("users.id IN (#{set_operator.to_sql})").to_a}.not_to raise_error
expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})")
end
it "uses #{operator_keyword} ALL when removing duplicates is disabled" do
set_operator = described_class
.new([relation_1, relation_2], remove_duplicates: false)
expect(set_operator.to_sql).to include("#{operator_keyword} ALL")
end
it 'returns `NULL` if all relations are empty' do
empty_relation = User.none
set_operator = described_class.new([empty_relation, empty_relation])
expect(set_operator.to_sql).to eq('NULL')
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'from set operator' do |sql_klass|
from_set_operator_concern = described_class
operator_keyword = sql_klass.operator_keyword
operator_method = "from_#{sql_klass.operator_keyword.downcase}"
describe "##{operator_method}" do
let(:model) do
Class.new(ActiveRecord::Base) do
self.table_name = 'users'
include from_set_operator_concern
end
end
it "selects from the results of the #{operator_keyword}" do
query = model.public_send(operator_method, [model.where(id: 1), model.where(id: 2)])
expect(query.to_sql).to match(/FROM \(\(SELECT.+\)\n#{operator_keyword}\n\(SELECT.+\)\) users/m)
end
it 'supports the use of a custom alias for the sub query' do
query = model.public_send(operator_method,
[model.where(id: 1), model.where(id: 2)],
alias_as: 'kittens'
)
expect(query.to_sql).to match(/FROM \(\(SELECT.+\)\n#{operator_keyword}\n\(SELECT.+\)\) kittens/m)
end
it 'supports keeping duplicate rows' do
query = model.public_send(operator_method,
[model.where(id: 1), model.where(id: 2)],
remove_duplicates: false
)
expect(query.to_sql)
.to match(/FROM \(\(SELECT.+\)\n#{operator_keyword} ALL\n\(SELECT.+\)\) users/m)
end
end
end
......@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.161.0.tgz#661e8d19862dfba0e4c558e2eb6d64b402c1453e"
integrity sha512-qsbboEICn08ZoEoAX/TuYygsFaXlzsCY+CfmdOzqvJbOdfHhVXmrJBxd2hP2qqjTZm2PkbRRmn+03+ce1jvatQ==
"@gitlab/ui@20.13.0":
version "20.13.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.13.0.tgz#03e50f42ff25777f0538cea4348b40700c73a247"
integrity sha512-00NxjSmFS78rUNtcqhKfmf9Ip8YjBMoO3JHY8a6sacYVJ+a+UKHvsi1ksCn7F5elSIZIuVmngSvIebw0LUqvpw==
"@gitlab/ui@20.16.0":
version "20.16.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-20.16.0.tgz#45bad5500e6f47bc4bebb95a17b446f254f9ee21"
integrity sha512-k1+X8RgBXknCu40dEWTjE0Sd9jzGmpoPWPlgmoo28tE1vewhRmCOa8aL0tAcm6VNg5av5ZEqoQEJvgFKxLQB1Q==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment