graphql query with different output - apollo

I have an api with an endpoint which can takes different query parameters.
/toto?number=&shipments.tracking_number=&customer.email=. The thing is that to get the query, I'm using only one input on the frontend and depending on that input, I have to fill the corresponding query parameter. But, depending on the query the user is passing, the JSON I receive in response is different. For example if he provides his email, the duplicates field won't be present in the response or if he provides a tracking number, the total won't be there either. Is there a way to make a query that is dynamic to handle if a field is in the response or not ?
export const GET_ORDER = gql`
query GetOrder($query: String!) {
getOrder(query: $query) {
id
tokenValue
number
total
duplicates
shippingTotal
}
}`

You can do this with two subtypes as an example using unions and interfaces, you can easily extend to additional ones.
In your typeDefs:
interface CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
}
Type Order1 implements CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
total: Float!
}
Type Order2 implements CoreOrder {
id: ID!
tokenValue: String!
number: Int!
shippingTotal: Float!
duplicates: Int!
}
union Order = Order1 | Order 2
Now your query can be defined to return whichever fields are appropriate:
query GetOrder($query: String!) {
getOrder(query: $query) {
__typename
id
tokenValue
number
shippingTotal
... on Order1 {
total
}
... on Order2 {
duplicates
}
}
}
You'll need to implement:
a __resolveType resolver that figures out what type your query is returning
resolvers for CoreOrder to handle those 4 fields
an Order1 resolver for the total field
an Order2 resolver for the duplicates field
Your GraphQL server will then figure out which resolvers to call and in what order and package up the results for your client.

Related

AWS Amplify searchable field by geo distance (location)

I'm developing an application that will allow users to search for other users by ordering them by geo location.
According to the information found on the internet, to do this on amplify I have to perform the following steps:
create an elastic search mapping, indicating the type 'geo_point' on the lastPosition attribute (I would like to define it once in the project file and not at each build in the elastic search console)
create a custom query and a custom vtl resolver and set the sort by lastPosition (of type '_geo_distance') in it.
But I don't understand how to do these 2 steps, so I need some suggestion :(.
// my schema.graphql
type User
#model
#auth(rules: [{allow: owner}, {allow: private, operations: [read]}])
#searchable {
// ...other fields
id: ID!
lastPosition: Position
positionUpdatedAt: AWSDateTime
}
type Position {
lat: Float!
lon: Float!
}
// my custom searchUsers query in graphql/custom_queries.ts
export const searchUsersNearby = /* GraphQL */ `
query SearchUsersNearby(
$filter: SearchableUserFilterInput
$sort: [SearchableUserSortInput]
$location: PositionInput // current user position that i need to use in vtl resolver to sort users by distance,
$limit: Int
$nextToken: String
$from: Int
$aggregates: [SearchableUserAggregationInput]
) {
searchUsersNearby(
filter: $filter
sort: $sort
limit: $limit
nextToken: $nextToken
from: $from
aggregates: $aggregates
) {
items {
id
// ...other fields
lastPosition
positionUpdatedAt
createdAt
updatedAt
owner
}
nextToken
total
aggregateItems {
name
result {
... on SearchableAggregateScalarResult {
value
}
... on SearchableAggregateBucketResult {
buckets {
key
doc_count
}
}
}
}
}
}
`;
I use amplify v8.1.0 with transformer v2.
Thank you guys

How to make subscription with arguments correctly in GraphQL?

In AWS AppSync I tried to test my GraphQL schema. I'm a little confused and need help. My subscription called watchMessages doesn't work when there is an argument. If I remove the argument from schema and test it, it works. But I only need to receive messages from a specific room. What am I missing?
input CreateMessageInput {
roomId: String!
messageText: String
messageAuthor: String
}
type CreateMessageOutput {
messageId: String!
messageCreatedDateTime: String!
messageText: String!
messageAuthor: String
}
type Mutation {
createMessage(input: CreateMessageInput): CreateMessageOutput
}
type Subscription {
watchMessages(roomId: String!): CreateMessageOutput
#aws_subscribe(mutations: ["createMessage"])
}
I make such subscription query:
subscription MySubscription {
watchMessages(roomId: "5d354323") {
messageId
messageCreatedDateTime
messageText
messageAuthor
}
}
I make such mutation query:
mutation MyMutation {
createMessage(input: {roomId: "5d354323", messageAuthor: "Bob", messageText: "Hello!"}) {
messageId
messageCreatedDateTime
messageText
messageAuthor
}
}
Finally, I found the solution. All subscription parameters must exist as a field in the returning type. This means that the type of parameter must also match the type of the field in the returning object. I added roomId field to the CreateMessageOutput type.
type CreateMessageOutput {
roomId: String!
messageId: String!
messageCreatedDateTime: String!
messageText: String!
messageAuthor: String
}

Passing variables to an external resolve reference

I'm using Apollo Federation for 2 months but I'm actually stuck. I've no idea how to pass a variable between my two graphql services.
I've got a website (website graphql service) which have orders (orders graphql service).
I have a query to find websites and for these websites I want some stats of orders for a date range. Here the typedef (website) :
type Query {
websites(orderFilter: OrderFilterInput): [Website!]
}
type Website #key(fields: "id") {
id: ID!
name: String!
url: String!
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary
}
input OrderSummaryFilterInput {
beginDate: Date
endDate: Date
}
extend type OrderSummary #key(fields: "websiteId") {
websiteId: String! #external
}
The resolver :
orderSummary: (website, { orderSummaryFilter }) => {
console.log("orderSummaryFilter", orderSummaryFilter); // filters are OK
// HOW CAN I PASS orderFilterSummary to my order graphql service here ????
return { __typename: "OrderSummary", websiteId: website.id };
}
And Order graphql service
Typedef part :
type OrderSummary #key(fields: "websiteId") {
websiteId: String!
count: Int
amount: Int
}
Resolver part :
// order gql service
OrderSummary: {
__resolveReference(website, args, info) {
console.log("website id :", website.id); // I ve got my website ID
// HOW TO GET OrderSummaryFilter here ????
},
},
How can I access to order summary filter variable in order graphql resolver ? Thank you.
From what I am aware of, it is not possible to send variables from one service to another other then the ID. But there is a solution to this.
If you want to pass in variables, extend your Website type in your order service instead of extending order type in website service.
Order typedef:
extend type Website #key(fields: "id") {
id: ID! #external
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary #requires(fields:"id")
}
Order resolver:
Website: {
orderSummary: async (website, { orderFilter }) => getOrderSummary(orderFilter) //get orderSummary with orderFilter
},
So I want to expound on the previous (and I believe correct) answer:
In Federation, you almost never should have to expose a field called somethingId (userId, websiteId, etc). That is often either a left-over from Schema Stitching, or you simply got your type origins backward. Instead of using somethingId, you should be able to just use the object. Often, moving the #extend to the other service will get rid of the somethingId field, and get rid of the type of problem you're currently facing:
Website Service:
type Query {
websites(orderFilter: OrderFilterInput): [Website!]
}
type Website #key(fields: "id") {
id: ID!
name: String!
url: String!
}
Order Service:
extend type Website #key(fields: "id") {
id: ID! #external
orderSummary(orderFilter: OrderSummaryFilterInput): OrderSummary
}
input OrderSummaryFilterInput {
beginDate: Date
endDate: Date
}
type OrderSummary {
website: Website!
count: Int
amount: Int
}
Resolvers:
const resolvers = {
Website: {
orderSummary(parent, args, context) {
const websiteId = parent.id;
// args is the data you wanted
}
},
};

AWS appsync query resolver

Currently I have my resolver as a lambda function :
import boto3
from boto3.dynamodb.conditions import Key
def lambda_handler(event, context):
list = []
for device in event['source']['devices'] :
dynamodb = boto3.resource('dynamodb')
readings = dynamodb.Table('readings')
response = readings.query(
KeyConditionExpression=Key('device').eq(device['device'])
)
items = response['Items']
list.extend(items)
return list
I would like to be able to have this as a VTL resolver on the dynamodb. My problem is that my table has a sort key
This means I can't use a batch resolver to query on a bunch of id's because I would also need to provide the sort key, and I just want all the results by primary partition key.
How do you query with a bunch of ids using VTL, basically replicating my lambda function in VTL. Is this even possible ?
Schema added, please excuse the mess it is a work in progress and am attempting many things. Still very new to graphQL
type Device {
id: String
device: String!
}
input DeviceInput {
id: String
device: String!
}
type DeviceReadings {
devices: [Device]
}
type Mutation {
createDevice(input: DeviceInput): Device
}
type PaginatedDevices {
devices: [Device]
readings: [Reading]
cows: [cow]
nextToken: String
}
type Query {
getAllUserDevices(nextToken: String, count: Int): PaginatedDevices
getAllDeviceReadings: DeviceReadings
getAllUserReadings: DeviceReadings
getAllReadings(deviceId: String!): Readings
getCowReadings(cowId: String!): UserCowReadings
}
type Reading {
device: String
time: Int
cow: Int
battery: String
}
type Readings {
items: [Reading]
}
type UserCowReadings {
devices: [Device]
readings: [Reading]
}
type cow {
id: Int
device: String
nait: String
}
schema {
query: Query
mutation: Mutation
}
Yes you can do this but you will need to tweak your schema a bit. In that lambda you are essentially saying "for each device do a DynamoDB query to get the most recent readings for that device". Conceptually I would say that devices have many readings. With this in mind, lets make a schema:
type Device {
id: ID!
name: String
# Get the most recent readings for this device.
# Do a Query where "device = $ctx.source.id"
readings(limit: Int, nextToken: String): ReadingConnection
}
type Reading {
# Use the source's device id in the table to fetch the real device
# GetItem where device = $ctx.source.device (and any sort key condition)
device: Device
time: Int
cow: Int
battery: String
}
type ReadingConnection {
items: [Reading]
nextToken: String
}
type DeviceConnection {
items: [Device]
nextToken: String
}
type Query {
getAllDevices(limit: Int, nextToken: String): DeviceConnection
}
You may then paginate through your devices and paginate through each devices readings separately:
query GetAllDevicesAndReadings {
getAllDevices(first: 10) {
items {
id
name
readings(limit: 10) {
time
cow
battery
}
}
}
}
I recommend using the drop down in the AppSync console's resolver page to get more ideas for what you can do with the resolver VTL to implement these GetItems and Queries. This is a good starting point. Let me know if you have trouble implementing the VTL.

AWS AppSync - Directive "aws_subscribe" may not be used on FIELD_DEFINITION

I'm trying to get to grips with AWS AppSync. I'm quite new to GraphQL. I've got the following GraphQL:
type Mutation {
deleteParcel(geoHash: String!, type_id: String!): Parcel
addParcel(input: ParcelInput!): Parcel
batchAddParcels(parcels: [ParcelInput]): [Parcel]
}
type Parcel {
geoHash: String!
type_id: String!
}
type ParcelConnection {
items: [Parcel]
}
input ParcelInput {
geoHash: String!
type_id: String!
}
input ParcelsInput {
parcels: [ParcelInput]
}
type Query {
getNearbyParcels(geoHash: String!): ParcelConnection
}
type Subscription {
onAddParcel(geoHash: String, type_id: String): Parcel
#aws_subscribe(mutations: ["addParcel"])
onBatchAddParcels(geoHash: String): Parcel
#aws_subscribe(mutations: ["batchAddParcels"])
onDeleteParcel(geoHash: String, type_id: String): Parcel
#aws_subscribe(mutations: ["deleteParcel"])
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
All seems to be setup fine on AWS console. I get the schema.json and then run command:
aws-appsync-codegen generate AWSGraphQL.graphql --schema schema.json --output AppsyncAPI.swift
and get the response:
../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
.../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
.../SnatchHQ/snatch_appsync/AppSync/AWSGraphQL.graphql: Directive "aws_subscribe" may not be used on FIELD_DEFINITION.
error: Validation of GraphQL query document failed
Can anyone help?
If the file AWSGraphQL.graphql is your API GraphQL schema, then that explains the problem. What you need to do is define a *.graphql file that defines your query, mutation, and subscription operations based on your GraphQL API. For example, the following query definitions would match your schema
mutation AddParcel($geoHash: String!, $type_id: String!) {
addParcel(input: {
geoHash: $geoHash
type_id: $typeId
}) {
...Parcel
}
}
query GetNearbyParcels($geoHash: String!) {
getNearbyParcels(
geoHash: $geoHash
) {
...ParcelConnection
}
}
subscription OnAddParcel {
onAddParcel {
...Parcel
}
}
fragment Parcel on Parcel {
geoHash
type_id
}
fragment ParcelConnection on Parcel Connection {
items {
...Parcel
}
}
Assuming you named it something like parcels.graphql, you can then call the following to generate a Swift implementation of the AddParcel mutation, the GetNearbyParcels query, and the OnAddParcel subscription
aws-appsync-codegen generate parcels.graphql \
--schema schema.json \
--output AppSyncParcelsAPI.swift