Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ag/more tag params #36

Merged
merged 6 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ object Content {

ReplaceField("tags", Field("tags", OptionType(ListType(Tags.Tag)),
arguments = TagQueryParameters.NonPaginatedTagQueryParameters,
resolve=ctx=> ctx.ctx.repo.tagsForList(ctx.value.tags, ctx arg TagQueryParameters.Section, ctx arg TagQueryParameters.TagType))
resolve=ctx=> ctx.ctx.repo.tagsForList(ctx.value.tags, ctx arg TagQueryParameters.Section, ctx arg TagQueryParameters.TagType, ctx arg TagQueryParameters.Category, ctx arg TagQueryParameters.Reference))
),
ExcludeFields("atomIds", "isGone", "isExpired", "sectionId"),
AddFields(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object ContentQueryParameters {
val ContentIdArg = Argument("id", OptionInputType(StringType), description = "get one article by ID")
val QueryString = Argument("q", OptionInputType(StringType), description = "an Elastic Search query string to search for content")
val QueryFields = Argument("queryFields", OptionInputType(ListInputType(StringType)), description = "fields to perform a query against. Defaults to webTitle and path.")
val TagArg = Argument("tags", OptionInputType(ListInputType(StringType)), description = "look up articles associated with all of these tag IDs")
val TagArg = Argument("tags", OptionInputType(ListInputType(StringType)), description = "look up articles associated with all of these tag IDs. If you don't have exact tag IDs you should instead make a root query on Tags and use the `matchingContent` or `matchingAnyTag` selectors")
val ExcludeTagArg = Argument("excludeTags", OptionInputType(ListInputType(StringType)), description = "don't include any articles with these tag IDs")
val SectionArg = Argument("sectionId", OptionInputType(ListInputType(StringType)), description = "look up articles in any of these sections")
val ExcludeSectionArg = Argument("excludeSections", OptionInputType(ListInputType(StringType)), description = "don't include any articles with these tag IDs")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import java.nio.charset.StandardCharsets
import java.util.Base64
import scala.util.Try
import io.circe.syntax._
@deprecated("you should be using com.gu.contentapi.porter.graphql")

case class Edge[T:io.circe.Decoder](totalCount:Long, endCursor:Option[String], hasNextPage:Boolean, nodes:Seq[T]) {
def map[V:io.circe.Decoder](mapper:(T)=>V) = Edge[V](totalCount, endCursor, hasNextPage, nodes.map(mapper))
}
@deprecated("you should be using com.gu.contentapi.porter.graphql")

object Edge {
private val logger = LoggerFactory.getLogger(getClass)
private val encoder = Base64.getEncoder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ object PaginationParameters {
object OrderDateSchema {
val definition = EnumType(
"OrderDate",
Some("Which date field to use for ordering the content"),
Some("Which date field to use for ordering the content, or whether to search on document score"),
List(
EnumValue("score", Some("Ignore when the content was made or published and sort by relevance to the query parameters"), "score"),
EnumValue("published", Some("When the content was published to web"), "webPublicationDate"),
EnumValue("firstPublished", Some("When the first version of this content was published"), "fields.firstPublicationDate"),
EnumValue("lastModified", Some("The last time the content was modified prior to publication"), "fields.lastModified"),
Expand Down
52 changes: 48 additions & 4 deletions src/main/scala/com/gu/contentapi/porter/graphql/RootQuery.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gu.contentapi.porter.graphql

import com.gu.contentapi.porter.model.{Content, Tag}
import com.sksamuel.elastic4s.requests.searches.sort.SortOrder
import sangria.schema._
import datastore.GQLQueryContext
import deprecated.anotherschema.Edge
Expand All @@ -10,6 +11,8 @@ import scala.concurrent.ExecutionContext.Implicits.global
import io.circe.generic.auto._
import org.slf4j.LoggerFactory

import scala.concurrent.Future

object RootQuery {
private val logger = LoggerFactory.getLogger(getClass)

Expand All @@ -24,14 +27,41 @@ object RootQuery {
)
)

val TagEdge: ObjectType[Unit, Edge[Tag]] = ObjectType(
val TagEdge: ObjectType[GQLQueryContext, Edge[Tag]] = ObjectType(
"TagEdge",
"A list of tags with pagination features",
() => fields[Unit, Edge[Tag]](
() => fields[GQLQueryContext, Edge[Tag]](
Field("totalCount", LongType, Some("Total number of results that match your query"), resolve = _.value.totalCount),
Field("endCursor", OptionType(StringType), Some("The last record cursor in the set"), resolve = _.value.endCursor),
Field("hasNextPage", BooleanType, Some("Whether there are any more records to retrieve"), resolve = _.value.hasNextPage),
Field("nodes", ListType(com.gu.contentapi.porter.graphql.Tags.Tag), Some("The actual tags returned"), resolve = _.value.nodes)
Field("nodes", ListType(com.gu.contentapi.porter.graphql.Tags.Tag), Some("The actual tags returned"), resolve = _.value.nodes),
Field("matchingAnyTag", ArticleEdge, Some("Content which matches any of the tags returned"),
arguments= ContentQueryParameters.AllContentQueryParameters,
resolve = { ctx=>
if(ctx.value.nodes.isEmpty) {
Future(Edge[Content](
0L,
None,
false,
Seq()
))
} else {
ctx.ctx.repo.marshalledDocs(ctx arg ContentQueryParameters.QueryString,
queryFields = ctx arg ContentQueryParameters.QueryFields,
atomId = None,
forChannel = ctx arg ContentQueryParameters.ChannelArg,
userTier = ctx.ctx.userTier,
tagIds = Some(ctx.value.nodes.map(_.id)),
excludeTags = ctx arg ContentQueryParameters.ExcludeTagArg,
sectionIds = ctx arg ContentQueryParameters.SectionArg,
excludeSections = ctx arg ContentQueryParameters.ExcludeSectionArg,
orderDate = ctx arg PaginationParameters.OrderDate,
orderBy = ctx arg PaginationParameters.OrderBy,
limit = ctx arg PaginationParameters.Limit,
cursor = ctx arg PaginationParameters.Cursor,
)
}
})
)
)

Expand All @@ -56,6 +86,8 @@ object RootQuery {
val Query = ObjectType[GQLQueryContext, Unit](
"Query", fields[GQLQueryContext, Unit](
Field("article", ArticleEdge,
description = Some("An Article is the main unit of our publication. You can search articles directly here, or query" +
" tags or sections to see what articles live within it."),
arguments = ContentQueryParameters.AllContentQueryParameters,
resolve = ctx =>
ctx arg ContentQueryParameters.ContentIdArg match {
Expand All @@ -71,11 +103,23 @@ object RootQuery {
}
),
Field("tag", TagEdge,
description = Some("The Guardian uses tags to group similar pieces of content together across multiple different viewpoints. " +
"Tags are a closed set, which can be searched here, and there are different types of tags which represent different viewpoints"),
arguments = TagQueryParameters.AllTagQueryParameters,
resolve = ctx =>
ctx.ctx.repo.marshalledTags(ctx arg TagQueryParameters.tagId, ctx arg TagQueryParameters.Section, ctx arg TagQueryParameters.TagType, ctx arg PaginationParameters.OrderBy, ctx arg PaginationParameters.Limit, ctx arg PaginationParameters.Cursor)
ctx.ctx.repo.marshalledTags(ctx arg TagQueryParameters.QueryString,
ctx arg TagQueryParameters.Fuzziness,
ctx arg TagQueryParameters.tagId,
ctx arg TagQueryParameters.Section,
ctx arg TagQueryParameters.TagType,
ctx arg TagQueryParameters.Category,
ctx arg TagQueryParameters.Reference,
ctx arg PaginationParameters.OrderBy,
ctx arg PaginationParameters.Limit, ctx arg PaginationParameters.Cursor)
),
Field("atom", AtomEdge,
description = Some("An Atom is a piece of content which can be linked to multiple articles but may have a production lifecycle independent" +
" of these articles. Examples are cartoons, videos, quizzes, call-to-action blocks, etc."),
arguments = AtomQueryParameters.AllParameters,
resolve = ctx=>
ctx.ctx.repo.atoms(ctx arg AtomQueryParameters.AtomIds, ctx arg AtomQueryParameters.QueryString, ctx arg AtomQueryParameters.QueryFields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,31 @@ object TagQueryParameters {
)
)

val FuzzinessOptions = EnumType(
"FuzzinessOptions",
Some("Valid options for making a fuzzy-match query"),
List(
EnumValue("AUTO",
value="AUTO",
description=Some("Generates an edit distance based on the length of the term. If the term is >5 chars, then 2 edits allowed; if <3 chars than no edits allowed")
),
EnumValue("OFF",
value="OFF",
description=Some("Disable fuzzy-matching")
)
)
)

val tagId = Argument("tagId", OptionInputType(StringType), description = "Retrieve this specific tag")
val Section = Argument("section", OptionInputType(StringType), description = "Only return tags from this section")
val TagType = Argument("type", OptionInputType(TagTypes), description = "Type of the tag to return")
val AllTagQueryParameters = tagId :: Section :: TagType :: Cursor :: OrderBy :: Limit :: Nil
val QueryString = Argument("q", OptionInputType(StringType), description = "Search for tags that match this public-facing name")
val Fuzziness = Argument("fuzzy", OptionInputType(FuzzinessOptions), description = "Perform a fuzzy-matching query (default). Set to `OFF` to disable fuzzy-matching.")
val Category = Argument("category", OptionInputType(StringType), description = "A category to match against tags")
val Reference = Argument("reference", OptionInputType(StringType), description = "A reference to match against tags")
val AllTagQueryParameters = QueryString :: tagId :: Section :: TagType :: Fuzziness :: Category ::
Reference :: Cursor :: OrderBy :: Limit :: Nil

val NonPaginatedTagQueryParameters = Section :: TagType :: Nil
val NonPaginatedTagQueryParameters = QueryString :: tagId :: Section :: TagType :: Fuzziness :: Category ::
Reference :: Nil
}
27 changes: 24 additions & 3 deletions src/main/scala/com/gu/contentapi/porter/graphql/Tags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import java.time.format.DateTimeFormatter
import io.circe.generic.auto._
import io.circe.syntax._
import com.gu.contentapi.porter.model
import datastore.GQLQueryContext

object Tags {
import Content.Reference
Expand All @@ -15,10 +16,30 @@ object Tags {
implicit val PodcastCategory = deriveObjectType[Unit, model.PodcastCategory]()
implicit val TagPodcast = deriveObjectType[Unit, model.TagPodcast]()

val Tag = deriveObjectType[Unit, model.Tag](
val Tag = deriveObjectType[GQLQueryContext, model.Tag](
ReplaceField("type", Field("type", OptionType(TagQueryParameters.TagTypes), resolve=_.value.`type`)),
ReplaceField("alternateIds", Field("alternateIds", ListType(StringType), arguments = AlternateIdParameters.AllAlternateIdParameters, resolve= AlternateIdParameters.TagResolver[Unit])),
ReplaceField("alternateIds", Field("alternateIds", ListType(StringType), arguments = AlternateIdParameters.AllAlternateIdParameters, resolve= AlternateIdParameters.TagResolver[GQLQueryContext])),
ReplaceField("tagCategories", Field("tagCategories", ListType(StringType), resolve = _.value.tagCategories.map(_.toSeq).getOrElse(Seq()))),
ReplaceField("entityIds", Field("entityIds", ListType(StringType), resolve = _.value.tagCategories.map(_.toSeq).getOrElse(Seq())))
ReplaceField("entityIds", Field("entityIds", ListType(StringType), resolve = _.value.tagCategories.map(_.toSeq).getOrElse(Seq()))),
AddFields(
Field("matchingContent", RootQuery.ArticleEdge, description=Some("Articles and other content which have this tag"),
arguments= ContentQueryParameters.AllContentQueryParameters.filterNot(_ == ContentQueryParameters.TagArg),
resolve = { ctx=>
ctx.ctx.repo.marshalledDocs(ctx arg ContentQueryParameters.QueryString,
queryFields=ctx arg ContentQueryParameters.QueryFields,
atomId = None,
forChannel = ctx arg ContentQueryParameters.ChannelArg,
userTier = ctx.ctx.userTier,
tagIds = Some(Seq(ctx.value.id)),
excludeTags = ctx arg ContentQueryParameters.ExcludeTagArg,
sectionIds = ctx arg ContentQueryParameters.SectionArg,
excludeSections = ctx arg ContentQueryParameters.ExcludeSectionArg,
orderDate = ctx arg PaginationParameters.OrderDate,
orderBy = ctx arg PaginationParameters.OrderBy,
limit = ctx arg PaginationParameters.Limit,
cursor = ctx arg PaginationParameters.Cursor,
)
})
)
)
}
16 changes: 12 additions & 4 deletions src/main/scala/datastore/DocumentRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ trait DocumentRepo {
orderDate:Option[String], orderBy:Option[SortOrder],
limit: Option[Int], cursor: Option[String]): Future[Edge[Content]]

def marshalledTags(maybeTagId:Option[String], maybeSection: Option[String], tagType:Option[String],
orderBy: Option[SortOrder], limit: Option[Int], cursor: Option[String]): Future[Edge[Tag]]

def tagsForList(tagIdList:Seq[String], maybeSection: Option[String], tagType:Option[String]):Future[Seq[Tag]]
def marshalledTags(maybeQuery:Option[String],
maybeFuzziness:Option[String],
maybeTagId:Option[String],
maybeSection: Option[String],
tagType:Option[String],
maybeCategory:Option[String],
maybeReferences:Option[String],
orderBy: Option[SortOrder],
limit: Option[Int],
cursor: Option[String]): Future[Edge[Tag]]

def tagsForList(tagIdList:Seq[String], maybeSection: Option[String], tagType:Option[String], maybeCategory:Option[String], maybeReferences:Option[String]):Future[Seq[Tag]]

def atomsForList(atomIds: Seq[String], atomType: Option[String]):Future[Seq[Json]]

Expand Down
Loading
Loading