├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── appveyor.yml ├── assets ├── bash_trigger.gif ├── graphiql.gif ├── simple-query.gif └── subscription.gif ├── azure-pipelines.yml ├── bin ├── run └── run.cmd ├── example └── index.js ├── package-lock.json ├── package.json ├── src ├── callbacks.js ├── client.js ├── command.js ├── error.js ├── events.js ├── graphiql │ ├── app │ │ ├── .babelrc │ │ ├── .bootstraprc │ │ ├── .eslintignore │ │ ├── .eslintrc │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── README.md │ │ ├── appconfig.js │ │ ├── bin │ │ │ ├── server.babel.js │ │ │ └── server.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ ├── client.js │ │ │ ├── components │ │ │ │ ├── 404 │ │ │ │ │ ├── 404-logo.png │ │ │ │ │ ├── PageNotFound.js │ │ │ │ │ └── Styles.scss │ │ │ │ ├── ApiExplorer │ │ │ │ │ ├── Actions.js │ │ │ │ │ ├── ApiExplorer.js │ │ │ │ │ ├── ApiExplorer.scss │ │ │ │ │ ├── ApiExplorerGenerator.js │ │ │ │ │ ├── ApiRequest.js │ │ │ │ │ ├── ApiRequestDetails.js │ │ │ │ │ ├── ApiRequestWrapper.js │ │ │ │ │ ├── Dropdown.svg │ │ │ │ │ ├── ErrorBoundary.js │ │ │ │ │ ├── GraphiQL.css │ │ │ │ │ ├── GraphiQLWrapper │ │ │ │ │ │ ├── GraphiQL.css │ │ │ │ │ │ ├── GraphiQLErrorBoundary.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── utils.js │ │ │ │ │ ├── OneGraphExplorer │ │ │ │ │ │ ├── OneGraphExplorer.css │ │ │ │ │ │ ├── OneGraphExplorer.js │ │ │ │ │ │ └── utils.js │ │ │ │ │ ├── chevron.svg │ │ │ │ │ ├── state.js │ │ │ │ │ ├── tick.png │ │ │ │ │ └── utils.js │ │ │ │ ├── App │ │ │ │ │ ├── Actions.js │ │ │ │ │ ├── App.js │ │ │ │ │ ├── ErrorBoundary.js │ │ │ │ │ ├── JSONFormat.js │ │ │ │ │ ├── State.js │ │ │ │ │ └── progress-bar.scss │ │ │ │ ├── AppState.js │ │ │ │ ├── Common │ │ │ │ │ ├── BreadCrumb │ │ │ │ │ │ ├── BreadCrumb.js │ │ │ │ │ │ └── BreadCrumb.scss │ │ │ │ │ ├── CSManage.scss │ │ │ │ │ ├── Common.scss │ │ │ │ │ ├── Spinner │ │ │ │ │ │ ├── Spinner.js │ │ │ │ │ │ └── Spinner.scss │ │ │ │ │ ├── generateSuggestionBox.js │ │ │ │ │ └── makeRequest.js │ │ │ │ ├── QueryBuilderJson │ │ │ │ │ ├── QueryBuilderJson.js │ │ │ │ │ └── Styles.scss │ │ │ │ └── index.js │ │ │ ├── config.js │ │ │ ├── helpers │ │ │ │ ├── Html.js │ │ │ │ ├── getStatusFromRoutes.js │ │ │ │ └── makeRouteHooksSafe.js │ │ │ ├── reducer.js │ │ │ ├── routes.js │ │ │ ├── server.js │ │ │ ├── theme │ │ │ │ ├── bootstrap.config.js │ │ │ │ ├── bootstrap.config.prod.js │ │ │ │ ├── bootstrap.overrides.scss │ │ │ │ ├── font-awesome.config.js │ │ │ │ ├── font-awesome.config.less │ │ │ │ ├── font-awesome.config.prod.js │ │ │ │ └── variables.scss │ │ │ └── utils │ │ │ │ ├── data.js │ │ │ │ ├── exceptions.js │ │ │ │ ├── localstorage.js │ │ │ │ ├── requestAction.js │ │ │ │ ├── requestActionPlain.js │ │ │ │ ├── router.js │ │ │ │ ├── string.js │ │ │ │ ├── validation.js │ │ │ │ └── websockets.js │ │ ├── static │ │ │ ├── favicon.ico │ │ │ ├── favicon.png │ │ │ ├── index.html │ │ │ └── logo.jpg │ │ └── webpack │ │ │ ├── bs3.yml │ │ │ ├── dev.config.js │ │ │ ├── prod.config.js │ │ │ ├── webpack-dev-server.js │ │ │ └── webpack-isomorphic-tools.js │ └── server.js ├── index.js ├── query.js ├── ui.js └── utils.js └── test ├── clearstate.test.js ├── index.test.js ├── mutation.test.js ├── query.test.js └── subscription.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/graphiql/app 2 | lib 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "oclif", 3 | "rules": { 4 | "max-params": "off", 5 | "no-console": "off", 6 | "max-depth": "off", 7 | "one-var": "off", 8 | "complexity": "off", 9 | "unicorn/no-process-exit": "off", 10 | "unicorn/filename-case": "off", 11 | "no-process-exit": "off", 12 | "no-throw-literal": "off", 13 | "node/no-unsupported-features": "off", 14 | "no-warning-comments": "off", 15 | "semi": [1, "always"], 16 | "no-await-in-loop": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-debug.log 2 | *-error.log 3 | /.nyc_output 4 | /dist 5 | lib 6 | /tmp 7 | /yarn.lock 8 | node_modules 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Graphqurl Changelog 2 | 3 | ## v1.0.1 4 | 5 | ### Remove usage of Apollo client 6 | 7 | With v1.0, graphqurl removed usage of Apollo Client and instead makes use of light-weight isomorphic HTTP clients which reduced the bundle size from 142 kB to 58 kB, a 56% size reduction. 8 | 9 | ### GraphiQL Improvements 10 | 11 | The custom graphiQL now supports graphiQL explorer and graphQL code explorer. 12 | 13 | ### Improved Scripting API 14 | 15 | GraphQL queries are no longer parsed before execution. For usage as a node library, v1.0 onwards, a client needs to be created before executing GraphQL operations. You can find a sample script in the [example](example/index.js) directory. 16 | 17 | ### CLI changes 18 | * Deprecates flag `--graphiqlAddress` in favour of the new flag `--graphiqlHost`. 19 | * Support for multiple queries in files and specify which query to execute through the newly added flag `--operationName`. 20 | 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at build@hasura.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to graphqurl 2 | 3 | *First*: if you're unsure or afraid of anything, just ask or submit the issue or 4 | pull request anyway. You won't be yelled at for giving your best effort. The 5 | worst that can happen is that you'll be politely asked to change something. We 6 | appreciate any sort of contributions, and don't want a wall of rules to get in 7 | the way of that. 8 | 9 | However, for those individuals who want a bit more guidance on the best way to 10 | contribute to the project, read on. This document will cover what we're looking 11 | for. By addressing all the points we're looking for, it raises the chances we 12 | can quickly merge or address your contributions. 13 | 14 | ## Issues 15 | 16 | ### Reporting an Issue 17 | 18 | - Make sure you test against the latest released version. It is possible we 19 | already fixed the bug you're experiencing. 20 | 21 | - Provide steps to reproduce the issue. 22 | 23 | - Please include logs, if relevant. 24 | 25 | ## Common guidelines 26 | 27 | - Please make sure there is an issue associated with the work that you're doing. 28 | 29 | - If you're working on an issue, please comment that you are doing so to prevent 30 | duplicate work by others also. 31 | 32 | - Squash your commits and refer to the issue using `fix #` or `close 33 | #` in the commit message, at the end. 34 | For example: `resolve answers to everything (fix #42)` or `resolve answers to everything, fix #42` 35 | 36 | - Rebase master with your branch before submitting pull request. 37 | 38 | 39 | ## Testing 40 | 41 | - Please make sure that your code is linted as per the rules in the project. To run an ESLint check, run `npm run eslint` from the root of the project. 42 | - To run the tests locally, you will need to have a [Hasura GraphQL engine](https://github.com/hasura/graphql-engine) URL. You can run the tests locally by passing the GraphQL Engine URL and the access key to the `npm test` command. 43 | 44 | ```bash 45 | $ GRAPHQURL_TEST_GRAPHQL_ENGINE_URL= \ 46 | GRAPHQURL_TEST_X_HASURA_ACCESS_KEY= \ 47 | npm test 48 | ``` 49 | 50 | > Tests will be run on the CI for every pull request. 51 | 52 | ## Commit messages 53 | 54 | - The first line should be a summary of the changes - not execeeding 50 55 | characters. Followed by an optional body which has more details about the 56 | changes. (https://github.com/erlang/otp/wiki/writing-good-commit-messages) 57 | 58 | - Use the imperative present tense: "add/fix/change", not "added/fixed/changed" nor "adds/fixes/changes". 59 | 60 | - Don't capitalize the first letter of the summary line. 61 | 62 | - Do not add a period/dot (.) at the end in the summary line. 63 | 64 | 65 | (Credits: Some sections are adapted from https://github.com/PostgREST/postgrest/blob/master/.github/CONTRIBUTING.md) 66 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "16" 3 | cache: 4 | - '%AppData%\npm-cache -> appveyor.yml' 5 | - node_modules -> package-lock.json 6 | 7 | install: 8 | - ps: Install-Product node $env:nodejs_version x64 9 | - npm install 10 | test_script: 11 | - .\bin\run --version 12 | - .\bin\run --help 13 | # - npm test 14 | #after_test: 15 | # - .\node_modules\.bin\nyc report --reporter text-lcov > coverage.lcov 16 | # - ps: | 17 | # $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH 18 | # Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 19 | # bash codecov.sh 20 | 21 | build: off 22 | 23 | -------------------------------------------------------------------------------- /assets/bash_trigger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasura/graphqurl/1d142d21a78f5d4e139840075d9d3e5a425b98dd/assets/bash_trigger.gif -------------------------------------------------------------------------------- /assets/graphiql.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasura/graphqurl/1d142d21a78f5d4e139840075d9d3e5a425b98dd/assets/graphiql.gif -------------------------------------------------------------------------------- /assets/simple-query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasura/graphqurl/1d142d21a78f5d4e139840075d9d3e5a425b98dd/assets/simple-query.gif -------------------------------------------------------------------------------- /assets/subscription.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasura/graphqurl/1d142d21a78f5d4e139840075d9d3e5a425b98dd/assets/subscription.gif -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js 2 | # Build a general Node.js project with npm. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | resources: 6 | containers: 7 | - container: nodejs 8 | image: node:16 9 | - container: postgres 10 | image: postgres:10-alpine 11 | env: 12 | POSTGRES_USER: gql_test 13 | POSTGRES_DB: gql_test 14 | - container: hasura 15 | image: hasura/graphql-engine:latest 16 | env: 17 | HASURA_GRAPHQL_DATABASE_URL: postgres://gql_test:@postgres:5432/gql_test 18 | HASURA_GRAPHQL_ACCESS_KEY: verysecretaccesskey 19 | 20 | services: 21 | postgres: postgres 22 | hasura: hasura 23 | 24 | trigger: 25 | - master 26 | 27 | pool: 28 | vmImage: 'Ubuntu-20.04' 29 | 30 | container: nodejs 31 | 32 | steps: 33 | - script: | 34 | export GRAPHQURL_TEST_GRAPHQL_ENGINE_URL=http://hasura:8080 35 | export GRAPHQURL_TEST_X_HASURA_ACCESS_KEY=verysecretaccesskey 36 | npm install 37 | npm test 38 | 39 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../src/command').run() 4 | .catch(require('@oclif/errors/handle')) 5 | -------------------------------------------------------------------------------- /bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* 4 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | const {createClient} = require('..'); 2 | 3 | const client = createClient({ 4 | endpoint: 'https://graphqurl-demo.hasura.app/v1/graphql', 5 | }); 6 | 7 | console.dir("Executing query 'query { menu_items { name } }': "); 8 | client.query( 9 | { 10 | query: 'query { menu_items { name } }', 11 | }, 12 | ).then(response => console.log(JSON.stringify(response))) 13 | .catch(error => console.error(error)); 14 | 15 | console.dir("Executing mutation: 'mutation {delete_menu_items(where: {name: {_eq: \"pasta\"}}){ returning { name }}}'"); 16 | client.query( 17 | { 18 | query: 'mutation ($name: String) {delete_menu_items(where: {name: {_eq: $name}}) {returning {name}}}', 19 | variables: {name: 'pizza'}, 20 | }, 21 | ).then(() => console.log('Successfully executed delete mutation.')) 22 | .catch(error => console.error(error)); 23 | 24 | console.dir("Executing insert mutation: 'mutation {insert_menu_items(objects: {name: \"pasta\",}) {returning {name}}}'"); 25 | client.query( 26 | { 27 | query: 'mutation ($name: String) {insert_menu_items(objects: {name: $name}) {returning {name}}}', 28 | variables: { 29 | name: 'pasta', 30 | }, 31 | }, 32 | ).then(() => console.log('Successfully executed insert mutation')) 33 | .catch(error => console.error(error)); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqurl", 3 | "description": "cURL for GraphQL - CLI and JS library for making GraphQL queries", 4 | "version": "2.0.0", 5 | "author": "Hasura", 6 | "bin": { 7 | "gq": "./bin/run", 8 | "graphqurl": "./bin/run" 9 | }, 10 | "bugs": "https://github.com/hasura/graphqurl/issues", 11 | "dependencies": { 12 | "@oclif/command": "1.8.0", 13 | "@oclif/config": "1.17.0", 14 | "@oclif/errors": "1.3.4", 15 | "@oclif/plugin-help": "^3.3.1", 16 | "cli-ux": "^4.7.3", 17 | "express": "^4.19.2", 18 | "graphql": "15.5.0", 19 | "graphql-language-service": "^5.3.0", 20 | "isomorphic-ws": "4.0.1", 21 | "open": "7.3.1", 22 | "terminal-kit": "^3.1.1", 23 | "ws": "^7.5.9" 24 | }, 25 | "devDependencies": { 26 | "@vercel/ncc": "^0.38.3", 27 | "eslint": "^7.26.0", 28 | "eslint-config-oclif": "^1.5.1" 29 | }, 30 | "engines": { 31 | "node": ">=18.0.0" 32 | }, 33 | "files": [ 34 | "/bin", 35 | "/src" 36 | ], 37 | "homepage": "https://github.com/hasura/graphqurl", 38 | "keywords": [ 39 | "oclif", 40 | "cli", 41 | "graphql", 42 | "autocomplete" 43 | ], 44 | "license": "Apache-2.0", 45 | "main": "src/index.js", 46 | "oclif": { 47 | "bin": "gq" 48 | }, 49 | "repository": "hasura/graphqurl", 50 | "scripts": { 51 | "eslint": "eslint .", 52 | "eslintfix": "eslint . --fix", 53 | "posttest": "npm run eslint", 54 | "prep": "npm run bundle", 55 | "prelink": "npm run prep", 56 | "launch": "npm run prep && npm publish", 57 | "clearteststate": "node test/clearstate.test.js", 58 | "test": "node test/index.test.js && npm run clearteststate", 59 | "bundle": "ncc build src/index.js -o lib" 60 | }, 61 | "pre-commit": [ 62 | "eslintfix" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /src/callbacks.js: -------------------------------------------------------------------------------- 1 | const {cli} = require('cli-ux'); 2 | const {handleGraphQLError, handleServerError} = require('./error.js'); 3 | const {buildClientSchema, printSchema} = require('graphql/utilities'); 4 | 5 | const querySuccessCb = (ctx, response, queryType) => { 6 | let out = ctx.flags.singleLine ? JSON.stringify({data: response.data}) : JSON.stringify({data: response.data}, null, 2); 7 | if (queryType === 'subscription') { 8 | cli.action.stop('event received'); 9 | ctx.log(out); 10 | cli.action.start('Waiting'); 11 | } else { 12 | if (ctx.flags.introspect) { 13 | if (ctx.flags.format === 'graphql') { 14 | const schema = buildClientSchema(response.data); 15 | out = printSchema(schema); 16 | } else { 17 | out = ctx.flags.singleLine ? JSON.stringify(response.data) : JSON.stringify(response.data, null, 2); 18 | } 19 | } 20 | cli.action.stop('done'); 21 | ctx.log(out); 22 | } 23 | }; 24 | 25 | const queryErrorCb = (ctx, queryError, queryType) => { 26 | cli.action.stop('error'); 27 | if (!queryType) { 28 | handleGraphQLError(queryError); 29 | } else if (queryType === 'subscription') { 30 | if (queryError.originalError) { 31 | const {code, path, error} = queryError.originalError; 32 | handleServerError(`[${code}] at [${path}]: ${error}`); 33 | } 34 | } else { 35 | handleServerError(queryError); 36 | } 37 | }; 38 | 39 | module.exports = { 40 | querySuccessCb, 41 | queryErrorCb, 42 | }; 43 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | const {cloneObject, wsScheme} = require('./utils'); 2 | const WebSocket = require('isomorphic-ws'); 3 | const { 4 | GQL_CONNECTION_INIT, 5 | GQL_START, 6 | GQL_STOP, 7 | GRAPHQL_SUBSCRIPTIONS_PROTOCOL, 8 | handler: wsEventHandler, 9 | } = require('./events'); 10 | 11 | const makeClient = options => { 12 | const { 13 | endpoint, 14 | websocket, 15 | headers, 16 | hook, 17 | } = options; 18 | 19 | const clientContext = { 20 | endpoint, 21 | headers: cloneObject(headers || {}), 22 | websocket: { 23 | ...websocket, 24 | endpoint: (websocket && websocket.endpoint) || wsScheme(endpoint), 25 | parameters: (websocket && websocket.parameters) || {}, 26 | client: null, 27 | open: false, 28 | subscriptions: {}, 29 | }, 30 | }; 31 | 32 | const executeQuery = async (queryOptions, successCallback, errorCallback) => { 33 | const { 34 | query, 35 | variables, 36 | headers: headerOverrides, 37 | } = queryOptions; 38 | 39 | const headers = { 40 | ...clientContext.headers, 41 | ...(headerOverrides || {}), 42 | }; 43 | const isExistContentTypeKey = Object.keys(headers).some(key => /content-type/gi.test(key)); 44 | if (!isExistContentTypeKey) { 45 | headers['Content-Type'] = 'application/json'; 46 | } 47 | 48 | try { 49 | const response = await fetch( 50 | clientContext.endpoint, 51 | { 52 | method: 'POST', 53 | headers, 54 | body: JSON.stringify({query, variables: (variables || {})}), 55 | }, 56 | ); 57 | const responseObj = await response.json(); 58 | if (hook) { 59 | hook(responseObj); 60 | } 61 | if (responseObj.errors) { 62 | if (errorCallback) { 63 | errorCallback(responseObj); 64 | } 65 | throw responseObj; 66 | } else { 67 | if (successCallback) { 68 | successCallback(responseObj); 69 | } 70 | return responseObj; 71 | } 72 | } catch (e) { 73 | if (e.errors) { 74 | throw e; 75 | } else { 76 | throw { 77 | errors: [{ 78 | message: 'failed to fetch', 79 | }], 80 | }; 81 | } 82 | } 83 | }; 84 | 85 | const makeWsClient = async () => { 86 | try { 87 | const wsConnection = new WebSocket(clientContext.websocket.endpoint, GRAPHQL_SUBSCRIPTIONS_PROTOCOL); 88 | return wsConnection; 89 | } catch (e) { 90 | console.log(e); 91 | throw new Error('Failed to establish the WebSocket connection: ', e); 92 | } 93 | }; 94 | 95 | const sendWsEvent = data => { 96 | clientContext.websocket.client.send(JSON.stringify(data)); 97 | }; 98 | 99 | const setWsClient = _wsClient => { 100 | clientContext.websocket.client = _wsClient; 101 | 102 | if (clientContext.websocket.shouldRetry) { 103 | _wsClient.onclose = () => { 104 | makeWsClient().then(setWsClient); 105 | }; 106 | } 107 | 108 | _wsClient.addEventListener('open', () => { 109 | const payload = { 110 | ...clientContext.websocket.parameters, 111 | headers: { 112 | ...clientContext.headers, 113 | ...clientContext.websocket.parameters.headers, 114 | }, 115 | }; 116 | sendWsEvent({ 117 | type: GQL_CONNECTION_INIT, 118 | payload, 119 | }); 120 | }); 121 | 122 | _wsClient.addEventListener('message', event => { 123 | wsEventHandler(clientContext.websocket, event); 124 | }); 125 | }; 126 | if (websocket) { 127 | makeWsClient().then(setWsClient).catch(e => { 128 | console.error(e); 129 | }); 130 | } 131 | 132 | const subscribe = (subscriptionOptions, successCallback, errorCallback) => { 133 | if (!clientContext.websocket.client) { 134 | console.log('WebSocket connection has not been established'); 135 | return; 136 | } 137 | 138 | const { 139 | subscription, 140 | variables, 141 | onGraphQLData, 142 | onGraphQLError, 143 | onGraphQLComplete, 144 | } = subscriptionOptions; 145 | 146 | const generateOperationId = () => { 147 | let id = ''; 148 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 149 | for (let _i = 0; _i < 5; _i++) { 150 | id += characters.charAt(Math.floor(Math.random() * characters.length)); 151 | } 152 | return id + (Object.keys(clientContext.websocket.subscriptions).length + 1); 153 | }; 154 | 155 | const operationId = generateOperationId(); 156 | clientContext.websocket.subscriptions[operationId] = { 157 | onGraphQLData: data => { 158 | if (onGraphQLData) { 159 | onGraphQLData(data); 160 | } 161 | if (successCallback) { 162 | successCallback(data); 163 | } 164 | }, 165 | onGraphQLComplete, 166 | onGraphQLError: data => { 167 | if (onGraphQLError) { 168 | onGraphQLError(data); 169 | } 170 | if (errorCallback) { 171 | errorCallback(data); 172 | } 173 | }, 174 | }; 175 | 176 | sendWsEvent({ 177 | type: GQL_START, 178 | id: operationId, 179 | payload: { 180 | query: subscription, 181 | variables: variables || {}, 182 | }, 183 | }); 184 | 185 | return { 186 | stop: () => { 187 | sendWsEvent({ 188 | type: GQL_STOP, 189 | id: operationId, 190 | }); 191 | }, 192 | }; 193 | }; 194 | 195 | const updateHeaders = newHeaders => { 196 | clientContext.headers = cloneObject(newHeaders); 197 | if (clientContext.websocket.client) { 198 | makeWsClient().then(setWsClient).catch(e => { 199 | console.error(e); 200 | }); 201 | } 202 | }; 203 | 204 | return { 205 | query: executeQuery, 206 | subscribe: subscribe, 207 | updateHeaders, 208 | }; 209 | }; 210 | 211 | module.exports = makeClient; 212 | -------------------------------------------------------------------------------- /src/command.js: -------------------------------------------------------------------------------- 1 | const {Command, flags} = require('@oclif/command'); 2 | const {CLIError} = require('@oclif/errors'); 3 | const url = require('url'); 4 | const fs = require('fs'); 5 | const util = require('util'); 6 | const {querySuccessCb, queryErrorCb} = require('./callbacks.js'); 7 | const executeQueryFromTerminalUI = require('./ui'); 8 | // const runGraphiQL = require('./graphiql/server'); 9 | const {getIntrospectionQuery} = require('graphql/utilities'); 10 | const {cli} = require('cli-ux'); 11 | const {getOperationFromQueryFileContent} = require('./utils'); 12 | const query = require('./query.js'); 13 | 14 | // Convert fs.readFile into Promise version of same 15 | const readFile = util.promisify(fs.readFile); 16 | 17 | class GraphqurlCommand extends Command { 18 | async run() { 19 | const {args, flags} = this.parse(GraphqurlCommand); 20 | const headers = this.parseHeaders(flags.header); 21 | const endpoint = this.getEndpoint(args); 22 | let queryString = await this.getQueryString(args, flags); 23 | const variables = await this.getQueryVariables(args, flags); 24 | 25 | if (endpoint === null) { 26 | throw new CLIError('endpoint is required: `gq `'); 27 | } 28 | 29 | const parsedEndpoint = url.parse(endpoint); 30 | if (!(parsedEndpoint.protocol && parsedEndpoint.host)) { 31 | throw new CLIError('endpoint is not a valid url'); 32 | } 33 | 34 | /* 35 | if (flags.graphiql) { 36 | runGraphiQL(endpoint, queryString, headers, variables, flags.graphiqlHost, flags.graphiqlPort); 37 | return; 38 | } 39 | */ 40 | 41 | if (flags.introspect) { 42 | queryString = getIntrospectionQuery(); 43 | } 44 | 45 | this.args = args; 46 | this.flags = flags; 47 | 48 | const successCallback = (response, queryType, parsedQuery) => { 49 | querySuccessCb(this, response, queryType, parsedQuery, endpoint); 50 | }; 51 | const errorCallback = (error, queryType, parsedQuery) => { 52 | queryErrorCb(this, error, queryType, parsedQuery); 53 | }; 54 | 55 | if (queryString === null) { 56 | try { 57 | queryString = await executeQueryFromTerminalUI({ 58 | endpoint: endpoint, 59 | headers, 60 | variables, 61 | name: flags.name, 62 | }, successCallback, errorCallback); 63 | } catch (e) { 64 | this.error(e); 65 | } 66 | } 67 | 68 | const queryOptions = { 69 | query: queryString, 70 | endpoint: endpoint, 71 | headers, 72 | variables, 73 | name: flags.name, 74 | }; 75 | 76 | cli.action.start('Executing query'); 77 | await query(queryOptions, successCallback, errorCallback); 78 | } 79 | 80 | parseHeaders(headersArray) { 81 | let headerObject = {}; 82 | if (headersArray) { 83 | for (let h of headersArray) { 84 | const parts = h.split(':'); 85 | if (parts.length < 2) { 86 | this.error(`cannot parse header '${h}' (no ':')`); 87 | } 88 | const headerName = parts.splice(0, 1)[0].trim(); 89 | headerObject[headerName] = parts.join(':').trimLeft(); 90 | } 91 | } 92 | return headerObject; 93 | } 94 | 95 | getEndpoint(args) { 96 | if (args.endpoint) { 97 | return args.endpoint; 98 | } 99 | if (process.env.GRAPHQURL_ENDPOINT) { 100 | return process.env.GRAPHQURL_ENDPOINT; 101 | } 102 | return null; 103 | } 104 | 105 | async getQueryString(args, flags) { 106 | if (flags.queryFile) { 107 | const fileContent = await readFile(flags.queryFile, 'utf8'); 108 | try { 109 | const operationString = getOperationFromQueryFileContent(fileContent, flags.operationName); 110 | return operationString; 111 | } catch (e) { 112 | this.error(e.message); 113 | } 114 | } 115 | if (flags.query) { 116 | return flags.query; 117 | } 118 | return null; 119 | } 120 | 121 | async getQueryVariables(args, flags) { 122 | let possibleFlags = [ 123 | flags.variable, 124 | flags.variablesFile, 125 | flags.variablesJSON, 126 | ]; 127 | let flagsCount = 0; 128 | for (const f of possibleFlags) { 129 | if (f) { 130 | flagsCount += 1; 131 | } 132 | } 133 | if (flagsCount > 1) { 134 | this.error('cannot use flags --variable, --variablesFile, --variablesJSON together'); 135 | } 136 | let variablesObject = {}; 137 | if (flags.variablesJSON) { 138 | try { 139 | variablesObject = JSON.parse(flags.variablesJSON); 140 | } catch (err) { 141 | this.error(`error parsing --variablesJSON: ${err}`); 142 | } 143 | } 144 | if (flags.variablesFile) { 145 | try { 146 | variablesObject = JSON.parse(await readFile(flags.variablesFile)); 147 | } catch (err) { 148 | this.error(`error reading and parsing --variablesFile: ${err}`); 149 | } 150 | } 151 | if (flags.variable) { 152 | for (let v of flags.variable) { 153 | const parts = v.split('='); 154 | if (parts.length !== 2) { 155 | this.error(`cannot parse variable '${v} (multiple '=')`); 156 | } 157 | var val = parts[1].trim(); 158 | try { 159 | val = JSON.parse(val); 160 | } catch (err) { 161 | // cannot parse as JSON. do nothing, proceed with raw value 162 | } 163 | variablesObject[parts[0].trim()] = val; 164 | } 165 | } 166 | return variablesObject; 167 | } 168 | } 169 | 170 | GraphqurlCommand.description = `GraphQURL: cURL for GraphQL 171 | • Execute GraphQL queries from terminal 172 | • Supports queries, mutations, subscriptions, with headers and variables 173 | • Auto-complete queries on the CLI 174 | 175 | # Examples: 176 | 177 | # Make a simple query 178 | gq https://my-graphql-endpoint -q 'query { table { column } }' 179 | 180 | # Make a query with CLI auto complete (this will show a gql prompt) 181 | gq https://my-graphql-endpoint 182 | 183 | # Add a custom header 184 | gq https://my-graphql-endpoint \\ 185 | -H 'Authorization: token token-value' \\ 186 | -q 'query { table { column } }' 187 | 188 | # Execute a mutation with variables 189 | gq https://my-graphql-endpoint \\ 190 | -q 'mutation { insert_table(objects:[{ column: $var }]) { returning { column } } }' \\ 191 | -v 'var=abcd' 192 | 193 | # Execute a live query (prints out each event data to stdout) 194 | gq https://my-graphql-endpoint \\ 195 | -q 'subscription { table { column } }' 196 | 197 | # Execute a live query (print each event line by line) 198 | gq https://my-graphql-endpoint \\ 199 | -l -q 'subscription { table { column } }' 200 | 201 | # Export GraphQL schema from an endpoint 202 | gq https://my-graphql-endpoint --introspect > schema.gql 203 | `; 204 | 205 | GraphqurlCommand.usage = 'ENDPOINT [-q QUERY]'; 206 | 207 | GraphqurlCommand.flags = { 208 | // add --version flag to show CLI version 209 | version: flags.version(), 210 | 211 | // add --help flag to show CLI version 212 | help: flags.help({char: 'h'}), 213 | 214 | // query for graphql 215 | query: flags.string({ 216 | char: 'q', 217 | description: 'graphql query to execute', 218 | }), 219 | 220 | // headers, comma separated if they are many 221 | header: flags.string({ 222 | char: 'H', 223 | description: 'request header', 224 | multiple: true, 225 | }), 226 | 227 | // variables for the query 228 | variable: flags.string({ 229 | char: 'v', 230 | description: 'query variables as key=value', 231 | multiple: true, 232 | }), 233 | 234 | // variables for the query as JSON 235 | variablesJSON: flags.string({ 236 | char: 'j', 237 | description: 'query variables as JSON string', 238 | multiple: false, 239 | }), 240 | 241 | // file to read query from 242 | queryFile: flags.string({ 243 | description: 'file to read the query from', 244 | }), 245 | operationName: flags.string({ 246 | description: 'name of the operation to execute from the query file', 247 | }), 248 | 249 | // file to read variables from 250 | variablesFile: flags.string({ 251 | description: 'JSON file to read the query variables from', 252 | }), 253 | 254 | // run graphiql 255 | /* 256 | graphiql: flags.boolean({ 257 | default: false, 258 | char: 'i', 259 | description: 'open graphiql with the given endpoint, headers, query and variables', 260 | }), 261 | 262 | // specify host to run graphiql at 263 | graphiqlHost: flags.string({ 264 | char: 'a', 265 | default: 'localhost', 266 | description: 'host to use for graphiql', 267 | }), 268 | 269 | // specify port to run graphiql at 270 | graphiqlPort: flags.integer({ 271 | char: 'p', 272 | default: 4500, 273 | description: 'port to use for graphiql', 274 | }), 275 | */ 276 | // do not prettify the output 277 | singleLine: flags.boolean({ 278 | char: 'l', 279 | default: false, 280 | description: 'show output in a single line, do not prettify', 281 | }), 282 | 283 | introspect: flags.boolean({ 284 | default: false, 285 | description: 'introspect the endpoint and get schema', 286 | }), 287 | 288 | format: flags.string({ 289 | description: 'output format for graphql schema after introspection', 290 | default: 'graphql', 291 | options: ['json', 'graphql'], 292 | }), 293 | }; 294 | 295 | GraphqurlCommand.args = [ 296 | { 297 | name: 'endpoint', 298 | description: 'graphql endpoint', 299 | }, 300 | ]; 301 | 302 | module.exports = GraphqurlCommand; 303 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | const throwError = err => { 2 | console.error('Error: ', err); 3 | process.exit(1); 4 | }; 5 | 6 | const handleGraphQLError = err => { 7 | if (err.message) { 8 | let errorMessage = err.message; 9 | if (err.locations) { 10 | let locs = []; 11 | for (const l of err.locations) { 12 | locs.push(`line: ${l.line}, column: ${l.column}`); 13 | } 14 | errorMessage += `\n${locs.join(',')}`; 15 | } 16 | throwError(errorMessage); 17 | } else { 18 | throwError(err); 19 | } 20 | }; 21 | 22 | const handleServerError = err => { 23 | if (err.networkError && err.networkError.statusCode) { 24 | if (err.networkError.result && err.networkError.result.errors) { 25 | let errorMessages = []; 26 | for (const e of err.networkError.result.errors) { 27 | errorMessages.push(`[${e.code}] at [${e.path}]: ${e.error}`); 28 | } 29 | throwError(errorMessages.join('\n')); 30 | } else { 31 | throwError(err.message); 32 | } 33 | } else { 34 | throwError(err); 35 | } 36 | }; 37 | 38 | module.exports = { 39 | handleGraphQLError, 40 | handleServerError, 41 | }; 42 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | const GRAPHQL_SUBSCRIPTIONS_PROTOCOL = 'graphql-ws'; 2 | 3 | // events emitted from client to server 4 | const GQL_CONNECTION_INIT = 'connection_init'; 5 | const GQL_CONNECTION_STOP = 'connection_terminate'; 6 | const GQL_START = 'start'; 7 | const GQL_STOP = 'stop'; 8 | 9 | // events received from server by client 10 | const GQL_CONNECTION_ERROR = 'connection_error'; 11 | const GQL_CONNECTION_ACK = 'connection_ack'; 12 | const GQL_CONNECTION_KEEP_ALIVE = 'ka'; 13 | const GQL_DATA = 'data'; 14 | const GQL_ERROR = 'error'; 15 | const GQL_COMPLETE = 'complete'; 16 | 17 | const handler = (ctx, event) => { 18 | let {data} = event; 19 | try { 20 | data = JSON.parse(data); 21 | } catch (e) { 22 | console.error('unable to parse event data; unexpected event from server'); 23 | return; 24 | } 25 | 26 | let s; 27 | switch (data.type) { 28 | case GQL_CONNECTION_ACK: 29 | if (ctx.onConnectionSuccess) { 30 | ctx.onConnectionSuccess(); 31 | } 32 | ctx.open = true; 33 | return; 34 | case GQL_CONNECTION_ERROR: 35 | if (ctx.onConnectionError) { 36 | ctx.onConnectionError(data); 37 | } 38 | return; 39 | case GQL_CONNECTION_KEEP_ALIVE: 40 | if (ctx.onConnectionKeepAlive) { 41 | ctx.onConnectionKeepAlive(); 42 | } 43 | return; 44 | case GQL_DATA: 45 | s = ctx.subscriptions[data.id]; 46 | if (s && s.onGraphQLData) { 47 | s.onGraphQLData(data.payload); 48 | } 49 | return; 50 | case GQL_ERROR: 51 | s = ctx.subscriptions[data.id]; 52 | if (s && s.onGraphQLError) { 53 | s.onGraphQLError(data.payload); 54 | } 55 | return; 56 | case GQL_CONNECTION_STOP: 57 | return; 58 | case GQL_COMPLETE: 59 | s = ctx.subscriptions[data.id]; 60 | if (s && s.onGraphQLComplete) { 61 | s.onGraphQLComplete(data.payload); 62 | } 63 | delete ctx.subscriptions[data.id]; 64 | } 65 | }; 66 | 67 | module.exports = { 68 | handler, 69 | GQL_CONNECTION_INIT, 70 | GQL_START, 71 | GQL_STOP, 72 | GRAPHQL_SUBSCRIPTIONS_PROTOCOL, 73 | }; 74 | -------------------------------------------------------------------------------- /src/graphiql/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "transform-react-remove-prop-types", 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-proposal-export-default-from", 10 | "@babel/plugin-proposal-nullish-coalescing-operator", 11 | "extract-hoc/babel", 12 | "react-hot-loader/babel", 13 | "istanbul" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/graphiql/app/.bootstraprc: -------------------------------------------------------------------------------- 1 | --- 2 | # Output debugging info 3 | # loglevel: debug 4 | 5 | # Major version of Bootstrap: 3 or 4 6 | bootstrapVersion: 3 7 | 8 | # If Bootstrap version 3 is used - turn on/off custom icon font path 9 | useCustomIconFontPath: false 10 | 11 | # Webpack loaders, order matters 12 | styleLoaders: 13 | - style 14 | - css 15 | - sass 16 | 17 | # Extract styles to stand-alone css file 18 | # Different settings for different environments can be used, 19 | # It depends on value of NODE_ENV environment variable 20 | # This param can also be set in webpack config: 21 | # entry: 'bootstrap-loader/extractStyles' 22 | #extractStyles: false 23 | env: 24 | development: 25 | extractStyles: false 26 | production: 27 | extractStyles: true 28 | 29 | 30 | # Customize Bootstrap variables that get imported before the original Bootstrap variables. 31 | # Thus, derived Bootstrap variables can depend on values from here. 32 | # See the Bootstrap _variables.scss file for examples of derived Bootstrap variables. 33 | # 34 | 35 | preBootstrapCustomizations: ./src/theme/variables.scss 36 | 37 | 38 | # This gets loaded after bootstrap/variables is loaded 39 | # Thus, you may customize Bootstrap variables 40 | # based on the values established in the Bootstrap _variables.scss file 41 | # 42 | 43 | bootstrapCustomizations: ./src/theme/bootstrap.overrides.scss 44 | 45 | 46 | # Import your custom styles here 47 | # Usually this endpoint-file contains list of @imports of your application styles 48 | # 49 | # appStyles: ./path/to/your/app/styles/endpoint.scss 50 | 51 | 52 | ### Bootstrap styles 53 | styles: 54 | 55 | # Mixins 56 | mixins: true 57 | 58 | # Reset and dependencies 59 | normalize: true 60 | print: true 61 | glyphicons: true 62 | 63 | # Core CSS 64 | scaffolding: true 65 | type: true 66 | code: true 67 | grid: true 68 | tables: true 69 | forms: true 70 | buttons: true 71 | 72 | # Components 73 | component-animations: true 74 | dropdowns: true 75 | button-groups: true 76 | input-groups: true 77 | navs: true 78 | navbar: true 79 | breadcrumbs: true 80 | pagination: true 81 | pager: true 82 | labels: true 83 | badges: true 84 | jumbotron: true 85 | thumbnails: true 86 | alerts: true 87 | progress-bars: true 88 | media: true 89 | list-group: true 90 | panels: true 91 | wells: true 92 | responsive-embed: true 93 | close: true 94 | 95 | # Components w/ JavaScript 96 | modals: true 97 | tooltip: true 98 | popovers: true 99 | carousel: true 100 | 101 | # Utility classes 102 | utilities: true 103 | responsive-utilities: true 104 | 105 | ### Bootstrap scripts 106 | #scripts: false 107 | 108 | scripts: 109 | transition: false 110 | alert: false 111 | button: true 112 | carousel: false 113 | collapse: false 114 | dropdown: true 115 | modal: true 116 | tooltip: false 117 | popover: false 118 | scrollspy: false 119 | tab: false 120 | affix: false 121 | -------------------------------------------------------------------------------- /src/graphiql/app/.eslintignore: -------------------------------------------------------------------------------- 1 | webpack/* 2 | src/utils.js 3 | -------------------------------------------------------------------------------- /src/graphiql/app/.eslintrc: -------------------------------------------------------------------------------- 1 | { "extends": "eslint-config-airbnb", 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "cypress/globals": true 7 | }, 8 | "parser": "babel-eslint", 9 | "rules": { 10 | "allowForLoopAfterthoughts": true, 11 | "react/no-multi-comp": 0, 12 | "import/default": 0, 13 | "import/no-duplicates": 0, 14 | "import/named": 0, 15 | "import/first": 0, 16 | "import/namespace": 0, 17 | "import/no-unresolved": 0, 18 | "import/no-named-as-default": 2, 19 | "import/extensions": 0, 20 | "import/no-extraneous-dependencies": 0, 21 | "import/prefer-default-export": 0, 22 | "comma-dangle": 0, 23 | "id-length": [1, {"min": 1, "properties": "never"}], 24 | "indent": [2, 2, {"SwitchCase": 1}], 25 | "no-console": 0, 26 | "arrow-parens": 0, 27 | "no-alert": 0, 28 | "no-plusplus": 0, 29 | "no-unsafe-negation": 0, 30 | "no-loop-func": 0, 31 | "no-lonely-if": 0, 32 | "no-bitwise": 0, 33 | "global-require": 0, 34 | "no-param-reassign": 0, 35 | "no-underscore-dangle": 0, 36 | "no-useless-return": 0, 37 | "no-restricted-syntax": 0, 38 | "no-prototype-builtins": 0, 39 | "array-callback-return": 0, 40 | "no-useless-concat": 0, 41 | "consistent-return": 0, 42 | "class-methods-use-this": 0, 43 | "arrow-body-style": 0, 44 | "prefer-template": 0, 45 | "prefer-spread": 0, 46 | "object-shorthand": 0, 47 | "object-curly-newline": 0, 48 | "spaced-comment": 0, 49 | "prefer-destructuring": ["error", {"object": false, "array": false}], 50 | "prefer-rest-params": 0, 51 | "function-paren-newline": 0, 52 | "no-case-declarations": 0, 53 | "no-restricted-globals": 0, 54 | "no-unneeded-ternary": 0, 55 | "no-mixed-operators": 0, 56 | "no-return-assign": 0, 57 | "operator-assignment": 0, 58 | "strict": 0, 59 | "react/jsx-no-duplicate-props": 0, 60 | "react/jsx-filename-extension": 0, 61 | "react/jsx-curly-brace-presence": 0, 62 | "react/forbid-prop-types": 0, 63 | "react/require-default-props": 0, 64 | "react/no-unused-prop-types": 0, 65 | "react/no-string-refs": 0, 66 | "react/no-unused-state": 0, 67 | "react/no-array-index-key": 0, 68 | "react/jsx-no-bind": 0, 69 | "react/prop-types": 0, 70 | "react/prefer-stateless-function": 0, 71 | "react/no-unescaped-entities": 0, 72 | "jsx-a11y/click-events-have-key-events": 0, 73 | "jsx-a11y/no-static-element-interactions": 0, 74 | "jsx-a11y/no-noninteractive-element-interactions": 0, 75 | "jsx-a11y/label-has-for": 0, 76 | "jsx-a11y/anchor-is-valid": 0, 77 | "jsx-a11y/lang": 0, 78 | "jsx-a11y/alt-text": 0, 79 | "max-len": 0 80 | }, 81 | "plugins": [ 82 | "react", "import", "cypress" 83 | ], 84 | 85 | "settings": { 86 | "import/parser": "babel-eslint", 87 | "parser": "babel-esling", 88 | "import/resolve": { 89 | "moduleDirectory": ["node_modules", "src"] 90 | } 91 | }, 92 | "globals": { 93 | "__DEVELOPMENT__": true, 94 | "__CLIENT__": true, 95 | "__SERVER__": true, 96 | "__DISABLE_SSR__": true, 97 | "__DEVTOOLS__": true, 98 | "socket": true, 99 | "webpackIsomorphicTools": true 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/graphiql/app/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | cypress/videos 3 | cypress/screenshots 4 | .nyc_output 5 | node_modules 6 | static/dist 7 | webpack-assets.json 8 | webpack-stats.json 9 | npm-debug.log 10 | *.swp 11 | coverage 12 | .idea/* 13 | -------------------------------------------------------------------------------- /src/graphiql/app/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | **/* 3 | !static/dist/main.js 4 | !static/dist/vendor.js 5 | !static/dist/main.css 6 | -------------------------------------------------------------------------------- /src/graphiql/app/README.md: -------------------------------------------------------------------------------- 1 | ## Usage of Environment Variables 2 | 3 | This app uses a few environment variables which are required for development. The production build uses values directly injected by the server serving this app. 4 | 5 | We use [dotenv](https://github.com/motdotla/dotenv) for setting environment variables for development. Create a `.env' file in the root directory (wherever package.json is) and set the following values. Replace accordingly for testing. 6 | 7 | ``` 8 | PORT=3000 9 | NODE_ENV=development 10 | GRAPHQL_ENDPOINT=http://localhost:8090/v1alpha1/graphql 11 | HEADER_STRING='{}' 12 | VARIABLE_STRING='{}' 13 | QUERY_STRING='query { test_table { id } }' 14 | ``` 15 | 16 | **Note** 17 | The .env file should not be in version control. 18 | -------------------------------------------------------------------------------- /src/graphiql/app/appconfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hmrPort: parseInt(process.env.PORT, 10) + 1 || 3001, 3 | hmrHost: process.env.HOST || '127.0.0.1', 4 | appHost: '0.0.0.0', 5 | port: { development: process.env.PORT, production: 8080 }, 6 | assetsPrefix: '/rstatic', 7 | webpackPrefix: '/rstatic/dist/', 8 | appPrefix: '/rapp', 9 | }; 10 | -------------------------------------------------------------------------------- /src/graphiql/app/bin/server.babel.js: -------------------------------------------------------------------------------- 1 | // enable runtime transpilation to use ES6/7 in node 2 | 3 | const fs = require('fs'); 4 | 5 | const babelrc = fs.readFileSync('.babelrc'); 6 | let config; 7 | 8 | try { 9 | config = JSON.parse(babelrc); 10 | } catch (err) { 11 | console.error('==> ERROR: Error parsing your .babelrc.'); 12 | console.error(err); 13 | } 14 | 15 | require('babel-core/register')(config); 16 | -------------------------------------------------------------------------------- /src/graphiql/app/bin/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./server.babel'); // babel registration (runtime transpilation for node) 3 | const path = require('path'); 4 | 5 | const rootDir = path.resolve(__dirname, '..'); 6 | /** 7 | * Define isomorphic constants. 8 | */ 9 | global.__CLIENT__ = false; 10 | global.__SERVER__ = true; 11 | global.__DISABLE_SSR__ = false; // <----- DISABLES SERVER SIDE RENDERING FOR ERROR DEBUGGING 12 | global.__DEVELOPMENT__ = process.env.NODE_ENV !== 'production'; 13 | 14 | if (__DEVELOPMENT__) { 15 | if ( 16 | !require('piping')({ 17 | //Fork the process and supervise the child for hot-reloading code 18 | hook: true, 19 | ignore: /(\/\.|~$|\.json|\.scss$)/i, 20 | }) 21 | ) { 22 | return; //The parent process ends, and child process continues from below 23 | } 24 | } 25 | 26 | const WebpackIsomorphicTools = require('webpack-isomorphic-tools'); 27 | global.webpackIsomorphicTools = new WebpackIsomorphicTools( 28 | require('../webpack/webpack-isomorphic-tools') 29 | ).server(rootDir, () => { 30 | require('../src/server'); 31 | }); 32 | 33 | require('../src/server'); 34 | -------------------------------------------------------------------------------- /src/graphiql/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqurl-graphiql", 3 | "description": "Explore GraphQL APIs with headers", 4 | "author": "Praveen ", 5 | "license": "MIT", 6 | "version": "0.2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/hasura/graphiql-online" 10 | }, 11 | "main": "index.js", 12 | "homepage": "https://hasura.io/", 13 | "keywords": [], 14 | "scripts": { 15 | "start": "concurrently --kill-others \"npm run start-prod\"", 16 | "start-prod": "better-npm-run start-prod", 17 | "build": "webpack --progress -p --colors --display-error-details --config webpack/prod.config.js", 18 | "now-build": "webpack --progress -p --colors --display-error-details --config webpack/prod.config.js", 19 | "build-unused": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js --json | webpack-unused -s src", 20 | "lint": "eslint -c .eslintrc src api", 21 | "start-dev": "better-npm-run start-dev", 22 | "watch-client": "better-npm-run watch-client", 23 | "dev": "concurrently --kill-others \"npm run watch-client\" \"npm run start-dev\" ", 24 | "deploy": "now" 25 | }, 26 | "betterScripts": { 27 | "start-prod": { 28 | "command": "node ./bin/server.js", 29 | "env": { 30 | "NODE_PATH": "./src", 31 | "NODE_ENV": "production", 32 | "PORT": 8080 33 | } 34 | }, 35 | "start-dev": { 36 | "command": "node -r dotenv/config ./bin/server.js" 37 | }, 38 | "watch-client": { 39 | "command": "node -r dotenv/config webpack/webpack-dev-server.js" 40 | } 41 | }, 42 | "dependencies": { 43 | "apollo-link": "^1.2.2", 44 | "apollo-link-ws": "^1.0.8", 45 | "babel-plugin-transform-class-properties": "^6.24.1", 46 | "graphiql": "1.0.0-alpha.0", 47 | "graphiql-code-exporter": "^2.0.8", 48 | "graphiql-explorer": "^0.6.2", 49 | "graphql": "^14.3.0", 50 | "history": "^3.0.0", 51 | "isomorphic-fetch": "^2.2.1", 52 | "less": "^3.7.1", 53 | "lru-memoize": "^1.0.0", 54 | "map-props": "^1.0.0", 55 | "match-sorter": "^2.3.0", 56 | "multireducer": "^1.0.2", 57 | "piping": "^0.3.2", 58 | "prettier": "^1.16.4", 59 | "pretty-error": "^1.2.0", 60 | "prop-types": "^15.6.0", 61 | "react": "16.8.2", 62 | "react-bootstrap": "^0.32.1", 63 | "react-copy-to-clipboard": "^5.0.0", 64 | "react-dom": "16.8.2", 65 | "react-helmet": "^5.2.0", 66 | "react-notification-system": "^0.2.17", 67 | "react-progress-bar-plus": "^1.3.1", 68 | "react-redux": "^5.0.6", 69 | "react-router": "^3.2.0", 70 | "react-router-redux": "^4.0.8", 71 | "react-tabs": "^2.1.0", 72 | "redux": "^4.0.0", 73 | "redux-logger": "^3.0.6", 74 | "redux-thunk": "^2.2.0", 75 | "semver": "5.5.1", 76 | "subscriptions-transport-ws": "^0.9.12", 77 | "valid-url": "^1.0.9" 78 | }, 79 | "devDependencies": { 80 | "@babel/core": "^7.3.3", 81 | "@babel/plugin-proposal-class-properties": "^7.12.1", 82 | "@babel/plugin-proposal-decorators": "^7.3.0", 83 | "@babel/plugin-proposal-do-expressions": "^7.0.0", 84 | "@babel/plugin-proposal-export-default-from": "^7.0.0", 85 | "@babel/plugin-proposal-export-namespace-from": "^7.2.0", 86 | "@babel/plugin-proposal-function-bind": "^7.0.0", 87 | "@babel/plugin-proposal-function-sent": "^7.2.0", 88 | "@babel/plugin-proposal-json-strings": "^7.2.0", 89 | "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", 90 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", 91 | "@babel/plugin-proposal-numeric-separator": "^7.2.0", 92 | "@babel/plugin-proposal-optional-chaining": "^7.0.0", 93 | "@babel/plugin-proposal-pipeline-operator": "^7.0.0", 94 | "@babel/plugin-proposal-throw-expressions": "^7.0.0", 95 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 96 | "@babel/plugin-syntax-import-meta": "^7.2.0", 97 | "@babel/preset-env": "^7.0.0", 98 | "@babel/preset-react": "^7.0.0", 99 | "@babel/register": "^7.0.0", 100 | "@babel/runtime": "^7.0.0", 101 | "babel-eslint": "^9.0.0", 102 | "babel-loader": "^8.0.0", 103 | "babel-plugin-istanbul": "^5.1.1", 104 | "babel-plugin-transform-react-remove-prop-types": "^0.4.10", 105 | "babel-plugin-typecheck": "^2.0.0", 106 | "better-npm-run": "^0.1.0", 107 | "bootstrap-loader": "^2.2.0", 108 | "bootstrap-sass": "^3.3.7", 109 | "clean-webpack-plugin": "^0.1.17", 110 | "concurrently": "^3.5.0", 111 | "css-loader": "^0.28.11", 112 | "dotenv": "^5.0.1", 113 | "eslint": "^4.19.1", 114 | "eslint-config-airbnb": "16.1.0", 115 | "eslint-loader": "^1.0.0", 116 | "eslint-plugin-chai-friendly": "^0.4.1", 117 | "eslint-plugin-import": "^2.12.0", 118 | "eslint-plugin-jsx-a11y": "^6.0.3", 119 | "eslint-plugin-react": "^7.9.1", 120 | "express": "^4.13.3", 121 | "express-session": "^1.12.1", 122 | "extract-hoc": "0.0.5", 123 | "extract-text-webpack-plugin": "^3.0.2", 124 | "file-loader": "^1.1.11", 125 | "font-awesome": "^4.7.0", 126 | "font-awesome-webpack": "0.0.4", 127 | "husky": "^0.14.3", 128 | "ignore-loader": "^0.1.2", 129 | "jquery": "^3.4.1", 130 | "json-loader": "^0.5.4", 131 | "less-loader": "^4.1.0", 132 | "lint-staged": "^6.1.1", 133 | "mini-css-extract-plugin": "^0.4.0", 134 | "node-sass": "^4.14.0", 135 | "nyc": "^13.3.0", 136 | "optimize-css-assets-webpack-plugin": "^4.0.2", 137 | "react-a11y": "^0.2.6", 138 | "react-addons-test-utils": "^15.0.3", 139 | "react-hot-loader": "^4.6.5", 140 | "redux-devtools": "^3.4.1", 141 | "redux-devtools-dock-monitor": "^1.1.2", 142 | "redux-devtools-log-monitor": "^1.3.0", 143 | "resolve-url-loader": "^2.3.0", 144 | "sass-loader": "^7.0.1", 145 | "sinon": "^1.17.7", 146 | "style-loader": "^0.20.3", 147 | "timekeeper": "1.0.0", 148 | "uglifyjs-webpack-plugin": "^1.2.7", 149 | "unused-files-webpack-plugin": "^3.4.0", 150 | "url-loader": "^1.0.1", 151 | "webpack": "4.42.0", 152 | "webpack-cli": "3.3.11", 153 | "webpack-dev-middleware": "3.7.2", 154 | "webpack-hot-middleware": "2.25.0", 155 | "terser-webpack-plugin": "2.3.5", 156 | "webpack-isomorphic-tools": "3.0.6" 157 | }, 158 | "engines": { 159 | "node": ">=8.9.1" 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/graphiql/app/src/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * THIS IS THE ENTRY POINT FOR THE CLIENT, JUST LIKE server.js IS THE ENTRY POINT FOR THE SERVER. 3 | */ 4 | // import 'babel-polyfill'; 5 | 6 | import React from 'react'; 7 | import ReactDOM from 'react-dom'; 8 | import { createLogger } from 'redux-logger'; 9 | import thunk from 'redux-thunk'; 10 | 11 | import { Provider } from 'react-redux'; 12 | 13 | import { Router, browserHistory } from 'react-router'; 14 | import { routerMiddleware, syncHistoryWithStore } from 'react-router-redux'; 15 | import { compose, createStore, applyMiddleware } from 'redux'; 16 | import { useBasename } from 'history'; 17 | 18 | import getRoutes from './routes'; 19 | 20 | import reducer from './reducer'; 21 | 22 | // Create the store 23 | let _finalCreateStore; 24 | 25 | if (__DEVELOPMENT__) { 26 | _finalCreateStore = compose( 27 | applyMiddleware(thunk, routerMiddleware(browserHistory), createLogger()), 28 | require('redux-devtools').persistState( 29 | window.location.href.match(/[?&]debug_session=([^&]+)\b/) 30 | ) 31 | )(createStore); 32 | } else { 33 | _finalCreateStore = compose( 34 | applyMiddleware(thunk, routerMiddleware(browserHistory)) 35 | )(createStore); 36 | } 37 | 38 | const hashLinkScroll = () => { 39 | const { hash } = window.location; 40 | if (hash !== '') { 41 | // Push onto callback queue so it runs after the DOM is updated, 42 | // this is required when navigating from a different page so that 43 | // the element is rendered on the page before trying to getElementById. 44 | setTimeout(() => { 45 | const id = hash.replace('#', ''); 46 | const element = document.getElementById(id); 47 | if (element) { 48 | element.scrollIntoView(); 49 | } 50 | }, 0); 51 | } else { 52 | // This is a hack to solve the issue with scroll retention during page change. 53 | setTimeout(() => { 54 | const element = document.getElementsByTagName('body'); 55 | if (element && element.length > 0) { 56 | element[0].scrollIntoView(); 57 | } 58 | }, 0); 59 | } 60 | }; 61 | 62 | const store = _finalCreateStore(reducer); 63 | const history = syncHistoryWithStore(browserHistory, store); 64 | 65 | /* ****************************************************************** */ 66 | 67 | // Enable hot reloading 68 | if (__DEVELOPMENT__ && module.hot) { 69 | module.hot.accept('./reducer', () => { 70 | store.replaceReducer(require('./reducer')); 71 | }); 72 | } 73 | 74 | // Main routes and rendering 75 | const main = ( 76 | history)({ basename: '/' })} 78 | routes={getRoutes(store)} 79 | onUpdate={hashLinkScroll} 80 | /> 81 | ); 82 | 83 | const dest = document.getElementById('content'); 84 | ReactDOM.render( 85 | 86 | {main} 87 | , 88 | dest 89 | ); 90 | 91 | if (process.env.NODE_ENV !== 'production') { 92 | window.React = React; // enable debugger 93 | } 94 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/404/404-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasura/graphqurl/1d142d21a78f5d4e139840075d9d3e5a425b98dd/src/graphiql/app/src/components/404/404-logo.png -------------------------------------------------------------------------------- /src/graphiql/app/src/components/404/PageNotFound.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { Link } from 'react-router'; 6 | import Helmet from 'react-helmet'; 7 | 8 | class PageNotFound extends Component { 9 | render() { 10 | const lostImage = require('./404-logo.png'); 11 | const styles = require('./Styles.scss'); 12 | return ( 13 |
14 | 15 |
16 |
17 |
18 |

404

19 | This page doesn't exist. 20 |
21 |
22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | PageNotFound.propTypes = { 29 | dispatch: PropTypes.func.isRequired, 30 | }; 31 | 32 | export default connect()(PageNotFound); 33 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/404/Styles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0; 3 | } 4 | 5 | .viewcontainer { 6 | height: 100vh; 7 | width: 100vw; 8 | display: table; 9 | 10 | .centerContent{ 11 | display: table-cell; 12 | vertical-align: middle; 13 | 14 | .message { 15 | padding: 50px 20%; 16 | } 17 | .message h1 { 18 | font-size: 54px; 19 | font-weight: bold; 20 | } 21 | .message p { 22 | margin-left: 15px; 23 | } 24 | .message p > a { 25 | font-weight: bold; 26 | } 27 | } 28 | } 29 | 30 | .header { 31 | background: #eee; 32 | h2 { 33 | margin: 0; 34 | padding: 26px; 35 | float: left; 36 | line-height: 26px; 37 | } 38 | .nav { 39 | padding: 20px; 40 | float: left; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/ApiExplorer/Actions.js: -------------------------------------------------------------------------------- 1 | import defaultState from './state'; 2 | // import fetch from 'isomorphic-fetch'; 3 | 4 | import { SubscriptionClient } from 'subscriptions-transport-ws'; 5 | import { WebSocketLink } from 'apollo-link-ws'; 6 | import { parse } from 'graphql/language'; 7 | import { execute } from 'apollo-link'; 8 | 9 | const REQUEST_PARAMS_CHANGED = 'ApiExplorer/REQUEST_PARAMS_CHANGED'; 10 | const REQUEST_HEADER_CHANGED = 'ApiExplorer/REQUEST_HEADER_CHANGED'; 11 | const REQUEST_HEADER_ADDED = 'ApiExplorer/REQUEST_HEADER_ADDED'; 12 | const REQUEST_HEADER_REMOVED = 'ApiExplorer/REQUEST_HEADER_REMOVED'; 13 | 14 | const FOCUS_ROLE_HEADER = 'ApiExplorer/FOCUS_ROLE_HEADER'; 15 | const UNFOCUS_ROLE_HEADER = 'ApiExplorer/UNFOCUS_ROLE_HEADER'; 16 | 17 | import { getHeadersAsJSON } from './utils'; 18 | 19 | const focusHeaderTextbox = () => ({ type: FOCUS_ROLE_HEADER }); 20 | const unfocusTypingHeader = () => ({ type: UNFOCUS_ROLE_HEADER }); 21 | 22 | const createWsClient = (url, headers) => { 23 | const gqlUrl = new URL(url); 24 | const windowUrl = new URL(window.location); 25 | let websocketProtocol = 'ws'; 26 | if (gqlUrl.protocol === 'https:') { 27 | websocketProtocol = 'wss'; 28 | } 29 | const headersFinal = getHeadersAsJSON(headers); 30 | const graphqlUrl = `${websocketProtocol}://${url.split('//')[1]}`; 31 | const client = new SubscriptionClient(graphqlUrl, { 32 | connectionParams: { 33 | headers: { 34 | ...headersFinal, 35 | }, 36 | }, 37 | reconnect: true, 38 | }); 39 | return client; 40 | }; 41 | 42 | const graphqlSubscriber = (graphQLParams, url, headers) => { 43 | const link = new WebSocketLink(createWsClient(url, headers)); 44 | try { 45 | const fetcher = operation => { 46 | operation.query = parse(operation.query); 47 | return execute(link, operation); 48 | }; 49 | return fetcher(graphQLParams); 50 | } catch (e) { 51 | return e.json(); 52 | } 53 | }; 54 | 55 | const isSubscription = graphQlParams => { 56 | const queryDoc = parse(graphQlParams.query); 57 | for (const definition of queryDoc.definitions) { 58 | if (definition.kind === 'OperationDefinition') { 59 | const operation = definition.operation; 60 | if (operation === 'subscription') { 61 | return true; 62 | } 63 | } 64 | } 65 | return false; 66 | }; 67 | 68 | const graphQLFetcherFinal = (graphQLParams, url, headers) => { 69 | if (isSubscription(graphQLParams)) { 70 | return graphqlSubscriber(graphQLParams, url, headers); 71 | } 72 | return fetch(url, { 73 | method: 'POST', 74 | headers: getHeadersAsJSON(headers), 75 | body: JSON.stringify(graphQLParams), 76 | }).then(response => response.json()); 77 | }; 78 | 79 | const changeRequestHeader = (index, key, newValue, isDisabled) => ({ 80 | type: REQUEST_HEADER_CHANGED, 81 | data: { 82 | index: index, 83 | keyName: key, 84 | newValue: newValue, 85 | isDisabled: isDisabled, 86 | }, 87 | }); 88 | 89 | const addRequestHeader = (key, value) => ({ 90 | type: REQUEST_HEADER_ADDED, 91 | data: { 92 | key: key, 93 | value: value, 94 | }, 95 | }); 96 | 97 | const removeRequestHeader = index => { 98 | return { 99 | type: REQUEST_HEADER_REMOVED, 100 | data: index, 101 | }; 102 | }; 103 | 104 | // This method adds the new header and moves the empty header to the bottom of the list 105 | const getHeadersAfterAddingNewHeader = (headers, newHeader) => { 106 | const nonEmptyHeaders = headers.filter(header => { 107 | return !header.isNewHeader; 108 | }); 109 | nonEmptyHeaders.push(newHeader); 110 | nonEmptyHeaders.push({ 111 | key: '', 112 | value: '', 113 | isActive: false, 114 | isNewHeader: true, 115 | }); 116 | return nonEmptyHeaders; 117 | }; 118 | 119 | // This method adds a new empty header if no empty header is present 120 | const getChangedHeaders = (headers, changedHeaderDetails) => { 121 | const newHeaders = Object.assign([], headers); 122 | if (newHeaders[changedHeaderDetails.index].isNewHeader) { 123 | newHeaders[changedHeaderDetails.index].isNewHeader = false; 124 | newHeaders[changedHeaderDetails.index].isActive = true; 125 | newHeaders[changedHeaderDetails.index].isDisabled = false; 126 | } 127 | if (changedHeaderDetails.keyName === 'isActive') { 128 | newHeaders[changedHeaderDetails.index].isActive = !newHeaders[ 129 | changedHeaderDetails.index 130 | ].isActive; 131 | } else { 132 | newHeaders[changedHeaderDetails.index][changedHeaderDetails.keyName] = 133 | changedHeaderDetails.newValue; 134 | } 135 | if (changedHeaderDetails.isDisabled === true) { 136 | newHeaders[changedHeaderDetails.index].isDisabled = true; 137 | } else { 138 | newHeaders[changedHeaderDetails.index].isDisabled = false; 139 | } 140 | const nonEmptyHeaders = newHeaders.filter(header => { 141 | return !header.isNewHeader; 142 | }); 143 | nonEmptyHeaders.push({ 144 | key: '', 145 | value: '', 146 | isActive: false, 147 | isNewHeader: true, 148 | isDisabled: false, 149 | }); 150 | return nonEmptyHeaders; 151 | }; 152 | 153 | const apiExplorerReducer = (state = defaultState, action) => { 154 | switch (action.type) { 155 | case REQUEST_HEADER_CHANGED: 156 | return { 157 | ...state, 158 | displayedApi: { 159 | ...state.displayedApi, 160 | request: { 161 | ...state.displayedApi.request, 162 | headers: getChangedHeaders( 163 | state.displayedApi.request.headers, 164 | action.data 165 | ), 166 | }, 167 | }, 168 | }; 169 | case REQUEST_HEADER_ADDED: 170 | return { 171 | ...state, 172 | displayedApi: { 173 | ...state.displayedApi, 174 | request: { 175 | ...state.displayedApi.request, 176 | headers: getHeadersAfterAddingNewHeader( 177 | state.displayedApi.request.headers, 178 | { 179 | key: action.data.key, 180 | value: action.data.value, 181 | isActive: true, 182 | isNewHeader: false, 183 | } 184 | ), 185 | }, 186 | }, 187 | }; 188 | case REQUEST_HEADER_REMOVED: 189 | return { 190 | ...state, 191 | displayedApi: { 192 | ...state.displayedApi, 193 | request: { 194 | ...state.displayedApi.request, 195 | headers: state.displayedApi.request.headers.filter((header, i) => { 196 | return !(i === action.data); 197 | }), 198 | }, 199 | }, 200 | }; 201 | case UNFOCUS_ROLE_HEADER: 202 | return { 203 | ...state, 204 | headerFocus: false, 205 | }; 206 | case FOCUS_ROLE_HEADER: 207 | return { 208 | ...state, 209 | headerFocus: true, 210 | }; 211 | default: 212 | return state; 213 | } 214 | }; 215 | 216 | export default apiExplorerReducer; 217 | 218 | export { 219 | changeRequestHeader, 220 | addRequestHeader, 221 | removeRequestHeader, 222 | graphQLFetcherFinal, 223 | focusHeaderTextbox, 224 | unfocusTypingHeader, 225 | }; 226 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/ApiExplorer/ApiExplorer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import ApiRequestWrapper from './ApiRequestWrapper'; 5 | import Helmet from 'react-helmet'; 6 | 7 | import { 8 | changeTabSelection, 9 | changeApiSelection, 10 | expandAuthApi, 11 | clearHistory, 12 | changeRequestParams, 13 | } from './Actions'; 14 | 15 | class ApiExplorer extends Component { 16 | 17 | render() { 18 | const styles = require('./ApiExplorer.scss'); 19 | let wrapperClass = styles.apiExplorerWrapper; 20 | let panelStyles = ''; 21 | let requestStyles = ''; 22 | let wdClass = ''; 23 | let requestWrapper = ( 24 | 36 | ); 37 | 38 | return ( 39 |
40 | 41 |
{requestWrapper}
42 |
43 | ); 44 | } 45 | } 46 | 47 | ApiExplorer.propTypes = { 48 | modalState: PropTypes.object.isRequired, 49 | dispatch: PropTypes.func.isRequired, 50 | route: PropTypes.object.isRequired, 51 | headerFocus: PropTypes.bool.isRequired, 52 | }; 53 | 54 | export default ApiExplorer; 55 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/ApiExplorer/ApiExplorerGenerator.js: -------------------------------------------------------------------------------- 1 | import ApiExplorer from './ApiExplorer'; 2 | 3 | const generatedApiExplorer = connect => { 4 | const mapStateToProps = state => { 5 | return { 6 | ...state.apiexplorer, 7 | credentials: {}, 8 | }; 9 | }; 10 | return connect(mapStateToProps)(ApiExplorer); 11 | }; 12 | 13 | export default generatedApiExplorer; 14 | -------------------------------------------------------------------------------- /src/graphiql/app/src/components/ApiExplorer/ApiRequest.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { 5 | addRequestHeader, 6 | changeRequestHeader, 7 | removeRequestHeader, 8 | focusHeaderTextbox, 9 | unfocusTypingHeader, 10 | } from './Actions'; 11 | 12 | import GraphiQLWrapper from './GraphiQLWrapper'; 13 | 14 | const styles = require('./ApiExplorer.scss'); 15 | 16 | class ApiRequest extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = {}; 20 | this.state.accessKeyVisible = false; 21 | this.state.bodyAllowedMethods = ['POST']; 22 | this.state.tabIndex = 0; 23 | } 24 | 25 | onHeaderValueChanged(e) { 26 | const index = parseInt(e.target.getAttribute('data-header-id'), 10); 27 | const key = e.target.getAttribute('data-element-name'); 28 | const newValue = e.target.value; 29 | this.props.dispatch(changeRequestHeader(index, key, newValue, false)); 30 | } 31 | 32 | onDeleteHeaderClicked(e) { 33 | const index = parseInt(e.target.getAttribute('data-header-id'), 10); 34 | this.props.dispatch(removeRequestHeader(index)); 35 | } 36 | 37 | onNewHeaderKeyChanged(e) { 38 | this.handleTypingTimeouts(); 39 | this.props.dispatch(addRequestHeader(e.target.value, '')); 40 | } 41 | 42 | onNewHeaderValueChanged(e) { 43 | this.handleTypingTimeouts(); 44 | this.props.dispatch(addRequestHeader('', e.target.value)); 45 | } 46 | 47 | onKeyUpAtNewHeaderField(e) { 48 | if (e.keyCode === 13) { 49 | this.props.dispatch( 50 | addRequestHeader(this.state.newHeader.key, this.state.newHeader.value) 51 | ); 52 | } 53 | } 54 | 55 | getUrlBar() { 56 | return ( 57 |