amplify-cli: update mutation with new cli does not work???

Note: If your issue/bug is regarding the AWS Amplify Console service, please log it in the Amplify Console GitHub Issue Tracker

Describe the bug A clear and concise description of what the bug is. Create a new project with schema. I am unable to update any of the values with generated mutation graphql mutations with cli. I make sure the required input values are included in the inputs to update the non-required values when I run my mutations but no change. I have also tried the appsync console with no avail. I have erased and tried a whole new environment in the cli but the bug keeps repeating. I am using 4.16.1. I have Merge enabled. The backend schema has

_version: Int!
  _deleted: Boolean
  _lastChangedAt: AWSTimestamp!

included. I also have iam provider and cognito user pools for auth rules. I am trying to update Profile table for my webapp.

here the VTL template

## [Start] Determine request authentication mode **
#if( $util.isNullOrEmpty($authMode) && !$util.isNull($ctx.identity) && !$util.isNull($ctx.identity.sub) && !$util.isNull($ctx.identity.issuer) && !$util.isNull($ctx.identity.username) && !$util.isNull($ctx.identity.claims) && !$util.isNull($ctx.identity.sourceIp) && !$util.isNull($ctx.identity.defaultAuthStrategy) )
  #set( $authMode = "userPools" )
#end
## [End] Determine request authentication mode **
## [Start] Check authMode and execute owner/group checks **
#if( $authMode == "userPools" )
  ## [Start] Static Group Authorization Checks **
  #set($isStaticGroupAuthorized = $util.defaultIfNull(
            $isStaticGroupAuthorized, false))
  ## Authorization rule: { allow: groups, groups: ["Users"], groupClaim: "cognito:groups" } **
  #set( $userGroups = $util.defaultIfNull($ctx.identity.claims.get("cognito:groups"), []) )
  #set( $allowedGroups = ["Users"] )
  #foreach( $userGroup in $userGroups )
    #if( $allowedGroups.contains($userGroup) )
      #set( $isStaticGroupAuthorized = true )
      #break
    #end
  #end
  ## [End] Static Group Authorization Checks **


  #if( ! $isStaticGroupAuthorized )
    ## No dynamic group authorization rules **


    ## No owner authorization rules **


    ## [Start] Collect Auth Condition **
    #set( $authCondition = $util.defaultIfNull($authCondition, {
  "expression": "",
  "expressionNames": {},
  "expressionValues": {}
}) )
    #set( $totalAuthExpression = "" )
    ## Add dynamic group auth conditions if they exist **
    #if( $groupAuthExpressions )
      #foreach( $authExpr in $groupAuthExpressions )
        #set( $totalAuthExpression = "$totalAuthExpression $authExpr" )
        #if( $foreach.hasNext )
          #set( $totalAuthExpression = "$totalAuthExpression OR" )
        #end
      #end
    #end
    #if( $groupAuthExpressionNames )
      $util.qr($authCondition.expressionNames.putAll($groupAuthExpressionNames))
    #end
    #if( $groupAuthExpressionValues )
      $util.qr($authCondition.expressionValues.putAll($groupAuthExpressionValues))
    #end
    ## Add owner auth conditions if they exist **
    #if( $totalAuthExpression != "" && $ownerAuthExpressions && $ownerAuthExpressions.size() > 0 )
      #set( $totalAuthExpression = "$totalAuthExpression OR" )
    #end
    #if( $ownerAuthExpressions )
      #foreach( $authExpr in $ownerAuthExpressions )
        #set( $totalAuthExpression = "$totalAuthExpression $authExpr" )
        #if( $foreach.hasNext )
          #set( $totalAuthExpression = "$totalAuthExpression OR" )
        #end
      #end
    #end
    #if( $ownerAuthExpressionNames )
      $util.qr($authCondition.expressionNames.putAll($ownerAuthExpressionNames))
    #end
    #if( $ownerAuthExpressionValues )
      $util.qr($authCondition.expressionValues.putAll($ownerAuthExpressionValues))
    #end
    ## Set final expression if it has changed. **
    #if( $totalAuthExpression != "" )
      #if( $util.isNullOrEmpty($authCondition.expression) )
        #set( $authCondition.expression = "($totalAuthExpression)" )
      #else
        #set( $authCondition.expression = "$authCondition.expression AND ($totalAuthExpression)" )
      #end
    #end
    ## [End] Collect Auth Condition **
  #end


  ## [Start] Throw if unauthorized **
  #if( !($isStaticGroupAuthorized == true || ($totalAuthExpression != "")) )
    $util.unauthorized()
  #end
  ## [End] Throw if unauthorized **
#end
## [End] Check authMode and execute owner/group checks **

#if( $authCondition && $authCondition.expression != "" )
  #set( $condition = $authCondition )
  #if( $modelObjectKey )
    #foreach( $entry in $modelObjectKey.entrySet() )
      $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#keyCondition$velocityCount)"))
      $util.qr($condition.expressionNames.put("#keyCondition$velocityCount", "$entry.key"))
    #end
  #else
    $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#id)"))
    $util.qr($condition.expressionNames.put("#id", "id"))
  #end
#else
  #if( $modelObjectKey )
    #set( $condition = {
  "expression": "",
  "expressionNames": {},
  "expressionValues": {}
} )
    #foreach( $entry in $modelObjectKey.entrySet() )
      #if( $velocityCount == 1 )
        $util.qr($condition.put("expression", "attribute_exists(#keyCondition$velocityCount)"))
      #else
        $util.qr($condition.put("expression", "$condition.expression AND attribute_exists(#keyCondition$velocityCount)"))
      #end
      $util.qr($condition.expressionNames.put("#keyCondition$velocityCount", "$entry.key"))
    #end
  #else
    #set( $condition = {
  "expression": "attribute_exists(#id)",
  "expressionNames": {
      "#id": "id"
  },
  "expressionValues": {}
} )
  #end
#end
## Automatically set the updatedAt timestamp. **
$util.qr($context.args.input.put("updatedAt", $util.defaultIfNull($ctx.args.input.updatedAt, $util.time.nowISO8601())))
$util.qr($context.args.input.put("__typename", "Profile"))
## Update condition if type is @versioned **
#if( $versionedCondition )
  $util.qr($condition.put("expression", "($condition.expression) AND $versionedCondition.expression"))
  $util.qr($condition.expressionNames.putAll($versionedCondition.expressionNames))
  $util.qr($condition.expressionValues.putAll($versionedCondition.expressionValues))
#end
#if( $context.args.condition )
  #set( $conditionFilterExpressions = $util.parseJson($util.transform.toDynamoDBConditionExpression($context.args.condition)) )
  $util.qr($condition.put("expression", "($condition.expression) AND $conditionFilterExpressions.expression"))
  $util.qr($condition.expressionNames.putAll($conditionFilterExpressions.expressionNames))
  $util.qr($condition.expressionValues.putAll($conditionFilterExpressions.expressionValues))
#end
#if( $condition.expressionValues && $condition.expressionValues.size() == 0 )
  #set( $condition = {
  "expression": $condition.expression,
  "expressionNames": $condition.expressionNames
} )
#end
#set( $expNames = {} )
#set( $expValues = {} )
#set( $expSet = {} )
#set( $expAdd = {} )
#set( $expRemove = [] )
#if( $modelObjectKey )
  #set( $keyFields = [] )
  #foreach( $entry in $modelObjectKey.entrySet() )
    $util.qr($keyFields.add("$entry.key"))
  #end
#else
  #set( $keyFields = ["id", "_version", "_deleted", "_lastChangedAt"] )
#end
#foreach( $entry in $util.map.copyAndRemoveAllKeys($context.args.input, $keyFields).entrySet() )
  #if( !$util.isNull($dynamodbNameOverrideMap) && $dynamodbNameOverrideMap.containsKey("$entry.key") )
    #set( $entryKeyAttributeName = $dynamodbNameOverrideMap.get("$entry.key") )
  #else
    #set( $entryKeyAttributeName = $entry.key )
  #end
  #if( $util.isNull($entry.value) )
    #set( $discard = $expRemove.add("#$entryKeyAttributeName") )
    $util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
  #else
    $util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
    $util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
    $util.qr($expValues.put(":$entryKeyAttributeName", $util.dynamodb.toDynamoDB($entry.value)))
  #end
#end
#set( $expression = "" )
#if( !$expSet.isEmpty() )
  #set( $expression = "SET" )
  #foreach( $entry in $expSet.entrySet() )
    #set( $expression = "$expression $entry.key = $entry.value" )
    #if( $foreach.hasNext() )
      #set( $expression = "$expression," )
    #end
  #end
#end
#if( !$expAdd.isEmpty() )
  #set( $expression = "$expression ADD" )
  #foreach( $entry in $expAdd.entrySet() )
    #set( $expression = "$expression $entry.key $entry.value" )
    #if( $foreach.hasNext() )
      #set( $expression = "$expression," )
    #end
  #end
#end
#if( !$expRemove.isEmpty() )
  #set( $expression = "$expression REMOVE" )
  #foreach( $entry in $expRemove )
    #set( $expression = "$expression $entry" )
    #if( $foreach.hasNext() )
      #set( $expression = "$expression," )
    #end
  #end
#end
#set( $update = {} )
$util.qr($update.put("expression", "$expression"))
#if( !$expNames.isEmpty() )
  $util.qr($update.put("expressionNames", $expNames))
#end
#if( !$expValues.isEmpty() )
  $util.qr($update.put("expressionValues", $expValues))
#end
{
  "version": "2018-05-29",
  "operation": "UpdateItem",
  "key": #if( $modelObjectKey ) $util.toJson($modelObjectKey) #else {
  "id": {
      "S": $util.toJson($context.args.input.id)
  }
} #end,
  "update": $util.toJson($update),
  "condition": $util.toJson($condition),
  "_version": $util.defaultIfNull($ctx.args.input["_version"], "0")
}

here’s schema for table

type Profile
@model
@auth(rules: [
  { allow: public, provider: iam, operations: [read] },
  {allow: groups, groups: ["public"], operations: [read]},
  {allow: groups, groups: ["Users"]},
])
{
  id: ID!
  userId: ID!
  name: String!
  aboutMe: String
  profilePicture: String
  following: [UserHost] @connection(keyName: "byUser", fields: ["id"])
  followingCount: String
  user: User @connection(fields: ["userId"])
  stats: Stats @connection(fields: ["userId"])
}

here’s my react code:

const updateUserProfileForm = async (values) => {
    try {
      const {
        files,
        aboutMe,
      } = values;

      //  upload picture
      const filteredFiles = files.filter(file => file !== null);
      console.log(values, ' this is the values object');
      console.log(files, ' this is files');
      if (filteredFiles
        && filteredFiles[0]
        && filteredFiles[0].name) await uploadPicture(filteredFiles[0]);

      const params = (filteredFiles && filteredFiles[0] && filteredFiles[0].name)
        ? {
          input: {
            id: profileId,
            aboutMe,
            profilePicture: `${credId}/${profileId}/${escapeUnsafe(filteredFiles[0].name)}`,
            userId: userProfile.userId,
            name: userProfile.name,
          },
        }
        : {
          input: {
            id: profileId,
            aboutMe,
            userId: userProfile.userId,
            name: userProfile.name,
          },
        };

      const userProfileData = await API
        .graphql(
          graphqlOperation(
            UpdateUserProfile, params,
          ),
        );
      const { data: { updateProfile: newUserProfile } } = userProfileData;
      setUserProfile(newUserProfile);
      console.log(newUserProfile, ' this is NewUserProfile');
      setSuccess({
        header: 'Update successful!',
        content: 'Profile updated successfully.',
      });
      setError(null);
    } catch (e) {
      setSuccess(null);
      setError({
        header: 'Oops...something went wrong',
        content: 'Something went wrong with updating profile, please try again.',
      });
    }
  };

Amplify CLI Version You can use amplify -v to check the amplify cli version on your system 4.16.1 To Reproduce Steps to reproduce the behavior or terminal output if applicable

  1. create new schema
  2. create values inside dynamodb table using appsync
  3. use update mutation while including the required fields.

Expected behavior A clear and concise description of what you expected to happen. The values should update.

Screenshots If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. Mac/Windows/Ubuntu]
  • Node Version. You can use node -v to check the node version on your system

v12.13.1

Additional context Add any other context about the problem here.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 24 (5 by maintainers)

Most upvoted comments

tl;dr: When you call the “updateProfile” mutation, because you have versioning turned on, you need to pass in the current _version number as part of the update input object. In your react code I can see you aren’t doing this.

To test this and show it working properly:

  1. Create and push schema (I’ve removed references to Stats, UserHost, Customer)
$ yarn global add @aws-amplify/cli@4.16.1

$ amplify init
Enter a name for the project testMutation
? Enter a name for the environment one
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using none
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation

$ amplify add auth
Using service: Cognito, provided by: awscloudformation
 
 The current configured provider is Amazon Cognito. 
 
 Do you want to use the default authentication and security configuration? Default configuration
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Username
 Do you want to configure advanced settings? No, I am done.

$ amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: testmutation
? Choose the default authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes.
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API IAM
? Configure conflict detection? Yes
? Select the default resolution strategy Auto Merge
? Do you have an annotated GraphQL schema? No
? Do you want a guided schema creation? No
? Provide a custom type name Profile

$ amplify push --yes
  1. In ./amplify/amplify-meta.json, note the values of UserPoolId and AppClientIDWeb
  2. create a user for testing
$ aws cognito-idp admin-create-user --user-pool-id ${UserPoolId} --username testing --temporary-password testing123 --user-attributes Name=email,Value="test@test.com"
$ aws cognito-idp create-group --user-pool-id ${UserPoolId} --group-name Users
$ aws cognito-idp admin-add-user-to-group --user-pool-id ${UserPoolId} --username testing2 --group Users
  1. navigate to AppSync in your region, https://eu-west-2.console.aws.amazon.com/appsync/ . Select queries on left hand side. Login, use AppClientIDWeb from step 2.

  2. execute the following mutations

mutation create {
  createProfile(input:{id: "456", userId:"testing2", name: "Ross"}) {
    id
    _version
  }
}

mutation update {
  updateProfile(input: {id:"456", profilePicture:"picture1", _version: 1}) {
    id
    profilePicture
    _version
  }
}

mutation update2 {
  updateProfile(input: {id:"456", profilePicture:"picture2", _version: 2}) {
    id
    profilePicture
  }
}
  1. Notice that profile picture starts undefined, is updated to “picture 1” and then is updated to “picture 2”