Skip to content

Commit a26fdab

Browse files
committed
Preserve highlights for returnable: false fields
1 parent caabea3 commit a26fdab

2 files changed

Lines changed: 79 additions & 20 deletions

File tree

  • elasticgraph-schema_definition

elasticgraph-schema_definition/lib/elastic_graph/schema_definition/mixins/has_indices.rb

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,22 +292,30 @@ def fields_with_sources
292292
indexing_fields_by_name_in_index.values.reject { |f| f.source.nil? }
293293
end
294294

295-
# Returns the list of `_source.excludes` paths for non-returnable fields.
295+
# Returns the list of `_source.excludes` paths for non-returnable, non-highlightable fields.
296+
#
297+
# Hidden highlightable fields must remain in `_source` only when the GraphQL
298+
# highlights API can still reach them.
296299
#
297300
# Uses `indexing_fields_by_name_in_index` for traversal (same as
298301
# `index_field_runtime_metadata_tuples`) to avoid infinite recursion
299302
# through interface/union subtype cycles.
300303
#
301304
# @private
302-
def source_excludes_paths(path_prefix: "")
305+
def source_excludes_paths(path_prefix = "", under_non_returnable_parent = false)
303306
indexing_fields_by_name_in_index.flat_map do |name, field|
304307
path = path_prefix + name
305308
object_type = field.type.fully_unwrapped.as_object_type
309+
non_returnable = under_non_returnable_parent || !field.returnable?
306310

307-
if !field.returnable?
308-
[object_type ? "#{path}.*" : path]
309-
elsif object_type
310-
object_type.source_excludes_paths(path_prefix: "#{path}.")
311+
if object_type
312+
if non_returnable && !field.highlightable?
313+
["#{path}.*"]
314+
else
315+
object_type.source_excludes_paths("#{path}.", non_returnable)
316+
end
317+
elsif non_returnable && !field.highlightable?
318+
[path]
311319
else
312320
[]
313321
end

elasticgraph-schema_definition/spec/unit/elastic_graph/schema_definition/datastore_config/index_mappings/miscellaneous_spec.rb

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,25 +117,38 @@ module SchemaDefinition
117117
})
118118
end
119119

120-
it "uses `name_in_index` in `_source.excludes` for `returnable: false` fields" do
120+
it "uses `name_in_index` in `_source.excludes` for non-highlightable `returnable: false` fields" do
121121
mapping = index_mapping_for "my_type" do |s|
122122
s.object_type "MyType" do |t|
123123
t.field "id", "ID"
124-
t.field "name", "String"
125-
t.field "internal_code_gql", "String", name_in_index: "internal_code", returnable: false
124+
t.field "internal_count_gql", "Int", name_in_index: "internal_count", returnable: false
126125
t.index "my_type"
127126
end
128127
end
129128

130-
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_code")
129+
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_count")
131130
# The field should still appear in properties (it's indexed, just not in _source)
131+
expect(mapping.dig("properties", "internal_count")).to eq({"type" => "integer"})
132+
end
133+
134+
it "keeps highlightable `returnable: false` fields in `_source` so they can still be highlighted" do
135+
mapping = index_mapping_for "my_type" do |s|
136+
s.object_type "MyType" do |t|
137+
t.field "id", "ID"
138+
t.field "internal_code", "String", returnable: false
139+
t.index "my_type"
140+
end
141+
end
142+
143+
expect(mapping).not_to have_key("_source")
132144
expect(mapping.dig("properties", "internal_code")).to eq({"type" => "keyword"})
133145
end
134146

135-
it "includes `.*` in `_source.excludes` for `returnable: false` object fields" do
147+
it "only excludes non-highlightable descendants for implicitly highlightable `returnable: false` object fields" do
136148
mapping = index_mapping_for "my_type" do |s|
137149
s.object_type "InternalMetadata" do |t|
138150
t.field "internal_code", "String"
151+
t.field "internal_count", "Int"
139152
end
140153

141154
s.object_type "MyType" do |t|
@@ -145,33 +158,55 @@ module SchemaDefinition
145158
end
146159
end
147160

161+
expect(mapping).to include("_source" => {"excludes" => ["internal_metadata.internal_count"]})
162+
expect(mapping.dig("properties", "internal_metadata", "properties", "internal_code")).to eq({"type" => "keyword"})
163+
expect(mapping.dig("properties", "internal_metadata", "properties", "internal_count")).to eq({"type" => "integer"})
164+
end
165+
166+
it "excludes all descendants for explicitly non-highlightable `returnable: false` object fields even when a child field is highlightable" do
167+
mapping = index_mapping_for "my_type" do |s|
168+
s.object_type "InternalMetadata" do |t|
169+
t.field "internal_code", "String", highlightable: true
170+
t.field "internal_count", "Int"
171+
end
172+
173+
s.object_type "MyType" do |t|
174+
t.field "id", "ID"
175+
t.field "internal_metadata", "InternalMetadata", returnable: false, highlightable: false
176+
t.index "my_type"
177+
end
178+
end
179+
148180
expect(mapping).to include("_source" => {"excludes" => ["internal_metadata.*"]})
149181
expect(mapping.dig("properties", "internal_metadata", "properties", "internal_code")).to eq({"type" => "keyword"})
182+
expect(mapping.dig("properties", "internal_metadata", "properties", "internal_count")).to eq({"type" => "integer"})
150183
end
151184

152-
it "excludes `returnable: false` indexing-only fields but not `graphql_only` fields" do
185+
it "excludes non-highlightable `returnable: false` indexing-only fields but not `graphql_only` fields" do
153186
mapping = index_mapping_for "my_type" do |s|
154187
s.object_type "MyType" do |t|
155188
t.field "id", "ID"
156189
t.field "name", "String"
157-
t.field "legacy_name", "String", graphql_only: true, name_in_index: "name", returnable: false
158-
t.field "internal_code", "String", indexing_only: true, returnable: false
190+
t.field "count", "Int"
191+
t.field "legacy_count", "Int", graphql_only: true, name_in_index: "count", returnable: false
192+
t.field "internal_count", "Int", indexing_only: true, returnable: false
159193
t.index "my_type"
160194
end
161195
end
162196

163-
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_code")
197+
expect(mapping.dig("_source", "excludes")).to contain_exactly("internal_count")
164198
expect(mapping.fetch("properties")).to include(
165199
"name" => {"type" => "keyword"},
166-
"internal_code" => {"type" => "keyword"}
200+
"count" => {"type" => "integer"},
201+
"internal_count" => {"type" => "integer"}
167202
)
168-
expect(mapping.fetch("properties")).not_to include("legacy_name")
203+
expect(mapping.fetch("properties")).not_to include("legacy_count")
169204
end
170205

171206
it "uses full indexed paths in `_source.excludes` for `returnable: false` fields under nested mappings" do
172207
mapping = index_mapping_for "my_type" do |s|
173208
s.object_type "Parent" do |t|
174-
t.field "child", "String", name_in_index: "child_in_index", returnable: false
209+
t.field "child_count_gql", "Int", name_in_index: "child_count", returnable: false
175210
end
176211

177212
s.object_type "Grandparent" do |t|
@@ -187,19 +222,35 @@ module SchemaDefinition
187222
end
188223
end
189224

190-
expect(mapping.dig("_source", "excludes")).to contain_exactly("grandparents_in_index.parent_in_index.child_in_index")
225+
expect(mapping.dig("_source", "excludes")).to contain_exactly("grandparents_in_index.parent_in_index.child_count")
191226
expect(mapping.dig("properties", "grandparents_in_index")).to include(
192227
"type" => "nested",
193228
"properties" => {
194229
"parent_in_index" => {
195230
"properties" => {
196-
"child_in_index" => {"type" => "keyword"}
231+
"child_count" => {"type" => "integer"}
197232
}
198233
}
199234
}
200235
)
201236
end
202237

238+
it "excludes all descendants for non-highlightable `returnable: false` object fields" do
239+
mapping = index_mapping_for "my_type" do |s|
240+
s.object_type "InternalMetrics" do |t|
241+
t.field "count", "Int"
242+
end
243+
244+
s.object_type "MyType" do |t|
245+
t.field "id", "ID"
246+
t.field "internal_metrics", "InternalMetrics", returnable: false
247+
t.index "my_type"
248+
end
249+
end
250+
251+
expect(mapping).to include("_source" => {"excludes" => ["internal_metrics.*"]})
252+
expect(mapping.dig("properties", "internal_metrics", "properties", "count")).to eq({"type" => "integer"})
253+
end
203254
it "does not include `_source` config when all fields are returnable" do
204255
mapping = index_mapping_for "my_type" do |s|
205256
s.object_type "MyType" do |t|

0 commit comments

Comments
 (0)