aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
89 stars 76 forks source link

@manyToMany causing DynamoDB:ValidationException #1275

Open zachegnermh opened 1 year ago

zachegnermh commented 1 year ago

Before opening, please confirm:

JavaScript Framework

Vue

Amplify APIs

GraphQL API

Amplify Categories

auth, api, hosting

Environment information

``` # Put output below this line System: OS: Windows 10 10.0.19044 CPU: (12) x64 Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz Memory: 20.49 GB / 31.77 GB Binaries: Node: 16.16.0 - C:\Program Files\nodejs\node.EXE npm: 8.11.0 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 110.0.5481.104 Edge: Spartan (44.19041.1266.0), Chromium (110.0.1587.46) Internet Explorer: 11.0.19041.1566 npmPackages: @vue/cli-plugin-eslint: ~4.5.0 => 4.5.19 @vue/cli-plugin-router: ~4.5.0 => 4.5.19 @vue/cli-plugin-vuex: ~4.5.0 => 4.5.19 @vue/cli-service: ~4.5.0 => 4.5.19 @vue/compiler-sfc: 2.7.14 @vue/eslint-config-prettier: ^6.0.0 => 6.0.0 aws-amplify: ^5.0.14 => 5.0.14 axios: ^0.21.1 => 0.21.4 (0.26.0) chart.js: ^2.9.4 => 2.9.4 chartjs-plugin-datalabels: ^1.0.0 => 1.0.0 dev: 1.0.0 element-ui: ^2.15.12 => 2.15.13 eslint: ^6.7.2 => 6.8.0 eslint-plugin-prettier: ^3.3.1 => 3.4.1 eslint-plugin-vue: ^6.2.2 => 6.2.2 moment: ^2.29.1 => 2.29.4 prettier: ^2.2.1 => 2.8.4 sass: ~1.32.0 => 1.32.13 sass-loader: ^10.0.0 => 10.4.1 vue: ^2.6.11 => 2.7.14 vue-chartjs: ^3.5.1 => 3.5.1 vue-cli-plugin-vuetify: ~2.4.1 => 2.4.8 vue-router: ^3.2.0 => 3.6.5 vue-speedometer: ^1.8.0 => 1.8.0 vue-template-compiler: ^2.6.11 => 2.7.14 vue-xlsx: ^0.2.1 => 0.2.1 vuedraggable: ^2.24.3 => 2.24.3 vuetify: ^2.4.0 => 2.6.14 vuetify-loader: ^1.7.0 => 1.9.2 vuex: ^3.4.0 => 3.6.2 ```

Describe the bug

After creating a manyToMany relationship between two Dashboards and Reports in graphql schema; a simple createDashboard request returns a DynamoDB:Validation Exception.

The message returned is "The Table does not have the specified index: byChannel"

I notice if I omit the reports from the mutation query, I do not receive error. Since I am using a manyToMany relationship between Reports and Dashboards, Reports is still being queried on createDashboard. The problem lies somewhere with the hasMany relationship that Channels has with Reports and Dashboards.

i.e. This works:

mutation MyMutation {
  createDashboard(input: {title: "test"}) {
    id
    title
    channelId
  }
}

Automatic query from amplify, does NOT work:

export const createDashboard = /* GraphQL */ `
  mutation CreateDashboard(
    $input: CreateDashboardInput!
    $condition: ModelDashboardConditionInput
  ) {
    createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        items {
          id
          reportID
          dashboardID
        }
        nextToken
      }
      channelId
    }
  }
`;

Expected behavior

The createDashboard mutation is run and a dashboard is created with basic info(i.e. id, title) in preparation for a @manyToMany link with "Reports" using createDashboardReports.

Reproduction steps

1.) run a createDashboard mutation using the provided schema.

Code Snippet

The schema looks like this:

type Dashboard @model @auth(rules: [{ allow: public }]) {
  id: ID!
  title: String!
  reports: [Report] @manyToMany(relationName: "DashboardReports")
  channelId: ID @index(name: "byChannel")
}

type Report @model @auth(rules: [{ allow: public }]) {
  id: ID!
  title: String!
  channelId: ID @index(name: "byChannel")
  dashboards: [Dashboard] @manyToMany(relationName: "DashboardReports")  
}

type Channel @model @auth(rules: [{ allow: public }]) {
  id: ID!
  title: String!
  reports: [Report] @hasMany(indexName: "byChannel", fields: ["id"])
  dashboards: [Dashboard] @hasMany(indexName: "byChannel", fields: ["id"])
}

addDashboard method:

async addDashboard({ dispatch }, dashboard) {
     try {
       await API.graphql(graphqlOperation(createDashboard, { input: dashboard }));
       dispatch("fetchDashboards");
     } catch (error) {
       console.log(error);
     }
  },

Error Log from graphQL

{
  "data": {
    "createDashboard": {
      "id": "real ID ommitted",
      "title": "test",
      "reports": null,
      "channelId": "real ID omitted",
    }
  },
  "errors": [
    {
      "message": "The table does not have the specified index: byChannel",
      "errorType": "DynamoDB:ValidationException",
      "data": null,
      "errorInfo": null,
      "path": [
        "createDashboard",
        "reports"
      ],
      "locations": [
        {
          "line": 6,
          "column": 5,
          "sourceName": "GraphQL request"
        }
      ]
    }
  ]
}

Log output

``` Error while executing Local DynamoDB { "version": "2018-05-29", "operation": "Query", "query": { "expression": "#partitionKey = :partitionKey", "expressionNames": { "#partitionKey": "channelId" }, "expressionValues": { ":partitionKey": { "S": "b0766a08-8ff0-4918-bdc2-b4803fb945fa" } } }, "scanIndexForward": true, "filter": null, "limit": 100, "nextToken": null, "index": "byChannel" } ValidationException: The table does not have the specified index: byChannel at Request.extractError (C:\snapshot\repo\build\node_modules\aws-sdk\lib\protocol\json.js:52:27) at Request.callListeners (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:106:20) at Request.emit (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:78:10) at Request.emit (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:686:14) at Request.transition (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:22:10) at AcceptorStateMachine.runTo (C:\snapshot\repo\build\node_modules\aws-sdk\lib\state_machine.js:14:12) at C:\snapshot\repo\build\node_modules\aws-sdk\lib\state_machine.js:26:10 at Request. (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:38:9) at Request. (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:688:12) at Request.callListeners (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:116:18) at Request.emit (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:78:10) at Request.emit (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:686:14) at Request.transition (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:22:10) at AcceptorStateMachine.runTo (C:\snapshot\repo\build\node_modules\aws-sdk\lib\state_machine.js:14:12) at C:\snapshot\repo\build\node_modules\aws-sdk\lib\state_machine.js:26:10 at Request. (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:38:9) at Request. (C:\snapshot\repo\build\node_modules\aws-sdk\lib\request.js:688:12) at Request.callListeners (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:116:18) at callNextListener (C:\snapshot\repo\build\node_modules\aws-sdk\lib\sequential_executor.js:96:12) at IncomingMessage.onEnd (C:\snapshot\repo\build\node_modules\aws-sdk\lib\event_listeners.js:380:13) at IncomingMessage.emit (events.js:412:35) at IncomingMessage.emit (domain.js:475:12) at endReadableNT (internal/streams/readable.js:1334:12) at processTicksAndRejections (internal/process/task_queues.js:82:21) { code: 'ValidationException', time: 2023-02-16T21:57:33.903Z, requestId: '1b54a417-ca1c-4deb-b6b4-505b11200957', statusCode: 400, retryable: false, retryDelay: 33.27337805699416 } ```

aws-exports.js

const awsmobile = {
    "aws_project_region": "us-east-1",
    "aws_appsync_graphqlEndpoint": "http://100.74.60.127:20002/graphql",
    "aws_appsync_region": "us-east-1",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "da2-fakeApiId123456",
    "aws_appsync_dangerously_connect_to_http_endpoint_for_testing": true,
    "aws_cognito_identity_pool_id": "us-east-1:b3f92c2c-4e0d-40c6-9d91-03b315d24dde",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": "us-east-1_5qN0j2x8u",
    "aws_user_pools_web_client_id": "1an1lokipnohurkumajdtea8q5",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ]
};

export default awsmobile;

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

chrisbonifacio commented 1 year ago

Hi @zachegnermh 👋 thanks for raising this issue! What version of the Amplify CLI were you using when the graphql mutations were generated? I just tried generating the mutations with the last version of the CLI and given schema but my createDashboard mutation looks different to yours.

export const createDashboard = /* GraphQL */ `
  mutation CreateDashboard(
    $input: CreateDashboardInput!
    $condition: ModelDashboardConditionInput
  ) {
    createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        nextToken
        startedAt
      }
      channelId
      createdAt
      updatedAt
      _version
      _deleted
      _lastChangedAt
    }
  }
`;

I was able to successfully create a Dashboard record

Screenshot 2023-03-08 at 12 38 04 PM

Can you try upgrading to the latest version of the Amplify CLI if not already on that version and re-running amplify codegen?

zachegnermh commented 1 year ago

Hi @chrisbonifacio, thanks for looking into this issue. I had noticed that I was running Amplify CLI version 10.6.2, so I upgraded today to 10.8.1. I erased mock data and reran amplify mock api as well as amplify codegen. I am still facing the same error as before when trying to "createDashboard" and my mutations file has not changed.


export const createDashboard = /* GraphQL */ 
  mutation CreateDashboard(
    $input: CreateDashboardInput!
    $condition: ModelDashboardConditionInput
  ) {
    createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        items {
          id
          reportID
          dashboardID
          createdAt
          updatedAt
        }
        nextToken
      }
      channelId
      createdAt
      updatedAt
    }
  }
;
chrisbonifacio commented 1 year ago

@zachegnermh Of course :) Maybe our codegens are configured differently. Can you share your graphqlconfig.yml file?

This is mine for comparison:

projects:
  dsnext13:
    schemaPath: amplify/backend/api/dsnext13/build/schema.graphql
    includes:
      - src/graphql/**/*.ts
    excludes:
      - ./amplify/**
    extensions:
      amplify:
        codeGenTarget: typescript
        generatedFileName: src/API.ts
        docsFilePath: src/graphql
        maxDepth: 2
extensions:
  amplify:
    version: 3

Might be that your max depth is configured a bit higher. I tried increasing it and codegen is now my mutation looks more like yours.

EDIT: I wasn't using mock but even with a higher max depth and mock, I'm still able to create Dashboards 🤔

Screenshot 2023-03-09 at 10 41 58 AM

Thought maybe linking to a channel might cause the issue but I was also able to create a Dashboard linked to a Channel

Screenshot 2023-03-09 at 10 58 53 AM
chrisbonifacio commented 1 year ago

I noticed some other small differences between our generated mutations. Our id fields are generated differently in the mutation selection set:

yours (reportID and dashboardID vs channelId) perhaps this has something to do with the issue?

    createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        items {
          id
          reportID
          dashboardID
        }
      }
      channelId # why is this one different to the others?
    }

mine (all formatted as Id)

   createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        items {
          id
          dashboardId
          reportId
        }
      }
      channelId
    }
  }
zachegnermh commented 1 year ago

@chrisbonifacio, here is my graphqlconfig.yml, maxDepth isn't set here.

projects:
  mrms:
    schemaPath: amplify/backend/api/mrms/build/schema.graphql
    includes:
      - src/graphql/**/*.js
    excludes:
      - ./amplify/**
    extensions:
      amplify:
        codeGenTarget: javascript
        generatedFileName: ''
        docsFilePath: src/graphql
extensions:
  amplify:
    version: 3

As to the reportID and dashboardID, I am not sure where this is coming from. The only instances of this in my code are in the autogenerated graphQL files.

I am also facing the same error in graphiQL image

Here's is trying with reportId and dashboardId image

And also without querying reportID or dashboardID image

chrisbonifacio commented 1 year ago

Hi @zachegnermh apologies for the delay. I still haven't been able to reproduce this issue. Have you tried setting the maxDepth in your graphqlconfig.yml? You can do this by running amplify codegen configure

Does this error still occur when running the same mutation the AppSync console?

I would also try adjusting the selection set to

    createDashboard(input: $input, condition: $condition) {
      id
      title
      reports {
        items {
          id
          reportID
          dashboardID
          channelId
        }
      }
      channelId # why is this one different to the others?
    }
zachegnermh commented 1 year ago

Hi @chrisbonifacio, I tried setting maxDepth to 4, but there was no difference in outcome. Still receiving the same error. Also, when trying the selection set you have provided, I receive this "Cannot query field \"channelId\" on type \"DashboardReports\".".

Very strange behavior, because even though I am receiving an error on create, some of the items were actually created on the DB. However, when trying to run a getDashboard or updateDashboard, I still get: {data: {…}, errors: Array(1)} data : {getDashboard: {…}} errors : Array(1) 0 : data : null errorInfo : null errorType : "DynamoDB:ValidationException" locations : [{…}] message : "The table does not have the specified index: byChannel" path : (2) ['getDashboard', 'reports']

I'm still unsure as to why my reportID and dashboardID are showing uppercase ID, as I haven't changed the schema since the one I have provided to you at the top.

chrisbonifacio commented 1 year ago

@zachegnermh apologies for the delay. Can you share the contents of your amplify/cli.json file? I'd like to compare the feature flags that we have enabled/disabled.

Also, please run amplify diagnose --send-report and share the output project identifier id.

zachegnermh commented 1 year ago

@chrisbonifacio No worries!

Project Identifier: 686f8d7fabdbbdcdf584a874e1f2007d

Here's the cli.json

{
  "features": {
    "graphqltransformer": {
      "addmissingownerfields": true,
      "improvepluralization": false,
      "validatetypenamereservedwords": true,
      "useexperimentalpipelinedtransformer": true,
      "enableiterativegsiupdates": true,
      "secondarykeyasgsi": true,
      "skipoverridemutationinputtypes": true,
      "transformerversion": 2,
      "suppressschemamigrationprompt": true,
      "securityenhancementnotification": false,
      "showfieldauthnotification": false,
      "usesubusernamefordefaultidentityclaim": true,
      "usefieldnameforprimarykeyconnectionfield": false,
      "enableautoindexquerynames": false,
      "respectprimarykeyattributesonconnectionfield": false,
      "shoulddeepmergedirectiveconfigdefaults": false,
      "populateownerfieldforstaticgroupauth": false
    },
    "frontend-ios": {
      "enablexcodeintegration": true
    },
    "auth": {
      "enablecaseinsensitivity": true,
      "useinclusiveterminology": true,
      "breakcirculardependency": true,
      "forcealiasattributes": false,
      "useenabledmfas": true
    },
    "codegen": {
      "useappsyncmodelgenplugin": true,
      "usedocsgeneratorplugin": true,
      "usetypesgeneratorplugin": true,
      "cleangeneratedmodelsdirectory": true,
      "retaincasestyle": true,
      "addtimestampfields": true,
      "handlelistnullabilitytransparently": true,
      "emitauthprovider": true,
      "generateindexrules": true,
      "enabledartnullsafety": true
    },
    "appsync": {
      "generategraphqlpermissions": true
    },
    "latestregionsupport": {
      "pinpoint": 1,
      "translate": 1,
      "transcribe": 1,
      "rekognition": 1,
      "textract": 1,
      "comprehend": 1
    },
    "project": {
      "overrides": true
    }
  },
  "debug": {
    "shareProjectConfig": true
  }
}
zachegnermh commented 1 year ago

Hi @chrisbonifacio ,

I hope this message finds you well. I just wanted to check in and see if there has been any progress made on this bug. If you need any further information or assistance from me, please let me know. I understand you may have a busy schedule, so I appreciate any update you can provide.

Thank You, @zachegnermh