├── .gitignore
├── Parser
├── anagraphCreator.js
├── parsertests
│ ├── anagraphCreator.test.js
│ ├── queryParser.test.js
│ ├── stringProperties.test.js
│ └── stringValidator.test.js
├── queryParser.js
├── queryValidator.js
├── ruleValidator.js
├── schemaParser.js
├── stringProperties.js
├── stringValidator.js
└── tests
│ ├── anagraphCreator.test.js
│ ├── queryParser.test.js
│ ├── stringProperties.test.js
│ └── stringValidator.test.js
├── README.md
├── anagraphqlEditor
├── .eslintrc.json
├── buildRun.sh
├── dist
│ ├── IMG_0672.jpg
│ ├── index.html
│ └── stylesheet.css
├── package-lock.json
├── package.json
├── src
│ ├── client
│ │ ├── actions
│ │ │ └── actions.js
│ │ ├── components
│ │ │ ├── App.jsx
│ │ │ ├── ApplicableRules.jsx
│ │ │ ├── CodeBlock.jsx
│ │ │ ├── CodeContainer.jsx
│ │ │ ├── CodeEditor.jsx
│ │ │ ├── Collapsible.jsx
│ │ │ ├── Graph.jsx
│ │ │ ├── Headline.jsx
│ │ │ ├── History.jsx
│ │ │ ├── JsonDisplay.jsx
│ │ │ ├── PoliciesContainer.jsx
│ │ │ ├── PolicySelector.jsx
│ │ │ ├── RulesConfiguration.jsx
│ │ │ ├── RulesDisplay.jsx
│ │ │ ├── SideBar.jsx
│ │ │ └── Visualizer.jsx
│ │ ├── constants
│ │ │ └── actionTypes.js
│ │ ├── index.jsx
│ │ ├── play.svg
│ │ ├── reducers
│ │ │ ├── queryReducer.js
│ │ │ ├── responseReducer.js
│ │ │ ├── rootReducer.js
│ │ │ └── rulesReducer.js
│ │ ├── store.js
│ │ └── utility
│ │ │ ├── d3DataCreator.js
│ │ │ └── introspectionQueries.js
│ └── server
│ │ └── server.js
└── webpack.config.js
├── buildRun.sh
├── docs
└── index.html
├── graphqlTestServer
├── .eslintrc
├── async_db.js
├── buildRun.sh
├── config
│ ├── asyncFakeData.js
│ ├── config.sh
│ ├── db.sql
│ └── fakeData.js
├── database.js
├── db_connectStr.js
├── index.js
├── package-lock.json
├── package.json
├── resolvers.js
└── schema.js
├── index.js
├── package-lock.json
├── package.json
└── renderGraphiql.js
/.gitignore:
--------------------------------------------------------------------------------
1 | anagraphqlEditor/node_modules/
2 | graphqlTestServer/node_modules/
3 | bundle.js
4 | node_modules
--------------------------------------------------------------------------------
/Parser/anagraphCreator.js:
--------------------------------------------------------------------------------
1 | const parser = require('./queryParser');
2 | const stringProperties = require('./stringProperties');
3 |
4 |
5 | // Exporting an anonyous function that takes one param, a query, and returns the anagraph object.
6 |
7 | // This file exports the anagraph object. The anagraph object contain values that are the result of passing the incoming query through the parser function and then passing that result (theOBJ) through the stringProperties function.
8 |
9 |
10 | const anagraphCreator = (query) => {
11 | const theOBJ = parser(query);
12 | const anagraph = {};
13 | anagraph.analytics = stringProperties.countResolvers(theOBJ);
14 | anagraph.analytics.maxNested = stringProperties.countDepth(query);
15 | anagraph.analytics.totalResolvers = stringProperties.combinedResolvers(anagraph);
16 | anagraph.analytics.totalFields = stringProperties.combinedFields(anagraph);
17 | return anagraph;
18 | };
19 |
20 | module.exports = anagraphCreator;
21 |
--------------------------------------------------------------------------------
/Parser/parsertests/anagraphCreator.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | Anagraph Object tests
3 | ************/
4 | const anagraphCreator = require('../anagraphCreator')
5 |
6 | const sampleQuery = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 | const anagraph = anagraphCreator(sampleQuery)
8 |
9 | describe('anagraphCreator', () => {
10 |
11 | test('is a function', () => {
12 | expect(typeof anagraphCreator).toBe('function');
13 | })
14 |
15 | test('returns an object', () => {
16 | expect(typeof anagraphCreator(sampleQuery)).toBe('object');
17 | })
18 |
19 | test('has a property called analytics', () => {
20 | expect(anagraph).toHaveProperty('analytics')
21 | })
22 |
23 | test('analytics property has a property called shallowResolvers', () => {
24 | expect(anagraph.analytics).toHaveProperty('shallowResolvers')
25 | })
26 |
27 | test('analytics property has a property called specificResolvers', () => {
28 | expect(anagraph.analytics).toHaveProperty('specificResolvers')
29 | })
30 |
31 | test('analytics property has a property called fields', () => {
32 | expect(anagraph.analytics).toHaveProperty('fields')
33 | })
34 |
35 | test('analytics property has a property called maxNested', () => {
36 | expect(anagraph.analytics).toHaveProperty('maxNested')
37 | })
38 |
39 | test('analytics property has a property called totalFields', () => {
40 | expect(anagraph.analytics).toHaveProperty('totalFields')
41 | })
42 |
43 | test('the value of anagraph.analytics.shallowResolvers is an object', () => {
44 | expect(typeof anagraph.analytics.shallowResolvers).toBe('object')
45 | })
46 |
47 | test('the value of anagraph.analytics.specificResolvers is an number', () => {
48 | expect(typeof anagraph.analytics.specificResolvers).toBe('object')
49 | })
50 |
51 | test('the value of anagraph.analytics.fields is an object', () => {
52 | expect(typeof anagraph.analytics.fields).toBe('object')
53 | })
54 |
55 | test('the value of anagraph.analytics.maxNested is an number', () => {
56 | expect(typeof anagraph.analytics.maxNested).toBe('number')
57 | })
58 |
59 | test('the value of anagraph.analytics.totalFields is an number', () => {
60 | expect(typeof anagraph.analytics.totalFields).toBe('number')
61 | })
62 |
63 |
64 |
65 |
66 | })
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Parser/parsertests/queryParser.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | Query Parser tests
3 | ************/
4 | const queryParser = require('../queryParser')
5 | const sampleQuery = '{ author { firstname }}'
6 |
7 | describe('queryParser', () => {
8 | test('is a function', () => {
9 | expect(typeof queryParser).toBe('function')
10 | })
11 | test('returns an object', () => {
12 | expect(typeof queryParser(sampleQuery)).toBe('object');
13 | })
14 |
15 | })
16 |
17 |
--------------------------------------------------------------------------------
/Parser/parsertests/stringProperties.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | stringProperties tests
3 | ************/
4 | const stringProperties = require('../stringProperties')
5 |
6 | const sampleString = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 |
8 | describe('stringProperties', () => {
9 |
10 | test('should throw an error on an empty string', () => {
11 | const emptyString = () => stringProperties('');
12 | expect(emptyString).toThrow(TypeError);
13 | });
14 |
15 | test('is an object', () => {
16 | expect(typeof stringProperties).toBe('object');
17 | })
18 |
19 | test('has a property called lengthOfString', () => {
20 | expect(stringProperties).toHaveProperty('lengthOfString')
21 | })
22 |
23 | test('has a property called countResolvers', () => {
24 | expect(stringProperties).toHaveProperty('countResolvers')
25 | })
26 |
27 | test('has a property called countDepth', () => {
28 | expect(stringProperties).toHaveProperty('countDepth')
29 | })
30 | test('has a property called combinedResolvers', () => {
31 | expect(stringProperties).toHaveProperty('combinedResolvers')
32 | })
33 |
34 | test('has a property called combinedFields', () => {
35 | expect(stringProperties).toHaveProperty('combinedFields')
36 | })
37 |
38 |
39 |
40 |
41 | })
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Parser/parsertests/stringValidator.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | stringProperties tests
3 | ************/
4 | const stringValidator = require('../stringValidator')
5 |
6 | const sampleString = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 |
8 | describe('stringValidator.balancedParentes', () => {
9 | test('is a function', () => {
10 | expect(typeof stringValidator.balancedParentes).toBe('function')
11 | })
12 | test('should return a boolean', () => {
13 | expect(typeof stringValidator.balancedParentes(sampleString)).toBe('boolean')
14 | })
15 |
16 |
17 | })
18 |
--------------------------------------------------------------------------------
/Parser/queryParser.js:
--------------------------------------------------------------------------------
1 |
2 | const parser = (q) => {
3 | const formatted = q.replace(/ *\([^)]*\) */g, '')
4 | .slice(1, -1)
5 | .replace(/(\s*{)/g, '{')
6 | .split(/\s/g)
7 | .filter(cv => cv !== '');
8 | let i = 0;
9 | const arr = formatted.slice();
10 | const helper = () => {
11 | const test = {};
12 | while (arr[i] && arr[i] !== '}') {
13 | if (arr[i] && arr[i][arr[i].length - 1] === '{') {
14 | test[arr[i++].slice(0, -1)] = helper();
15 | } else if (arr[i] !== '}') {
16 | test[arr[i]] = 'val';
17 | }
18 | i += 1;
19 | }
20 | return test;
21 | };
22 | return helper();
23 | };
24 |
25 |
26 | module.exports = parser;
27 |
--------------------------------------------------------------------------------
/Parser/queryValidator.js:
--------------------------------------------------------------------------------
1 | module.exports = (anagraph, rules) => {
2 | // console.log(anagraph);
3 | // console.log(rules);
4 | const queryValidation = {};
5 |
6 | const keyRules = Object.keys(rules);
7 |
8 | for (let i = 0; i < keyRules.length; i += 1) {
9 | if (typeof rules[keyRules[i]] !== 'object') {
10 | if (anagraph.analytics[keyRules[i]] > rules[keyRules[i]]) {
11 | queryValidation.error = (`${[keyRules[i]]} - Rule violation!`);
12 | return queryValidation;
13 | }
14 | }
15 | }
16 |
17 | if (rules.shallowResolvers) {
18 | const ruleShallowResolvers = Object.keys(rules.shallowResolvers);
19 | for (let i = 0; i < ruleShallowResolvers.length; i += 1) {
20 | if (anagraph.analytics.shallowResolvers[ruleShallowResolvers[i]] > rules.shallowResolvers[ruleShallowResolvers[i]]) {
21 | queryValidation.error = (`${[ruleShallowResolvers[i]]} - shallowResolvers rule violation!`);
22 | return queryValidation;
23 | }
24 | }
25 | }
26 |
27 | return queryValidation;
28 | };
29 |
--------------------------------------------------------------------------------
/Parser/ruleValidator.js:
--------------------------------------------------------------------------------
1 | module.exports = (applicableRules, rules) => {
2 | // console.log(applicableRules, ' applicableRules');
3 |
4 | const validateRules = {};
5 |
6 | const keyRules = Object.keys(rules);
7 |
8 | for (let i = 0; i < keyRules.length; i += 1) {
9 | if (!applicableRules.hasOwnProperty(keyRules[i])) {
10 | validateRules.error = `${keyRules[i]} is not a valid rule`;
11 | return validateRules;
12 | }
13 | }
14 |
15 | if (rules.hasOwnProperty('shallowResolvers')) {
16 | const rulesShallowResolvers = Object.keys(rules.shallowResolvers);
17 | for (let i = 0; i < rulesShallowResolvers.length; i += 1) {
18 | if (!applicableRules.shallowResolvers.hasOwnProperty(rulesShallowResolvers[i])) {
19 | validateRules.error = `${rulesShallowResolvers[i]} is not a shallowResolver rule`;
20 | return validateRules;
21 | }
22 | }
23 | }
24 |
25 | if (rules.hasOwnProperty('specificResolvers')) {
26 | const rulesSpecificResolvers = Object.keys(rules.specificResolvers);
27 | for (let i = 0; i < rulesSpecificResolvers.length; i += 1) {
28 | if (!applicableRules.specificResolvers.hasOwnProperty(rulesSpecificResolvers[i])) {
29 | validateRules.error = `${rulesSpecificResolvers[i]} is not a shallowResolver rule`;
30 | return validateRules;
31 | }
32 | }
33 | }
34 |
35 | return validateRules;
36 | };
37 |
--------------------------------------------------------------------------------
/Parser/schemaParser.js:
--------------------------------------------------------------------------------
1 | const { introspectionFromSchema } = require('../graphqlTestServer/node_modules/graphql');
2 |
3 | module.exports = (schema) => {
4 | const defaultTypes = {
5 | // RootQueryType: true,
6 | ID: true,
7 | String: true,
8 | Int: true,
9 | __Schema: true,
10 | __Type: true,
11 | __TypeKind: true,
12 | Boolean: true,
13 | __Field: true,
14 | __InputValue: true,
15 | __EnumValue: true,
16 | __Directive: true,
17 | __DirectiveLocation: true,
18 | };
19 |
20 | const sure = introspectionFromSchema(schema);
21 | const jsonifiedSchema = JSON.parse(JSON.stringify(sure.__schema.types));
22 |
23 | const schemaRules = {
24 | fields: {},
25 | shallowResolvers: {},
26 | specificResolvers: {},
27 | maxNested: true,
28 | totalResolvers: true,
29 | totalFields: true,
30 | };
31 |
32 | for (let i = 0; i < jsonifiedSchema.length; i += 1) {
33 | if (!defaultTypes.hasOwnProperty(jsonifiedSchema[i].name)) {
34 | if (jsonifiedSchema[i].name === 'RootQueryType') {
35 | for (let z = 0; z < jsonifiedSchema[i].fields.length; z += 1) {
36 | schemaRules.specificResolvers[`${jsonifiedSchema[i].name}_${jsonifiedSchema[i].fields[z].name}`] = true;
37 | schemaRules.shallowResolvers[jsonifiedSchema[i].fields[z].name] = true;
38 | }
39 | }
40 | for (let j = 0; j < jsonifiedSchema[i].fields.length; j += 1) {
41 | if (jsonifiedSchema[i].fields[j].type.kind === 'SCALAR') {
42 | schemaRules.fields[jsonifiedSchema[i].name] = true;
43 | }
44 | if (jsonifiedSchema[i].fields[j].type.kind !== 'SCALAR') {
45 | if (jsonifiedSchema[i].name !== 'RootQueryType') {
46 | schemaRules.shallowResolvers[jsonifiedSchema[i].name] = true;
47 | schemaRules.specificResolvers[`${jsonifiedSchema[i].name}_${jsonifiedSchema[i].fields[j].name}`] = true;
48 | }
49 | }
50 | }
51 | }
52 | }
53 | return schemaRules;
54 | };
55 |
--------------------------------------------------------------------------------
/Parser/stringProperties.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | lengthOfString: string => string.length,
3 | maxNested: 0,
4 | currentNested: 0,
5 |
6 | countResolvers: (object) => {
7 | const analytics = {
8 | specificResolvers: {},
9 | shallowResolvers: {},
10 | fields: {},
11 | };
12 | const recursiveObj = (object, strName, number = 0) => {
13 | const keys = Object.keys(object);
14 | for (let i = 0; i < keys.length; i += 1) {
15 | if (typeof object[keys[i]] === 'object') {
16 | if (analytics.specificResolvers.hasOwnProperty(`${strName}_${keys[i]}`)) {
17 | analytics.specificResolvers[`${strName}_${keys[i]}`] += 1;
18 | } else {
19 | analytics.specificResolvers[`${strName}_${keys[i]}`] = 1;
20 | }
21 | if (analytics.shallowResolvers.hasOwnProperty(keys[i])) {
22 | analytics.shallowResolvers[keys[i]] += 1;
23 | } else {
24 | analytics.shallowResolvers[keys[i]] = 1;
25 | }
26 | recursiveObj(object[keys[i]], keys[i]);
27 | } else if (analytics.fields.hasOwnProperty(keys[i])) {
28 | analytics.fields[keys[i]] += 1;
29 | } else {
30 | analytics.fields[keys[i]] = 1;
31 | }
32 | }
33 | };
34 | recursiveObj(object, 'RootQueryType');
35 | return analytics;
36 | },
37 |
38 | countDepth: (query) => {
39 | let num = -1;
40 | let max = -1;
41 | for (let i = 0; i < query.length; i += 1) {
42 | if (query[i] === '{') num += 1;
43 | if (query[i] === '}') num -= 1;
44 | if (num > max) max = num;
45 | }
46 | return max;
47 | },
48 | combinedResolvers: (obj) => {
49 | const arr = Object.values(obj.analytics.shallowResolvers);
50 | return arr.reduce((acc, ite) => acc += ite);
51 | },
52 |
53 | combinedFields: (obj) => {
54 | const arr = Object.values(obj.analytics.fields);
55 | return arr.reduce((acc, ite) => acc += ite);
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/Parser/stringValidator.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | firstAndLastParentes: string => (string[0] === '{' && string[string.length - 1] === '}'),
4 | balancedParentes: (string) => {
5 | const bracks = string.match(/[{}[\]()]/g, '').join('');
6 | const stack = [];
7 | const map = {
8 | '(': ')',
9 | '[': ']',
10 | '{': '}',
11 | };
12 | for (let i = 0; i < bracks.length; i += 1) {
13 | if (bracks[i] === '(' || bracks[i] === '{' || bracks[i] === '[') {
14 | stack.push(bracks[i]);
15 | } else {
16 | const last = stack.pop();
17 | if (bracks[i] !== map[last]) return false;
18 | }
19 | }
20 | return (stack.length !== 0);
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/Parser/tests/anagraphCreator.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | Anagraph Object tests
3 | ************/
4 | const anagraphCreator = require('../anagraphCreator')
5 |
6 | const sampleQuery = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 | const anagraph = anagraphCreator(sampleQuery)
8 |
9 | describe('AnagraphCreator', () => {
10 |
11 | test('is a function', () => {
12 | expect(typeof anagraphCreator).toBe('function');
13 | })
14 |
15 | test('returns an object', () => {
16 | expect(typeof anagraphCreator(sampleQuery)).toBe('object');
17 | })
18 |
19 | test('has a property called analytics', () => {
20 | expect(anagraph).toHaveProperty('analytics')
21 | })
22 |
23 | test('analytics property has a property called shallowResolvers', () => {
24 | expect(anagraph.analytics).toHaveProperty('shallowResolvers')
25 | })
26 |
27 | test('analytics property has a property called specificResolvers', () => {
28 | expect(anagraph.analytics).toHaveProperty('specificResolvers')
29 | })
30 |
31 | test('analytics property has a property called fields', () => {
32 | expect(anagraph.analytics).toHaveProperty('fields')
33 | })
34 |
35 | test('analytics property has a property called maxNested', () => {
36 | expect(anagraph.analytics).toHaveProperty('maxNested')
37 | })
38 |
39 | test('analytics property has a property called totalFields', () => {
40 | expect(anagraph.analytics).toHaveProperty('totalFields')
41 | })
42 |
43 | test('the value of anagraph.analytics.shallowResolvers is an object', () => {
44 | expect(typeof anagraph.analytics.shallowResolvers).toBe('object')
45 | })
46 |
47 | test('the value of anagraph.analytics.specificResolvers is an number', () => {
48 | expect(typeof anagraph.analytics.specificResolvers).toBe('object')
49 | })
50 |
51 | test('the value of anagraph.analytics.fields is an object', () => {
52 | expect(typeof anagraph.analytics.fields).toBe('object')
53 | })
54 |
55 | test('the value of anagraph.analytics.maxNested is an number', () => {
56 | expect(typeof anagraph.analytics.maxNested).toBe('number')
57 | })
58 |
59 | test('the value of anagraph.analytics.totalFields is an number', () => {
60 | expect(typeof anagraph.analytics.totalFields).toBe('number')
61 | })
62 |
63 |
64 |
65 |
66 | })
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Parser/tests/queryParser.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | Query Parser tests
3 | ************/
4 | const queryParser = require('../queryParser')
5 | const sampleQuery = '{ author { firstname }}'
6 |
7 | describe('queryParser', () => {
8 | test('is a function', () => {
9 | expect(typeof queryParser).toBe('function')
10 | })
11 | test('returns an object', () => {
12 | expect(typeof queryParser(sampleQuery)).toBe('object');
13 | })
14 |
15 | })
16 |
17 |
--------------------------------------------------------------------------------
/Parser/tests/stringProperties.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | stringProperties tests
3 | ************/
4 | const stringProperties = require('../stringProperties')
5 |
6 | const sampleString = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 |
8 | describe('stringProperties', () => {
9 |
10 | test('should throw an error on an empty string', () => {
11 | const emptyString = () => stringProperties('');
12 | expect(emptyString).toThrow(TypeError);
13 | });
14 |
15 | test('is an object', () => {
16 | expect(typeof stringProperties).toBe('object');
17 | })
18 |
19 | test('has a property called lengthOfString', () => {
20 | expect(stringProperties).toHaveProperty('lengthOfString')
21 | })
22 |
23 | test('has a property called countResolvers', () => {
24 | expect(stringProperties).toHaveProperty('countResolvers')
25 | })
26 |
27 | test('has a property called countDepth', () => {
28 | expect(stringProperties).toHaveProperty('countDepth')
29 | })
30 | test('has a property called combinedResolvers', () => {
31 | expect(stringProperties).toHaveProperty('combinedResolvers')
32 | })
33 |
34 | test('has a property called combinedFields', () => {
35 | expect(stringProperties).toHaveProperty('combinedFields')
36 | })
37 |
38 |
39 |
40 |
41 | })
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Parser/tests/stringValidator.test.js:
--------------------------------------------------------------------------------
1 | /***********
2 | stringProperties tests
3 | ************/
4 | const stringValidator = require('../stringValidator')
5 |
6 | const sampleString = '“{ authors { firstname books { title authors { firstname books { title authors { firstname books { title } } } } } } }“'
7 |
8 | describe('stringValidator.balancedParentes', () => {
9 | test('is a function', () => {
10 | expect(typeof stringValidator.balancedParentes).toBe('function')
11 | })
12 | test('should return a boolean', () => {
13 | expect(typeof stringValidator.balancedParentes(sampleString)).toBe('boolean')
14 | })
15 |
16 |
17 | })
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What is anagraphql?
2 |
3 | Anagraphql an express middleware package that can be used to analyze graphQL queries before they interact with a database.
4 |
5 | # Installation
6 |
7 | To install anagraphql, run the following command in the terminal:
8 |
9 | ```
10 | npm i anagraphql
11 | ```
12 |
13 | # Getting started
14 |
15 | To use anagraphql in your application, require it into the file where you're setting up your server.
16 |
17 | ```
18 | const anagraphql = require('anagraphql');
19 | ```
20 |
21 |
22 | From there, add anagraphql to your app.use function, as you would with any other middleware function. To render the anagraphql interactive playground, set graphiql equal to true, as shown below:
23 |
24 | ```
25 | app.use('/graphql',
26 | anagraphql({
27 | schema,
28 | rules,
29 | anagraphqlPlayground: true
30 | }),
31 | graphqlHTTP({
32 | schema,
33 | }));
34 | ```
35 | # Anangraphql Interactive Playground
36 |
37 | More soon...
38 |
39 |
--------------------------------------------------------------------------------
/anagraphqlEditor/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "root": true,
4 | "env": {
5 | "browser": true,
6 | "node": true
7 | },
8 | "rules": {
9 | "react/prop-types": 0
10 | }
11 | }
--------------------------------------------------------------------------------
/anagraphqlEditor/buildRun.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | npm run build
3 | cd ../graphqlTestServer/
4 | npm i
5 | nodemon index.js
--------------------------------------------------------------------------------
/anagraphqlEditor/dist/IMG_0672.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/anagraphql/c1ebb52e98675f14118c7214756b86da0634a34b/anagraphqlEditor/dist/IMG_0672.jpg
--------------------------------------------------------------------------------
/anagraphqlEditor/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AnagraphQL
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/anagraphqlEditor/dist/stylesheet.css:
--------------------------------------------------------------------------------
1 |
2 | .history{
3 | display:flex;
4 | }
5 |
6 | .rule-item {
7 | display: flex;
8 | justify-content: space-between;
9 | }
10 |
11 | .content {
12 | padding: 0 18px;
13 | background-color: #f1f1f1;
14 | max-height: 0;
15 | overflow: hidden;
16 | transition: max-height 0.2s ease-out;
17 | }
18 | .collapsible {
19 | background-color: #eee;
20 | color: #444;
21 | cursor: pointer;
22 | padding: 18px;
23 | width: 100%;
24 | border: none;
25 | text-align: left;
26 | outline: none;
27 | font-size: 15px;
28 | }
29 | .active, .collapsible:hover {
30 | background-color: #ccc;
31 | }
32 |
33 |
34 | #rules-container {
35 | display: grid;
36 | grid-template-columns: 33.3% 33.3% 33.3%;
37 | }
38 |
39 | .json-title {
40 | border-style: solid;
41 | border-width: medium;
42 | }
43 | .about {
44 | padding: 10px;
45 | }
46 |
47 | #rules {
48 | display:flex;
49 | flex-direction: column;
50 | }
51 | .current {
52 | border-width: 2px;
53 | border-bottom-width:2px;
54 | border-bottom-color:Black;
55 | border-bottom-style: solid;
56 | }
57 | .CodeMirror {
58 | border: 1px solid #eee;
59 | /* height: 100%; */
60 | }
61 |
62 | body {
63 | margin: 0;
64 | padding: 0;
65 | height: 100%;
66 | color: #333;
67 | background-color: #FFF;
68 | font-family: 'Montserrat', sans-serif;
69 |
70 | }
71 | #policy-options{
72 | display:grid;
73 | grid-template-columns: 20% 80%;
74 | }
75 |
76 | .radio-options{
77 | display: flex;
78 | justify-content: center;
79 | align-items: center;
80 | }
81 |
82 | #btn-container{
83 | display:grid;
84 | grid-template-rows: 50% 50%;
85 | }
86 |
87 | #options{
88 | display:grid;
89 | grid-template-columns: 33.3% 33.3% 33.3%;
90 | }
91 |
92 |
93 |
94 |
95 | #top{
96 | display:grid;
97 | grid-template-columns: 50% 50%;
98 | }
99 |
100 |
101 | #title {
102 | text-align: center;
103 | }
104 |
105 | #nav {
106 | margin: 0;
107 | padding: 0;
108 | width: 200px;
109 | background-color: #FAFAFA;
110 | height: 100vh;
111 | border-right: 1px solid #bbb;
112 | /* overflow: auto; */
113 | /* box-shadow: inset -4px 0px 15px #967d7d; */
114 |
115 | }
116 |
117 | .timelineFlex {
118 | width: 100%;
119 | padding-left: 12px;
120 | display: flex;
121 | justify-content: space-between;
122 |
123 |
124 | }
125 |
126 | #nav img{
127 | max-width: 100%;
128 | max-height:8rem;
129 | display: block;
130 | margin-left: auto;
131 | margin-right: auto;
132 | opacity: 0.6;
133 | }
134 |
135 | #nav a {
136 | display: block;
137 | color: #333;
138 | text-decoration: none;
139 | text-shadow: 2px;
140 | text-align: center;
141 | /* box-shadow: 0 5px #775d5d; */
142 | transform: translateY(40px);
143 | font-size: 20px;
144 | padding-top: 15px;
145 |
146 | }
147 |
148 | #nav a:hover {
149 | text-decoration: underline;
150 | }
151 |
152 | h2 {
153 | background-color: #FAFAFA;
154 | color: #666;
155 | margin: 0px;
156 | padding: 0px;
157 | padding-left: 3vh;
158 | }
159 |
160 | h2.query {
161 | background-color: #FAFAFA;
162 | color: #222;
163 | margin: 0px;
164 | padding: 0px;
165 | padding-left: 3vh;
166 | }
167 |
168 | .historyTimeline {
169 | width: 90% !important;
170 | height: 50px !important;
171 | }
172 | #timeline{ direction: rtl}
173 | .policiesContainer{
174 | padding: 10px;
175 | }
176 | option {
177 | size: 30px;
178 | color: green;
179 | }
180 | .marker {
181 | padding: 5px;
182 | width: 90%;
183 | display: flex;
184 | justify-content: space-between;
185 | }
186 |
187 | .historySlider {
188 | text-align: center;
189 | width: 90%;
190 | }
191 |
192 |
193 | .grid-container {
194 | height: 100%;
195 | width: 100%;
196 | display: grid;
197 | grid-template-columns: 1fr 1fr;
198 | grid-template-rows: 5fr 5fr 2fr;
199 | grid-template-areas: "GraphQL-Query Response" "Anagraph Policies" "Timeline Timeline";
200 | }
201 |
202 | .Timeline { grid-area: Timeline; border-top: 1px solid #bbb;}
203 | .GraphQL-Query { grid-area: GraphQL-Query; border-bottom: 1px solid #bbb;}
204 | .Response { grid-area: Response; border-left: 1px solid #bbb; border-bottom: 1px solid #bbb; }
205 | .Policies { grid-area: Policies; border-left: 1px solid #bbb;}
206 | .Anagraph { grid-area: Anagraph; }
--------------------------------------------------------------------------------
/anagraphqlEditor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anagraphqlEditor",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "dev": "NODE_ENV=development webpack-dev-server --open",
9 | "build": "NODE_ENV=production webpack",
10 | "start": "bash buildRun.sh"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "codemirror": "^5.48.2",
17 | "codemirror-graphql": "^0.8.3",
18 | "d3": "^5.9.7",
19 | "express": "^4.17.1",
20 | "graphql": "^14.4.2",
21 | "graphql-language-service-parser": "^1.2.2",
22 | "react": "^16.8.6",
23 | "react-animated-css": "^1.2.1",
24 | "react-codemirror2": "^6.0.0",
25 | "react-dom": "^16.8.6",
26 | "react-markdown": "^4.1.0",
27 | "react-redux": "^7.1.0",
28 | "react-router-dom": "^5.0.1",
29 | "react-syntax-highlighter": "^11.0.2",
30 | "redux": "^4.0.4",
31 | "redux-thunk": "^2.3.0"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "^7.5.5",
35 | "@babel/preset-env": "^7.5.5",
36 | "@babel/preset-react": "^7.0.0",
37 | "babel-loader": "^8.0.6",
38 | "css-loader": "^3.1.0",
39 | "eslint": "^6.1.0",
40 | "eslint-config-airbnb": "^17.1.1",
41 | "eslint-config-airbnb-base": "^13.2.0",
42 | "eslint-plugin-import": "^2.18.2",
43 | "eslint-plugin-jsx-a11y": "^6.2.3",
44 | "eslint-plugin-react": "^7.14.3",
45 | "jest": "^24.8.0",
46 | "nodemon": "^1.19.1",
47 | "style-loader": "^0.23.1",
48 | "webpack": "^4.37.0",
49 | "webpack-cli": "^3.3.6",
50 | "webpack-dev-server": "^3.7.2"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/actions/actions.js:
--------------------------------------------------------------------------------
1 | import { buildClientSchema } from 'graphql';
2 | import * as types from '../constants/actionTypes';
3 |
4 | export const updateQueryHistory = query => ({
5 | type: types.UPDATE_QUERY_HISTORY,
6 | payload: query,
7 | });
8 |
9 | export const updateQuery = query => ({
10 | type: types.UPDATE_QUERY,
11 | payload: query,
12 | });
13 |
14 | export const updateCurrAnagraph = anagraph => ({
15 | type: types.UPDATE_CURR_ANAGRAPH,
16 | payload: anagraph,
17 | });
18 |
19 | export const getSchema = body => (dispatch) => {
20 | fetch('/graphql', {
21 | method: 'POST',
22 | headers: {
23 | Accept: 'application/json',
24 | 'Content-Type': 'application/json',
25 | },
26 | body: JSON.stringify(body),
27 | credentials: 'include',
28 | })
29 | .then(response => response.json())
30 | .then((schema) => {
31 | dispatch({
32 | type: types.GET_SCHEMA,
33 | payload: {
34 | applicableRules: schema.applicableRules,
35 | schema: buildClientSchema(schema.data),
36 | },
37 | });
38 | });
39 | };
40 |
41 | export const updateCurrResponse = resp => ({
42 | type: types.UPDATE_CURR_RESPONSE,
43 | payload: resp,
44 | });
45 |
46 | export const getQueryResponse = ({ query, currRule }) => (dispatch) => {
47 | fetch('/graphql', {
48 | method: 'POST',
49 | headers: {
50 | Accept: 'application/json',
51 | 'Content-Type': 'application/json',
52 | },
53 | body: JSON.stringify({
54 | query,
55 | currRule,
56 | }),
57 | credentials: 'include',
58 | })
59 | .then(response => response.json())
60 | .then((data) => {
61 | dispatch({ type: types.GET_QUERY_RESPONSE, payload: data });
62 | })
63 | .catch(err => console.log(`error in fetch: ${err}`));
64 | };
65 |
66 | export const saveConfiguration = rules => ({
67 | type: types.SAVE_CONFIGURATION,
68 | payload: rules,
69 | });
70 |
71 | export const updateNestedQueries = num => ({
72 | type: types.UPDATE_NESTED_QUERIES,
73 | payload: num,
74 | });
75 |
76 | export const updateFields = num => ({
77 | type: types.UPDATE_FIELDS,
78 | payload: num,
79 | });
80 |
81 | export const updateResolvers = num => ({
82 | type: types.UPDATE_RESOLVERS,
83 | payload: num,
84 | });
85 |
86 | export const updateCurrRule = (rule) => {
87 | console.log(rule);
88 | return ({
89 | type: types.UPDATE_CURR_RULE,
90 | payload: rule,
91 | });
92 | };
93 |
94 | export const updateShallowResolvers = obj => ({
95 | type: types.UPDATE_SHALLOW_RESOLVERS,
96 | payload: obj,
97 | });
98 |
99 | export const updateSpecificResolvers = obj => ({
100 | type: types.UPDATE_SPECIFIC_RESOLVERS,
101 | payload: obj,
102 | });
103 |
104 | export const deleteRule = (...params) => ({
105 | type: types.DELETE_RULE,
106 | payload: params,
107 | });
108 |
109 | export const updateClientRules = rule => ({
110 | type: types.UPDATE_CLIENT_RULES,
111 | payload: rule,
112 | });
113 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import SideBar from './SideBar';
5 |
6 |
7 | const App = () => (
8 |
9 |
10 |
11 |
12 |
13 | );
14 |
15 | export default App;
16 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/ApplicableRules.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import {
4 | updateFields, updateNestedQueries, updateResolvers,
5 | updateShallowResolvers, updateSpecificResolvers,
6 | } from '../actions/actions';
7 | import Collapsible from './Collapsible';
8 |
9 | const ApplicableRules = ({ availableRules, setRules }) => {
10 | const dispatch = useDispatch();
11 | const { CLIENT_RULES } = useSelector(state => state.rules);
12 | const globalRules = [];
13 | if (availableRules.maxNested) {
14 | globalRules.push((
15 |
16 |
Max Nested
17 |
30 |
31 | ));
32 | }
33 | if (availableRules.totalResolvers) {
34 | globalRules.push((
35 |
36 |
Max Number of Resolvers
37 |
50 |
51 | ));
52 | }
53 |
54 | if (availableRules.totalFields) {
55 | globalRules.push((
56 |
57 |
Max Number of Fields
58 |
71 |
72 | ));
73 | }
74 |
75 |
76 | const shallowResolvers = Object.keys(availableRules.shallowResolvers).map((key) => {
77 | const payload = {};
78 | payload[key] = 4;
79 | return (
80 |
81 | {' '}
82 |
{`Max ${key}`}
83 |
96 |
97 | );
98 | });
99 |
100 | const specificResolvers = Object.keys(availableRules.specificResolvers).map((key) => {
101 | const payload = {};
102 | payload[key] = 4;
103 | return (
104 |
105 | {' '}
106 |
{`Max ${key}`}
107 |
120 |
121 | );
122 | });
123 |
124 | return (
125 |
126 |
127 |
128 |
129 |
130 | );
131 | };
132 |
133 | export default ApplicableRules;
134 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/CodeBlock.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SyntaxHighlighter from 'react-syntax-highlighter';
3 |
4 | export default class CodeBlock extends React.PureComponent {
5 | render() {
6 | const { language, value } = this.props;
7 |
8 | return (
9 |
10 | {value}
11 |
12 | );
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/CodeContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { parse, print } from 'graphql';
4 | import {
5 | getQueryResponse, updateQueryHistory, updateQuery,
6 | } from '../actions/actions';
7 | import CodeEditor from './CodeEditor';
8 | import JsonDisplay from './JsonDisplay';
9 | import History from './History';
10 | import Headline from './Headline';
11 | // import MainContainer from "./containers/MainContainer.jsx";
12 | const CodeContainer = () => {
13 | const response = useSelector(state => state.response.currResponse);
14 | const currAnagraph = useSelector(state => state.response.currAnagraph);
15 | const { currRule } = useSelector(state => state.rules);
16 | const { query } = useSelector(state => state.query);
17 |
18 | const [hasErrors, setErrors] = useState(true);
19 | const dispatch = useDispatch();
20 |
21 | const prettifyQuery = () => dispatch(updateQuery(print(parse(query))));
22 |
23 | const handleQuery = () => {
24 | if (!hasErrors) {
25 | prettifyQuery();
26 | dispatch(getQueryResponse({ query, currRule }));
27 | dispatch(updateQueryHistory(print(parse(query))));
28 | }
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default CodeContainer;
62 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/CodeEditor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Controlled as CodeMirror } from 'react-codemirror2';
3 | import { useSelector, useDispatch } from 'react-redux';
4 | import { updateQuery } from '../actions/actions';
5 |
6 | import 'codemirror/lib/codemirror';
7 | import 'codemirror/lib/codemirror.css';
8 | import 'codemirror/addon/hint/show-hint';
9 | import 'codemirror/addon/hint/show-hint.css';
10 | import 'codemirror/addon/comment/comment';
11 | import 'codemirror/addon/edit/matchbrackets';
12 | import 'codemirror/addon/edit/closebrackets';
13 | import 'codemirror/addon/fold/foldgutter';
14 | import 'codemirror/addon/fold/brace-fold';
15 | import 'codemirror/addon/search/search';
16 | import 'codemirror/addon/search/jump-to-line';
17 | import 'codemirror/addon/dialog/dialog';
18 | import 'codemirror/keymap/sublime';
19 | import 'codemirror/addon/lint/lint';
20 | import 'codemirror/addon/lint/lint.css';
21 | import 'codemirror-graphql/hint';
22 | import 'codemirror-graphql/lint';
23 | import 'codemirror-graphql/info';
24 | import 'codemirror-graphql/jump';
25 | import 'codemirror-graphql/mode';
26 |
27 | const CodeEditor = ({ hasErrors, setErrors, prettifyQuery }) => {
28 | const AUTO_COMPLETE_AFTER_KEY = /^[a-zA-Z0-9_@(]$/;
29 |
30 | const { query, schema } = useSelector(state => ({
31 | query: state.query.query,
32 | schema: state.query.schema,
33 | }));
34 | const dispatch = useDispatch();
35 | const options = {
36 | lineNumbers: true,
37 | tabSize: 2,
38 | keyMap: 'sublime',
39 | autoCloseBrackets: true,
40 | matchBrackets: true,
41 | showCursorWhenSelecting: true,
42 | foldGutter: {
43 | minFoldSize: 4,
44 | },
45 | mode: 'graphql',
46 | gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
47 | lint: {
48 | schema,
49 | },
50 | hintOptions: {
51 | schema,
52 | closeOnUnfocus: false,
53 | completeSingle: false,
54 | },
55 | extraKeys: {
56 | 'Alt-P': () => {
57 | if (!hasErrors) prettifyQuery();
58 | },
59 | },
60 | theme: 'default',
61 | };
62 |
63 |
64 | return (
65 |
66 | {
69 | if (AUTO_COMPLETE_AFTER_KEY.test(event.key)) {
70 | editor.execCommand('autocomplete');
71 | }
72 | }}
73 | onBeforeChange={(editor, data, value) => dispatch(updateQuery(value))}
74 | onUpdate={editor => setErrors(editor.state.lint.marked.length !== 0)}
75 | options={options}
76 | />
77 |
78 |
79 | );
80 | };
81 |
82 | export default CodeEditor;
83 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/Collapsible.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | export default ({ rules, text }) => {
4 | const [active, setActive] = useState(false);
5 | const toggle = () => setActive(!active);
6 | return (
7 |
8 |
9 |
10 | {rules}
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/Graph.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import * as d3 from 'd3';
3 |
4 | const Graph = (props) => {
5 | useEffect(() => {
6 | const { data } = props;
7 |
8 | const adjlist = [];
9 | function neigh(a, b) {
10 | return a == b || adjlist[`${a}-${b}`];
11 | }
12 | data.links.forEach((d) => {
13 | adjlist[`${d.source.index}-${d.target.index}`] = true;
14 | adjlist[`${d.target.index}-${d.source.index}`] = true;
15 | });
16 |
17 | function focus(d) {
18 | const { index } = d3.select(d3.event.target).datum();
19 | /* node.style('opacity', o => (neigh(index, o.index) ? 1 : 0.1)) */
20 | // labelNode.attr('display', o => (neigh(index, o.node.index) ? 'block' : 'none'));
21 | link.style('stroke', o => (o.source.index == index ? 'blue' : o.target.index == index ? 'red' : 'black'));
22 | /* link.style('stroke', o => ( o.target.index == index ? 'red' : 'black')) */
23 | link.attr('stroke-width', o => (o.source.index == index || o.target.index == index ? '3px' : '2px'));
24 | }
25 |
26 | function unfocus() {
27 | // labelNode.attr('display', 'block');
28 | /* node.style('opacity', 1) */
29 | link.style('stroke', 'black');
30 | link.attr('stroke-width', '2px');
31 | }
32 |
33 | // Create somewhere to put the force directed graph
34 | const svg = d3.select('svg');
35 | const width = +svg.attr('width');
36 | const height = +svg.attr('height');
37 |
38 | const rectWidth = 140;
39 | const rectHeight = 120;
40 | const minDistance = Math.sqrt(rectWidth * rectWidth + rectHeight * rectHeight);
41 |
42 | // Set up the simulation and add forces
43 | const simulation = d3.forceSimulation()
44 | .nodes(data.nodes);
45 |
46 | const link_force = d3.forceLink(data.links)
47 | .id(d => d.id).distance(minDistance).strength(1);
48 |
49 | const charge_force = d3.forceManyBody()
50 | .strength(-1000);
51 |
52 | const center_force = d3.forceCenter(width / 2, height / 2);
53 |
54 | simulation
55 | .force('charge_force', charge_force)
56 | .force('center_force', center_force)
57 | .force('links', link_force)
58 | .force('y', d3.forceY(height / 2).strength(0.10));
59 |
60 |
61 | // Add tick instructions:
62 | simulation.on('tick', tickActions);
63 | svg.append('svg:defs').append('svg:marker')
64 | .attr('id', 'arrow')
65 | .attr('viewBox', '0 0 12 12')
66 | .attr('refX', 10)
67 | .attr('refY', 6)
68 | .attr('markerWidth', 12)
69 | .attr('markerHeight', 12)
70 | .attr('orient', 'auto')
71 | .attr('fill', 'darkgreen')
72 | .append('svg:path')
73 | .attr('d', 'M2,2 L10,6 L2,10 L6,6 L2,2');
74 |
75 |
76 | // Add encompassing group for the zoom
77 | const g = svg.append('g')
78 | .attr('class', 'everything');
79 |
80 | const div = g.select('body').append('div')
81 | .attr('class', 'tooltip')
82 | .style('opacity', 0);
83 |
84 | // Draw lines for the links
85 | const link = g.append('g')
86 | .attr('class', 'links')
87 | .selectAll('line')
88 | .data(data.links)
89 | .enter()
90 | .append('path')
91 | .style('stroke', 'black')
92 | .style('fill', 'none')
93 | .attr('stroke-width', '2px')
94 | .attr('marker-start', 'url(#arrow)')
95 | .attr('marker-mid', 'url(#arrow)')
96 | .attr('marker-end', 'url(#arrow)');
97 | /* .attr('marker-start', (d) => "url(#arrow)")
98 | .style( "stroke-width", 3 ); */
99 |
100 | // Draw rects and texts for the nodes
101 | const nodes = g.append('g')
102 | .attr('class', 'nodes');
103 |
104 | const node = nodes.selectAll('node')
105 | .data(data.nodes)
106 | .enter()
107 | .append('g');
108 |
109 | node.on('mouseover', focus).on('mouseout', unfocus);
110 |
111 |
112 | const rect = node.append('rect')
113 | .attr('x', -rectWidth / 2)
114 | .attr('y', -rectHeight / 2)
115 | .attr('width', rectWidth)
116 | .attr('height', rectHeight)
117 | .attr('fill', rectColour);
118 |
119 | const textName = node.append('text')
120 | .text(d => d.name)
121 | .attr('y', function () { return this.previousSibling.y.baseVal.value; })
122 | .style('text-anchor', 'middle')
123 | .attr('font-weight', 'bold');
124 |
125 | /* const textCvr = node.append('text')
126 | .text(d => d.fields)
127 | .attr('y', 0)
128 | .style('text-anchor', 'middle'); */
129 | const textCvr = node.each(function (d, i) {
130 | d3.select(this).selectAll('node')
131 | .data(d.fields)
132 | .enter()
133 | .append('text')
134 | .text(t => t)
135 | .attr('y', function (t, i) { return this.parentElement.firstChild.y.baseVal.value + (i + 1) * 16; })
136 | .style('text-anchor', 'middle');
137 | });
138 |
139 |
140 | /* const textOwned = node.append('text')
141 | .text('%')
142 | .attr('y', 15)
143 | .style('text-anchor', 'middle'); */
144 |
145 | node.attr('transform', d => `translate(${d.x},${d.y})`);
146 |
147 | // Add drag capabilities
148 | const drag_handler = d3.drag()
149 | .on('start', drag_start)
150 | .on('drag', drag_drag)
151 | .on('end', drag_end);
152 |
153 | drag_handler(node);
154 |
155 | // Add zoom capabilities
156 | const zoom_handler = d3.zoom()
157 | .on('zoom', zoom_actions);
158 |
159 | zoom_handler(svg);
160 |
161 | /** Functions * */
162 |
163 | function rectColour(d) {
164 | if (d.person) {
165 | return 'blue';
166 | }
167 | return 'pink';
168 | }
169 |
170 | // Function to choose the line colour and thickness
171 | function linkColour(d) {
172 | return 'black';
173 | }
174 |
175 | // Drag functions
176 | function drag_start(d) {
177 | if (!d3.event.active) simulation.alphaTarget(0.3).restart();
178 | d.fx = d.x;
179 | d.fy = d.y;
180 | }
181 |
182 | // Make sure you can't drag the rect outside the box
183 | function drag_drag(d) {
184 | d.fx = d3.event.x;
185 | d.fy = d3.event.y;
186 | }
187 |
188 | function drag_end(d) {
189 | if (!d3.event.active) simulation.alphaTarget(0);
190 | // d.fx = null;
191 | // d.fy = null;
192 | d.fx = d3.event.x;
193 | d.fy = d3.event.y;
194 | }
195 |
196 | // Zoom functions
197 | function zoom_actions() {
198 | g.attr('transform', d3.event.transform);
199 | }
200 |
201 | function tickActions() {
202 | // update node positions each tick of the simulation
203 | node.attr('transform', d => `translate(${d.x},${d.y})`);
204 | // update link positions
205 | link.attr('d', (d) => {
206 | const dx = d.target.x - d.source.x;
207 | const dy = d.target.y - d.source.y;
208 | const dr = Math.sqrt(dx * dx + dy * dy);
209 | return `M${
210 | d.source.x},${
211 | d.source.y}A${
212 | dr},${dr} 0 0,1 ${
213 | d.target.x},${
214 | d.target.y}`;
215 | });
216 | }
217 | }, []);
218 |
219 | return ;
220 | };
221 |
222 | export default Graph;
223 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/Headline.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PolicySelector from './PolicySelector';
3 |
4 | export default function Headline({ header }) {
5 | if (header === 'Policies') {
6 | return (
7 |
11 | );
12 | }
13 | return (
14 |
15 |
{header}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/History.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { Animated } from 'react-animated-css';
4 | import { updateCurrResponse, updateCurrAnagraph, updateQuery } from '../actions/actions';
5 | // import play from '../play.svg';
6 |
7 | const History = ({ handleQuery, hasErrors }) => {
8 | const responseList = useSelector(state => state.response.history.response);
9 | const anagraphList = useSelector(state => state.response.history.anagraph);
10 | const queryList = useSelector(state => state.response.history.query);
11 |
12 | const dispatch = useDispatch();
13 | const handleUpdate = (e) => {
14 | const ind = e.target.value;
15 | // document.querySelectorAll('.current').forEach(cv => cv.removeAttribute('class'));
16 | // e.currentTarget.parentElement.parentElement.setAttribute('class', 'current');
17 | dispatch(updateCurrResponse(responseList[ind]));
18 | dispatch(updateCurrAnagraph(anagraphList[ind]));
19 | dispatch(updateQuery(queryList[ind]));
20 | };
21 | let slider = 'Timeline will be built after a query...';
22 | if (responseList.length > 0) {
23 | slider = ;
24 | }
25 |
26 | const markers = responseList.map((x, i) => (
27 |
30 | ));
31 |
32 | return (
33 |
34 |
35 |
36 |
37 | {markers}
38 |
39 | {slider}
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default History;
48 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/JsonDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Controlled as CodeMirror } from 'react-codemirror2';
3 | import 'codemirror/addon/lint/lint.css';
4 | import 'codemirror/addon/fold/foldgutter';
5 | import 'codemirror/addon/dialog/dialog';
6 | import 'codemirror/addon/search/search';
7 | import 'codemirror/addon/search/searchcursor';
8 | import 'codemirror/addon/search/jump-to-line';
9 | import 'codemirror/keymap/sublime';
10 | import 'codemirror/addon/fold/brace-fold';
11 | import 'codemirror/addon/lint/lint';
12 | import 'codemirror/addon/lint/json-lint';
13 | import 'codemirror/mode/javascript/javascript';
14 | import 'codemirror/lib/codemirror.css';
15 |
16 |
17 | const JsonDisplay = ({ json }) => {
18 | const options = {
19 | lineNumbers: true,
20 | tabSize: 2,
21 | keyMap: 'sublime',
22 | autoCloseBrackets: true,
23 | matchBrackets: true,
24 | showCursorWhenSelecting: true,
25 | foldGutter: {
26 | minFoldSize: 4,
27 | },
28 | mode: 'application/json',
29 | readOnly: true,
30 | gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
31 | };
32 | return (
33 |
37 | );
38 | };
39 |
40 | export default JsonDisplay;
41 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/PoliciesContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useSelector } from 'react-redux';
3 | // import RulesDisplay from './RulesDisplay';
4 | import RulesConfiguration from './RulesConfiguration';
5 | import JsonDisplay from './JsonDisplay';
6 | import ApplicableRules from './ApplicableRules';
7 |
8 | const PoliciesContainer = () => {
9 | const { CLIENT_RULES } = useSelector(state => state.rules);
10 | const { applicableRules } = useSelector(state => state.query);
11 | const [availableRules, setRules] = useState(applicableRules);
12 |
13 | return (
14 |
21 | );
22 | };
23 |
24 | export default PoliciesContainer;
25 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/PolicySelector.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { updateCurrRule } from '../actions/actions';
4 |
5 | const PolicySelector = () => {
6 | const [option, setOption] = useState('MERGE');
7 | const { CLIENT_RULES, SERVER_RULES } = useSelector(state => state.rules);
8 | const dispatch = useDispatch();
9 | const update = (str) => {
10 | setOption(str);
11 | switch (str) {
12 | case 'SERVER':
13 | dispatch(updateCurrRule(SERVER_RULES));
14 | break;
15 | case 'CLIENT':
16 | dispatch(updateCurrRule(CLIENT_RULES));
17 | break;
18 | case 'MERGE':
19 | dispatch(updateCurrRule({
20 | ...SERVER_RULES,
21 | ...CLIENT_RULES,
22 | shallowResolvers: {
23 | ...SERVER_RULES.shallowResolvers,
24 | ...CLIENT_RULES.shallowResolvers,
25 | },
26 | specificResolvers: {
27 | ...SERVER_RULES.specificResolvers,
28 | ...CLIENT_RULES.specificResolvers,
29 | },
30 | }));
31 | break;
32 | default:
33 | dispatch(updateCurrRule(CLIENT_RULES));
34 | }
35 | };
36 | useEffect(() => update(option), [option]);
37 | return (
38 |
73 | );
74 | };
75 |
76 | export default PolicySelector;
77 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/RulesConfiguration.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import {
4 | updateFields, updateNestedQueries, updateResolvers,
5 | updateShallowResolvers, updateSpecificResolvers, deleteRule,
6 | } from '../actions/actions';
7 |
8 | const buildGlobal = (maxNested, totalResolvers, totalFields, dispatch, avail, setRules) => {
9 | const globalRules = [];
10 | if (maxNested) {
11 | globalRules.push(
12 |
13 |
25 |
Max number of nested queries allowed
26 |
{ dispatch(updateNestedQueries(Number(e.target.value))); }} id="numberOfNestedQueries" />
27 | {maxNested}
28 |
,
29 | );
30 | }
31 |
32 | if (totalResolvers) {
33 | globalRules.push(
34 |
35 |
46 |
Max number of resolvers allowed
47 |
{ dispatch(updateResolvers(Number(e.target.value))); }} id="numberOfResolvers" />
48 | {totalResolvers}
49 |
,
50 | );
51 | }
52 |
53 | if (totalFields) {
54 | globalRules.push(
55 |
56 |
68 |
69 |
Max number of fields allowed
70 |
{ dispatch(updateFields(Number(e.target.value))); }} id="numberOfFields" />
71 | {totalFields}
72 |
,
73 | );
74 | }
75 |
76 | return globalRules;
77 | };
78 |
79 | const buildResolvers = (type, obj, action, dispatch, avail, setRules) => Object.keys(obj).map(cv => (
80 |
81 |
93 |
94 | {cv}
95 | {' '}
96 | {`${type} resolver`}
97 |
98 |
{
104 | const toReturn = {};
105 | toReturn[cv] = Number(e.target.value);
106 | dispatch(action(toReturn));
107 | }}
108 | />
109 | {obj[cv] || 0}
110 |
111 | ));
112 |
113 | const RulesConfiguration = ({ setRules, availableRules }) => {
114 | const {
115 | maxNested, totalResolvers, totalFields, shallowResolvers, specificResolvers,
116 | } = useSelector(state => state.rules.CLIENT_RULES);
117 | const dispatch = useDispatch();
118 | const globalRules = buildGlobal(maxNested, totalResolvers, totalFields, dispatch, availableRules, setRules);
119 | const shallowRules = buildResolvers('Shallow', shallowResolvers, updateShallowResolvers, dispatch, availableRules, setRules);
120 | const specificRules = buildResolvers('Specific', specificResolvers, updateSpecificResolvers, dispatch, availableRules, setRules);
121 |
122 | return (
123 |
124 |
125 | {specificRules}
126 | {shallowRules}
127 | {globalRules}
128 |
129 | );
130 | };
131 |
132 | export default RulesConfiguration;
133 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/RulesDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector, useDispatch } from 'react-redux';
3 | import { saveConfiguration, updateCurrRule } from '../actions/actions';
4 |
5 | const RulesDisplay = () => {
6 | const rulesObject = useSelector(state => state.rules.currRule);
7 | const rulesList = useSelector(state => state.rules.rules);
8 | const dispatch = useDispatch();
9 |
10 | const handleUpdate = (e) => {
11 | e.preventDefault();
12 | dispatch(saveConfiguration({ name: e.target.firstChild.value, rules: rulesObject }));
13 | e.target.firstChild.value = '';
14 | };
15 |
16 | const onClickHandler = (index) => {
17 | dispatch(updateCurrRule(index));
18 | };
19 |
20 | const list = rulesList.map((rule, index) => (
21 | onClickHandler(index)}
24 | // onKeyUp={(e) => {
25 | // console.log(e.keyCode);
26 | // onClickHandler(index);
27 | // }}
28 | >
29 | {rule.name}
30 | {rule.rules.numberOfNestedQueries}
31 | {rule.rules.numberOfResolvers}
32 | {rule.rules.numberOfFields}
33 |
34 | ));
35 |
36 | return (
37 |
45 | );
46 | };
47 |
48 | export default RulesDisplay;
49 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/SideBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Animated } from 'react-animated-css';
3 | import {
4 | Route,
5 | Link,
6 | } from 'react-router-dom';
7 | import { useSelector } from 'react-redux';
8 | import ReactMarkdown from 'react-markdown';
9 | import CodeBlock from './CodeBlock';
10 | import CodeContainer from './CodeContainer';
11 | import Visualizer from './Visualizer';
12 | import PoliciesContainer from './PoliciesContainer';
13 |
14 | const SideBar = () => {
15 | const routes = [
16 | {
17 | path: '/graphql',
18 | exact: true,
19 | main: () => (
20 |
21 | ),
22 | },
23 | {
24 | path: '/graphql/about',
25 | exact: true,
26 | main: () => (
27 |
34 | ),
35 | },
36 | {
37 | path: '/graphql/policies',
38 | exact: true,
39 | main: () => {
40 | if (!useSelector(state => state.query.applicableRules)) return Loading...
;
41 | return ;
42 | },
43 | },
44 | {
45 | path: '/graphql/visualizer',
46 | exact: true,
47 | main: () => ,
48 | },
49 | ];
50 | return (
51 |
52 |
53 |
61 |

62 |
66 | - Query
67 | - About
68 | - Policies
69 | - Visualizer
70 |
71 |
72 |
78 | {routes.map(route => (
79 |
85 | ))}
86 |
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default SideBar;
94 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/components/Visualizer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector } from 'react-redux';
3 | import d3DataCreator from '../utility/d3DataCreator';
4 | import Graph from './Graph';
5 |
6 | const Visualizer = () => {
7 | const data = d3DataCreator(useSelector(state => state.query.schema));
8 | return (
9 |
10 |
11 |
12 | );
13 | };
14 |
15 | export default Visualizer;
16 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/constants/actionTypes.js:
--------------------------------------------------------------------------------
1 | // Describes actions needed for redux reducers
2 | // Basically CRUD actions for interacting with query state on the frontend
3 | // The functionality for these actionTypes are in the action file
4 |
5 | export const UPDATE_QUERY = 'UPDATE_QUERY';
6 | export const GET_QUERY_RESPONSE = 'GET_QUERY_RESPONSE';
7 | export const GET_SCHEMA = 'GET_SCHEMA';
8 | export const UPDATE_CURR_RESPONSE = 'UPDATE_CURR_RESPONSE';
9 | export const CREATE_ANAGRAPH = 'CREATE_ANAGRAPH';
10 | export const UPDATE_CURR_ANAGRAPH = 'UPDATE_CURR_ANAGRAPH';
11 | export const UPDATE_QUERY_HISTORY = 'UPDATE_QUERY_HISTORY';
12 | export const SAVE_CONFIGURATION = 'SAVE_CONFIGURATION';
13 | export const UPDATE_NESTED_QUERIES = 'UPDATE_NESTED_QUERIES';
14 | export const UPDATE_FIELDS = 'UPDATE_FIELDS';
15 | export const UPDATE_RESOLVERS = 'UPDATE_RESOLVERS';
16 | export const UPDATE_CURR_RULE = 'UPDATE_CURR_RULE';
17 | export const UPDATE_SHALLOW_RESOLVERS = 'UPDATE_SHALLOW_RESOLVERS';
18 | export const UPDATE_SPECIFIC_RESOLVERS = 'UPDATE_SPECIFIC_RESOLVERS';
19 | export const DELETE_RULE = 'DELETE_RULE';
20 | export const UPDATE_CLIENT_RULES = 'UPDATE_CLIENT_RULES';
21 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/index.jsx:
--------------------------------------------------------------------------------
1 | import { render } from 'react-dom';
2 | import React from 'react';
3 | import { Provider } from 'react-redux';
4 | import App from './components/App';
5 | import store from './store';
6 | import '../../dist/stylesheet.css';
7 |
8 | render(
9 | , document.getElementById('root'),
10 | );
11 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
43 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/reducers/queryReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_QUERY, GET_SCHEMA,
3 | } from '../constants/actionTypes';
4 |
5 |
6 | // Sets variables for inital query store
7 | const initialState = {
8 | query: '',
9 | schema: null,
10 | applicableRules: null,
11 | };
12 |
13 | // The queryReducer function contains a seriers of conditionals that perform a specific action based on what action from our imported actions are passed in as an argument
14 | const queryReducer = (state = initialState, action) => {
15 | switch (action.type) {
16 | case UPDATE_QUERY:
17 | return { ...state, query: action.payload };
18 | case GET_SCHEMA:
19 | return {
20 | ...state,
21 | schema: action.payload.schema,
22 | applicableRules: action.payload.applicableRules,
23 | };
24 | default:
25 | return state;
26 | }
27 | };
28 |
29 | export default queryReducer;
30 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/reducers/responseReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_QUERY_RESPONSE,
3 | UPDATE_CURR_RESPONSE,
4 | CREATE_ANAGRAPH,
5 | UPDATE_CURR_ANAGRAPH,
6 | UPDATE_QUERY_HISTORY,
7 | } from '../constants/actionTypes';
8 |
9 |
10 | // Sets variables for initial response store
11 | const initialState = {
12 | currResponse: null,
13 | currAnagraph: null,
14 | // responseList: [],
15 | // anagraphList: [],
16 | history: { response: [], anagraph: [], query: [] },
17 | };
18 |
19 | // The responseReducer function returns either a default response (state) or, if the GET_QUERY_RESPONSE action type is passed as an argument, returns the current response and response list.
20 |
21 | const responseReducer = (state = initialState, action) => {
22 | switch (action.type) {
23 | case GET_QUERY_RESPONSE:
24 | return {
25 | ...state,
26 | currResponse: { ...action.payload.data },
27 | currAnagraph: action.payload.anagraph,
28 | // responseList: [...state.responseList, action.payload],
29 | history: {
30 | anagraph: [...state.history.anagraph, action.payload.anagraph],
31 | response: [...state.history.response, { ...action.payload.data }],
32 | query: [...state.history.query],
33 | },
34 | };
35 | case UPDATE_CURR_RESPONSE:
36 | return {
37 | ...state,
38 | currResponse: action.payload,
39 | };
40 | case CREATE_ANAGRAPH:
41 | return {
42 | ...state,
43 | // anagraphList: [...state.anagraphList, action.payload],
44 | history: {
45 | anagraph: [...state.history.anagraph, action.payload],
46 | response: [...state.history.response],
47 | query: [...state.history.query],
48 | },
49 | currAnagraph: action.payload,
50 | };
51 | case UPDATE_CURR_ANAGRAPH:
52 | return {
53 | ...state,
54 | currAnagraph: action.payload,
55 | };
56 | case UPDATE_QUERY_HISTORY:
57 | return {
58 | ...state,
59 | // anagraphList: [...state.anagraphList, action.payload],
60 | history: {
61 | anagraph: [...state.history.anagraph],
62 | response: [...state.history.response],
63 | query: [...state.history.query, action.payload],
64 | },
65 | };
66 | default:
67 | return state;
68 | }
69 | };
70 |
71 | export default responseReducer;
72 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/reducers/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import queryReducer from './queryReducer';
3 | import responseReducer from './responseReducer';
4 | import rulesReducer from './rulesReducer';
5 |
6 | //The combineReducers function (which is a method on the redux object) takes the evaluated results of both the queryReducer and the responseReducer and exports them an a single object.
7 |
8 | export default combineReducers({
9 | query: queryReducer,
10 | response: responseReducer,
11 | rules: rulesReducer,
12 | });
13 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/reducers/rulesReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_CURR_RULE, SAVE_CONFIGURATION, UPDATE_NESTED_QUERIES,
3 | UPDATE_RESOLVERS, UPDATE_FIELDS, UPDATE_SHALLOW_RESOLVERS,
4 | UPDATE_SPECIFIC_RESOLVERS, DELETE_RULE, UPDATE_CLIENT_RULES,
5 | } from '../constants/actionTypes';
6 |
7 | const initialState = {
8 | currRule: queryRules || {
9 | shallowResolvers: {},
10 | specificResolvers: {},
11 | },
12 | CLIENT_RULES: {
13 | shallowResolvers: {},
14 | specificResolvers: {},
15 | },
16 | SERVER_RULES: queryRules || {
17 | shallowResolvers: {},
18 | specificResolvers: {},
19 | },
20 | rules: [],
21 | };
22 |
23 |
24 | const rulesReducer = (state = initialState, action) => {
25 | switch (action.type) {
26 | case SAVE_CONFIGURATION:
27 | return {
28 | ...state,
29 | rules: [...state.rules, action.payload],
30 | };
31 | case UPDATE_CURR_RULE:
32 | return {
33 | ...state,
34 | currRule: action.payload,
35 | };
36 | case UPDATE_NESTED_QUERIES:
37 | return {
38 | ...state,
39 | CLIENT_RULES: {
40 | ...state.CLIENT_RULES,
41 | maxNested: action.payload,
42 | },
43 | };
44 | case UPDATE_FIELDS:
45 | return {
46 | ...state,
47 | CLIENT_RULES: {
48 | ...state.CLIENT_RULES,
49 | totalFields: action.payload,
50 | },
51 | };
52 | case UPDATE_RESOLVERS:
53 | return {
54 | ...state,
55 | CLIENT_RULES: {
56 | ...state.CLIENT_RULES,
57 | totalResolvers: action.payload,
58 | },
59 | };
60 | case UPDATE_SHALLOW_RESOLVERS:
61 | return {
62 | ...state,
63 | CLIENT_RULES: {
64 | ...state.CLIENT_RULES,
65 | shallowResolvers: { ...state.CLIENT_RULES.shallowResolvers, ...action.payload },
66 | },
67 | };
68 | case UPDATE_SPECIFIC_RESOLVERS:
69 | return {
70 | ...state,
71 | CLIENT_RULES: {
72 | ...state.CLIENT_RULES,
73 | specificResolvers: { ...state.CLIENT_RULES.specificResolvers, ...action.payload },
74 | },
75 | };
76 | case DELETE_RULE: {
77 | if (action.payload.length === 2) {
78 | const temp = {};
79 | temp[action.payload[0]] = { ...state.CLIENT_RULES[action.payload[0]] };
80 | delete temp[action.payload[0]][action.payload[1]];
81 | return {
82 | ...state,
83 | CLIENT_RULES: {
84 | ...state.CLIENT_RULES,
85 | ...temp,
86 | },
87 | };
88 | }
89 |
90 | const temp = { ...state, CLIENT_RULES: { ...state.CLIENT_RULES } };
91 | delete temp.CLIENT_RULES[action.payload[0]];
92 | return temp;
93 | }
94 | case UPDATE_CLIENT_RULES:
95 | return {
96 | ...state,
97 | CLIENT_RULES: action.payload,
98 | };
99 | default:
100 | return state;
101 | }
102 | };
103 |
104 | export default rulesReducer;
105 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import {
4 | introspectionQuery,
5 | introspectionQueryName,
6 | } from './utility/introspectionQueries';
7 | import reducers from './reducers/rootReducer';
8 | import { getSchema } from './actions/actions';
9 |
10 | // Creates Redux store
11 |
12 | const store = createStore(reducers, applyMiddleware(thunk));
13 | store.dispatch(getSchema({ query: introspectionQuery, operationName: introspectionQueryName }));
14 |
15 | export default store;
16 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/utility/d3DataCreator.js:
--------------------------------------------------------------------------------
1 | export default (schema) => {
2 | const start = schema._queryType;
3 | const cache = {};
4 | let i = 1;
5 | const nodes = [{ name: start.name, id: 1, fields: Object.keys(start._fields) }];
6 | cache[start.name] = 1;
7 | const links = [];
8 | const helper = (x, prev) => {
9 | Object.values(x).forEach((obj) => {
10 | const nm = obj.type.name || obj.type.ofType.name;
11 | if (!(nm in cache)) {
12 | if (obj.type._fields || (obj.type.ofType && obj.type.ofType._fields)) {
13 | i += 1;
14 | const fields = obj.type._fields || obj.type.ofType._fields;
15 | const temp = { name: nm, id: i, fields: Object.keys(fields) };
16 | nodes.push(temp);
17 | cache[nm] = i;
18 | links.push({ source: prev, target: i });
19 | helper(fields, i);
20 | }
21 | } else {
22 | links.push({ source: i, target: cache[nm] });
23 | }
24 | });
25 | };
26 | helper(start._fields, i);
27 | return { nodes, links };
28 | };
29 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/client/utility/introspectionQueries.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2019 GraphQL Contributors.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | import { getOperationAST, parse, introspectionQuery } from 'graphql';
9 |
10 | export { introspectionQuery } from 'graphql';
11 | export const introspectionQueryName = getOperationAST(parse(introspectionQuery))
12 | .name.value;
13 |
14 | // Some GraphQL services do not support subscriptions and fail an introspection
15 | // query which includes the `subscriptionType` field as the stock introspection
16 | // query does. This backup query removes that field.
17 | export const introspectionQuerySansSubscriptions = `
18 | query ${introspectionQueryName} {
19 | __schema {
20 | queryType { name }
21 | mutationType { name }
22 | types {
23 | ...FullType
24 | }
25 | directives {
26 | name
27 | description
28 | locations
29 | args {
30 | ...InputValue
31 | }
32 | }
33 | }
34 | }
35 |
36 | fragment FullType on __Type {
37 | kind
38 | name
39 | description
40 | fields(includeDeprecated: true) {
41 | name
42 | description
43 | args {
44 | ...InputValue
45 | }
46 | type {
47 | ...TypeRef
48 | }
49 | isDeprecated
50 | deprecationReason
51 | }
52 | inputFields {
53 | ...InputValue
54 | }
55 | interfaces {
56 | ...TypeRef
57 | }
58 | enumValues(includeDeprecated: true) {
59 | name
60 | description
61 | isDeprecated
62 | deprecationReason
63 | }
64 | possibleTypes {
65 | ...TypeRef
66 | }
67 | }
68 |
69 | fragment InputValue on __InputValue {
70 | name
71 | description
72 | type { ...TypeRef }
73 | defaultValue
74 | }
75 |
76 | fragment TypeRef on __Type {
77 | kind
78 | name
79 | ofType {
80 | kind
81 | name
82 | ofType {
83 | kind
84 | name
85 | ofType {
86 | kind
87 | name
88 | ofType {
89 | kind
90 | name
91 | ofType {
92 | kind
93 | name
94 | ofType {
95 | kind
96 | name
97 | ofType {
98 | kind
99 | name
100 | }
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 | }
108 | `;
109 |
--------------------------------------------------------------------------------
/anagraphqlEditor/src/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 |
4 | const app = express();
5 |
6 |
7 | // Uses static file
8 | app.use('/dist', express.static(path.join(__dirname, '..', '..', 'dist')));
9 |
10 | // Serves dist file and index.html?
11 | app.get('/', (req, res) => {
12 | res.sendFile(path.join(__dirname, '..', '..', 'dist', 'index.html'));
13 | });
14 |
15 | app.listen(3000, () => console.log('listening on 3000'));
16 |
--------------------------------------------------------------------------------
/anagraphqlEditor/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: process.env.NODE_ENV,
5 | entry: path.join(__dirname, 'src', 'client', 'index.jsx'),
6 | output: {
7 | publicPath: path.join(__dirname, '..'),
8 | path: path.join(__dirname, '..'),
9 | filename: 'bundle.js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.jsx?/,
15 | exclude: /node_modules/,
16 | loader: 'babel-loader',
17 | options: {
18 | presets: ['@babel/preset-env', '@babel/preset-react'],
19 | },
20 | },
21 | {
22 | test: /\.s?css$/,
23 | loaders: ['style-loader', 'css-loader'],
24 | },
25 | {
26 | test: /\.mjs$/,
27 | include: /node_modules/,
28 | type: 'javascript/auto',
29 | },
30 | ],
31 | },
32 | resolve: {
33 | extensions: ['.js', '.jsx', '.mjs', '.gql', '.graphql'],
34 | },
35 | devServer: {
36 | contentBase: path.join(__dirname, 'dist'),
37 | publicPath: '/dist/',
38 | proxy: {
39 | '/api': 'http://localhost:3000',
40 | '/graphql': 'http://localhost:3000',
41 | },
42 |
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/buildRun.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd ./anagraphqlEditor/
3 | npm run build
4 | cd ../graphqlTestServer/
5 | npm i
6 | nodemon index.js
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Wild Document!
9 |
10 |
11 |
12 |
13 | Check it out!
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/graphqlTestServer/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "root": true
4 | }
--------------------------------------------------------------------------------
/graphqlTestServer/async_db.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const user = require('os').userInfo().username;
4 |
5 | const host = 'localhost';
6 | const database = 'anagraph_db';
7 | const password = 'node_password';
8 | const port = 5432;
9 |
10 | // postgres://YourUserName:YourPassword@YourHost:5432/YourDatabase
11 | const connString = `postgresql://${user}:${password}@${host}:${port}/${database}`;
12 |
13 |
14 | module.exports = connString;
15 |
--------------------------------------------------------------------------------
/graphqlTestServer/buildRun.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | cd ../anagraphqlEditor/
3 | npm run build
4 | cd ../graphqlTestServer/
5 | npm i
6 | nodemon index.js
--------------------------------------------------------------------------------
/graphqlTestServer/config/asyncFakeData.js:
--------------------------------------------------------------------------------
1 | const faker = require('faker');
2 | const pool = require('../database.js');
3 |
4 | const genFakeData = async () => {
5 | const addPeople = async () => {
6 | const client = await pool.connect();
7 | // Add People
8 | const promises = [];
9 | for (let i = 0; i < 1000; i += 1) {
10 | promises.push(
11 | client.query(
12 | `
13 | INSERT INTO person
14 | (firstname,
15 | lastname)
16 | VALUES
17 | ($1, $2)
18 | `,
19 | [faker.name.firstName(), faker.name.lastName()],
20 | ),
21 | );
22 | }
23 | return Promise.all(promises);
24 | };
25 | const addBooks = async () => {
26 | const client = await pool.connect();
27 | const promises = [];
28 | // Add People
29 | for (let i = 0; i < 1000; i += 1) {
30 | promises.push(
31 | client.query(
32 | `
33 | INSERT INTO book
34 | (title,
35 | author_id)
36 | VALUES
37 | ($1, $2)
38 | `,
39 | [faker.commerce.productName(), Math.floor(Math.random() * 999) + 1],
40 | ),
41 | );
42 | }
43 | return Promise.all(promises);
44 | };
45 | const addReviews = async () => {
46 | const client = await pool.connect();
47 | const promises = [];
48 | // Add People
49 | for (let i = 0; i < 600; i += 1) {
50 | promises.push(
51 | client.query(
52 | `
53 | INSERT INTO review
54 | (book_id,
55 | person_id,
56 | review_body)
57 | VALUES
58 | ($1, $2, $3)
59 | `,
60 | [
61 | Math.floor(Math.random() * 999) + 1,
62 | Math.floor(Math.random() * 999) + 1,
63 | faker.lorem.paragraph(),
64 | ],
65 | ),
66 | );
67 | }
68 | return Promise.all(promises);
69 | };
70 | const addUserReads = async () => {
71 | const client = await pool.connect();
72 | // Add People
73 | const promises = [];
74 | for (let i = 0; i < 600; i += 1) {
75 | promises.push(
76 | client.query(
77 | `
78 | INSERT INTO userRead
79 | (book_id,
80 | person_id)
81 | VALUES
82 | ($1, $2)
83 | `,
84 | [
85 | Math.floor(Math.random() * 999) + 1,
86 | Math.floor(Math.random() * 999) + 1,
87 | ],
88 | ),
89 | );
90 | }
91 | return Promise.all(promises);
92 | };
93 | await addPeople();
94 | await addBooks();
95 | await addReviews();
96 | await addUserReads();
97 | console.log('worked!');
98 | };
99 | genFakeData();
100 |
--------------------------------------------------------------------------------
/graphqlTestServer/config/config.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | echo "Configuring database"
4 |
5 | export PGPASSWORD='node_password'
6 |
7 | #
8 | # Should grab your username
9 | #
10 | dropdb -U $USER anagraph_db
11 | createdb -U $USER anagraph_db;
12 | psql -U $USER anagraph_db < ./config/db.sql
13 |
14 | echo "Ana-graphql was configered";
15 |
16 | node ./config/asyncFakeData.js
17 |
18 | echo "I am done I think!! :)";
--------------------------------------------------------------------------------
/graphqlTestServer/config/db.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE person (
2 | person_id SERIAL PRIMARY KEY,
3 | firstname VARCHAR,
4 | lastname VARCHAR
5 | );
6 |
7 | CREATE TABLE book (
8 | book_id SERIAL PRIMARY KEY,
9 | title VARCHAR(256),
10 | author_id INTEGER REFERENCES person(person_id)
11 | );
12 |
13 | CREATE TABLE review (
14 | review_id SERIAL PRIMARY KEY,
15 | book_id INTEGER REFERENCES book(book_id),
16 | person_id INTEGER REFERENCES person(person_id),
17 | review_body TEXT
18 | );
19 |
20 | CREATE TABLE userRead(
21 | person_id INTEGER REFERENCES book(book_id),
22 | book_id INTEGER REFERENCES book(book_id),
23 | CONSTRAINT userread_personid_bookid_key UNIQUE (person_id, book_id)
24 | )
--------------------------------------------------------------------------------
/graphqlTestServer/config/fakeData.js:
--------------------------------------------------------------------------------
1 | const faker = require('faker');
2 | const pool = require('../database.js');
3 |
4 |
5 | const addPeopleTemplate = () => {
6 | pool.connect((err, client, done) => {
7 | if (err) throw err;
8 | // Add People
9 | for (let i = 0; i < 1000; i += 1) {
10 | client.query(`
11 | INSERT INTO person
12 | (firstname,
13 | lastname)
14 | VALUES
15 | ($1, $2)
16 | `,
17 | [faker.name.firstName(), faker.name.lastName()],
18 | (err, res) => {
19 | done();
20 | if (err) {
21 | console.log(err.stack);
22 | } else {
23 | console.log(res.rows[0]);
24 | }
25 | });
26 | }
27 | });
28 | };
29 |
30 | const addBooks = () => {
31 | pool.connect((err, client, done) => {
32 | if (err) throw err;
33 | // Add People
34 | for (let i = 0; i < 1000; i += 1) {
35 | client.query(`
36 | INSERT INTO book
37 | (title,
38 | author_id)
39 | VALUES
40 | ($1, $2)
41 | `,
42 | [faker.commerce.productName(), (Math.floor(Math.random() * 999) + 1)],
43 | (err, res) => {
44 | done();
45 | if (err) {
46 | console.log(err.stack);
47 | } else {
48 | console.log(res.rows[0]);
49 | }
50 | });
51 | }
52 | });
53 | };
54 |
55 |
56 | const addReviews = () => {
57 | pool.connect((err, client, done) => {
58 | if (err) throw err;
59 | // Add People
60 | for (let i = 0; i < 600; i += 1) {
61 | client.query(`
62 | INSERT INTO review
63 | (book_id,
64 | person_id,
65 | review_body)
66 | VALUES
67 | ($1, $2, $3)
68 | `,
69 | [(Math.floor(Math.random() * 999) + 1), (Math.floor(Math.random() * 999) + 1), faker.lorem.paragraph()],
70 | (err, res) => {
71 | done();
72 | if (err) {
73 | console.log(err.stack);
74 | } else {
75 | console.log(res.rows[0]);
76 | }
77 | });
78 | }
79 | });
80 | };
81 |
82 | const addUserReads = () => {
83 | pool.connect((err, client, done) => {
84 | if (err) throw err;
85 | // Add People
86 | for (let i = 0; i < 600; i += 1) {
87 | client.query(`
88 | INSERT INTO userRead
89 | (book_id,
90 | person_id)
91 | VALUES
92 | ($1, $2)
93 | `,
94 | [(Math.floor(Math.random() * 999) + 1), (Math.floor(Math.random() * 999) + 1)],
95 | (err, res) => {
96 | done();
97 | if (err) {
98 | console.log(err.stack);
99 | } else {
100 | console.log(res.rows[0]);
101 | }
102 | });
103 | }
104 | });
105 | };
106 |
107 | // addPeople();
108 | // addBooks();
109 | // addReviews();
110 | // addUserReads();
111 |
--------------------------------------------------------------------------------
/graphqlTestServer/database.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | const user = require('os').userInfo().username;
3 |
4 |
5 | module.exports = new Pool({
6 | user,
7 | host: 'localhost',
8 | database: 'anagraph_db',
9 | password: 'node_password',
10 | port: 5432,
11 | });
12 |
--------------------------------------------------------------------------------
/graphqlTestServer/db_connectStr.js:
--------------------------------------------------------------------------------
1 |
2 | const user = require('os').userInfo().username;
3 |
4 | const host = 'localhost';
5 | const database = 'anagraph_db';
6 | const password = 'node_password';
7 | const port = 5432;
8 |
9 |
10 | const connString = `postgresql://${user}:${password}@${host}:${port}/${database}`;
11 |
12 |
13 | module.exports = connString;
14 |
--------------------------------------------------------------------------------
/graphqlTestServer/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const graphqlHTTP = require('express-graphql');
4 | const anagraphql = require('../');
5 | const schema = require('./schema');
6 |
7 | const app = express();
8 | const port = 3000;
9 | app.use(bodyParser.json());
10 |
11 | const rules = {
12 | specificResolvers: {
13 | RootQueryType_authors: 3,
14 | },
15 | shallowResolvers: {
16 | authors: 2,
17 | },
18 | maxNested: 4,
19 | totalResolvers: 25,
20 | totalFields: 60,
21 | };
22 |
23 |
24 | app.use('/graphql',
25 | (req, res, next) => next(),
26 | anagraphql({ schema, rules, graphiql: true }),
27 | graphqlHTTP({
28 | schema,
29 | // graphiql: true,
30 | }));
31 |
32 |
33 | app.listen(port, () => (console.log(`something on port ${port}`)));
34 |
--------------------------------------------------------------------------------
/graphqlTestServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphqltestserver",
3 | "version": "1.0.1",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "bash buildRun.sh",
7 | "config": "sh config/config.sh",
8 | "test": "jest"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "anagraphql": "file:..",
14 | "body-parser": "^1.19.0",
15 | "express": "^4.17.1",
16 | "express-graphql": "^0.9.0",
17 | "faker": "^4.1.0",
18 | "graphql": "^14.4.2",
19 | "nodemon": "^1.19.1",
20 | "pg": "^7.11.0",
21 | "pg-promise": "^8.7.5"
22 | },
23 | "devDependencies": {
24 | "eslint": "^6.1.0",
25 | "eslint-config-airbnb": "^17.1.1",
26 | "eslint-plugin-import": "^2.18.2",
27 | "eslint-plugin-jsx-a11y": "^6.2.3",
28 | "eslint-plugin-react": "^7.14.3",
29 | "jest": "^24.8.0"
30 | },
31 | "description": ""
32 | }
33 |
--------------------------------------------------------------------------------
/graphqlTestServer/resolvers.js:
--------------------------------------------------------------------------------
1 | const pool = require('./database');
2 |
3 | const resolvers = {
4 |
5 | Query: {
6 | getAllperson() {
7 | const sql = 'SELECT * FROM "person";';
8 | return pool.query(sql)
9 | .then(res => res.rows)
10 | .catch(err => console.error('Error is: ', err));
11 | },
12 | getperson(parent, args, context, info) {
13 | let sql = 'SELECT * FROM "person"';
14 | let whereClause = ' WHERE ';
15 | Object.keys(args).forEach((fieldName, i, arr) => {
16 | whereClause += `"${fieldName}" = '${args[fieldName]}'`;
17 | if (i !== arr.length - 1) whereClause += ' AND ';
18 | else whereClause += ';';
19 | });
20 | sql += whereClause;
21 | return pool.query(sql)
22 | .then(res => res.rows)
23 | .catch(err => console.error('Error is: ', err));
24 | },
25 | getAllbook() {
26 | const sql = 'SELECT * FROM "book";';
27 | return pool.query(sql)
28 | .then(res => res.rows)
29 | .catch(err => console.error('Error is: ', err));
30 | },
31 | getbook(parent, args, context, info) {
32 | let sql = 'SELECT * FROM "book"';
33 | let whereClause = ' WHERE ';
34 | Object.keys(args).forEach((fieldName, i, arr) => {
35 | whereClause += `"${fieldName}" = '${args[fieldName]}'`;
36 | if (i !== arr.length - 1) whereClause += ' AND ';
37 | else whereClause += ';';
38 | });
39 | sql += whereClause;
40 | return pool.query(sql)
41 | .then(res => res.rows)
42 | .catch(err => console.error('Error is: ', err));
43 | },
44 | getAllauthor() {
45 | const sql = 'SELECT * FROM "author";';
46 | return pool.query(sql)
47 | .then(res => res.rows)
48 | .catch(err => console.error('Error is: ', err));
49 | },
50 | getauthor(parent, args, context, info) {
51 | let sql = 'SELECT * FROM "author"';
52 | let whereClause = ' WHERE ';
53 | Object.keys(args).forEach((fieldName, i, arr) => {
54 | whereClause += `"${fieldName}" = '${args[fieldName]}'`;
55 | if (i !== arr.length - 1) whereClause += ' AND ';
56 | else whereClause += ';';
57 | });
58 | sql += whereClause;
59 | return pool.query(sql)
60 | .then(res => res.rows)
61 | .catch(err => console.error('Error is: ', err));
62 | },
63 | getAllreview() {
64 | const sql = 'SELECT * FROM "review";';
65 | return pool.query(sql)
66 | .then(res => res.rows)
67 | .catch(err => console.error('Error is: ', err));
68 | },
69 | getreview(parent, args, context, info) {
70 | let sql = 'SELECT * FROM "review"';
71 | let whereClause = ' WHERE ';
72 | Object.keys(args).forEach((fieldName, i, arr) => {
73 | whereClause += `"${fieldName}" = '${args[fieldName]}'`;
74 | if (i !== arr.length - 1) whereClause += ' AND ';
75 | else whereClause += ';';
76 | });
77 | sql += whereClause;
78 | return pool.query(sql)
79 | .then(res => res.rows)
80 | .catch(err => console.error('Error is: ', err));
81 | },
82 | },
83 |
84 | person: {
85 | person_id: (parent, args, context, info) => parent.person_id,
86 | firstname: (parent, args, context, info) => parent.firstname,
87 | lastname: (parent, args, context, info) => parent.lastname,
88 | },
89 |
90 | book: {
91 | book_id: (parent, args, context, info) => parent.book_id,
92 | title: (parent, args, context, info) => parent.title,
93 | person: (parent, args, context, info) => {
94 | const sql = `SELECT * FROM "person" WHERE "person_id" = '${parent.author_id}';`;
95 | return pool.query(sql)
96 | .then(res => res.rows[0])
97 | .catch(err => console.error('Error is: ', err));
98 | },
99 | },
100 |
101 | author: {
102 | person: (parent, args, context, info) => {
103 | const sql = `SELECT * FROM "person" WHERE "person_id" = '${parent.person_id}';`;
104 | return pool.query(sql)
105 | .then(res => res.rows[0])
106 | .catch(err => console.error('Error is: ', err));
107 | },
108 | book: (parent, args, context, info) => {
109 | const sql = `SELECT * FROM "book" WHERE "book_id" = '${parent.book_id}';`;
110 | return pool.query(sql)
111 | .then(res => res.rows[0])
112 | .catch(err => console.error('Error is: ', err));
113 | },
114 | title: (parent, args, context, info) => parent.title,
115 | },
116 |
117 | review: {
118 | review_id: (parent, args, context, info) => parent.review_id,
119 | book: (parent, args, context, info) => {
120 | const sql = `SELECT * FROM "book" WHERE "book_id" = '${parent.book_id}';`;
121 | return pool.query(sql)
122 | .then(res => res.rows[0])
123 | .catch(err => console.error('Error is: ', err));
124 | },
125 | person: (parent, args, context, info) => {
126 | const sql = `SELECT * FROM "person" WHERE "person_id" = '${parent.person_id}';`;
127 | return pool.query(sql)
128 | .then(res => res.rows[0])
129 | .catch(err => console.error('Error is: ', err));
130 | },
131 | review_body: (parent, args, context, info) => parent.review_body,
132 | },
133 |
134 | };
135 |
136 | module.exports = resolvers;
137 |
--------------------------------------------------------------------------------
/graphqlTestServer/schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | GraphQLObjectType,
3 | GraphQLID,
4 | GraphQLString,
5 | GraphQLInt,
6 | GraphQLNonNull,
7 | GraphQLList,
8 | GraphQLSchema,
9 | } = require('graphql');
10 | const pgp = require('pg-promise')();
11 | const connectionString = require('./async_db');
12 |
13 | const db = {};
14 | db.conn = pgp(connectionString);
15 |
16 | const BookType = new GraphQLObjectType({
17 | name: 'book',
18 | fields: () => ({
19 | book_id: { type: GraphQLID },
20 | title: { type: GraphQLString },
21 | author_id: { type: GraphQLID },
22 | authors: {
23 | type: new GraphQLList(AuthorType),
24 | resolve(parentValue, args) {
25 | const query = `SELECT * FROM "person" WHERE person_id=${parentValue.author_id}`;
26 | return db.conn.many(query)
27 | .then(data => data);
28 | },
29 | },
30 | }),
31 | });
32 |
33 | const AuthorType = new GraphQLObjectType({
34 | name: 'author',
35 | fields: () => ({
36 | person_id: { type: GraphQLID },
37 | firstname: { type: GraphQLString },
38 | lastname: { type: GraphQLString },
39 | books: {
40 | type: new GraphQLList(BookType),
41 | resolve(parentValue, args) {
42 | const query = `SELECT * FROM "book" WHERE author_id=${parentValue.person_id}`;
43 | return db.conn.many(query)
44 | .then(data => data);
45 | },
46 | },
47 | }),
48 | });
49 |
50 | const PersonType = new GraphQLObjectType({
51 | name: 'person',
52 | fields: () => ({
53 | person_id: { type: GraphQLID },
54 | firstname: { type: GraphQLString },
55 | lastname: { type: GraphQLString },
56 | }),
57 | });
58 |
59 | const ReviewType = new GraphQLObjectType({
60 | name: 'review',
61 | fields: () => ({
62 | review_id: { type: GraphQLID },
63 | book_id: { type: GraphQLID },
64 | person_id: { type: GraphQLID },
65 | review_body: { type: GraphQLString },
66 | book: {
67 | type: BookType,
68 | resolve(parentValue, args) {
69 | const query = `SELECT * FROM "book" WHERE book_id=${parentValue.book_id}`;
70 | return db.conn.one(query)
71 | .then(data => data);
72 | },
73 | },
74 | reviewAuthor: {
75 | type: PersonType,
76 | resolve(parentValue, args) {
77 | const query = `SELECT * FROM "person" WHERE person_id=${parentValue.person_id}`;
78 | return db.conn.one(query)
79 | .then(data => data);
80 | },
81 | },
82 | }),
83 | });
84 |
85 |
86 | const RootQuery = new GraphQLObjectType({
87 | name: 'RootQueryType',
88 | fields: {
89 | person: {
90 | type: PersonType,
91 | args: { id: { type: GraphQLID } },
92 | resolve(parentValue, args) {
93 | const query = `SELECT * FROM "person" WHERE person_id=${args.id}`;
94 | return db.conn.one(query)
95 | .then(data => data);
96 | },
97 | },
98 | authors: {
99 | type: new GraphQLList(AuthorType),
100 | args: { limit: { type: GraphQLInt } },
101 | resolve(parentValue, args) {
102 | const query = `SELECT DISTINCT person.person_id, person.firstname, person.lastname
103 | FROM book join person
104 | ON book.author_id = person.person_id LIMIT ${args.limit};`;
105 | return db.conn.many(query)
106 | .then(data => data);
107 | },
108 | },
109 | reviews: {
110 | type: new GraphQLList(ReviewType),
111 | args: { limit: { type: new GraphQLNonNull(GraphQLInt) } },
112 | resolve(parentValue, args) {
113 | const query = `SELECT * FROM review LIMIT ${args.limit};`;
114 | return db.conn.many(query)
115 | .then(data => data);
116 | },
117 | },
118 | },
119 | });
120 |
121 | module.exports = new GraphQLSchema({
122 | query: RootQuery,
123 | });
124 |
125 |
126 | // Queries to be performed:
127 |
128 | // Select all Authors of books:
129 | const selectAuthors = `
130 | SELECT DISTINCT book.author_id, person.firstname
131 | FROM book join person
132 | ON book.author_id = person.person_id;
133 | `;
134 |
135 | const selectReviewers = `
136 | `;
137 | const selectReaders = `
138 | `;
139 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 |
2 | const renderGraphiql = require('./renderGraphiql');
3 | const anagraphCreator = require('./Parser/anagraphCreator');
4 | const schemaParser = require('./Parser/schemaParser');
5 | const ruleValidator = require('./Parser/ruleValidator');
6 | const queryValidator = require('./Parser/queryValidator');
7 |
8 | const anagraphql = options => ((req, res, next) => {
9 | if (req.method !== 'GET' && req.method !== 'POST') {
10 | res.setHeader('Allow', 'GET, POST');
11 | res.status(405).send('GraphQL only supports GET and POST requests.');
12 | res.end();
13 | }
14 |
15 | const { schema, graphiql, rules } = options;
16 | if (!schema) throw new Error('GraphQL middleware options must contain a schema.');
17 | const applicableRules = schemaParser(schema);
18 |
19 | if (req.body.operationName !== undefined && graphiql) {
20 | const oldSend = res.send;
21 | res.send = function (...data) {
22 | // arguments[0] (or `data`) contains the response body
23 | res.send = oldSend;
24 | data[0] = JSON.stringify({ ...JSON.parse(data[0]), applicableRules });
25 | oldSend.apply(res, data);
26 | };
27 | return next();
28 | }
29 |
30 |
31 | if (graphiql && req.method === 'GET') {
32 | if (!graphiql) return next();
33 | if (rules === undefined) res.send(renderGraphiql());
34 | if (rules) res.send(renderGraphiql(rules));
35 | return res.end();
36 | }
37 |
38 | if (req.body.query) {
39 | const RULES_TO_CHECK = req.body.currRule || rules
40 | console.log(`RULES IN BACKEND: ${JSON.stringify(RULES_TO_CHECK)}`);
41 | const anagraph = anagraphCreator(req.body.query);
42 |
43 | if (RULES_TO_CHECK) {
44 | const validateRules = ruleValidator(applicableRules, RULES_TO_CHECK);
45 | if (validateRules.error) {
46 | res.status(422).send({ error: validateRules.error });
47 | return res.end();
48 | }
49 | const evaluation = queryValidator(anagraph, RULES_TO_CHECK);
50 | if (evaluation.error) {
51 | res.status(422).send({ error: evaluation.error });
52 | return res.end();
53 | }
54 | }
55 |
56 |
57 | if (graphiql) {
58 | const oldSend = res.send;
59 | res.send = function(...data){
60 | // arguments[0] (or `data`) contains the response body
61 | res.send = oldSend
62 | data[0] = JSON.stringify({ ...JSON.parse(data[0]), anagraph });
63 | oldSend.apply(res, data);
64 | }
65 | return next();
66 | }
67 |
68 | return next();
69 |
70 | }
71 | });
72 |
73 | module.exports = anagraphql;
74 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anagraphql",
3 | "version": "1.0.1",
4 | "description": "Express middleware used to analyze graphQL queries before interacting with the database.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "start": "bash buildRun.sh"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/oslabs-beta/anagraphql.git"
13 | },
14 | "keywords": [
15 | "graphql",
16 | "analyze",
17 | "policies",
18 | "monitoring"
19 | ],
20 | "author": "",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/oslabs-beta/anagraphql/issues"
24 | },
25 | "homepage": "https://github.com/oslabs-beta/anagraphql#readme",
26 | "devDependencies": {
27 | "jest": "^24.8.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/renderGraphiql.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | module.exports = (rules) => {
5 | const content = fs.readFileSync(path.join(__dirname, 'bundle.js'), 'utf-8');
6 | const readMe = fs.readFileSync(path.join(__dirname, 'README.md'), 'utf-8').replace(/`/g, '\\`');
7 |
8 | return `
9 |
10 |
11 |
12 |
13 |
14 | AnagraphQL
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | `;
29 | };
30 |
--------------------------------------------------------------------------------