I have created a full stack application using Angular and Amplify using the Tutorial at https://docs.amplify.aws/start/getting-started/data-model/q/integration/angular/. The frontend is successfully creating GraphQL entries through AppSync which triggers the onCreate subscription, but changes to that data from a python lambda function on the backend is not triggering the onUpdate subscription.
body.component.ts
ngOnInit(): void {
this.api.ListDestinationFiles().then((event) => {
// do stuff here... (this works)
});
this.update_subscription = <Subscription>(
this.api.OnUpdateDestinationFileListener(user.username).subscribe((event: any) => {
// this never fires
console.log('OnUpdate', event);
}));
this.create_subscription = <Subscription>(
this.api.OnCreateDestinationFileListener(user.username).subscribe((event: any) => {
// this does fire.
console.log('OnCreate', event);
}));
})
}
My webpage allows the user to upload a file which triggers this function in the same body.component.ts:
upload(): void {
API.post(this.apiName, this.path, this.myInit)
.then(response => {
let keys = Object.keys(response['dest_file_urls']);
this.graphql_api
.CreateDestinationFile({
filename: keys[0],
signedURL: response['dest_file_urls'][keys[0]],
status: 'Started'
})
.then((event) => {
// this triggers successfully, and the item is indeed created
console.log('item created', event);
})
So far everything is great and working as expected. The API.post sends the file to a python lambda function that saves the file to an S3 bucket, triggers a second lambda function to do the heavy lifting of processing the file, and returns a 201 to the client (that's when the 'item created' event occurs).
The second lambda function contains this code:
destination_file = '{id: "' str(gql_id) '", filename: "' d_filename '", signedURL: "' signedURL '", status: "Finished"}'
mutation = 'mutation update {updateDestinationFile(input: ' destination_file ') { filename status }}'
response = session.request(url=endpoint, method='POST', headers=headers, json={'query': mutation})
The AppSync POST is working just fine, the data is being updated in the backend, and there are no error messages in CloudWatch, Java Console, etc. The only thing that doesn't work is the triggering of the onUpdate subscription where I have code to update the webpage indicating the process is complete.
I've found various articles and documentation about enabling real-time updates for AppSync that include adding or replacing the auto-generated resolver with data source type of NONE and doing a pass-through of sorts, but that appears to be legacy documentation no longer relevant to the latest version of AppSync. My auto-generated resolver for update is a 4-step pipeline handling authentication, updating the "updatedAt" field, etc., and already has the NONE datasource in two of those steps.
Here is the complete Schema as I have a strong inclination this is what I need to modify somehow, although it's being auto-generated and I haven't found clear documentation for making modifications inside the schema.graphql file.
input CreateDestinationFileInput {
id: ID
filename: String!
signedURL: String
status: String
}
input DeleteDestinationFileInput {
id: ID!
}
type DestinationFile {
id: ID!
filename: String!
signedURL: String
status: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
owner: String
}
enum ModelAttributeTypes {
binary
binarySet
bool
list
map
number
numberSet
string
stringSet
_null
}
input ModelBooleanInput {
ne: Boolean
eq: Boolean
attributeExists: Boolean
attributeType: ModelAttributeTypes
}
input ModelDestinationFileConditionInput {
filename: ModelStringInput
signedURL: ModelStringInput
status: ModelStringInput
and: [ModelDestinationFileConditionInput]
or: [ModelDestinationFileConditionInput]
not: ModelDestinationFileConditionInput
}
type ModelDestinationFileConnection {
items: [DestinationFile!]!
nextToken: String
}
input ModelDestinationFileFilterInput {
id: ModelIDInput
filename: ModelStringInput
signedURL: ModelStringInput
status: ModelStringInput
and: [ModelDestinationFileFilterInput]
or: [ModelDestinationFileFilterInput]
not: ModelDestinationFileFilterInput
}
input ModelFloatInput {
ne: Float
eq: Float
le: Float
lt: Float
ge: Float
gt: Float
between: [Float]
attributeExists: Boolean
attributeType: ModelAttributeTypes
}
input ModelIDInput {
ne: ID
eq: ID
le: ID
lt: ID
ge: ID
gt: ID
contains: ID
notContains: ID
between: [ID]
beginsWith: ID
attributeExists: Boolean
attributeType: ModelAttributeTypes
size: ModelSizeInput
}
input ModelIntInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
between: [Int]
attributeExists: Boolean
attributeType: ModelAttributeTypes
}
input ModelSizeInput {
ne: Int
eq: Int
le: Int
lt: Int
ge: Int
gt: Int
between: [Int]
}
enum ModelSortDirection {
ASC
DESC
}
input ModelStringInput {
ne: String
eq: String
le: String
lt: String
ge: String
gt: String
contains: String
notContains: String
between: [String]
beginsWith: String
attributeExists: Boolean
attributeType: ModelAttributeTypes
size: ModelSizeInput
}
type Mutation {
createDestinationFile(input: CreateDestinationFileInput!, condition: ModelDestinationFileConditionInput): DestinationFile
updateDestinationFile(input: UpdateDestinationFileInput!, condition: ModelDestinationFileConditionInput): DestinationFile
deleteDestinationFile(input: DeleteDestinationFileInput!, condition: ModelDestinationFileConditionInput): DestinationFile
}
type Query {
getDestinationFile(id: ID!): DestinationFile
listDestinationFiles(filter: ModelDestinationFileFilterInput, limit: Int, nextToken: String): ModelDestinationFileConnection
}
type Subscription {
onCreateDestinationFile(owner: String): DestinationFile
@aws_subscribe(mutations: ["createDestinationFile"])
onUpdateDestinationFile(owner: String): DestinationFile
@aws_subscribe(mutations: ["updateDestinationFile"])
onDeleteDestinationFile(owner: String): DestinationFile
@aws_subscribe(mutations: ["deleteDestinationFile"])
}
input UpdateDestinationFileInput {
id: ID!
filename: String
signedURL: String
status: String
}
This is all getting created from this in the schema.graphql file:
type DestinationFile @model @auth(rules: [{ allow: owner }]) {
id: ID!
filename: String!
signedURL: String
status: String
}
CodePudding user response:
Have you by any chance enabled DataStore? If you have then it adds a _version field to all your Models (similar to the updateAt fields you mention). When doing any update in GraphQL you will have to pass through that _version field with the last good value stored for that Model otherwise the update will fail (and so not fire off your update event). But this theory only stands if you've actually enabled DataStore :)
CodePudding user response:
I should have known it would be something silly.
mutation = 'mutation update {updateDestinationFile(input: ' destination_file ') { filename status }}'
The line above was missing owner
from the return set. Since my schema includes:
type DestinationFile @model @auth(rules: [{ allow: owner }])
I have to return owner
or the update subscription never see's it.
mutation = 'mutation update {updateDestinationFile(input: ' destination_file ') { filename status owner }}
The above works