AWS Amplify GraphQL mutation is not updating a boolean field - amazon-web-services

I'm playing with the AWS Amplify GraphQL console and I've noticed that updating a boolean field is not working and I'm not sure exactly why. This is also not working from my React Native app. Basically, what I'm trying to do, is at onPress, change the isOnline to true or false (see full code below):
isOnline: !car.isOnline
schema.graphql
type Car #model
#key(name: "byUser", fields: ["userId"])
{
id: ID!
type: String!
latitude: Float
longitude: Float
heading: Float
plate: String
isOnline: Boolean
isActive: Boolean
orders: [Order] #connection(keyName: "byCar", fields: ["id"])
userId: ID!
user: User #connection(fields: ["userId"])
}
mutations.js
export const updateCar = /* GraphQL */ `
mutation UpdateCar(
$input: UpdateCarInput!
$condition: ModelCarConditionInput
) {
updateCar(input: $input, condition: $condition) {
id
type
latitude
longitude
heading
plate
isOnline
isActive
userId
_version
_deleted
_lastChangedAt
createdAt
updatedAt
}
}
`;
index.js
try {
const userData = await Auth.currentAuthenticatedUser();
const input = {
id: userData.attributes.sub,
isOnline: !car.isOnline,
}
const updatedCarData = await API.graphql(
graphqlOperation(
updateCar, {
input
})
)
console.log("Updated car: ", updatedCarData.data.updateCar)
setCar(updatedCarData.data.updateCar);
} catch (e) {
console.error(e);
}
From the app, every time I get the isOnline field set to true, even if I tried setting it to false instead of !car.isOnline.
I also tried creating a new field called isActive which was null initially, I created a mutation on AWS AppSync GraphQL console and was able to set it to false, but then, when trying to set it to true, updating is not working, it's always returning false.
As a note, updating the other fields, for example String fields, is working.
Can you please guide me into this issue?

I was able to solve this issue by:
Running the below command:
amplify update api
Select GraphQL, then Disable Conflict Detection
Last, but not least, run:
amplify push
I haven't found any explanation for this on the Amplify Docs, but if you're running into this issue, make sure to follow the above steps.

Related

AWS Amplify graphql Subscription Filter Returns All

When using AWS Amplify graphql API, does a subscription filter require any further config beyond adding the filter object to the subscription options?
The documentation here suggests simply adding the filter to the subscription.
Example of the subscription:
const subscribeDeviceState = () => {
const sub = API.graphql(
graphqlOperation(onUpdateDevice, {
filter: {
id: { eq: "abc-123" },
},
})
).subscribe({
next: ({ provider, value }) => {
console.log({ provider, value });
processDeviceData(value.data.onUpdateDevice);
},
error: (error) => console.warn(error),
});
return sub;
};
I have tried multiple parameters in the filter object but each time, whenever any items is updated the subscription returns values for that item, even if the filter does not match.
I have only generated the schema manually and I'm using the Amplify options to generate the graphql code and operations on push.
schema.graphql:
type Device #model #auth(rules: [{ allow: private, provider: userPools }]) {
id: ID!
name: String
reported: AWSJSON
features: AWSJSON
}
Amplify generated subscription:
export const onUpdateDevice = `
subscription OnUpdateDevice {
onUpdateDevice {
id
name
reported
features
createdAt
updatedAt
}
}
`;

Type 'string' is not assignable to type 'Date nest js unit testing

I created a sample project with CRUD using NestJs. I create a User with a randomly generated Id, name, birthdate and email. I need to do unit testing for this create CRUD operation. But I do not know how to test the birthDate. It gives me an error.
This is what I got when I tried to pass the date
User Entity:
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
} from 'typeorm';
#Entity('users')
export class User {
//auto increment
#PrimaryGeneratedColumn('increment')
id: number;
#Column({ nullable: true, default: null })
name: string;
#CreateDateColumn({ nullable: true })
birthDate: Date;
#Column({ type: 'varchar' })
email: string;
}
You're passing a string, but typescript expect Date.
try changing birthDate: '2000.6.7' to birthDate: new Date('2000-06-07').

Filtering List Query By Another Table's Field (a.k.a Cross-Table or Nested Filtering) in AWS Amplify GraphQL DynamoDB

Which Category is your question related to?
DynamoDB, AppSync(GraphQL)
Amplify CLI Version
4.50.2
Provide additional details e.g. code snippets
BACKGROUND:
I'm new in AWS serverless app systems and as a frontend dev, I'm quite enjoying it thanks to auto-generated APIs, tables, connections, resolvers etc. I'm using Angular/Ionic in frontend and S3, DynamoDB, AppSync, Cognito, Amplify-cli for the backend.
WHAT I HAVE:
Here is a part of my schema. I can easily use auto-generated APIs to List/Get Feedbacks with additional filters (i.e. score: { ge: 3 }). And thanks to the #connection I can see the User's details in the listed Feedback items.
type User #model #auth(rules: [{ allow: owner }]) {
id: ID!
email: String!
name: String!
region: String!
sector: String!
companyType: String!
}
type Feedback #model #auth(rules: [{ allow: owner }]) {
id: ID!
user: User #connection
score: Int!
content: String
}
WHAT I WANT:
I want to list Feedbacks based on several fields on User type, such as user's region (i.e. user.region: { contains: 'United States' }). Now I searched for a solution quite a lot like, #2311 , and I learned that amplify codegen only creates top-level filtering. In order to use cross-table filtering, I believe I need to modify resolvers, lambda functions, queries and inputs. Which, for a beginner, it looks quite complex.
WHAT I TRIED/CONSIDERED:
I tried listing all Users and Feedbacks separately and filtering them in front-end. But then the client downloads all these unnecessary data. Also because of the pagination limit, user experience takes a hit as they see an empty list and repeatedly need to click Load More button.
Thanks to some suggestions, I also thought about duplicating the User details in Feedback table to be able to search/filter them. Then the problem is that if User updates his/her info, duplicated values will be out-of-date. Also there will be too many duplicated data, as I need this feature for other tables also.
I also heard about using ElasticSearch for this problem but someone mentioned for a simple filtering he got 30$ monthly cost, so I got cold feet.
I tried the resolver solution to add a custom filtering in it. But I found that quite complex for a beginner. Also I will need this cross-table filtering in many other tables as well, so I think would be hard to manage. If that is the best-practice, I'd appreciate it if someone can guide me through it.
QUESTIONS:
What would be the easiest/beginner-friendly solution for me to achieve this cross-table filtering? I am open to alternative solutions.
Is this cross-table filtering a bad approach for a no-SQL setup? Since I need some relationship between two tables. (I thought #connection would be enough). Should I switch to an SQL setup before it is too late?
Is it possible for Amplify to auto-generate a solution for this in the future? I feel like many people are experiencing the same issue.
Thank you in advance.
Amplify, and really DynamoDB in general, requires you to think about your access patterns ahead of time. There is a lot of really good information out there to help guide you through what this thought process can look like. Particularly, I like Nader Dabit's https://dev.to/dabit3/data-modeling-in-depth-with-graphql-aws-amplify-17-data-access-patterns-4meh
At first glance, I think I would add a new #key called byCountry to the User model, which will create a new Global Secondary Index on that property for you in DDB and will give you some new query methods as well. Check out https://docs.amplify.aws/cli/graphql-transformer/key#designing-data-models-using-key for more examples.
Once you have User.getByCountry in place, you should then be able to also bring back each user's Feedbacks.
query USAUsersWithFeedbacks {
listUsersByCountry(country: "USA") {
items {
feedbacks {
items {
content
}
nextToken
}
}
nextToken
}
}
Finally, you can use JavaScript to fetch all while the nextToken is not null. You will be able to re-use this function for each country you are interested in and you should be able to extend this example for other properties by adding additional #keys.
My former answer can still be useful for others in specific scenarios, but I found a better way to achieve nested filtering when I realized you can filter nested items in custom queries.
Schema:
type User #model {
id: ID!
email: String!
name: String!
region: String!
sector: String!
companyType: String!
feedbacks: [Feedback] #connection # <-- User has many feedbacks
}
Custom query:
query ListUserWithFeedback(
$filter: ModelUserFilterInput # <-- Filter Users by Region or any other User field
$limit: Int
$nextToken: String
$filterFeedback: ModelFeedbackFilterInput # <-- Filter inner Feedbacks by Feedback fields
$nextTokenFeedback: String
) {
listUsers(filter: $filter, limit: $limit, nextToken: $nextToken) {
items {
id
email
name
region
sector
companyType
feedbacks(filter: $filterFeedback, nextToken: $nextTokenFeedback) {
items {
content
createdAt
id
score
}
nextToken
}
createdAt
updatedAt
}
nextToken
}
}
$filter can be something like:
{ region: { contains: 'Turkey' } }
$filterFeedback can be like:
{
and: [{ content: { contains: 'hello' }, score: { ge: 4 } }]
}
This way both Users and Feedbacks can be filtered at the same time.
Ok thanks to #alex's answers I implemented the following. The idea is instead of listing Feedbacks and trying to filter them by User fields, we list Users and collect their Feedbacks from the response:
Updated schema.graphql as follows:
type User
#model
#auth(rules: [{ allow: owner }])
#key(name: "byRegion", fields: ["region"], queryField: "userByRegion") # <-- added byRegion key {
id: ID!
email: String!
name: String!
region: String!
sector: String!
companyType: String!
feedbacks: [Feedback] #connection # <-- added feedbacks connection
}
Added userFeedbacksId parameter while calling CreateFeedback. So they will appear while listing Users.
Added custom query UserByRegionWithFeedback under src/graphql/custom-queries.graphl and used amplify codegen to build it:
query UserByRegionWithFeedback(
$region: String
$sortDirection: ModelSortDirection
$filter: ModelUserFilterInput
$limit: Int
$nextToken: String # <-- nextToken for getting more Users
$nextTokenFeedback: String # <-- nextToken for getting more Feedbacks
) {
userByRegion(
region: $region
sortDirection: $sortDirection
filter: $filter
limit: $limit
nextToken: $nextToken
) {
items {
id
email
name
region
sector
companyType
feedbacks(nextToken: $nextTokenFeedback) {
items {
content
createdAt
id
score
}
nextToken
}
createdAt
updatedAt
owner
}
nextToken
}
}
Now I call this API like the following:
nextToken = {
user: null,
feedback: null
};
feedbacks: any;
async listFeedbacks() {
try {
const res = await this.api.UserByRegionWithFeedback(
'Turkey', // <-- region: filter Users by their region, I will add UI input later
null, // <-- sortDirection
null, // <-- filter
null, // <-- limit
this.nextToken.feedback == null ? this.nextToken.user : null, // <-- User nextToken: Only send if Feedback NextToken is null
this.nextToken.feedback // <-- Feedback nextToken
);
// Get User NextToken
this.nextToken.user = res.nextToken;
// Initialize Feedback NextToken as null
this.nextToken.feedback = null;
// Loop Users in the response
res.items.map((user) => {
// Get Feedback NextToken from User if it is not null (Or else last User in the list could overrite it)
if (user.feedbacks.nextToken) {
this.nextToken.feedback = user.feedbacks.nextToken;
}
// Push the feedback items into the list to diplay in UI
this.feedbacks.push(...user.feedbacks.items);
});
} catch (error) {
this.handleError.show(error);
}
}
Lastly I added a Load More button in the UI which calls listFeedbacks() function. So if there is any Feedback NextToken, I send it to the API. (Note that multiple user feedbacks can have a nextToken).
If all feedbacks are ok and if there is a User NextToken, I send that to the API and repeat the process for new Users.
I believe this could be much simpler with an SQL setup, but this will work for now. I hope it helps others in my situation. And if there is any ideas to make this better I'm all ears.

How should be the schema on relations between microservices

I'm using NestJS + Prisma + Apollo Federation.
On microservice A is the definition of user, on microservice B is defined posts.
The relation is 1 - N, a user can have N posts.
In Prisma, datamodel of Post is defined with a String for user, since userId is a uuid.
type Post {
id: Int! #id
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
user: String!
}
In generated schema (with https://graphql-code-generator.com), Post has a attribute of type User, and this type User extends the id and a array of posts:
type Post #key(fields: "id") {
id: Int!
createdAt: DateTime!
updatedAt: DateTime!
user: User!
}
extend type User #key(fields: "id") {
id: ID! #external
posts: [Post]
}
In apollo federation, all works as expected, except when a query is made trying to link between both microservices.
On playground, if you try to query posts with its user without setting subfields, it breaks the schema and say you have to set the subfields of User, and if you set the subfields graphql responds with a message that you cannot use subfields because its type is String.
The only way that I could make this work correctly was setting in Prisma a userId field of type string and setting another field in schema called user of type User. But all the examples didn't show a field to work with db and a field to work with schema.
My question is if that is the recommended or am I missing something.
In order to get User from Post, you have to create a resolver in your post and user service.
Post Service
const resolvers = {
Post:{//before you do this you have to extend User schema which you already did.
// you are basically asking the 'User' service, which field should be used to query user.
user: ref => ({ __typename: 'User', id: ref.userId })
}
Query:{
// query resolvers
},
Mutation:{
// mutation resolvers
}
User service
const resolvers = {
User:{//the code below allows other services to extend User in their own schemas
__resolveReference: (ref, { userDataLoader }) => userDataLoader.load(ref.id),
}
Query:{
// query resolvers
},
Mutation:{
// mutation resolvers
}
Now linking arrays like [Post] must be done purely in the post service
Post Service
const resolvers = {
Post:{//before you do this you have to extend User schema which you already did.
// you are basically telling the user service, which field should be used to query user.
user: ref => ({ __typename: 'User', id: ref.user })
},
User:{
posts:(ref, args, {postDataLoader}) => getOrders(ref.postIds) //or ref.userId(foreign key)
},
Query:{
// query resolvers
},
Mutation:{
// mutation resolvers
}

AWS AppSync only returns 10 items on query on connection

I'm new to AppSync and trying to see how this works and what's the proper way to set this up.
I created schema.graphql looks like below.
type User #model {
id: String!
following: [String]
follower: [String]
journals: [Journal] #connection(name: "UserJournals", sortField: "createdAt")
notifications: [Notification] #connection(name: "UserNotifications", sortField: "createdAt")
}
type Journal #model {
id: ID!
author: User! #connection(name: "UserJournals")
privacy: String!
content: AWSJSON!
loved: [String]
createdAt: String
updatedAt: String
}
and this created queries.js automatically by AppSync.
export const getUser = `query GetUser($id: ID!) {
getUser(id: $id) {
id
following
follower
journals {
items {
id
privacy
content
loved
createdAt
updatedAt
}
nextToken
}
notifications {
items {
id
content
category
link
createdAt
}
nextToken
}
}
}
`;
I noticed that querying getUser only returns 10 journals items and not sure how to set that to more than 10 or proper way to query and add more journals into that 10 items that were queried by getUser.
Since you do not pass the limit argument explicitly in your query, the Request Mapping Template of the journals resolver defaults it to 10 items. If you would like to change this default value, go to your schema page on the AppSync console, navigate to the journals field, found under the Resolvers section of the schema page. This will then show the resolver definition for this field, and you can then update the default value of 10 to anything you like. Alternatively, you can pass this as your query argument.
FYI - This default value is defined in the amplify-cli repo on GitHub and can be found here.