From ceb0a3de238344d20a2e75dd489fa06709950460 Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Tue, 28 Nov 2023 09:51:26 +0530 Subject: [PATCH] Add CLI tool to compare watcher GQL responses (#494) * Integrate gql response compare tool * Add README for compare-gql tool * Update readme * Update CLI output logs --------- Co-authored-by: neeraj --- packages/cli/README.md | 40 +++++++++ packages/cli/config.yaml | 12 +++ packages/cli/graphql/azimuth-query.graphql | 34 +++++++ .../cli/graphql/non-subgraph-query.graphql | 12 +++ packages/cli/graphql/subgraph-query.graphql | 19 ++++ packages/cli/package.json | 9 +- packages/cli/src/compare-gql.ts | 90 +++++++++++++++++++ yarn.lock | 24 ++++- 8 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 packages/cli/config.yaml create mode 100644 packages/cli/graphql/azimuth-query.graphql create mode 100644 packages/cli/graphql/non-subgraph-query.graphql create mode 100644 packages/cli/graphql/subgraph-query.graphql create mode 100644 packages/cli/src/compare-gql.ts diff --git a/packages/cli/README.md b/packages/cli/README.md index 3818db6d..65ef7168 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -54,3 +54,43 @@ A basic CLI to pass messages between peers using `stdin`/`stdout` * `enable-debug-info`: Whether to broadcast node's info over pubsub on request * The process starts reading from `stdin` and outputs messages from others peers over the `/chat/1.0.0` protocol to `stdout`. + +## compare-gql + +A basic CLI to to fetch and compare watcher GQL response. + +* Create a `config.yaml` file in the following format in `packages/cli`: + + ```yaml + # Watcher URL + url: "" + + # Boolean to specify if it is a subgraph watcher + isSubgraph: false + + # File path for saving GQL result + gqlResultFilepath: "./result.json" + + # Optional parameter to override default GQL query + graphqlQuery: "" + ``` + +* Run the following command to fetch and save first GQL response: + + ``` + yarn compare-gql --config config.yaml + ``` + + * On running CLI for the first time + + ``` + vulcanize:compare-gql Fetching response for the first time, re run CLI to compare with latest GQL response + ``` + +* Re run CLI after some time (average time taken for new block in Ethereum: `~12 seconds`) + + ``` + yarn compare-gql --config config.yaml + ``` + +* The diff for latest and previous GQL responses is shown diff --git a/packages/cli/config.yaml b/packages/cli/config.yaml new file mode 100644 index 00000000..e0a3141c --- /dev/null +++ b/packages/cli/config.yaml @@ -0,0 +1,12 @@ +# Watcher URL +url: "" + +# Boolean to specify if it is a subgraph watcher +isSubgraph: false + +# File path for saving GQL result +gqlResultFilepath: "./result.json" + +# Optional parameter to override default GQL query +# Set GQL filepath containing query +graphqlQuery: "" diff --git a/packages/cli/graphql/azimuth-query.graphql b/packages/cli/graphql/azimuth-query.graphql new file mode 100644 index 00000000..b2046b53 --- /dev/null +++ b/packages/cli/graphql/azimuth-query.graphql @@ -0,0 +1,34 @@ +{ + azimuthGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + claimsGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + censuresGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + conditionalStarReleaseGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + delegatedSendingGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + eclipticGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + linearStarReleaseGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } + pollsGetSyncStatus { + latestProcessedBlockNumber + latestIndexedBlockNumber + } +} diff --git a/packages/cli/graphql/non-subgraph-query.graphql b/packages/cli/graphql/non-subgraph-query.graphql new file mode 100644 index 00000000..d5b32b5d --- /dev/null +++ b/packages/cli/graphql/non-subgraph-query.graphql @@ -0,0 +1,12 @@ +{ + getSyncStatus { + initialIndexedBlockHash + initialIndexedBlockNumber + latestCanonicalBlockHash + latestIndexedBlockHash + latestCanonicalBlockNumber + latestIndexedBlockNumber + latestProcessedBlockHash + latestProcessedBlockNumber + } +} diff --git a/packages/cli/graphql/subgraph-query.graphql b/packages/cli/graphql/subgraph-query.graphql new file mode 100644 index 00000000..65b3521a --- /dev/null +++ b/packages/cli/graphql/subgraph-query.graphql @@ -0,0 +1,19 @@ +{ + getSyncStatus { + initialIndexedBlockHash + initialIndexedBlockNumber + latestCanonicalBlockHash + latestIndexedBlockHash + latestCanonicalBlockNumber + latestIndexedBlockNumber + latestProcessedBlockHash + latestProcessedBlockNumber + } + _meta { + block { + hash + number + timestamp + } + } +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 7d74c812..908f0eea 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -8,7 +8,8 @@ "build": "yarn clean && tsc && yarn copy-assets", "clean": "rm -rf ./dist", "copy-assets": "copyfiles -u 1 src/**/*.gql dist/", - "chat": "DEBUG='vulcanize:*, laconic:*' node dist/chat.js" + "chat": "DEBUG='vulcanize:*, laconic:*' node dist/chat.js", + "compare-gql": "DEBUG='vulcanize:*' node dist/compare-gql.js" }, "dependencies": { "@apollo/client": "^3.7.1", @@ -27,7 +28,11 @@ "debug": "^4.3.1", "ethers": "^5.4.4", "express": "^4.18.2", + "graphql": "^15.5.0", + "graphql-request": "^6.1.0", "graphql-subscriptions": "^2.0.0", + "js-yaml": "^4.0.0", + "json-diff": "^0.5.4", "lodash": "^4.17.21", "pluralize": "^8.0.0", "reflect-metadata": "^0.1.13", @@ -36,6 +41,8 @@ }, "devDependencies": { "@types/express": "^4.17.14", + "@types/js-yaml": "^4.0.3", + "@types/json-diff": "^0.5.2", "@types/node": "16.11.7", "@types/pluralize": "^0.0.29", "@types/yargs": "^17.0.0", diff --git a/packages/cli/src/compare-gql.ts b/packages/cli/src/compare-gql.ts new file mode 100644 index 00000000..41ac9a37 --- /dev/null +++ b/packages/cli/src/compare-gql.ts @@ -0,0 +1,90 @@ +import { request } from 'graphql-request'; +import * as fs from 'fs'; +import jsonDiff from 'json-diff'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; +import path from 'path'; +import yaml from 'js-yaml'; +import debug from 'debug'; + +const SUBGRAPH_QUERY_FILEPATH = 'graphql/subgraph-query.graphql'; +const NON_SUBGRAPH_QUERY_FILEPATH = 'graphql/non-subgraph-query.graphql'; + +const log = debug('vulcanize:compare-gql'); + +function readFromJSONFile (filename: string): {[key: string]: any} | null { + const fileContents = fs.readFileSync(filename, 'utf-8'); + + if (fileContents !== '') { + return JSON.parse(fileContents); + } + + return null; +} + +function getQuery (isSubgraph: boolean, queryFilepath?: string): any { + if (queryFilepath) { + return fs.readFileSync(queryFilepath, 'utf-8'); + } + + if (isSubgraph) { + return fs.readFileSync(SUBGRAPH_QUERY_FILEPATH, 'utf-8'); + } + + return fs.readFileSync(NON_SUBGRAPH_QUERY_FILEPATH, 'utf-8'); +} + +async function main (): Promise { + const argv = await yargs(hideBin(process.argv)) + .option('config', { + alias: 'c', + demandOption: true, + describe: 'Watcher config file path (yaml)', + type: 'string' + }).argv; + + const configFilePath = path.resolve(argv.config); + const inputConfig = yaml.load(fs.readFileSync(configFilePath, 'utf8')) as any; + + log('Config:', inputConfig); + + const isSubgraph = inputConfig.isSubgraph; + const watcherUrl = inputConfig.url; + const configFileDirectory = path.dirname(configFilePath); + const gqlResultFilepath = path.resolve(configFileDirectory, inputConfig.gqlResultFilepath); + const graphqlQueryFilepath = inputConfig.graphqlQuery ? path.resolve(configFileDirectory, inputConfig.graphqlQuery) : undefined; + + if (!fs.existsSync(gqlResultFilepath)) { + fs.writeFileSync(gqlResultFilepath, '', 'utf-8'); + } + + const query = getQuery(isSubgraph, graphqlQueryFilepath); + + let gqlResponse; + + try { + gqlResponse = await request(watcherUrl, query); + } catch (err) { + throw new Error('Error in GQL request: ' + (err as Error).message); + } + + const readOutputData = readFromJSONFile(gqlResultFilepath); + + if (readOutputData !== null) { + const diff = jsonDiff.diffString(readOutputData, gqlResponse); + + if (diff !== '') { + log('Diff detected', diff); + } else { + log('No diff detected, GQL response', gqlResponse); + } + } else { + log('Fetching response for the first time, re run CLI to compare with latest GQL response'); + } + + fs.writeFileSync(gqlResultFilepath, JSON.stringify(gqlResponse, null, 2)); +} + +main().catch(err => { + log(err); +}); diff --git a/yarn.lock b/yarn.lock index eef09dea..71842345 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1226,7 +1226,7 @@ "@graphql-typed-document-node/core" "^3.1.1" tslib "^2.4.0" -"@graphql-typed-document-node/core@^3.1.1": +"@graphql-typed-document-node/core@^3.1.1", "@graphql-typed-document-node/core@^3.2.0": version "3.2.0" resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== @@ -6512,6 +6512,13 @@ cross-fetch@^3.1.4: dependencies: node-fetch "2.6.7" +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" @@ -9021,6 +9028,14 @@ graphql-compose@^9.0.3: dependencies: graphql-type-json "0.3.2" +graphql-request@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f" + integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw== + dependencies: + "@graphql-typed-document-node/core" "^3.2.0" + cross-fetch "^3.1.5" + graphql-subscriptions@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-2.0.0.tgz" @@ -12223,6 +12238,13 @@ node-fetch@^2.6.1: resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@~1.7.1: version "1.7.3" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz"