diff --git a/ee/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping.rb b/ee/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping.rb new file mode 100644 index 0000000000000000000000000000000000000000..9d66e64ec1d345568f48e16399d3287296ba348e --- /dev/null +++ b/ee/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AddLabelIdsAndSchemaVersionToIssuesMapping < Elastic::Migration + include Elastic::MigrationUpdateMappingsHelper + + private + + def index_name + Issue.__elasticsearch__.index_name + end + + def new_mappings + { + label_ids: { + type: 'keyword' + }, + schema_version: { + type: 'short' + } + } + end +end diff --git a/ee/elastic/migrate/20220825102900_backfill_label_ids_for_issues.rb b/ee/elastic/migrate/20220825102900_backfill_label_ids_for_issues.rb new file mode 100644 index 0000000000000000000000000000000000000000..e776218497d3a22ce9a90340a61285903e3581d2 --- /dev/null +++ b/ee/elastic/migrate/20220825102900_backfill_label_ids_for_issues.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class BackfillLabelIdsForIssues < Elastic::Migration + include Elastic::MigrationBackfillHelper + + batched! + batch_size 5000 + throttle_delay 3.minutes + + DOCUMENT_TYPE = Issue + UPDATE_BATCH_SIZE = 100 + + private + + def index_name + DOCUMENT_TYPE.__elasticsearch__.index_name + end + + def field_name + # We use schema_version here because it doesn't exist for documents without label_ids + # We can't use label_ids because Elasticsearch treats [] as a non-existent value + :schema_version + end +end diff --git a/ee/lib/elastic/latest/issue_class_proxy.rb b/ee/lib/elastic/latest/issue_class_proxy.rb index 6373fb4cf8f53b5d29aadf48d180b2e214a2a2f2..e537da4f48dc1691b71c4d65fbbcde089cbbec5d 100644 --- a/ee/lib/elastic/latest/issue_class_proxy.rb +++ b/ee/lib/elastic/latest/issue_class_proxy.rb @@ -31,7 +31,7 @@ def elastic_search(query, options: {}) # rubocop: disable CodeReuse/ActiveRecord def preload_indexing_data(relation) - relation.includes(:issue_assignees, project: [:project_feature, :namespace]) + relation.includes(:issue_assignees, :labels, project: [:project_feature, :namespace]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/ee/lib/elastic/latest/issue_config.rb b/ee/lib/elastic/latest/issue_config.rb index 330f2a665eacb56c61e10ee711d5227d2a1210b2..e468b73db2286cde9b1efac985f9fa7175a042e4 100644 --- a/ee/lib/elastic/latest/issue_config.rb +++ b/ee/lib/elastic/latest/issue_config.rb @@ -37,6 +37,9 @@ module IssueConfig indexes :upvotes, type: :integer indexes :namespace_ancestry, type: :text, index_prefixes: { min_chars: 1, max_chars: 19 } # deprecated indexes :namespace_ancestry_ids, type: :keyword + indexes :label_ids, type: :keyword + + indexes :schema_version, type: :short end end end diff --git a/ee/lib/elastic/latest/issue_instance_proxy.rb b/ee/lib/elastic/latest/issue_instance_proxy.rb index 18081869a72b42efc970d5382820058a15eeb48c..53981ad566fdf2867695aba7a3eb16a66d5d6371 100644 --- a/ee/lib/elastic/latest/issue_instance_proxy.rb +++ b/ee/lib/elastic/latest/issue_instance_proxy.rb @@ -12,6 +12,10 @@ def as_indexed_json(options = {}) data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr) end + # Schema version. The format is Date.today.strftime('%y_%m') + # Please update if you're changing the schema of the document + data['schema_version'] = 22_08 + # Load them through the issue_assignees table since calling # assignee_ids can't be easily preloaded and does # unnecessary joins @@ -23,6 +27,8 @@ def as_indexed_json(options = {}) data['upvotes'] = target.upvotes_count data['namespace_ancestry_ids'] = target.namespace_ancestry + data['label_ids'] = target.label_ids.map(&:to_s) + data.merge(generic_attributes) end diff --git a/ee/spec/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping_spec.rb b/ee/spec/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..5fd3f549d2a528c6e5d40bb0c707af817e25ca20 --- /dev/null +++ b/ee/spec/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative 'migration_shared_examples' +require File.expand_path('ee/elastic/migrate/20220824123000_add_label_ids_and_schema_version_to_issues_mapping.rb') + +RSpec.describe AddLabelIdsAndSchemaVersionToIssuesMapping, :elastic, :sidekiq_inline do + let(:version) { 20220824123000 } + + include_examples 'migration adds mapping' +end diff --git a/ee/spec/elastic/migrate/20220825102900_backfill_label_ids_for_issues_spec.rb b/ee/spec/elastic/migrate/20220825102900_backfill_label_ids_for_issues_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a0500f947647edb490c22d51ce71cee882a9e0f --- /dev/null +++ b/ee/spec/elastic/migrate/20220825102900_backfill_label_ids_for_issues_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_relative 'migration_shared_examples' +require File.expand_path('ee/elastic/migrate/20220825102900_backfill_label_ids_for_issues.rb') + +RSpec.describe BackfillLabelIdsForIssues, :elastic, :sidekiq_inline do + let(:version) { 20220825102900 } + + include_examples 'migration backfills fields' do + let(:label) { create(:label) } + let(:objects) { create_list(:labeled_issue, 3, labels: [label]) } + let(:expected_fields) { { label_ids: [label.id.to_s], schema_version: 22_08 } } + + let(:expected_throttle_delay) { 3.minutes } + let(:expected_batch_size) { 5000 } + end +end diff --git a/ee/spec/elastic/migrate/migration_shared_examples.rb b/ee/spec/elastic/migrate/migration_shared_examples.rb index 466a0c2f37b08635ca57e7508da1a5a1e1cf41c2..492187a34da85c3caf8061aff68492d996665b55 100644 --- a/ee/spec/elastic/migrate/migration_shared_examples.rb +++ b/ee/spec/elastic/migrate/migration_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'migration backfills a field' do +RSpec.shared_examples 'migration backfills fields' do let(:migration) { described_class.new(version) } let(:klass) { objects.first.class } @@ -102,20 +102,26 @@ private def add_field_for_objects(objects) + source_script = expected_fields.map do |field_name, _| + "ctx._source['#{field_name}'] = params.#{field_name};" + end.join + script = { - source: "ctx._source['#{field_name}'] = params.#{field_name};", + source: source_script, lang: "painless", - params: { - field_name => field_value - } + params: expected_fields } update_by_query(objects, script) end def remove_field_from_objects(objects) - script = { - source: "ctx._source.remove('#{field_name}')" + source_script = expected_fields.map do |field_name, _| + "ctx._source.remove('#{field_name}');" + end.join + + script = { + source: source_script } update_by_query(objects, script) diff --git a/ee/spec/models/concerns/elastic/issue_spec.rb b/ee/spec/models/concerns/elastic/issue_spec.rb index e984ac265e1771461499b95f6e9d3a6765f6adf0..1fa8693dc3ef3d458bfca5f98c5edcb9a0915b97 100644 --- a/ee/spec/models/concerns/elastic/issue_spec.rb +++ b/ee/spec/models/concerns/elastic/issue_spec.rb @@ -109,7 +109,8 @@ let_it_be(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) } let_it_be(:project) { create(:project, :internal, namespace: subgroup) } - let_it_be(:issue) { create(:issue, project: project, assignees: [assignee]) } + let_it_be(:label) { create(:label) } + let_it_be(:issue) { create(:labeled_issue, project: project, assignees: [assignee], labels: [label]) } let_it_be(:award_emoji) { create(:award_emoji, :upvote, awardable: issue) } it "returns json with all needed elements" do @@ -127,7 +128,9 @@ 'type' => issue.es_type, 'state' => issue.state, 'upvotes' => 1, - 'namespace_ancestry_ids' => "#{group.id}-#{subgroup.id}-" + 'namespace_ancestry_ids' => "#{group.id}-#{subgroup.id}-", + 'label_ids' => [label.id.to_s], + 'schema_version' => 22_08 }) expected_hash['assignee_id'] = [assignee.id]