Commit 0355e90a authored by Mark Chao's avatar Mark Chao

ES: Decouple other ActiveRecord models

See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14428
parent 61a2d19f
......@@ -100,11 +100,7 @@ def instrument_classes(instrumentation)
instrumentation.instrument_instance_methods(Gitlab::Elastic::SnippetSearchResults)
instrumentation.instrument_methods(Gitlab::Elastic::Helper)
instrumentation.instrument_instance_methods(Elastic::ApplicationSearch)
instrumentation.instrument_instance_methods(Elastic::IssuesSearch)
instrumentation.instrument_instance_methods(Elastic::MergeRequestsSearch)
instrumentation.instrument_instance_methods(Elastic::MilestonesSearch)
instrumentation.instrument_instance_methods(Elastic::NotesSearch)
instrumentation.instrument_instance_methods(Elastic::ApplicationVersionedSearch)
instrumentation.instrument_instance_methods(Elastic::ProjectsSearch)
instrumentation.instrument_instance_methods(Elastic::RepositoriesSearch)
instrumentation.instrument_instance_methods(Elastic::SnippetsSearch)
......
This diff is collapsed.
# frozen_string_literal: true
module Elastic
module IssuesSearch
extend ActiveSupport::Concern
included do
include ApplicationSearch
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
[:id, :iid, :title, :description, :created_at, :updated_at, :state, :project_id, :author_id, :confidential].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
data['assignee_id'] = safely_read_attribute_for_elasticsearch(:assignee_ids)
data.merge(generic_attributes)
end
def self.nested?
true
end
def self.elastic_search(query, options: {})
query_hash =
if query =~ /#(\d+)\z/
iid_query_hash(Regexp.last_match(1))
else
basic_query_hash(%w(title^2 description), query)
end
options[:features] = 'issues'
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user])
self.__elasticsearch__.search(query_hash)
end
def self.confidentiality_filter(query_hash, current_user)
return query_hash if current_user && current_user.full_private_access?
filter = if current_user
{
bool: {
should: [
{ term: { confidential: false } },
{
bool: {
must: [
{ term: { confidential: true } },
{
bool: {
should: [
{ term: { author_id: current_user.id } },
{ term: { assignee_id: current_user.id } },
{ terms: { project_id: current_user.authorized_projects(Gitlab::Access::REPORTER).pluck(:id) } }
]
}
}
]
}
}
]
}
}
else
{ term: { confidential: false } }
end
query_hash[:query][:bool][:filter] << filter
query_hash
end
end
end
end
......@@ -4,13 +4,7 @@ module Elastic
module ProjectsSearch
extend ActiveSupport::Concern
TRACKED_FEATURE_SETTINGS = %w(
issues_access_level
merge_requests_access_level
snippets_access_level
wiki_access_level
repository_access_level
).freeze
include ApplicationVersionedSearch
INDEXED_ASSOCIATIONS = [
:issues,
......@@ -21,97 +15,10 @@ module Elastic
].freeze
included do
include ApplicationSearch
def use_elasticsearch?
::Gitlab::CurrentSettings.elasticsearch_indexes_project?(self)
end
def as_indexed_json(options = {})
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
data = {}
[
:id,
:name,
:path,
:description,
:namespace_id,
:created_at,
:updated_at,
:archived,
:visibility_level,
:last_activity_at,
:name_with_namespace,
:path_with_namespace
].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
# Set it as a parent in our `project => child` JOIN field
data['join_field'] = es_type
# ES6 is now single-type per index, so we implement our own typing
data['type'] = 'project'
TRACKED_FEATURE_SETTINGS.each do |feature|
data[feature] = project_feature.public_send(feature) # rubocop:disable GitlabSecurity/PublicSend
end
data
end
def self.elastic_search(query, options: {})
options[:in] = %w(name^10 name_with_namespace^2 path_with_namespace path^9 description)
query_hash = basic_query_hash(options[:in], query)
filters = []
if options[:namespace_id]
filters << {
terms: {
namespace_id: [options[:namespace_id]].flatten
}
}
end
if options[:non_archived]
filters << {
terms: {
archived: [!options[:non_archived]].flatten
}
}
end
if options[:visibility_levels]
filters << {
terms: {
visibility_level: [options[:visibility_levels]].flatten
}
}
end
if options[:project_ids]
filters << {
bool: project_ids_query(options[:current_user], options[:project_ids], options[:public_and_internal_projects])
}
end
query_hash[:query][:bool][:filter] = filters
query_hash[:sort] = [:_score]
self.__elasticsearch__.search(query_hash)
end
def self.indexed_association_classes
INDEXED_ASSOCIATIONS.map do |association_name|
reflect_on_association(association_name).klass
end
end
def each_indexed_association
INDEXED_ASSOCIATIONS.each do |association_name|
association = self.association(association_name)
......
......@@ -11,7 +11,7 @@ module EE
WEIGHT_ANY = 'Any'.freeze
WEIGHT_NONE = 'None'.freeze
include Elastic::IssuesSearch
include Elastic::ApplicationVersionedSearch
scope :order_weight_desc, -> { reorder ::Gitlab::Database.nulls_last_order('weight', 'DESC') }
scope :order_weight_asc, -> { reorder ::Gitlab::Database.nulls_last_order('weight') }
......
......@@ -5,7 +5,7 @@ module EE
extend ActiveSupport::Concern
prepended do
include Elastic::NotesSearch
include Elastic::ApplicationVersionedSearch
end
end
end
......@@ -11,7 +11,7 @@ module EE
include FromUnion
prepended do
include Elastic::MergeRequestsSearch
include Elastic::ApplicationVersionedSearch
has_many :reviews, inverse_of: :merge_request
has_many :approvals, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
......
......@@ -5,7 +5,7 @@ module EE
extend ActiveSupport::Concern
prepended do
include Elastic::MilestonesSearch
include Elastic::ApplicationVersionedSearch
has_many :boards
end
......
......@@ -7,7 +7,7 @@ module EE
prepended do
include ::ObjectStorage::BackgroundMove
include Elastic::NotesSearch
include Elastic::ApplicationVersionedSearch
belongs_to :review, inverse_of: :notes
......
# frozen_string_literal: true
module Elastic
module Latest
class IssueClassProxy < ApplicationClassProxy
def nested?
true
end
def elastic_search(query, options: {})
query_hash =
if query =~ /#(\d+)\z/
iid_query_hash(Regexp.last_match(1))
else
basic_query_hash(%w(title^2 description), query)
end
options[:features] = 'issues'
query_hash = project_ids_filter(query_hash, options)
query_hash = confidentiality_filter(query_hash, options[:current_user])
search(query_hash)
end
private
def confidentiality_filter(query_hash, current_user)
return query_hash if current_user && current_user.full_private_access?
filter =
if current_user
{
bool: {
should: [
{ term: { confidential: false } },
{
bool: {
must: [
{ term: { confidential: true } },
{
bool: {
should: [
{ term: { author_id: current_user.id } },
{ term: { assignee_id: current_user.id } },
{ terms: { project_id: current_user.authorized_projects(Gitlab::Access::REPORTER).pluck_primary_key } }
]
}
}
]
}
}
]
}
}
else
{ term: { confidential: false } }
end
query_hash[:query][:bool][:filter] << filter
query_hash
end
end
end
end
# frozen_string_literal: true
module Elastic
module Latest
class IssueInstanceProxy < ApplicationInstanceProxy
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
[:id, :iid, :title, :description, :created_at, :updated_at, :state, :project_id, :author_id, :confidential].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
data['assignee_id'] = safely_read_attribute_for_elasticsearch(:assignee_ids)
data.merge(generic_attributes)
end
end
end
end
# frozen_string_literal: true
module Elastic
module Latest
class MergeRequestClassProxy < ApplicationClassProxy
def nested?
true
end
def elastic_search(query, options: {})
query_hash =
if query =~ /\!(\d+)\z/
iid_query_hash(Regexp.last_match(1))
else
basic_query_hash(%w(title^2 description), query)
end
options[:features] = 'merge_requests'
query_hash = project_ids_filter(query_hash, options)
search(query_hash)
end
end
end
end
# frozen_string_literal: true
module Elastic
module MergeRequestsSearch
extend ActiveSupport::Concern
included do
include ApplicationSearch
module Latest
class MergeRequestInstanceProxy < ApplicationInstanceProxy
def as_indexed_json(options = {})
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
......@@ -32,28 +28,6 @@ module Elastic
data.merge(generic_attributes)
end
def es_parent
"project_#{target_project_id}"
end
def self.nested?
true
end
def self.elastic_search(query, options: {})
query_hash =
if query =~ /\!(\d+)\z/
iid_query_hash(Regexp.last_match(1))
else
basic_query_hash(%w(title^2 description), query)
end
options[:features] = 'merge_requests'
query_hash = project_ids_filter(query_hash, options)
self.__elasticsearch__.search(query_hash)
end
end
end
end
# frozen_string_literal: true
module Elastic
module Latest
class MilestoneClassProxy < ApplicationClassProxy
def nested?
true
end
def elastic_search(query, options: {})
options[:in] = %w(title^2 description)
query_hash = basic_query_hash(options[:in], query)
query_hash = project_ids_filter(query_hash, options)
search(query_hash)
end
end
end
end
# frozen_string_literal: true
module Elastic
module MilestonesSearch
extend ActiveSupport::Concern
included do
include ApplicationSearch
module Latest
class MilestoneInstanceProxy < ApplicationInstanceProxy
def as_indexed_json(options = {})
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
......@@ -18,20 +14,6 @@ module Elastic
data.merge(generic_attributes)
end
def self.nested?
true
end
def self.elastic_search(query, options: {})
options[:in] = %w(title^2 description)
query_hash = basic_query_hash(options[:in], query)
query_hash = project_ids_filter(query_hash, options)
self.__elasticsearch__.search(query_hash)
end
end
end
end
# frozen_string_literal: true
module Elastic
module NotesSearch
extend ActiveSupport::Concern
included do
include ApplicationSearch
module Latest
class NoteClassProxy < ApplicationClassProxy
def es_type
'note'
end
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
[:id, :note, :project_id, :noteable_type, :noteable_id, :created_at, :updated_at].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
if noteable.is_a?(Issue)
data['issue'] = {
assignee_id: noteable.assignee_ids,
author_id: noteable.author_id,
confidential: noteable.confidential
}
end
data.merge(generic_attributes)
end
def self.nested?
def nested?
true
end
def self.elastic_search(query, options: {})
def elastic_search(query, options: {})
options[:in] = ['note']
query_hash = basic_query_hash(%w[note], query)
......@@ -49,11 +25,13 @@ module Elastic
query_hash[:highlight] = highlight_options(options[:in])
self.__elasticsearch__.search(query_hash)
search(query_hash)
end
def self.confidentiality_filter(query_hash, current_user)
return query_hash if current_user && current_user.full_private_access?
private
def confidentiality_filter(query_hash, current_user)
return query_hash if current_user&.full_private_access?
filter = {
bool: {
......@@ -74,7 +52,7 @@ module Elastic
should: [
{ term: { "issue.author_id" => current_user.id } },
{ term: { "issue.assignee_id" => current_user.id } },
{ terms: { "project_id" => current_user.authorized_projects(Gitlab::Access::REPORTER).pluck(:id) } }
{ terms: { "project_id" => current_user.authorized_projects(Gitlab::Access::REPORTER).pluck_primary_key } }
]
}
}
......
# frozen_string_literal: true
module Elastic
module Latest
class NoteInstanceProxy < ApplicationInstanceProxy
delegate :noteable, to: :target
def as_indexed_json(options = {})
data = {}
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
[:id, :note, :project_id, :noteable_type, :noteable_id, :created_at, :updated_at].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
if noteable.is_a?(Issue)
data['issue'] = {
assignee_id: noteable.assignee_ids,
author_id: noteable.author_id,
confidential: noteable.confidential
}
end
data.merge(generic_attributes)
end
end
end
end
# frozen_string_literal: true
module Elastic
module Latest
class ProjectClassProxy < ApplicationClassProxy
def elastic_search(query, options: {})
options[:in] = %w(name^10 name_with_namespace^2 path_with_namespace path^9 description)
query_hash = basic_query_hash(options[:in], query)
filters = []
if options[:namespace_id]
filters << {
terms: {
namespace_id: [options[:namespace_id]].flatten
}
}
end
if options[:non_archived]
filters << {
terms: {
archived: [!options[:non_archived]].flatten
}
}
end
if options[:visibility_levels]
filters << {
terms: {
visibility_level: [options[:visibility_levels]].flatten
}
}
end
if options[:project_ids]
filters << {
bool: project_ids_query(options[:current_user], options[:project_ids], options[:public_and_internal_projects])
}
end
query_hash[:query][:bool][:filter] = filters
query_hash[:sort] = [:_score]
search(query_hash)
end
end
end
end
# frozen_string_literal: true
module Elastic
module Latest
class ProjectInstanceProxy < ApplicationInstanceProxy
TRACKED_FEATURE_SETTINGS = %w(
issues_access_level
merge_requests_access_level
snippets_access_level
wiki_access_level
repository_access_level
).freeze
def as_indexed_json(options = {})
# We don't use as_json(only: ...) because it calls all virtual and serialized attributtes
# https://gitlab.com/gitlab-org/gitlab-ee/issues/349
data = {}
[
:id,
:name,
:path,
:description,
:namespace_id,
:created_at,
:updated_at,
:archived,
:visibility_level,
:last_activity_at,
:name_with_namespace,
:path_with_namespace
].each do |attr|
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end
# Set it as a parent in our `project => child` JOIN field
data['join_field'] = es_type
# ES6 is now single-type per index, so we implement our own typing
data['type'] = 'project'
TRACKED_FEATURE_SETTINGS.each do |feature|
data[feature] = target.project_feature.public_send(feature) # rubocop:disable GitlabSecurity/PublicSend
end
data
end
end
end
end
# frozen_string_literal: true
module Elastic
module V12p1
IssueClassProxy = Elastic::Latest::IssueClassProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
IssueInstanceProxy = Elastic::Latest::IssueInstanceProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
MergeRequestClassProxy = Elastic::Latest::MergeRequestClassProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
MergeRequestInstanceProxy = Elastic::Latest::MergeRequestInstanceProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
MilestoneClassProxy = Elastic::Latest::MilestoneClassProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
MilestoneInstanceProxy = Elastic::Latest::MilestoneInstanceProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
NoteClassProxy = Elastic::Latest::NoteClassProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
NoteInstanceProxy = Elastic::Latest::NoteInstanceProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
ProjectClassProxy = Elastic::Latest::ProjectClassProxy
end
end
# frozen_string_literal: true
module Elastic
module V12p1
ProjectInstanceProxy = Elastic::Latest::ProjectInstanceProxy
end
end
......@@ -199,7 +199,7 @@ module Gitlab
def milestones
strong_memoize(:milestones) do
# Must pass 'issues' and 'merge_requests' to check
# if any of the features is available for projects in Elastic::ApplicationSearch#project_ids_query
# if any of the features is available for projects in ApplicationClassProxy#project_ids_query
# Otherwise it will ignore project_ids and return milestones
# from projects with milestones disabled.
options = base_options
......
......@@ -89,7 +89,7 @@ describe Issue, :elastic do
expected_hash['assignee_id'] = [assignee.id]
expect(issue.as_indexed_json).to eq(expected_hash)
expect(issue.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
......
......@@ -61,7 +61,7 @@ describe MergeRequest, :elastic do
'type' => merge_request.es_type
})
expect(merge_request.as_indexed_json).to eq(expected_hash)
expect(merge_request.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
......
......@@ -53,7 +53,7 @@ describe Milestone, :elastic do
'type' => milestone.es_type
})
expect(milestone.as_indexed_json).to eq(expected_hash)
expect(milestone.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
it_behaves_like 'no results when the user cannot read cross project' do
......
......@@ -88,7 +88,7 @@ describe Note, :elastic do
type
)
expect(note.as_indexed_json.keys).to eq(expected_hash_keys)
expect(note.__elasticsearch__.as_indexed_json.keys).to eq(expected_hash_keys)
end
it "does not create ElasticIndexerWorker job for system messages" do
......@@ -105,7 +105,7 @@ describe Note, :elastic do
Note.subclasses.each do |note_class|
expect(note_class.index_name).to eq(Note.index_name)
expect(note_class.document_type).to eq(Note.document_type)
expect(note_class.mappings.to_hash).to eq(Note.mappings.to_hash)
expect(note_class.__elasticsearch__.mappings.to_hash).to eq(Note.__elasticsearch__.mappings.to_hash)
end
end
......
......@@ -159,6 +159,6 @@ describe Project, :elastic do
expected_hash['name_with_namespace'] = project.full_name
expected_hash['path_with_namespace'] = project.full_path
expect(project.as_indexed_json).to eq(expected_hash)
expect(project.__elasticsearch__.as_indexed_json).to eq(expected_hash)
end
end
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