console: add remoteSchemaTree component

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3643
Co-authored-by: Matt Hardman <28978422+mattshardman@users.noreply.github.com>
GitOrigin-RevId: 4797a3167f308ea8a7b144c7e12b3273217ebe3a
This commit is contained in:
Abhijeet Khangarot 2022-03-09 16:08:09 +05:30 committed by hasura-bot
parent 20b2a13f22
commit 62aaa7f1e3
17 changed files with 6993 additions and 6 deletions

View File

@ -4,6 +4,43 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
"requires": {
"@ctrl/tinycolor": "^3.4.0"
}
},
"@ant-design/icons": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-4.7.0.tgz",
"integrity": "sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-svg": "^4.2.1",
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"rc-util": "^5.9.4"
}
},
"@ant-design/icons-svg": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz",
"integrity": "sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw=="
},
"@ant-design/react-slick": {
"version": "0.28.4",
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-0.28.4.tgz",
"integrity": "sha512-j9eAHTn7GxbXUFNknJoHS2ceAsqrQi2j8XykjZE1IXCD8kJF+t28EvhBLniDpbOsBk/3kjalnhriTfZcjBHNqg==",
"requires": {
"@babel/runtime": "^7.10.4",
"classnames": "^2.2.5",
"json2mq": "^0.2.0",
"lodash": "^4.17.21",
"resize-observer-polyfill": "^1.5.0"
}
},
"@ardatan/aggregate-error": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz",
@ -2677,6 +2714,11 @@
"minimist": "^1.2.0"
}
},
"@ctrl/tinycolor": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
},
"@cypress/request": {
"version": "2.88.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz",
@ -22832,6 +22874,62 @@
}
}
},
"antd": {
"version": "4.18.6",
"resolved": "https://registry.npmjs.org/antd/-/antd-4.18.6.tgz",
"integrity": "sha512-CgliJK1iFUYVER2zJsBUManwxLKhdrRVdhpYeHByglZ8w/WXmgIJ3xoSwwM0o4A/FA+gRuDa0f7kVnzPDiqdYw==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
"@ant-design/react-slick": "~0.28.1",
"@babel/runtime": "^7.12.5",
"@ctrl/tinycolor": "^3.4.0",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
"lodash": "^4.17.21",
"memoize-one": "^6.0.0",
"moment": "^2.25.3",
"rc-cascader": "~3.2.1",
"rc-checkbox": "~2.3.0",
"rc-collapse": "~3.1.0",
"rc-dialog": "~8.6.0",
"rc-drawer": "~4.4.2",
"rc-dropdown": "~3.2.5",
"rc-field-form": "~1.22.0-2",
"rc-image": "~5.2.5",
"rc-input-number": "~7.3.0",
"rc-mentions": "~1.6.1",
"rc-menu": "~9.2.1",
"rc-motion": "^2.4.4",
"rc-notification": "~4.5.7",
"rc-pagination": "~3.1.9",
"rc-picker": "~2.5.17",
"rc-progress": "~3.2.1",
"rc-rate": "~2.9.0",
"rc-resize-observer": "^1.2.0",
"rc-select": "~14.0.0-alpha.15",
"rc-slider": "~9.7.4",
"rc-steps": "~4.1.0",
"rc-switch": "~3.2.0",
"rc-table": "~7.22.2",
"rc-tabs": "~11.10.0",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.1.1",
"rc-tree": "~5.4.3",
"rc-tree-select": "~5.1.1",
"rc-trigger": "^5.2.10",
"rc-upload": "~4.3.0",
"rc-util": "^5.14.0",
"scroll-into-view-if-needed": "^2.2.25"
},
"dependencies": {
"memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
}
}
},
"any-observable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
@ -23057,6 +23155,11 @@
"is-string": "^1.0.5"
}
},
"array-tree-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@ -23414,6 +23517,11 @@
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"async-validator": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
"integrity": "sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -25701,8 +25809,7 @@
"compute-scroll-into-view": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==",
"dev": true
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
},
"concat-map": {
"version": "0.0.1",
@ -27072,14 +27179,12 @@
"date-fns": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==",
"dev": true
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
},
"dayjs": {
"version": "1.10.5",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz",
"integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==",
"dev": true
"integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g=="
},
"debounce": {
"version": "1.2.0",
@ -27378,6 +27483,11 @@
"integrity": "sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==",
"dev": true
},
"dom-align": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.2.tgz",
"integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg=="
},
"dom-converter": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
@ -34665,6 +34775,14 @@
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json2mq": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
"integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=",
"requires": {
"string-convert": "^0.2.0"
}
},
"json5": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
@ -40398,6 +40516,394 @@
}
}
},
"rc-align": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.11.tgz",
"integrity": "sha512-n9mQfIYQbbNTbefyQnRHZPWuTEwG1rY4a9yKlIWHSTbgwI+XUMGRYd0uJ5pE2UbrNX0WvnMBA1zJ3Lrecpra/A==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"dom-align": "^1.7.0",
"lodash": "^4.17.21",
"rc-util": "^5.3.0",
"resize-observer-polyfill": "^1.5.1"
}
},
"rc-cascader": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.2.5.tgz",
"integrity": "sha512-kGyDPkVnCPheNqjZ+YeACbyZ9q6pDP1SYdMzM7G3JfdbJj3lpCWZkil/14AExstsmas2eddEQ5WGZzxG9707TQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"array-tree-filter": "^2.1.0",
"classnames": "^2.3.1",
"rc-select": "~14.0.0-alpha.23",
"rc-tree": "~5.4.3",
"rc-util": "^5.6.1"
},
"dependencies": {
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
}
}
},
"rc-checkbox": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-2.3.2.tgz",
"integrity": "sha512-afVi1FYiGv1U0JlpNH/UaEXdh6WUJjcWokj/nUN2TgG80bfG+MDdbfHKlLcNNba94mbjy2/SXJ1HDgrOkXGAjg==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1"
}
},
"rc-collapse": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.1.2.tgz",
"integrity": "sha512-HujcKq7mghk/gVKeI6EjzTbb8e19XUZpakrYazu1MblEZ3Hu3WBMSN4A3QmvbF6n1g7x6lUlZvsHZ5shABWYOQ==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.3.4",
"rc-util": "^5.2.1",
"shallowequal": "^1.1.0"
}
},
"rc-dialog": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-8.6.0.tgz",
"integrity": "sha512-GSbkfqjqxpZC5/zc+8H332+q5l/DKUhpQr0vdX2uDsxo5K0PhvaMEVjyoJUTkZ3+JstEADQji1PVLVb/2bJeOQ==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.6",
"rc-motion": "^2.3.0",
"rc-util": "^5.6.1"
}
},
"rc-drawer": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-4.4.3.tgz",
"integrity": "sha512-FYztwRs3uXnFOIf1hLvFxIQP9MiZJA+0w+Os8dfDh/90X7z/HqP/Yg+noLCIeHEbKln1Tqelv8ymCAN24zPcfQ==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.6",
"rc-util": "^5.7.0"
}
},
"rc-dropdown": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.2.5.tgz",
"integrity": "sha512-dVO2eulOSbEf+F4OyhCY5iGiMVhUYY/qeXxL7Ex2jDBt/xc89jU07mNoowV6aWxwVOc70pxEINff0oM2ogjluA==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.6",
"rc-trigger": "^5.0.4"
}
},
"rc-field-form": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.22.1.tgz",
"integrity": "sha512-LweU7nBeqmC5r3HDUjRprcOXXobHXp/TGIxD7ppBq5FX6Iptt3ibdpRVg4RSyNulBNGHOuknHlRcguuIpvVMVg==",
"requires": {
"@babel/runtime": "^7.8.4",
"async-validator": "^4.0.2",
"rc-util": "^5.8.0"
}
},
"rc-image": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/rc-image/-/rc-image-5.2.5.tgz",
"integrity": "sha512-qUfZjYIODxO0c8a8P5GeuclYXZjzW4hV/5hyo27XqSFo1DmTCs2HkVeQObkcIk5kNsJtgsj1KoPThVsSc/PXOw==",
"requires": {
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"rc-dialog": "~8.6.0",
"rc-util": "^5.0.6"
}
},
"rc-input-number": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-7.3.4.tgz",
"integrity": "sha512-W9uqSzuvJUnz8H8vsVY4kx+yK51SsAxNTwr8SNH4G3XqQNocLVmKIibKFRjocnYX1RDHMND9FFbgj2h7E7nvGA==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-util": "^5.9.8"
}
},
"rc-mentions": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-1.6.1.tgz",
"integrity": "sha512-LDzGI8jJVGnkhpTZxZuYBhMz3avcZZqPGejikchh97xPni/g4ht714Flh7DVvuzHQ+BoKHhIjobHnw1rcP8erg==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.6",
"rc-menu": "^9.0.0",
"rc-textarea": "^0.3.0",
"rc-trigger": "^5.0.4",
"rc-util": "^5.0.1"
}
},
"rc-menu": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.2.1.tgz",
"integrity": "sha512-UbEtn3rflJ8zS+etYGTVQuzy7Fm+yWXR5c0Rl6ecNTS/dPknRyWAyhJcbeR0Hu1+RdQT+0VCqrUPrgKnm4iY+w==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.4.3",
"rc-overflow": "^1.2.0",
"rc-trigger": "^5.1.2",
"rc-util": "^5.12.0",
"shallowequal": "^1.1.0"
}
},
"rc-motion": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.4.4.tgz",
"integrity": "sha512-ms7n1+/TZQBS0Ydd2Q5P4+wJTSOrhIrwNxLXCZpR7Fa3/oac7Yi803HDALc2hLAKaCTQtw9LmQeB58zcwOsqlQ==",
"requires": {
"@babel/runtime": "^7.11.1",
"classnames": "^2.2.1",
"rc-util": "^5.2.1"
}
},
"rc-notification": {
"version": "4.5.7",
"resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-4.5.7.tgz",
"integrity": "sha512-zhTGUjBIItbx96SiRu3KVURcLOydLUHZCPpYEn1zvh+re//Tnq/wSxN4FKgp38n4HOgHSVxcLEeSxBMTeBBDdw==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.2.0",
"rc-util": "^5.0.1"
}
},
"rc-overflow": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.3.tgz",
"integrity": "sha512-Bz6dXTn/ww8nmu70tUQfRV0wT3BkfXY6j1lB1O38OVkDPz4xwfAcGK+LJ2zewUR5cTXkJ8hAN7YULohG8z4M7Q==",
"requires": {
"@babel/runtime": "^7.11.1",
"classnames": "^2.2.1",
"rc-resize-observer": "^1.0.0",
"rc-util": "^5.15.0"
}
},
"rc-pagination": {
"version": "3.1.15",
"resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-3.1.15.tgz",
"integrity": "sha512-4L3fot8g4E+PjWEgoVGX0noFCg+8ZFZmeLH4vsnZpB3O2T2zThtakjNxG+YvSaYtyMVT4B+GLayjKrKbXQpdAg==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1"
}
},
"rc-picker": {
"version": "2.5.19",
"resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-2.5.19.tgz",
"integrity": "sha512-u6myoCu/qiQ0vLbNzSzNrzTQhs7mldArCpPHrEI6OUiifs+IPXmbesqSm0zilJjfzrZJLgYeyyOMSznSlh0GKA==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1",
"date-fns": "2.x",
"dayjs": "1.x",
"moment": "^2.24.0",
"rc-trigger": "^5.0.4",
"rc-util": "^5.4.0",
"shallowequal": "^1.1.0"
}
},
"rc-progress": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.2.4.tgz",
"integrity": "sha512-M9WWutRaoVkPUPIrTpRIDpX0SPSrVHzxHdCRCbeoBFrd9UFWTYNWRlHsruJM5FH1AZI+BwB4wOJUNNylg/uFSw==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.6",
"rc-util": "^5.16.1"
}
},
"rc-rate": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.9.1.tgz",
"integrity": "sha512-MmIU7FT8W4LYRRHJD1sgG366qKtSaKb67D0/vVvJYR0lrCuRrCiVQ5qhfT5ghVO4wuVIORGpZs7ZKaYu+KMUzA==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-util": "^5.0.1"
}
},
"rc-resize-observer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.2.0.tgz",
"integrity": "sha512-6W+UzT3PyDM0wVCEHfoW3qTHPTvbdSgiA43buiy8PzmeMnfgnDeb9NjdimMXMl3/TcrvvWl5RRVdp+NqcR47pQ==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1",
"rc-util": "^5.15.0",
"resize-observer-polyfill": "^1.5.1"
}
},
"rc-select": {
"version": "14.0.0-alpha.25",
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.0.0-alpha.25.tgz",
"integrity": "sha512-U9AMzXsOCCdtn96YIZdUrYbxk+5u6uWUCaYH2129X3FTjQITqAjEPYHfPcxU/G7+lwiD0pIaU95W0NMkg+26qw==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.0.1",
"rc-overflow": "^1.0.0",
"rc-trigger": "^5.0.4",
"rc-util": "^5.16.1",
"rc-virtual-list": "^3.2.0"
}
},
"rc-slider": {
"version": "9.7.5",
"resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-9.7.5.tgz",
"integrity": "sha512-LV/MWcXFjco1epPbdw1JlLXlTgmWpB9/Y/P2yinf8Pg3wElHxA9uajN21lJiWtZjf5SCUekfSP6QMJfDo4t1hg==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-tooltip": "^5.0.1",
"rc-util": "^5.16.1",
"shallowequal": "^1.1.0"
}
},
"rc-steps": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-4.1.4.tgz",
"integrity": "sha512-qoCqKZWSpkh/b03ASGx1WhpKnuZcRWmvuW+ZUu4mvMdfvFzVxblTwUM+9aBd0mlEUFmt6GW8FXhMpHkK3Uzp3w==",
"requires": {
"@babel/runtime": "^7.10.2",
"classnames": "^2.2.3",
"rc-util": "^5.0.1"
}
},
"rc-switch": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-3.2.2.tgz",
"integrity": "sha512-+gUJClsZZzvAHGy1vZfnwySxj+MjLlGRyXKXScrtCTcmiYNPzxDFOxdQ/3pK1Kt/0POvwJ/6ALOR8gwdXGhs+A==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1",
"rc-util": "^5.0.1"
}
},
"rc-table": {
"version": "7.22.2",
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.22.2.tgz",
"integrity": "sha512-Ng2gNkGi6ybl6dzneRn2H4Gp8XhIbRa5rXQ7ZhZcgWVmfVMok70UHGPXcf68tXW6O0/qckTf/eOVsoviSvK4sw==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-resize-observer": "^1.1.0",
"rc-util": "^5.14.0",
"shallowequal": "^1.1.0"
}
},
"rc-tabs": {
"version": "11.10.5",
"resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.10.5.tgz",
"integrity": "sha512-DDuUdV6b9zGRYLtjI5hyejWLKoz1QiLWNgMeBzc3aMeQylZFhTYnFGdDc6HRqj5IYearNTsFPVSA+6VIT8g5cg==",
"requires": {
"@babel/runtime": "^7.11.2",
"classnames": "2.x",
"rc-dropdown": "^3.2.0",
"rc-menu": "^9.0.0",
"rc-resize-observer": "^1.0.0",
"rc-util": "^5.5.0"
}
},
"rc-textarea": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-0.3.7.tgz",
"integrity": "sha512-yCdZ6binKmAQB13hc/oehh0E/QRwoPP1pjF21aHBxlgXO3RzPF6dUu4LG2R4FZ1zx/fQd2L1faktulrXOM/2rw==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.1",
"rc-resize-observer": "^1.0.0",
"rc-util": "^5.7.0",
"shallowequal": "^1.1.0"
}
},
"rc-tooltip": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-5.1.1.tgz",
"integrity": "sha512-alt8eGMJulio6+4/uDm7nvV+rJq9bsfxFDCI0ljPdbuoygUscbsMYb6EQgwib/uqsXQUvzk+S7A59uYHmEgmDA==",
"requires": {
"@babel/runtime": "^7.11.2",
"rc-trigger": "^5.0.0"
}
},
"rc-tree": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.4.3.tgz",
"integrity": "sha512-WAHV8FkBerulj9J/+61+Qn0TD/Zo37PrDG8/45WomzGTYavxFMur9YguKjQj/J+NxjVJzrJL3lvdSZsumfdbiA==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-motion": "^2.0.1",
"rc-util": "^5.16.1",
"rc-virtual-list": "^3.4.1"
}
},
"rc-tree-select": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.1.2.tgz",
"integrity": "sha512-sTulyQZB1SgF2HzAR49i4vjMNvV/tfPxCTc+qNuWOwaAtSm2bwGH6/wfi3k3Dw2/5PPOGpRRpgCMltoP9aG0+w==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "2.x",
"rc-select": "~14.0.0-alpha.8",
"rc-tree": "~5.4.3",
"rc-util": "^5.16.1"
}
},
"rc-trigger": {
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.2.10.tgz",
"integrity": "sha512-FkUf4H9BOFDaIwu42fvRycXMAvkttph9AlbCZXssZDVzz2L+QZ0ERvfB/4nX3ZFPh1Zd+uVGr1DEDeXxq4J1TA==",
"requires": {
"@babel/runtime": "^7.11.2",
"classnames": "^2.2.6",
"rc-align": "^4.0.0",
"rc-motion": "^2.0.0",
"rc-util": "^5.5.0"
}
},
"rc-upload": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.3.3.tgz",
"integrity": "sha512-YoJ0phCRenMj1nzwalXzciKZ9/FAaCrFu84dS5pphwucTC8GUWClcDID/WWNGsLFcM97NqIboDqrV82rVRhW/w==",
"requires": {
"@babel/runtime": "^7.10.1",
"classnames": "^2.2.5",
"rc-util": "^5.2.0"
}
},
"rc-util": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.17.0.tgz",
"integrity": "sha512-HWuTIKzBeZQQ7IBqdokE0wMp/xx39/KfUJ0gcquBigoldDCrf3YBcWFHrrQlJG7sI82Wg8mwp1uAKV3zMGfAgg==",
"requires": {
"@babel/runtime": "^7.12.5",
"react-is": "^16.12.0",
"shallowequal": "^1.1.0"
}
},
"rc-virtual-list": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.4.2.tgz",
"integrity": "sha512-OyVrrPvvFcHvV0ssz5EDZ+7Rf5qLat/+mmujjchNw5FfbJWNDwkpQ99EcVE6+FtNRmX9wFa1LGNpZLUTvp/4GQ==",
"requires": {
"classnames": "^2.2.6",
"rc-resize-observer": "^1.0.0",
"rc-util": "^5.0.7"
}
},
"react": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
@ -41881,6 +42387,11 @@
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
@ -42441,6 +42952,14 @@
}
}
},
"scroll-into-view-if-needed": {
"version": "2.2.29",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz",
"integrity": "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==",
"requires": {
"compute-scroll-into-view": "^1.0.17"
}
},
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
@ -43216,6 +43735,11 @@
"integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==",
"dev": true
},
"string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
"integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c="
},
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",

View File

@ -66,6 +66,7 @@
"@types/lodash.get": "^4.4.6",
"@xstate/react": "^2.0.0",
"ace-builds": "^1.4.11",
"antd": "4.18.6",
"apollo-link": "1.2.14",
"apollo-link-ws": "1.0.20",
"brace": "0.11.1",

View File

@ -0,0 +1,162 @@
import React, { useMemo } from 'react';
import 'antd/dist/antd.css';
import { Tree as AntTree } from 'antd';
import { GraphQLSchema } from 'graphql';
import { EventDataNode } from 'antd/lib/tree';
import {
AllowedRootFields,
AntdTreeNode,
HasuraColumn,
RelationshipFields,
TreeNode,
} from './types';
import {
buildTree,
findRemoteField,
getFieldData,
getExpandedKeys,
getCheckedKeys,
} from './utils';
export interface RemoteSchemaTreeProps {
/**
* Graphql schema for setting new permissions.
*/
schema: GraphQLSchema;
relationshipFields: RelationshipFields[];
rootFields: AllowedRootFields;
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>;
/**
* Columns array from the current table.
*/
columns: HasuraColumn;
}
export const RemoteSchemaTree = ({
schema,
relationshipFields,
rootFields,
setRelationshipFields,
columns,
}: RemoteSchemaTreeProps) => {
const tree: TreeNode[] = useMemo(
() =>
buildTree(
schema,
relationshipFields,
setRelationshipFields,
columns,
rootFields
),
[relationshipFields, schema, rootFields, columns]
);
const expandedKeys = useMemo(() => getExpandedKeys(relationshipFields), [
relationshipFields,
]);
const checkedKeys = useMemo(() => getCheckedKeys(relationshipFields), [
relationshipFields,
]);
const onCheck = (
// onCheck props expects checked param
checked:
| React.Key[]
| {
checked: React.Key[];
halfChecked: React.Key[];
},
// CheckInfo is not exported by the library
// https://github.com/react-component/tree/issues/411
checkedNodeInfo: Record<string, any>
) => {
const nodeInfo = checkedNodeInfo.node as AntdTreeNode;
const selectedField = findRemoteField(relationshipFields, nodeInfo);
const fieldData = getFieldData(nodeInfo);
if (selectedField) {
setRelationshipFields(
relationshipFields.filter(field => !(field.key === nodeInfo.key))
);
} else {
setRelationshipFields([
...relationshipFields.filter(field => !(field.key === nodeInfo.key)),
fieldData,
]);
}
};
const onExpand = (
expanded: React.Key[],
expandedNodeInfo: {
node: EventDataNode;
expanded: boolean;
nativeEvent: MouseEvent;
}
) => {
const nodeInfo = expandedNodeInfo.node as AntdTreeNode;
const selectedField = findRemoteField(relationshipFields, nodeInfo);
const fieldData = getFieldData(nodeInfo);
if (selectedField) {
// if the node is already expanded, collapse the node,
// and remove all its children
setRelationshipFields(
relationshipFields.filter(
field =>
!(
field.key === nodeInfo.key ||
field.key.includes(`${nodeInfo.key}.`)
)
)
);
} else {
// `fields` at same or higher depth, if the current node is `argument` we skip this
const levelDepthFields =
nodeInfo.type === 'field'
? relationshipFields
.filter(
field => field.type === 'field' && field.depth >= nodeInfo.depth
)
.map(field => field.key)
: [];
// remove all the fields and their children which are on same/higher depth, and add the current field
// as one parent can have only one field at a certain depth
setRelationshipFields([
...relationshipFields.filter(
field =>
!(
field.key === nodeInfo.key ||
// remove all current or higher depth fields and their children
(nodeInfo.type === 'field' &&
levelDepthFields.some(
refFieldKey =>
field.key === refFieldKey ||
field.key.includes(`${refFieldKey}.`)
))
)
),
fieldData,
]);
}
};
return (
<AntTree
checkable
checkStrictly
blockNode
selectable={false}
onCheck={onCheck}
onExpand={onExpand}
treeData={tree}
expandedKeys={expandedKeys}
checkedKeys={checkedKeys}
// disable animation onExpand to improve performance
motion={null}
/>
);
};

View File

@ -0,0 +1,52 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { buildClientSchema } from 'graphql';
import countries from './fixtures/countries.json';
import hasura_schema from './fixtures/hasura_schema.json';
import { customer_columns, remote_rel_definition } from './fixtures/constants';
import {
RemoteSchemaTreeWrapper,
RemoteSchemaTreeWrapperProps,
} from './RemoteSchemaTreeWrapper';
export default {
title: 'Remote Relationships/Components/Remote Schema Tree Wrapper',
component: RemoteSchemaTreeWrapper,
} as Meta;
export const RemoteSchemaTreeWithCountriesSchema: Story<RemoteSchemaTreeWrapperProps> = args => (
<RemoteSchemaTreeWrapper {...args} />
);
RemoteSchemaTreeWithCountriesSchema.args = {
schema: buildClientSchema(countries as any),
columns: customer_columns,
rootFields: ['query', 'mutation', 'subscription'],
};
RemoteSchemaTreeWithCountriesSchema.parameters = {
// Disable chromatic snapshot for playground stories
chromatic: { disableSnapshot: true },
};
export const RemoteSchemaTreeWithHasuraCloudSchema: Story<RemoteSchemaTreeWrapperProps> = args => (
<RemoteSchemaTreeWrapper {...args} />
);
RemoteSchemaTreeWithHasuraCloudSchema.args = {
schema: buildClientSchema(hasura_schema as any),
columns: customer_columns,
rootFields: ['query', 'mutation'],
};
RemoteSchemaTreeWithHasuraCloudSchema.parameters = {
// Disable chromatic snapshot for playground stories
chromatic: { disableSnapshot: true },
};
export const RemoteSchemaTreeWithExistingRelationship: Story<RemoteSchemaTreeWrapperProps> = args => (
<RemoteSchemaTreeWrapper {...args} />
);
RemoteSchemaTreeWithExistingRelationship.args = {
schema: buildClientSchema(hasura_schema as any),
columns: customer_columns,
rootFields: ['query', 'mutation'],
serverRelationship: remote_rel_definition as any,
};

View File

@ -0,0 +1,71 @@
import React, { useState, useEffect } from 'react';
import { GraphQLSchema } from 'graphql';
import { RemoteRelationship } from '@/metadata/types';
import { Button } from '@/new-components/Button';
import { RemoteSchemaTree } from './RemoteSchemaTree';
import { HasuraColumn, AllowedRootFields, RelationshipFields } from './types';
import { buildServerRemoteFieldObject, parseServerRelationship } from './utils';
export interface RemoteSchemaTreeWrapperProps {
/**
* Graphql schema for setting new permissions.
*/
schema: GraphQLSchema;
/**
* Columns array from the current table.
*/
columns: HasuraColumn;
/**
* Remote relationship object from server, for already present permissions.
* This will be parsed and tree will be populated accordingly
*/
serverRelationship?: RemoteRelationship;
rootFields?: AllowedRootFields;
}
export const RemoteSchemaTreeWrapper = ({
schema,
columns,
serverRelationship,
rootFields = ['query'],
}: RemoteSchemaTreeWrapperProps) => {
const [relationshipFields, setRelationshipFields] = useState<
RelationshipFields[]
>([]);
const [serverRelObject, setServerRelObject] = useState<string>('');
useEffect(() => {
if (serverRelationship) {
setRelationshipFields(parseServerRelationship(serverRelationship));
}
}, [serverRelationship]);
const handleSave = () => {
setServerRelObject(
JSON.stringify(
buildServerRemoteFieldObject(relationshipFields) ?? '',
null,
4
)
);
};
return (
<div>
<RemoteSchemaTree
schema={schema}
relationshipFields={relationshipFields}
setRelationshipFields={setRelationshipFields}
columns={columns}
rootFields={rootFields}
/>
<div className="mt-lg">
<Button onClick={handleSave}>Generate Relationship Object</Button>
</div>
<div className="mt-lg">
<code className="whitespace-pre-wrap">{serverRelObject}</code>
</div>
</div>
);
};

View File

@ -0,0 +1,371 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Utils.ts buildServerRemoteFieldObject 1`] = `
Object {
"arguments": Object {
"where": Object {
"id": Object {
"_eq": "$id",
},
},
},
"field": Object {
"aggregate": Object {
"arguments": Object {},
"field": Object {
"count": Object {
"arguments": Object {
"distinct": "$id",
},
},
},
},
},
}
`;
exports[`Utils.ts buildTree 1`] = `
Array [
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__query.field.testUser.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__query.field.testUser",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__query.field.testUser_aggregate.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__query.field.testUser_aggregate",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser_aggregate"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__query.field.testUser_by_pk.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__query.field.testUser_by_pk",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser_by_pk"
/>,
"type": "field",
},
],
"depth": 0,
"key": "__query",
"title": <RootFieldTitle
title="Query"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.delete_testUser.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.delete_testUser",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="delete_testUser"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.delete_testUser_by_pk.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.delete_testUser_by_pk",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="delete_testUser_by_pk"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.insert_testUser.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.insert_testUser",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="insert_testUser"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.insert_testUser_one.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.insert_testUser_one",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="insert_testUser_one"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.update_testUser.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.update_testUser",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="update_testUser"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__mutation.field.update_testUser_by_pk.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__mutation.field.update_testUser_by_pk",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="update_testUser_by_pk"
/>,
"type": "field",
},
],
"depth": 0,
"key": "__mutation",
"title": <RootFieldTitle
title="Mutation"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__subscription.field.testUser.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__subscription.field.testUser",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__subscription.field.testUser_aggregate.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__subscription.field.testUser_aggregate",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser_aggregate"
/>,
"type": "field",
},
Object {
"checkable": false,
"children": Array [
Object {
"checkable": false,
"depth": 2,
"disabled": true,
"key": "__subscription.field.testUser_by_pk.__placeholder.1",
"title": "",
"type": "field",
},
],
"depth": 1,
"disabled": false,
"key": "__subscription.field.testUser_by_pk",
"title": <SubFieldTitle
enabled={true}
isSubfield={false}
title="testUser_by_pk"
/>,
"type": "field",
},
],
"depth": 0,
"key": "__subscription",
"title": <RootFieldTitle
title="Subscription"
/>,
"type": "field",
},
]
`;
exports[`Utils.ts parseServerRelationship 1`] = `
Array [
Object {
"argValue": null,
"checkable": false,
"depth": 0,
"key": "__query",
"type": "field",
},
Object {
"argValue": null,
"checkable": false,
"depth": 1,
"key": "__query.field.testUser_aggregate",
"type": "field",
},
Object {
"argValue": null,
"checkable": false,
"depth": 2,
"key": "__query.field.testUser_aggregate.field.aggregate",
"type": "field",
},
Object {
"argValue": null,
"checkable": false,
"depth": 3,
"key": "__query.field.testUser_aggregate.field.aggregate.field.count",
"type": "field",
},
Object {
"argValue": Object {
"kind": "column",
"type": "String",
"value": "firstName",
},
"checkable": true,
"depth": 4,
"key": "__query.field.testUser_aggregate.field.aggregate.field.count.arguments.columns",
"type": "arg",
},
]
`;

View File

@ -0,0 +1,60 @@
import { buildClientSchema } from 'graphql';
import hasura_schema from '../fixtures/hasura_schema.json';
import {
getExpandedKeys,
getCheckedKeys,
buildServerRemoteFieldObject,
parseServerRelationship,
buildTree,
} from '../utils';
import {
relationship_fields,
remote_rel_definition,
customer_columns,
} from '../fixtures/constants';
describe('Utils.ts', () => {
it('buildServerRemoteFieldObject', () => {
const remoteRelObj = buildServerRemoteFieldObject(relationship_fields);
expect(remoteRelObj).toMatchSnapshot();
});
it('parseServerRelationship', () => {
const relationshipFields = parseServerRelationship(remote_rel_definition);
expect(relationshipFields).toMatchSnapshot();
});
it('buildTree', () => {
const schema = buildClientSchema(hasura_schema as any);
const tree = buildTree(schema, [], () => {}, customer_columns, [
'query',
'mutation',
'subscription',
]);
expect(tree).toMatchSnapshot();
});
it('getExpandedKeys', () => {
const expandedKeys = [
'__query',
'__query.testUser_aggregate',
'__query.testUser_aggregate.arguments.where',
'__query.testUser_aggregate.arguments.where.id',
'__query.testUser_aggregate.field.aggregate',
'__query.testUser_aggregate.field.aggregate.field.count',
];
expect(getExpandedKeys(relationship_fields)).toEqual(
expect.arrayContaining(expandedKeys)
);
});
it('gets checked keys', () => {
const checkedKeys = [
'__query.testUser_aggregate.arguments.where.id._eq',
'__query.testUser_aggregate.field.aggregate.field.count.arguments.distinct',
];
expect(getCheckedKeys(relationship_fields)).toEqual(
expect.arrayContaining(checkedKeys)
);
});
});

View File

@ -0,0 +1,43 @@
import React from 'react';
import { ArgValueForm } from './ArgValueForm';
import { RelationshipFields, ArgValue, HasuraColumn } from '../types';
type ArgFieldTitleProps = {
title: string;
argKey: string;
relationshipFields: RelationshipFields[];
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>;
showForm: boolean;
argValue: ArgValue;
columns: HasuraColumn;
};
const titleStyles =
'flex items-center cursor-pointer text-purple-600 whitespace-nowrap hover:text-purple-900';
export const ArgFieldTitle = ({
title,
argKey,
relationshipFields,
setRelationshipFields,
showForm,
argValue,
columns,
}: ArgFieldTitleProps) => {
return showForm ? (
<>
<div className={titleStyles}>{title}</div>
<ArgValueForm
argKey={argKey}
relationshipFields={relationshipFields}
setRelationshipFields={setRelationshipFields}
argValue={argValue}
columns={columns}
/>
</>
) : (
<div className={titleStyles}>{title}</div>
);
};

View File

@ -0,0 +1,165 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useDebouncedEffect } from '@/hooks/useDebounceEffect';
import {
ArgValue,
ArgValueKind,
HasuraColumn,
RelationshipFields,
} from '../types';
import { defaultArgValue } from '../utils';
export interface ArgValueFormProps {
argKey: string;
relationshipFields: RelationshipFields[];
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>;
argValue: ArgValue;
columns: HasuraColumn;
}
const fieldStyle =
'block w-full h-input shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400';
const argValueTypeOptions = [
{ key: 'column', content: 'Source Column' },
{ key: 'static', content: 'Static Value' },
];
export const ArgValueForm = ({
argKey,
relationshipFields,
setRelationshipFields,
argValue,
columns,
}: ArgValueFormProps) => {
const allColumns = useMemo(
() => [...(columns.columns ?? []), ...(columns.computedFields ?? [])],
[columns]
);
const [localArgValue, setLocalArgValue] = useState(argValue);
useEffect(() => {
setLocalArgValue(argValue);
}, [argValue]);
useDebouncedEffect(
() => {
setRelationshipFields(
relationshipFields.map(f => {
if (f.key === argKey) {
return {
...f,
argValue: {
...(f.argValue ?? defaultArgValue),
value: localArgValue.value,
},
};
}
return f;
})
);
},
400,
[localArgValue.value]
);
const changeInputType = (e: React.ChangeEvent<HTMLSelectElement>) => {
setRelationshipFields(
relationshipFields.map(f => {
if (f.key === argKey) {
return {
...f,
argValue: {
...(f.argValue ?? defaultArgValue),
value: '',
kind: e.target.value as ArgValueKind,
},
};
}
return f;
})
);
};
const changeInputColumnValue = (e: React.ChangeEvent<HTMLSelectElement>) => {
setRelationshipFields(
relationshipFields.map(f => {
if (f.key === argKey) {
return {
...f,
argValue: {
...(f.argValue ?? defaultArgValue),
value: e.target.value,
},
};
}
return f;
})
);
};
const onValueChangeHandler = (value: string) => {
setLocalArgValue({ ...localArgValue, value });
};
return (
<div
onClick={e => e.stopPropagation()}
className="rounded bg-white shadow pt-xs pb-sm px-sm mb-sm border-l-2 border-yellow-400 w-full"
>
<div className="grid grid-cols-2 gap-2">
<div>
<p className="mb-xs text-muted font-semibold">Fill From</p>
<select
className={fieldStyle}
value={localArgValue.kind}
onChange={changeInputType}
>
<option disabled>Select an arugment...</option>
{argValueTypeOptions.map(option => (
<option key={option.key} value={option.key}>
{option.content}
</option>
))}
</select>
</div>
<div>
{localArgValue.kind === 'column' ? (
<>
<p className="mb-xs text-muted font-semibold">
<i className="fa fa-columns text-sm mr-xs" /> From Column
</p>
<select
className={fieldStyle}
value={localArgValue.value}
onChange={changeInputColumnValue}
>
<option value="" disabled>
Select Column...
</option>
{allColumns.map(option => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</>
) : (
<>
<p className="mb-xs text-muted font-semibold">Static Value</p>
<input
type="text"
name="argValue"
id="argValue"
className={fieldStyle}
value={localArgValue.value}
onChange={e => onValueChangeHandler(e.target.value)}
/>
</>
)}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,13 @@
import React from 'react';
type FieldLabelProps = {
title: string;
};
export const FieldLabel = ({ title }: FieldLabelProps) => {
return (
<div className="text-xs pt-2 uppercase text-gray-400 tracking-wide font-semibold">
{title}
</div>
);
};

View File

@ -0,0 +1,14 @@
import React from 'react';
import { FaProjectDiagram } from 'react-icons/fa';
type RootFieldTitleProps = {
title: string;
};
export const RootFieldTitle = ({ title }: RootFieldTitleProps) => {
return (
<div className="flex font-semibold items-center cursor-pointer text-gray-900 w-max whitespace-nowrap hover:text-gray-900">
<FaProjectDiagram className="w-4 mr-xs h-5 w-5" /> {title}
</div>
);
};

View File

@ -0,0 +1,34 @@
import { ToolTip } from '@/new-components/Tooltip';
import React from 'react';
type SubFieldTitleProps = {
title: string;
enabled?: boolean;
isSubfield?: boolean;
};
export const SubFieldTitle = ({
title,
enabled,
isSubfield,
}: SubFieldTitleProps) => {
return (
<>
<div className="flex items-center cursor-pointer w-max whitespace-nowrap">
{!enabled ? (
<>
<ToolTip
className="mr-sm text-gray-400"
message="Only fields with arguments or subfields can be toggled"
/>
<span className="text-gray-400">{title}</span>
</>
) : isSubfield ? (
<span className="text-blue-600 hover:text-blue-900">{title}</span>
) : (
<span className="text-gray-900">{title}</span>
)}
</div>
</>
);
};

View File

@ -0,0 +1,99 @@
import { RelationshipFields, RemoteRelationship } from '../types';
export const customer_columns = {
columns: ['id', 'firstName', 'lastName', 'age', 'countryCode', 'country'],
computedFields: ['field1', 'field2'],
};
export const remote_rel_definition: RemoteRelationship = {
definition: {
remote_field: {
testUser_aggregate: {
field: {
aggregate: {
field: {
count: {
arguments: {
columns: '$firstName',
},
},
},
arguments: {},
},
},
arguments: {},
},
},
hasura_fields: ['name'],
remote_schema: 'hasura_cloud',
},
name: 'some_relationship',
};
export const relationship_fields: RelationshipFields[] = [
{
key: '__query',
depth: 0,
checkable: false,
argValue: null,
type: 'field',
},
{
key: '__query.testUser_aggregate',
depth: 1,
checkable: false,
argValue: null,
type: 'field',
},
{
key: '__query.testUser_aggregate.arguments.where',
depth: 1,
checkable: false,
argValue: null,
type: 'arg',
},
{
key: '__query.testUser_aggregate.arguments.where.id',
depth: 2,
checkable: false,
argValue: null,
type: 'arg',
},
{
key: '__query.testUser_aggregate.arguments.where.id._eq',
depth: 3,
checkable: true,
argValue: {
kind: 'column',
value: 'id',
type: 'String',
},
type: 'arg',
},
{
key: '__query.testUser_aggregate.field.aggregate',
depth: 2,
checkable: false,
argValue: null,
type: 'field',
},
{
key: '__query.testUser_aggregate.field.aggregate.field.count',
depth: 3,
checkable: false,
argValue: null,
type: 'field',
},
{
key:
'__query.testUser_aggregate.field.aggregate.field.count.arguments.distinct',
depth: 3,
checkable: true,
argValue: {
kind: 'column',
value: 'id',
type: 'String',
},
type: 'arg',
},
];

View File

@ -0,0 +1,74 @@
import { EventDataNode } from 'antd/lib/tree';
export type HasuraColumn = {
columns: string[];
computedFields: string[];
};
export type AllowedRootFields = ('query' | 'mutation' | 'subscription')[];
export type ArgValueKind = 'column' | 'static';
export type ArgValue = {
kind: ArgValueKind;
value: string;
type: string;
};
export type RelationshipFields = {
key: string;
argValue: ArgValue | null;
checkable: boolean;
depth: number;
type: 'field' | 'arg';
};
export type TreeNode = {
title: JSX.Element | string;
key: string;
checkable: boolean;
depth: number;
type: 'field' | 'arg';
disabled?: boolean;
argValue?: ArgValue | null;
children?: TreeNode[];
};
export type RemoteField = {
[FieldName: string]: {
arguments: InputArgumentsType | never;
field?: RemoteField;
};
};
export type InputArgumentValueType =
| string
| boolean
| number
| InputArgumentsType;
export type InputArgumentsType = {
[key: string]: InputArgumentValueType;
};
// we should extend RemoteRelationship type from `metadata/types.ts` once we have
// the correct types in place, for both old and new format
export type RemoteRelationship = {
name: string;
definition: {
hasura_fields: string[];
remote_schema: string;
remote_field: RemoteField;
};
};
export interface AntdTreeNode extends EventDataNode {
title: JSX.Element | string;
key: string;
checkable: boolean;
depth: number;
type: 'field' | 'arg';
disabled?: boolean;
argValue?: ArgValue | null;
children?: TreeNode[];
}

View File

@ -0,0 +1,560 @@
import React from 'react';
import {
isInputObjectType,
isInterfaceType,
isObjectType,
GraphQLSchema,
GraphQLField,
GraphQLType,
GraphQLArgument,
GraphQLInputField,
isWrappingType,
isListType,
isNonNullType,
} from 'graphql';
import { isEmpty, isFloat, isNumber } from '@/components/Common/utils/jsUtils';
import {
AllowedRootFields,
ArgValue,
HasuraColumn,
RelationshipFields,
TreeNode,
RemoteRelationship,
RemoteField,
InputArgumentsType,
InputArgumentValueType,
AntdTreeNode,
} from './types';
import { SubFieldTitle } from './components/SubFieldTitle';
import { RootFieldTitle } from './components/RootFieldTitle';
import { FieldLabel } from './components/FieldLabel';
import { ArgFieldTitle } from './components/ArgFieldTitle';
export const getFieldData = (nodeData: AntdTreeNode): RelationshipFields => ({
key: nodeData.key,
depth: nodeData.depth,
checkable: nodeData.checkable,
argValue: nodeData.argValue ?? null,
type: nodeData.type,
});
export const defaultArgValue: ArgValue = {
kind: 'column',
value: '',
type: 'String',
};
export const findRemoteField = (
fields: RelationshipFields[],
field: AntdTreeNode
) => {
return fields.find(f => f.key === field.key);
};
const isElementActive = (
relationshipFields: RelationshipFields[],
fieldKey: string
) => {
return relationshipFields.some(f => f.key === fieldKey);
};
export const getUnderlyingType = (_type: Record<string, any>) => {
let type = Object.assign(Object.create(_type), _type);
const wraps = [];
while (isWrappingType(type)) {
if (isListType(type)) wraps.push('l');
if (isNonNullType(type)) wraps.push('n');
type = type.ofType;
}
return {
wraps,
type,
};
};
/* returns checked value if arg is checked
* returns null if arg isn't involved in the relationship
*/
export const getCheckedArgValue = (
relationshipFields: RelationshipFields[],
key: string
): ArgValue | null => {
const field = relationshipFields.find(r => r.key === key);
if (field) {
return field.argValue;
}
return null;
};
const getPlaceholderChild = (parentKey: string, depth: number): TreeNode => ({
title: '',
key: `${parentKey}.__placeholder.${depth}`,
depth: depth + 1,
checkable: false,
type: 'field',
disabled: true,
});
const buildArgElement = ({
arg,
parentKey,
relationshipFields,
setRelationshipFields,
columns,
depth,
}: {
arg: GraphQLArgument | GraphQLInputField;
parentKey: string;
relationshipFields: RelationshipFields[];
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>;
columns: HasuraColumn;
depth: number;
}): TreeNode => {
const { type: argType }: { type: GraphQLType } = getUnderlyingType(arg.type);
let children: TreeNode[] = [];
let checkable = true;
const argKey = `${parentKey}.${arg.name}`;
const isActive = isElementActive(relationshipFields, argKey);
const argValue = getCheckedArgValue(relationshipFields, argKey);
if (isInputObjectType(argType) || isInterfaceType(argType)) {
const argFields = argType.getFields();
if (!isEmpty(argFields)) {
checkable = false;
children = [getPlaceholderChild(argKey, depth + 1)];
if (isActive) {
children = [
...Object.values(argFields).map(argField =>
buildArgElement({
arg: argField,
parentKey: argKey,
relationshipFields,
setRelationshipFields,
columns,
depth: depth + 1,
})
),
];
}
}
}
return {
title: (
<ArgFieldTitle
title={arg.name}
argKey={argKey}
relationshipFields={relationshipFields}
setRelationshipFields={setRelationshipFields}
columns={columns}
showForm={isActive && checkable}
argValue={argValue || defaultArgValue}
/>
),
key: argKey,
depth,
type: 'arg',
checkable,
argValue: checkable ? argValue || defaultArgValue : null,
children,
};
};
const buildFieldElement = ({
field,
parentKey,
relationshipFields,
setRelationshipFields,
columns,
depth,
isSubfield,
}: {
field: GraphQLField<any, any, Record<string, any>>;
parentKey: string;
relationshipFields: RelationshipFields[];
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>;
columns: HasuraColumn;
depth: number;
isSubfield: boolean;
}): TreeNode => {
const { type: fieldType }: { type: GraphQLType } = getUnderlyingType(
field.type
);
const fieldKey = `${parentKey}.${field.name}`;
const enabled =
(field.args && !!field.args.length) || isObjectType(fieldType);
let children: TreeNode[] = [];
if (enabled) {
children = [getPlaceholderChild(fieldKey, depth + 1)];
}
const isActive = isElementActive(relationshipFields, fieldKey);
if (isActive) {
children = [];
if (field.args && !!field.args.length) {
children = [
{
title: <FieldLabel title="Arguments" />,
key: `${fieldKey}.__placeholder.args.${depth}`,
checkable: false,
type: 'field',
depth,
},
...field.args.map(arg =>
buildArgElement({
arg,
parentKey: `${fieldKey}.arguments`,
relationshipFields,
setRelationshipFields,
columns,
depth: depth + 1,
})
),
];
}
if (isObjectType(fieldType) || isInterfaceType(fieldType)) {
const subFields = fieldType.getFields();
if (!isEmpty(subFields)) {
children = [
...children,
{
title: <FieldLabel title="Sub-Fields" />,
key: `${fieldKey}.__placeholder.sub_fields.${depth}`,
checkable: false,
type: 'field',
depth,
},
...Object.values(subFields).map(subField =>
buildFieldElement({
field: subField,
parentKey: `${fieldKey}.field`,
relationshipFields,
setRelationshipFields,
columns,
depth: depth + 1,
isSubfield: true,
})
),
];
}
}
}
return {
title: (
<SubFieldTitle
title={field.name}
enabled={enabled}
isSubfield={isSubfield}
/>
),
key: fieldKey,
depth: depth + 1,
checkable: false,
type: 'field',
disabled: !enabled,
children,
};
};
export const buildTree = (
schema: GraphQLSchema,
relationshipFields: RelationshipFields[],
setRelationshipFields: React.Dispatch<
React.SetStateAction<RelationshipFields[]>
>,
columns: HasuraColumn,
rootFields: AllowedRootFields
): TreeNode[] => {
const treeData: TreeNode[] = [];
if (rootFields.includes('query')) {
const queryType = schema.getQueryType();
const fields = queryType?.getFields();
const fieldKey = '__query';
if (fields) {
treeData.push({
title: <RootFieldTitle title="Query" />,
key: fieldKey,
checkable: false,
depth: 0,
type: 'field',
children: Object.values(fields).map(field =>
buildFieldElement({
field,
parentKey: `${fieldKey}.field`,
relationshipFields,
setRelationshipFields,
columns,
depth: 0,
isSubfield: false,
})
),
});
}
}
if (rootFields.includes('mutation')) {
const mutationType = schema.getMutationType();
const fields = mutationType?.getFields();
const fieldKey = '__mutation';
if (fields) {
treeData.push({
title: <RootFieldTitle title="Mutation" />,
key: fieldKey,
checkable: false,
depth: 0,
type: 'field',
children: Object.values(fields).map(field =>
buildFieldElement({
field,
parentKey: `${fieldKey}.field`,
relationshipFields,
setRelationshipFields,
columns,
depth: 0,
isSubfield: false,
})
),
});
}
}
if (rootFields.includes('subscription')) {
const subscriptionType = schema.getSubscriptionType();
const fields = subscriptionType?.getFields();
const fieldKey = '__subscription';
if (fields) {
treeData.push({
title: <RootFieldTitle title="Subscription" />,
key: fieldKey,
checkable: false,
depth: 0,
type: 'field',
children: Object.values(fields).map(field =>
buildFieldElement({
field,
parentKey: `${fieldKey}.field`,
relationshipFields,
setRelationshipFields,
columns,
depth: 0,
isSubfield: false,
})
),
});
}
}
return treeData;
};
const getRemoteFieldObject = (
ukSplit: string[][],
depth: number,
maxDepth: number
): RemoteField | InputArgumentsType | undefined => {
if (depth > maxDepth) return;
const obj: RemoteField | InputArgumentsType = {};
const depthRemoteFields: string[] = ukSplit.map(uk => uk[depth] ?? '');
// get unique depth remote fields at current depth
const uniqueDepthRemoteFields = Array.from(new Set(depthRemoteFields));
uniqueDepthRemoteFields.forEach((uniqueField, i) => {
if (uniqueField.length > 0) {
const newUkSplit: string[][] = [];
depthRemoteFields.forEach((depthField, index) => {
if (depthField === uniqueField) {
newUkSplit.push(ukSplit[index]);
}
});
// if leaf, push arg value
if (ukSplit[i][depth + 1] === '__argVal') {
obj[uniqueField] = ukSplit[i][depth + 2];
} else {
obj[uniqueField] = {
...getRemoteFieldObject(newUkSplit, depth + 1, maxDepth),
};
if (ukSplit[i][depth - 1] === 'field') {
obj[uniqueField] = {
...(obj[uniqueField] as RemoteField),
arguments: {
...(obj[uniqueField] as RemoteField).arguments,
},
};
}
}
}
});
return Object.keys(obj).length ? obj : undefined;
};
const getUniqueRelFields = (relationshipFields: RelationshipFields[]) => {
return relationshipFields?.filter(
f =>
!relationshipFields.some(
refF => refF.key !== f.key && refF.key.includes(f.key)
)
);
};
const getKeysWithArgValues = (relationshipFields: RelationshipFields[]) =>
relationshipFields?.map(field => {
if (field.type === 'arg') {
if (field.argValue && field.argValue?.kind === 'static')
return `${field.key}.__argVal.${field.argValue?.value}`;
else if (field.argValue && field.argValue?.kind === 'column')
return `${field.key}.__argVal.$${field.argValue?.value}`;
return `${field.key}.__argVal.$`;
}
return field.key;
});
export const buildServerRemoteFieldObject = (
relationshipFields: RelationshipFields[]
) => {
const uniqueRelFields = getUniqueRelFields(relationshipFields);
const uniqueKeys = getKeysWithArgValues(uniqueRelFields);
const ukSplit: string[][] = [];
uniqueKeys.forEach(uk => ukSplit.push(uk.split('.')));
let maxDepth = 0;
ukSplit.forEach(ar => {
if (ar.length > maxDepth) maxDepth = ar.length;
});
const relationshipObject = getRemoteFieldObject(ukSplit, 2, maxDepth);
return (relationshipObject as RemoteField) || {};
};
export const getExpandedKeys = (relationshipFields: RelationshipFields[]) =>
relationshipFields.filter(rf => !rf.argValue).map(rf => rf.key);
export const getCheckedKeys = (relationshipFields: RelationshipFields[]) =>
relationshipFields.filter(rf => rf.argValue).map(rf => rf.key);
export const parseArgValue = (
argValue: InputArgumentValueType
): ArgValue | null => {
if (typeof argValue === 'object' && argValue !== null) {
return null;
}
if (typeof argValue === 'string') {
const isStatic = !argValue.startsWith('$');
return {
value: isStatic ? argValue.toString() : argValue.substr(1),
kind: isStatic ? 'static' : 'column',
type: 'String',
};
}
if (typeof argValue === 'boolean') {
return {
kind: 'static',
value: argValue.toString(),
type: 'Boolean',
};
}
return {
kind: 'static',
value: argValue.toString(),
type:
(isNumber(argValue) && (isFloat(argValue) ? 'Float' : 'Int')) || 'String',
};
};
const serialiseArguments = (
args: InputArgumentValueType,
key: string,
depth: number,
callback: (f: RelationshipFields) => void
): void => {
if (typeof args === 'object') {
Object.keys(args).forEach(argName => {
const argValue = args[argName];
const argValueMetadata = parseArgValue(argValue);
if (argValueMetadata) {
callback({
key: `${key}.${argName}`,
depth,
checkable: true,
argValue: argValueMetadata,
type: 'arg',
});
} else {
callback({
key: `${key}.${argName}`,
depth,
checkable: false,
argValue: null,
type: 'arg',
});
serialiseArguments(argValue, `${key}.${argName}`, depth + 1, callback);
}
});
}
};
const serialiseRemoteField = (
field: {
arguments: InputArgumentsType | never;
field?: RemoteField | undefined;
},
key: string,
depth: number,
callback: (f: RelationshipFields) => void
): void => {
callback({
key,
depth,
checkable: false,
argValue: null,
type: 'field',
});
if (field.field) {
const subFieldName = field.field ? Object.keys(field.field)[0] : '';
const subField = field.field?.[subFieldName];
serialiseRemoteField(
subField,
`${key}.field.${subFieldName}`,
depth + 1,
callback
);
}
if (field.arguments) {
serialiseArguments(
field.arguments,
`${key}.arguments`,
depth + 1,
callback
);
}
};
// TODO: this only parses the remote relationship in old format, and the type `RemoteRelationship` is old format
// we should extend this for both old & new format once the server work is done, and remove this comment
export const parseServerRelationship = (
serverRelationship: RemoteRelationship
): RelationshipFields[] => {
const remoteFields = serverRelationship?.definition?.remote_field;
if (!remoteFields || isEmpty(remoteFields)) {
return [];
}
// only query root is supported by server relationships, so we only expect fields under query root
// this could be extended to use mutation and subscription if server support exists in future
const key = '__query';
const depth = 0;
const relationshipFields: RelationshipFields[] = [
{ key, depth, checkable: false, argValue: null, type: 'field' },
];
Object.keys(remoteFields).forEach(rf => {
serialiseRemoteField(
remoteFields[rf],
`${key}.field.${rf}`,
depth + 1,
(field: RelationshipFields) => relationshipFields.push(field)
);
});
return relationshipFields;
};