From 520a8e4703c689c90ff9102b9932f600f1859666 Mon Sep 17 00:00:00 2001 From: Jonathon Herbert Date: Fri, 12 Jul 2024 15:49:44 +0100 Subject: [PATCH] Refactor to add date typeaheads to field keys --- src/main/scala/Handler.scala | 15 ++-- src/main/scala/HttpServer.scala | 5 +- src/main/scala/lang/CapiQueryString.scala | 8 +- src/main/scala/lang/Parser.scala | 4 +- src/main/scala/lang/Typeahead.scala | 82 ++++++++++--------- .../scala/lang/TypeaheadHelpersCapi.scala | 24 +++++- src/test/scala/lang/TypeaheadTest.scala | 9 +- 7 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/main/scala/Handler.scala b/src/main/scala/Handler.scala index f91553d..c77442f 100644 --- a/src/main/scala/Handler.scala +++ b/src/main/scala/Handler.scala @@ -28,8 +28,11 @@ class Handler val guardianContentClient = new GuardianContentClient("test") val typeaheadHelpers = new TypeaheadHelpersCapi(guardianContentClient) - val typeahead = new Typeahead(typeaheadHelpers.fieldResolvers, typeaheadHelpers.outputModifierResolvers) - + val typeahead = new Typeahead( + typeaheadHelpers.fieldResolvers, + typeaheadHelpers.outputModifierResolvers + ) + private val cql = new Cql(typeahead) def handleRequest( @@ -76,9 +79,11 @@ class Handler logger.info(s"Responding with status ${statusCode}: ${responseBody}") new APIGatewayProxyResponseEvent() - .withHeaders(Map( - "Access-Control-Allow-Origin" -> "*" - ).asJava) + .withHeaders( + Map( + "Access-Control-Allow-Origin" -> "*" + ).asJava + ) .withStatusCode(statusCode) .withBody( responseBody diff --git a/src/main/scala/HttpServer.scala b/src/main/scala/HttpServer.scala index a9cb2ed..bf43542 100644 --- a/src/main/scala/HttpServer.scala +++ b/src/main/scala/HttpServer.scala @@ -16,7 +16,10 @@ import com.gu.contentapi.client.GuardianContentClient object HttpServer extends QueryJson { val guardianContentClient = new GuardianContentClient("test") val typeaheadHelpers = new TypeaheadHelpersCapi(guardianContentClient) - val typeahead = new Typeahead(typeaheadHelpers.fieldResolvers, typeaheadHelpers.outputModifierResolvers) + val typeahead = new Typeahead( + typeaheadHelpers.fieldResolvers, + typeaheadHelpers.outputModifierResolvers + ) val cql = new Cql(typeahead) diff --git a/src/main/scala/lang/CapiQueryString.scala b/src/main/scala/lang/CapiQueryString.scala index 4fa30de..d2bb38f 100644 --- a/src/main/scala/lang/CapiQueryString.scala +++ b/src/main/scala/lang/CapiQueryString.scala @@ -15,13 +15,15 @@ object CapiQueryString { program: QueryList ): String = val (searchStrs, otherQueries) = program.exprs.partitionMap { - case q: QueryBinary => Left(strFromBinary(q)) - case QueryField(key, Some(value)) => Right(s"${key.literal.getOrElse("")}=${value.literal.getOrElse("")}") + case q: QueryBinary => Left(strFromBinary(q)) + case QueryField(key, Some(value)) => + Right(s"${key.literal.getOrElse("")}=${value.literal.getOrElse("")}") case QueryField(key, None) => throw new CapiQueryStringError( s"The field '+$key' needs a value after it (e.g. +$key:tone/news)" ) - case QueryOutputModifier(key, Some(value)) => Right(s"${key.literal.getOrElse("")}=${value.literal.getOrElse("")}") + case QueryOutputModifier(key, Some(value)) => + Right(s"${key.literal.getOrElse("")}=${value.literal.getOrElse("")}") case QueryOutputModifier(key, None) => throw new CapiQueryStringError( s"The output modifier '@$key' needs a value after it (e.g. +$key:all)" diff --git a/src/main/scala/lang/Parser.scala b/src/main/scala/lang/Parser.scala index 23f126f..09a1e59 100644 --- a/src/main/scala/lang/Parser.scala +++ b/src/main/scala/lang/Parser.scala @@ -44,7 +44,9 @@ class Parser(tokens: List[Token]): else if (startOfQueryOutputModifier.contains(peek().tokenType)) queryOutputModifier else if (startOfQueryValue.contains(peek().tokenType)) - throw new ParseError("I found an unexpected ':'. Did you intend to search for a tag, section or similar, e.g. tag:news? If you would like to add a search phrase containing a ':' character, please surround it in double quotes.") + throw new ParseError( + "I found an unexpected ':'. Did you intend to search for a tag, section or similar, e.g. tag:news? If you would like to add a search phrase containing a ':' character, please surround it in double quotes." + ) else queryBinary private def queryBinary = diff --git a/src/main/scala/lang/Typeahead.scala b/src/main/scala/lang/Typeahead.scala index 35f69c2..978bc8f 100644 --- a/src/main/scala/lang/Typeahead.scala +++ b/src/main/scala/lang/Typeahead.scala @@ -13,7 +13,6 @@ case class TypeaheadSuggestion( suggestions: Suggestions ) - sealed trait Suggestions case class TextSuggestion(suggestions: List[TextSuggestionOption]) @@ -80,44 +79,41 @@ class Typeahead( } .map(_.flatten) + private def getSuggestionsForKeyToken(keyToken: Token) = + Future.successful(suggestFieldKey(keyToken.literal.getOrElse("")).map { + suggestions => + TypeaheadSuggestion( + keyToken.start, + keyToken.end, + ":", + suggestions + ) + }.toList) + private def suggestQueryField( q: QueryField ): Future[List[TypeaheadSuggestion]] = q match { case QueryField(keyToken, None) => - Future.successful( - List( - TypeaheadSuggestion( - keyToken.start, - keyToken.end, - ":", - suggestFieldKey(keyToken.literal.getOrElse("")) - ) - ) - ) + getSuggestionsForKeyToken(keyToken) case QueryField(keyToken, Some(valueToken)) => - val keySuggestions = Future.successful( - TypeaheadSuggestion( - keyToken.start, - keyToken.end, - ":", - suggestFieldKey(keyToken.literal.getOrElse("")) - ) - ) + val keySuggestions = getSuggestionsForKeyToken(keyToken) val valueSuggestions = suggestFieldValue( keyToken.literal.getOrElse(""), valueToken.literal.getOrElse("") ) - .map { suggestions => - TypeaheadSuggestion( - valueToken.start, - valueToken.end, - " ", - TextSuggestion(suggestions) - ) + .map { + _.map { suggestions => + TypeaheadSuggestion( + valueToken.start, + valueToken.end, + " ", + suggestions + ) + } } - Future.sequence(List(keySuggestions, valueSuggestions)) + Future.sequence(List(keySuggestions, valueSuggestions)).map(_.flatten) } private def suggestQueryOutputModifier( @@ -137,7 +133,6 @@ class Typeahead( ) } case QueryOutputModifier(keyToken, Some(valueToken)) => - val keySuggestion = suggestOutputModifierKey( keyToken.literal.getOrElse("") ).map { suggestion => @@ -163,23 +158,36 @@ class Typeahead( Future.sequence(List(keySuggestion, valueSuggestion)) } - private def suggestFieldKey(str: String): TextSuggestion = + private def suggestFieldKey(str: String): Option[TextSuggestion] = str match - case "" => typeaheadFieldEntries + case "" => Some(typeaheadFieldEntries) case str => - typeaheadFieldEntries.copy( - suggestions = typeaheadFieldEntries.suggestions - .filter(_.value.contains(str.toLowerCase())) - ) + typeaheadFieldEntries.suggestions + .filter(_.value.contains(str.toLowerCase())) match { + case suggestions @ nonEmptyList :: Nil => + Some( + typeaheadFieldEntries.copy( + suggestions = suggestions + ) + ) + case _ => None + } private def suggestFieldValue( key: String, str: String - ): Future[List[TextSuggestionOption]] = + ): Future[Option[Suggestions]] = fieldResolvers .find(_.id == key) - .map(_.resolveSuggestions(str)) - .getOrElse(Future.successful(List.empty)) + .map { + case typeaheadField if typeaheadField.suggestionType == "DATE" => + Future.successful(Some(DateSuggestion(None, None))) + case textField => + textField + .resolveSuggestions(str) + .map(options => Some(TextSuggestion(options))) + } + .getOrElse(Future.successful(None)) private def suggestOutputModifierKey(str: String): Future[TextSuggestion] = Future.successful( diff --git a/src/main/scala/lang/TypeaheadHelpersCapi.scala b/src/main/scala/lang/TypeaheadHelpersCapi.scala index 3130712..13303b8 100644 --- a/src/main/scala/lang/TypeaheadHelpersCapi.scala +++ b/src/main/scala/lang/TypeaheadHelpersCapi.scala @@ -18,6 +18,20 @@ class TypeaheadHelpersCapi(client: GuardianContentClient) { "Section", "Search by content sections, e.g. section/news", getSections + ), + TypeaheadField( + "from-date", + "From date", + "The date to search from", + List.empty, + "DATE" + ), + TypeaheadField( + "to-date", + "To date", + "The date to search to", + List.empty, + "DATE" ) ) @@ -79,9 +93,7 @@ class TypeaheadHelpersCapi(client: GuardianContentClient) { ), TextSuggestionOption("starRating", "starRating", "Description") ) - ), - TypeaheadField("from-date", "From date", "The date to search from", List.empty), - TypeaheadField("to-date","To date", "The date to search to", List.empty) + ) ) private def getTags(str: String): Future[List[TextSuggestionOption]] = @@ -90,7 +102,11 @@ class TypeaheadHelpersCapi(client: GuardianContentClient) { case str => ContentApiClient.tags.q(str) client.getResponse(query).map { response => response.results.map { tag => - TextSuggestionOption(tag.webTitle, tag.id, tag.description.getOrElse("")) + TextSuggestionOption( + tag.webTitle, + tag.id, + tag.description.getOrElse("") + ) }.toList } diff --git a/src/test/scala/lang/TypeaheadTest.scala b/src/test/scala/lang/TypeaheadTest.scala index cb82f79..6c57ebd 100644 --- a/src/test/scala/lang/TypeaheadTest.scala +++ b/src/test/scala/lang/TypeaheadTest.scala @@ -19,14 +19,7 @@ class TypeaheadTest extends BaseTest { QueryList(List(queryField("example", None))) ) .map { result => - result shouldBe List( - TypeaheadSuggestion( - 0, - 7, - ":", - TextSuggestion(List.empty) - ) - ) + result shouldBe List.empty } }