Skip to content

Commit

Permalink
Add execution stats to GraphQL responses
Browse files Browse the repository at this point in the history
  • Loading branch information
frankbille committed Nov 19, 2024
1 parent 03875e1 commit 57a0935
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/main/kotlin/info/jotajoti/jampuz/JampuzApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package info.jotajoti.jampuz

import graphql.scalars.*
import info.jotajoti.jampuz.event.*
import info.jotajoti.jampuz.graphql.*
import info.jotajoti.jampuz.jidcode.*
import info.jotajoti.jampuz.security.*
import org.springframework.aot.hint.annotation.*
Expand All @@ -24,6 +25,7 @@ class GraphQlConfig {
fun runtimeWiringConfigurer() =
RuntimeWiringConfigurer { wiringBuilder ->
wiringBuilder.scalar(ExtendedScalars.Date)
wiringBuilder.registerCostDirective()
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/main/kotlin/info/jotajoti/jampuz/graphql/CostDirective.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package info.jotajoti.jampuz.graphql

import graphql.schema.*
import graphql.schema.idl.*
import graphql.schema.idl.RuntimeWiring.*
import org.springframework.graphql.server.*

fun Builder.registerCostDirective(): Builder =
directive("Cost", CostDirective)

val WebGraphQlResponse.calculatedCost: Int get() = executionInput.graphQLContext.getOrDefault(COST_KEY, 0)

private object CostDirective : SchemaDirectiveWiring {
override fun onField(environment: SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition>): GraphQLFieldDefinition =
environment.setFieldDataFetcher(
CostDataFetcher(
environment.appliedDirective,
environment.fieldDataFetcher
)
)
}

private class CostDataFetcher<T>(val appliedDirective: GraphQLAppliedDirective, dataFetcher: DataFetcher<T>) :
DataFetcherDelegate<T>(dataFetcher) {

override fun get(environment: DataFetchingEnvironment): T {
val cost = appliedDirective.getArgument("value").getValue<Int>()
environment.graphQlContext.compute<Int>(COST_KEY) { t, u ->
u?.let { u + cost } ?: cost
}
return super.get(environment)
}
}

private const val COST_KEY = "cost"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package info.jotajoti.jampuz.graphql

import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment

abstract class DataFetcherDelegate<T>(val dataFetcher: DataFetcher<T>) : DataFetcher<T> {

override fun get(environment: DataFetchingEnvironment): T {
return dataFetcher.get(environment)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package info.jotajoti.jampuz.graphql

import org.springframework.graphql.server.*
import org.springframework.graphql.server.WebGraphQlInterceptor.*
import org.springframework.stereotype.*
import reactor.core.publisher.*

@Component
class GraphQLQueryStatsInterceptor : WebGraphQlInterceptor {

override fun intercept(request: WebGraphQlRequest, chain: Chain): Mono<WebGraphQlResponse> {
val start = System.currentTimeMillis()

return chain.next(request).map { response ->
val end = System.currentTimeMillis()
response.transform {
it.extensions(
mapOf(
"executionStats" to ExecutionStats(
duration = end - start,
cost = response.calculatedCost
)
)
)
}
}
}
}

data class ExecutionStats(
val duration: Long,
val cost: Int,
)
2 changes: 1 addition & 1 deletion src/main/resources/graphql/admin.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ extend type Mutation {

extend type Query {
# Return null if not authenticated or if authentication is not an admin
authenticatedAdmin: Admin
authenticatedAdmin: Admin @Cost(value: 1)
}
16 changes: 8 additions & 8 deletions src/main/resources/graphql/event.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ type Event {
code: JidCode!
year: Int!
active: Boolean!
location: Location!
location: Location! @Cost(value: 5)
# Returns true if event is latest for the location
isLatest: Boolean!
isLatest: Boolean! @Cost(value: 5)
# Return previous year's event
previous: Event
previous: Event @Cost(value: 5)
# Return next year's event
next: Event
next: Event @Cost(value: 5)
}

input CreateEventInput {
Expand All @@ -27,18 +27,18 @@ input UpdateEventInput {
}

extend type Location {
events: [Event!]!
latestEvent: Event
events: [Event!]! @Cost(value: 10)
latestEvent: Event @Cost(value: 5)
}

extend type Query {
# If no year is provided the latest event is taken
# Can be loaded without authentication
event(code: String!, year: Int): Event
event(code: String!, year: Int): Event @Cost(value: 5)

# Load specific event by ID.
# Requires admin authentication
eventById(eventId: ID!): Event
eventById(eventId: ID!): Event @Cost(value: 5)
}

extend type Mutation {
Expand Down
16 changes: 8 additions & 8 deletions src/main/resources/graphql/jidcode.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ enum Region {
}

type JidCode {
value: String!
region: Region!
value: String! @Cost(value: 1)
region: Region! @Cost(value: 1)
# A lowercase [ISO3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code
country: String!
country: String! @Cost(value: 1)
}

type JidCodeStats {
id: ID!
count: Int!
uniqueCount: Int!
uniqueCountryCount: Int!
countryStats: [CountryStat!]!
uniqueCountries: [String!]!
countryStats: [CountryStat!]! @Cost(value: 1)
uniqueCountries: [String!]! @Cost(value: 1)
uniqueRegionCount: Int!
uniqueRegions: [Region!]!
uniqueRegions: [Region!]! @Cost(value: 1)
}

type CountryStat {
Expand All @@ -34,9 +34,9 @@ type CountryStat {
}

extend type Participant {
jidCodeStats: JidCodeStats!
jidCodeStats: JidCodeStats! @Cost(value: 10)
}

extend type Event {
jidCodeStats: JidCodeStats!
jidCodeStats: JidCodeStats! @Cost(value: 10)
}
6 changes: 3 additions & 3 deletions src/main/resources/graphql/location.graphqls
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type Location {
id: ID!
name: String!
owners: [Admin!]
owners: [Admin!] @Cost(value: 3)
}

input CreateLocationInput {
Expand All @@ -10,9 +10,9 @@ input CreateLocationInput {

extend type Query {
# Only returns locations owned by authenticated admin
locations: [Location!]
locations: [Location!] @Cost(value: 10)
# Only returns location if owned by authenticated admin
location(locationId: ID!): Location
location(locationId: ID!): Location @Cost(value: 5)
}

extend type Mutation {
Expand Down
22 changes: 11 additions & 11 deletions src/main/resources/graphql/participant.graphqls
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
type Participant {
id: ID!
name: String!
id: ID! @Cost(value: 1)
name: String! @Cost(value: 1)
# Pin code is only set if authenticated user is participant itself or an owner on the location
pinCode: String
pinCode: String @Cost(value: 1)
# If not null, means this participant is linked to the Admin user so the admin also can act as participant.
admin: Admin
event: Event!
foundJidCodes: [FoundJidCode!]
admin: Admin @Cost(value: 5)
event: Event! @Cost(value: 5)
foundJidCodes: [FoundJidCode!] @Cost(value: 5)
}

type FoundJidCode {
id: ID!
code: JidCode!
participant: Participant!
id: ID! @Cost(value: 1)
code: JidCode! @Cost(value: 1)
participant: Participant! @Cost(value: 5)
}

input CreateParticipantInput {
Expand All @@ -26,7 +26,7 @@ input RegisterFoundJidCode {
}

extend type Event {
participants: [Participant!]!
participants: [Participant!]! @Cost(value: 5)
}

extend type Mutation {
Expand All @@ -40,5 +40,5 @@ extend type Query {
# Return null if not authenticated or if authentication is not a participant
# If eventJidCode is provided it will only return participant if the authenticated
# participant is associated with the event
authenticatedParticipant(eventJidCode: String, year: Int): Participant
authenticatedParticipant(eventJidCode: String, year: Int): Participant @Cost(value: 1)
}
1 change: 1 addition & 0 deletions src/main/resources/graphql/schema.graphqls
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
scalar Date @specifiedBy(url: "https://tools.ietf.org/html/rfc3339")
directive @Cost(value: Int!) on FIELD_DEFINITION

type Mutation {
}
Expand Down

0 comments on commit 57a0935

Please sign in to comment.