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

Partition calls #591

Merged
merged 16 commits into from
Oct 16, 2024
Merged

Partition calls #591

merged 16 commits into from
Oct 16, 2024

Conversation

felipe-gdr
Copy link
Contributor

Please make sure you consider the following:

  • [ yes ] Add tests that use __typename in queries
  • [ yes ] Does this change work with all nadel transformations (rename, type rename, hydration, etc)? Add tests for this.
  • [ FF can be in AGG] Is it worth using hints for this change in order to be able to enable a percentage rollout?
  • [ yes ] Do we need to add integration tests for this change in the graphql gateway?
  • [ maybe ] Do we need a pollinator check for this?

@felipe-gdr felipe-gdr marked this pull request as draft September 25, 2024 06:24
@felipe-gdr felipe-gdr marked this pull request as ready for review October 3, 2024 03:40
Copy link
Collaborator

@sbarker2 sbarker2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work 👍

@sbarker2
Copy link
Collaborator

Love the comprehensive test coverage

* A GraphQL type is considered to be a "mutation payload" (for the purposes of this transform) if
* it has a `success` field of type `Boolean!` plus an `errors` field of type List and any other number
* of fields of type List
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think atlassian public schema defines Payload as

"""
The general shape of a mutation response.
"""
interface Payload {
    "Was this mutation successful"
    success: Boolean!

    "A list of errors if the mutation was not successful"
    errors: [MutationError!]
}

would it be reasonable to check if the type extends Payload

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered it, but eventually decided not to do it because the type for the use case we're targeting doesn't actually implement Payload. But you make a good point, that type should really be implementing this interface. They're already adhering to the contract, so it shouldn't be a big change for them 👍

Copy link
Collaborator

@gnawf gnawf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments, we can address them later too.

In general looks good.

scalarValue: ScalarValue<*>,
inputValueDef: GraphQLInputValueDefinition,
context: Any,
) -> String?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to see this made into an interface so it's easier to parse e.g.

interface NadelPartitionKeyExtractor {
  fun getPartitionKey(
        scalarValue: ScalarValue<*>,
        inputValueDef: GraphQLInputValueDefinition,
        context: Any,
    ): String
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

import graphql.schema.GraphQLSchema
import graphql.schema.GraphQLTypeUtil

class NadelFieldPartition(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be internal so code outside library can't access it.

class NadelPartitionGraphQLErrorException(
message: String,
path: List<Any>? = null,
errorClassification: ErrorClassification? = partitioningErrorClassification,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think errorClassification is ever variable, so we should hard code it into the super call i.e.

class NadelPartitionGraphQLErrorException(
    message: String,
    path: List<Any>? = null,
) : NadelGraphQLErrorException(message, path, partitioningErrorClassification)

import graphql.schema.GraphQLOutputType
import graphql.schema.GraphQLScalarType

object NadelPartitionListMerger {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be internal

val thisNodesDataCast = thisNodesData?.let {
check(it is List<*>) { "Expected a list, but got ${it::class.simpleName}" }
it
} ?: emptyList<NadelResultInstruction.Set>()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the generics here correct?

Should just be emptyList<Any?>() right?

)

if (parentNodes.size != 1) {
// TODO: Support multiple parent nodes (interfaces, unions)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple parent nodes should only be if it's a list in the result. Don't think abstract types would cause that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's a list in the result

Hm, when would that happen? I'm deailng with fields that have type of List and parentNodes is always 1 🤔

return this is GraphQLObjectType
&& this.getField("success")?.let { it.type.unwrapNonNull() as? GraphQLScalarType }?.name == "Boolean"
&& this.getField("errors") != null
&& this.fields.filter { it.name != "success" }.all { it.type.unwrapNonNull() is GraphQLList }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be

&& this.fields.all { it.name == "success" || it.type.unwrapNonNull() is GraphQLList }

Since .filter and other transform functions return an entire new List unless the transform is on a Sequence

val parentNode = parentNodes.first()

val listDataFromPartitionCalls = dataFromPartitionCalls
.mapNotNull {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be

.flatMap {
  if (it == null) {
    emptyList()
  } else {
    check(it is List<*>) { "Expected a list, but got ${it::class.simpleName}" }
    it
  }
}

Also I think that ClassCastException will print out the expected/actual type. So could just straight up cast it.

.flatMap {
  it as List<*>?
}

}

// TODO: handle HTTP errors
val resultFromPartitionCalls = state.partitionCalls.awaitAll()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

class NadelFieldPartition(
private val pathToPartitionArg: List<String>,
private val fieldPartitionContext: Any,
private val graphQLSchema: GraphQLSchema,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe be explicit about it being the engineSchema since there's a few schema objects.

@felipe-gdr
Copy link
Contributor Author

Left some comments, we can address them later too.

In general looks good.

Thanks for the great suggestions 🙏

I'll merge it as is to unblock subsequent changes quickly and raise follow-up PRs to address your feedback.

@felipe-gdr felipe-gdr merged commit e8e1b6d into master Oct 16, 2024
2 checks passed
@gnawf gnawf mentioned this pull request Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants