Highlight fields in request order
Because json objects are unordered this also adds an explicit order syntax that looks like "highlight": { "fields": [ {"title":{ /*params*/ }}, {"text":{ /*params*/ }} ] } This is not useful for any of the builtin highlighters but will be useful in plugins. Closes #4649
This commit is contained in:
parent
81cddacffa
commit
3573822b7e
|
@ -547,3 +547,20 @@ keep in mind that scoring more phrases consumes more time and memory.
|
||||||
|
|
||||||
If using `matched_fields` keep in mind that `phrase_limit` phrases per
|
If using `matched_fields` keep in mind that `phrase_limit` phrases per
|
||||||
matched field are considered.
|
matched field are considered.
|
||||||
|
|
||||||
|
[[explicit-field-order]]
|
||||||
|
=== Field Highlight Order
|
||||||
|
Elasticsearch highlights the fields in the order that they are sent. Per the
|
||||||
|
json spec objects are unordered but if you need to be explicit about the order
|
||||||
|
that fields are highlighted then you can use an array for `fields` like this:
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
"highlight": {
|
||||||
|
"fields": [
|
||||||
|
{"title":{ /*params*/ }},
|
||||||
|
{"text":{ /*params*/ }}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
None of the highlighters built into Elasticsearch care about the order that the
|
||||||
|
fields are highlighted but a plugin may.
|
||||||
|
|
|
@ -806,6 +806,15 @@ public class SearchRequestBuilder extends ActionRequestBuilder<SearchRequest, Se
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the fields to be highlighted using a syntax that is specific about the order in which they should be highlighted.
|
||||||
|
* @return this for chaining
|
||||||
|
*/
|
||||||
|
public SearchRequestBuilder setHighlighterExplicitFieldOrder(boolean explicitFieldOrder) {
|
||||||
|
highlightBuilder().useExplicitFieldOrder(explicitFieldOrder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates to {@link org.elasticsearch.search.suggest.SuggestBuilder#setText(String)}.
|
* Delegates to {@link org.elasticsearch.search.suggest.SuggestBuilder#setText(String)}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -74,6 +74,8 @@ public class HighlightBuilder implements ToXContent {
|
||||||
|
|
||||||
private Boolean forceSource;
|
private Boolean forceSource;
|
||||||
|
|
||||||
|
private boolean useExplicitFieldOrder = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a field to be highlighted with default fragment size of 100 characters, and
|
* Adds a field to be highlighted with default fragment size of 100 characters, and
|
||||||
* default number of fragments of 5 using the default encoder
|
* default number of fragments of 5 using the default encoder
|
||||||
|
@ -289,6 +291,15 @@ public class HighlightBuilder implements ToXContent {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the fields to be highlighted using a syntax that is specific about the order in which they should be highlighted.
|
||||||
|
* @return this for chaining
|
||||||
|
*/
|
||||||
|
public HighlightBuilder useExplicitFieldOrder(boolean useExplicitFieldOrder) {
|
||||||
|
this.useExplicitFieldOrder = useExplicitFieldOrder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject("highlight");
|
builder.startObject("highlight");
|
||||||
|
@ -347,8 +358,15 @@ public class HighlightBuilder implements ToXContent {
|
||||||
builder.field("force_source", forceSource);
|
builder.field("force_source", forceSource);
|
||||||
}
|
}
|
||||||
if (fields != null) {
|
if (fields != null) {
|
||||||
|
if (useExplicitFieldOrder) {
|
||||||
|
builder.startArray("fields");
|
||||||
|
} else {
|
||||||
builder.startObject("fields");
|
builder.startObject("fields");
|
||||||
|
}
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
|
if (useExplicitFieldOrder) {
|
||||||
|
builder.startObject();
|
||||||
|
}
|
||||||
builder.startObject(field.name());
|
builder.startObject(field.name());
|
||||||
if (field.preTags != null) {
|
if (field.preTags != null) {
|
||||||
builder.field("pre_tags", field.preTags);
|
builder.field("pre_tags", field.preTags);
|
||||||
|
@ -406,10 +424,16 @@ public class HighlightBuilder implements ToXContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
if (useExplicitFieldOrder) {
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (useExplicitFieldOrder) {
|
||||||
|
builder.endArray();
|
||||||
|
} else {
|
||||||
|
builder.endObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
builder.endObject();
|
builder.endObject();
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.search.SearchParseElement;
|
||||||
import org.elasticsearch.search.SearchParseException;
|
import org.elasticsearch.search.SearchParseException;
|
||||||
import org.elasticsearch.search.internal.SearchContext;
|
import org.elasticsearch.search.internal.SearchContext;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -92,6 +93,24 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
postTagsList.add(parser.text());
|
postTagsList.add(parser.text());
|
||||||
}
|
}
|
||||||
globalOptionsBuilder.postTags(postTagsList.toArray(new String[postTagsList.size()]));
|
globalOptionsBuilder.postTags(postTagsList.toArray(new String[postTagsList.size()]));
|
||||||
|
} else if ("fields".equals(topLevelFieldName)) {
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||||
|
if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
String highlightFieldName = null;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
if (highlightFieldName != null) {
|
||||||
|
throw new SearchParseException(context, "If highlighter fields is an array it must contain objects containing a single field");
|
||||||
|
}
|
||||||
|
highlightFieldName = parser.currentName();
|
||||||
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
fieldsOptions.add(Tuple.tuple(highlightFieldName, parseFields(parser, context)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new SearchParseException(context, "If highlighter fields is an array it must contain objects containing a single field");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (token.isValue()) {
|
} else if (token.isValue()) {
|
||||||
if ("order".equals(topLevelFieldName)) {
|
if ("order".equals(topLevelFieldName)) {
|
||||||
|
@ -141,6 +160,32 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
highlightFieldName = parser.currentName();
|
highlightFieldName = parser.currentName();
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||||
|
fieldsOptions.add(Tuple.tuple(highlightFieldName, parseFields(parser, context)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("highlight_query".equals(topLevelFieldName) || "highlightQuery".equals(topLevelFieldName)) {
|
||||||
|
globalOptionsBuilder.highlightQuery(context.queryParserService().parse(parser).query());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchContextHighlight.FieldOptions globalOptions = globalOptionsBuilder.build();
|
||||||
|
if (globalOptions.preTags() != null && globalOptions.postTags() == null) {
|
||||||
|
throw new SearchParseException(context, "Highlighter global preTags are set, but global postTags are not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SearchContextHighlight.Field> fields = Lists.newArrayList();
|
||||||
|
// now, go over and fill all fieldsOptions with default values from the global state
|
||||||
|
for (Tuple<String, SearchContextHighlight.FieldOptions.Builder> tuple : fieldsOptions) {
|
||||||
|
fields.add(new SearchContextHighlight.Field(tuple.v1(), tuple.v2().merge(globalOptions).build()));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.highlight(new SearchContextHighlight(fields));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchContextHighlight.FieldOptions.Builder parseFields(XContentParser parser, SearchContext context) throws IOException {
|
||||||
|
XContentParser.Token token;
|
||||||
|
|
||||||
SearchContextHighlight.FieldOptions.Builder fieldOptionsBuilder = new SearchContextHighlight.FieldOptions.Builder();
|
SearchContextHighlight.FieldOptions.Builder fieldOptionsBuilder = new SearchContextHighlight.FieldOptions.Builder();
|
||||||
String fieldName = null;
|
String fieldName = null;
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
@ -179,9 +224,9 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
fieldOptionsBuilder.scoreOrdered("score".equals(parser.text()));
|
fieldOptionsBuilder.scoreOrdered("score".equals(parser.text()));
|
||||||
} else if ("require_field_match".equals(fieldName) || "requireFieldMatch".equals(fieldName)) {
|
} else if ("require_field_match".equals(fieldName) || "requireFieldMatch".equals(fieldName)) {
|
||||||
fieldOptionsBuilder.requireFieldMatch(parser.booleanValue());
|
fieldOptionsBuilder.requireFieldMatch(parser.booleanValue());
|
||||||
} else if ("boundary_max_scan".equals(topLevelFieldName) || "boundaryMaxScan".equals(topLevelFieldName)) {
|
} else if ("boundary_max_scan".equals(fieldName) || "boundaryMaxScan".equals(fieldName)) {
|
||||||
fieldOptionsBuilder.boundaryMaxScan(parser.intValue());
|
fieldOptionsBuilder.boundaryMaxScan(parser.intValue());
|
||||||
} else if ("boundary_chars".equals(topLevelFieldName) || "boundaryChars".equals(topLevelFieldName)) {
|
} else if ("boundary_chars".equals(fieldName) || "boundaryChars".equals(fieldName)) {
|
||||||
char[] charsArr = parser.text().toCharArray();
|
char[] charsArr = parser.text().toCharArray();
|
||||||
Character[] boundaryChars = new Character[charsArr.length];
|
Character[] boundaryChars = new Character[charsArr.length];
|
||||||
for (int i = 0; i < charsArr.length; i++) {
|
for (int i = 0; i < charsArr.length; i++) {
|
||||||
|
@ -207,26 +252,6 @@ public class HighlighterParseElement implements SearchParseElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fieldsOptions.add(Tuple.tuple(highlightFieldName, fieldOptionsBuilder));
|
return fieldOptionsBuilder;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ("highlight_query".equals(topLevelFieldName) || "highlightQuery".equals(topLevelFieldName)) {
|
|
||||||
globalOptionsBuilder.highlightQuery(context.queryParserService().parse(parser).query());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchContextHighlight.FieldOptions globalOptions = globalOptionsBuilder.build();
|
|
||||||
if (globalOptions.preTags() != null && globalOptions.postTags() == null) {
|
|
||||||
throw new SearchParseException(context, "Highlighter global preTags are set, but global postTags are not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SearchContextHighlight.Field> fields = Lists.newArrayList();
|
|
||||||
// now, go over and fill all fieldsOptions with default values from the global state
|
|
||||||
for (Tuple<String, SearchContextHighlight.FieldOptions.Builder> tuple : fieldsOptions) {
|
|
||||||
fields.add(new SearchContextHighlight.Field(tuple.v1(), tuple.v2().merge(globalOptions).build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
context.highlight(new SearchContextHighlight(fields));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class SearchContextHighlight {
|
||||||
|
|
||||||
public SearchContextHighlight(Collection<Field> fields) {
|
public SearchContextHighlight(Collection<Field> fields) {
|
||||||
assert fields != null;
|
assert fields != null;
|
||||||
this.fields = Maps.newHashMap();
|
this.fields = new LinkedHashMap<String, Field>(fields.size());
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
this.fields.put(field.field, field);
|
this.fields.put(field.field, field);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.elasticsearch.common.text.StringText;
|
||||||
import org.elasticsearch.common.text.Text;
|
import org.elasticsearch.common.text.Text;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,9 +39,24 @@ public class CustomHighlighter implements Highlighter {
|
||||||
@Override
|
@Override
|
||||||
public HighlightField highlight(HighlighterContext highlighterContext) {
|
public HighlightField highlight(HighlighterContext highlighterContext) {
|
||||||
SearchContextHighlight.Field field = highlighterContext.field;
|
SearchContextHighlight.Field field = highlighterContext.field;
|
||||||
|
CacheEntry cacheEntry = (CacheEntry) highlighterContext.hitContext.cache().get("test-custom");
|
||||||
|
if (cacheEntry == null) {
|
||||||
|
cacheEntry = new CacheEntry();
|
||||||
|
highlighterContext.hitContext.cache().put("test-custom", cacheEntry);
|
||||||
|
cacheEntry.docId = highlighterContext.hitContext.docId();
|
||||||
|
cacheEntry.position = 1;
|
||||||
|
} else {
|
||||||
|
if (cacheEntry.docId == highlighterContext.hitContext.docId()) {
|
||||||
|
cacheEntry.position++;
|
||||||
|
} else {
|
||||||
|
cacheEntry.docId = highlighterContext.hitContext.docId();
|
||||||
|
cacheEntry.position = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Text> responses = Lists.newArrayList();
|
List<Text> responses = Lists.newArrayList();
|
||||||
responses.add(new StringText("standard response"));
|
responses.add(new StringText(String.format(Locale.ENGLISH, "standard response for %s at position %s", field.field(),
|
||||||
|
cacheEntry.position)));
|
||||||
|
|
||||||
if (field.fieldOptions().options() != null) {
|
if (field.fieldOptions().options() != null) {
|
||||||
for (Map.Entry<String, Object> entry : field.fieldOptions().options().entrySet()) {
|
for (Map.Entry<String, Object> entry : field.fieldOptions().options().entrySet()) {
|
||||||
|
@ -50,4 +66,9 @@ public class CustomHighlighter implements Highlighter {
|
||||||
|
|
||||||
return new HighlightField(highlighterContext.fieldName, responses.toArray(new Text[]{}));
|
return new HighlightField(highlighterContext.fieldName, responses.toArray(new Text[]{}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CacheEntry {
|
||||||
|
private int position;
|
||||||
|
private int docId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ package org.elasticsearch.search.highlight;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.common.Priority;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
||||||
|
import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@ import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
|
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHighlight;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHighlight;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
@ -53,12 +51,12 @@ public class CustomHighlighterSearchTests extends ElasticsearchIntegrationTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
protected void setup() throws Exception{
|
protected void setup() throws Exception{
|
||||||
client().prepareIndex("test", "test", "1").setSource(XContentFactory.jsonBuilder()
|
indexRandom(true,
|
||||||
.startObject()
|
client().prepareIndex("test", "test", "1").setSource(
|
||||||
.field("name", "arbitrary content")
|
"name", "arbitrary content", "other_name", "foo", "other_other_name", "bar"),
|
||||||
.endObject())
|
client().prepareIndex("test", "test", "2").setSource(
|
||||||
.setRefresh(true).execute().actionGet();
|
"other_name", "foo", "other_other_name", "bar"));
|
||||||
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).setWaitForYellowStatus().execute().actionGet();
|
ensureYellow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -67,7 +65,7 @@ public class CustomHighlighterSearchTests extends ElasticsearchIntegrationTest {
|
||||||
.setQuery(QueryBuilders.matchAllQuery())
|
.setQuery(QueryBuilders.matchAllQuery())
|
||||||
.addHighlightedField("name").setHighlighterType("test-custom")
|
.addHighlightedField("name").setHighlighterType("test-custom")
|
||||||
.execute().actionGet();
|
.execute().actionGet();
|
||||||
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response"));
|
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response for name at position 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,7 +81,7 @@ public class CustomHighlighterSearchTests extends ElasticsearchIntegrationTest {
|
||||||
.addHighlightedField(highlightConfig)
|
.addHighlightedField(highlightConfig)
|
||||||
.execute().actionGet();
|
.execute().actionGet();
|
||||||
|
|
||||||
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response"));
|
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response for name at position 1"));
|
||||||
assertHighlight(searchResponse, 0, "name", 1, equalTo("field:myFieldOption:someValue"));
|
assertHighlight(searchResponse, 0, "name", 1, equalTo("field:myFieldOption:someValue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +97,27 @@ public class CustomHighlighterSearchTests extends ElasticsearchIntegrationTest {
|
||||||
.addHighlightedField("name")
|
.addHighlightedField("name")
|
||||||
.execute().actionGet();
|
.execute().actionGet();
|
||||||
|
|
||||||
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response"));
|
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response for name at position 1"));
|
||||||
assertHighlight(searchResponse, 0, "name", 1, equalTo("field:myGlobalOption:someValue"));
|
assertHighlight(searchResponse, 0, "name", 1, equalTo("field:myGlobalOption:someValue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThatCustomHighlighterReceivesFieldsInOrder() throws Exception {
|
||||||
|
SearchResponse searchResponse = client().prepareSearch("test").setTypes("test")
|
||||||
|
.setQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery()).should(QueryBuilders
|
||||||
|
.termQuery("name", "arbitrary")))
|
||||||
|
.setHighlighterType("test-custom")
|
||||||
|
.addHighlightedField("name")
|
||||||
|
.addHighlightedField("other_name")
|
||||||
|
.addHighlightedField("other_other_name")
|
||||||
|
.setHighlighterExplicitFieldOrder(true)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
assertHighlight(searchResponse, 0, "name", 0, equalTo("standard response for name at position 1"));
|
||||||
|
assertHighlight(searchResponse, 0, "other_name", 0, equalTo("standard response for other_name at position 2"));
|
||||||
|
assertHighlight(searchResponse, 0, "other_other_name", 0, equalTo("standard response for other_other_name at position 3"));
|
||||||
|
assertHighlight(searchResponse, 1, "name", 0, equalTo("standard response for name at position 1"));
|
||||||
|
assertHighlight(searchResponse, 1, "other_name", 0, equalTo("standard response for other_name at position 2"));
|
||||||
|
assertHighlight(searchResponse, 1, "other_other_name", 0, equalTo("standard response for other_other_name at position 3"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue