├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile-test ├── Dockerrun.aws.json ├── LICENSE ├── README.md ├── __mocks__ ├── backendMockServer.js ├── mockCases.js ├── mockServer.js └── setupTests.js ├── __tests__ ├── backend │ └── routes.test.js └── frontend │ ├── enzyme.test.js │ └── jest.test.js ├── client ├── App.jsx ├── assets │ ├── advice.gif │ ├── backsplash.png │ ├── daniel.jpg │ ├── emily.jpg │ ├── export.gif │ ├── github.png │ ├── graphgif.gif │ ├── heidi.jpg │ ├── idea.png │ ├── linkedin.png │ ├── logoclear.png │ ├── logodark.png │ ├── logotext.png │ ├── medium.png │ ├── modal.gif │ ├── playground.gif │ ├── ross.jpg │ ├── splashgif.gif │ ├── story.png │ ├── teamdraqlabs.png │ ├── tommy.jpg │ ├── twitter.png │ └── types.gif ├── components │ ├── URILink.jsx │ ├── adviceCodeBlock.jsx │ ├── codeBox.jsx │ ├── demoRow.jsx │ ├── exportButton.jsx │ ├── helpButton.jsx │ ├── navbar.jsx │ ├── teamCard.jsx │ └── undoButton.jsx ├── containers │ ├── aboutContainer.jsx │ ├── adviceContainer.jsx │ ├── codeContainer.jsx │ ├── contactContainer.jsx │ ├── demoContainer.jsx │ ├── featureContainer.jsx │ ├── footerContainer.jsx │ ├── graphContainer.jsx │ ├── splashContainer.jsx │ └── teamContainer.jsx ├── graphs │ ├── adviceGraph.jsx │ ├── helperFunctions.js │ └── psqlGraph.jsx ├── index.html ├── index.js ├── modals │ ├── URIModal.jsx │ └── helpModal.jsx ├── pages │ ├── appPage.jsx │ └── homePage.jsx ├── state │ ├── contexts.jsx │ ├── customHooks.js │ └── reducers.js ├── styles │ ├── _colors.scss │ ├── _fonts.scss │ ├── adviceCodeBlock.scss │ ├── adviceGraph.scss │ ├── codeMirror.scss │ ├── demo.scss │ ├── homeContainers.scss │ ├── homePage.scss │ ├── index.scss │ ├── modals.scss │ ├── navbar.scss │ ├── psqlGraph.scss │ ├── splash.scss │ └── team.scss └── zipFiles │ └── zipFileHelpers.js ├── docker-compose-test.yml ├── package-lock.json ├── package.json ├── scripts └── deploy.sh ├── server ├── controllers │ ├── mongoController.js │ ├── pgController.js │ └── testPSQL.js ├── graphQLServer │ └── schema.js ├── mongoGenerators │ ├── helperFunctions.js │ ├── resolverGenerators.js │ └── schemaGenerators.js ├── pgGenerators │ ├── helperFunctions.js │ ├── resolverGenerators.js │ ├── schemaGenerators.js │ └── typeGenerator.js ├── queries │ └── tables.sql ├── router.js └── server.js ├── tsconfig.json ├── typescript ├── clientInterface.ts └── serverInterface.ts └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | "@babel/preset-env", 5 | "@babel/preset-typescript" 6 | ], 7 | "plugins": [ 8 | "@babel/plugin-transform-react-jsx", 9 | ] 10 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "root": true, 4 | "ignorePatterns": [ 5 | "**/test", 6 | "**/__tests__" 7 | ], 8 | "env": { 9 | "node": true, 10 | "browser": true, 11 | "es2021": true 12 | }, 13 | "plugins": [ 14 | "react" 15 | ], 16 | "extends": [ 17 | "eslint:recommended", 18 | "plugin:react/recommended", 19 | "plugin:@typescript-eslint/recommended" 20 | ], 21 | "parserOptions": { 22 | "ecmaVersion": 2018, 23 | "sourceType": "module", 24 | "ecmaFeatures": { 25 | "jsx": true 26 | } 27 | }, 28 | "rules": { 29 | "@typescript-eslint/no-var-requires": 0, 30 | "indent": [ 31 | "warn", 32 | 2 33 | ], 34 | "no-unused-vars": [ 35 | "off", 36 | { 37 | "vars": "local" 38 | } 39 | ], 40 | "no-case-declarations": "off", 41 | "prefer-const": "warn", 42 | "quotes": [ 43 | "warn", 44 | "single" 45 | ], 46 | "react/prop-types": "off", 47 | "semi": [ 48 | "warn", 49 | "always" 50 | ], 51 | "space-infix-ops": "warn" 52 | }, 53 | "settings": { 54 | "react": { 55 | "version": "detect" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker 3 | 4 | dist: xenial 5 | 6 | script: 7 | - docker-compose -f docker-compose-test.yml up --abort-on-container-exit 8 | - python3 -VV 9 | - pip -V 10 | 11 | before_deploy: 12 | - python3 -m pip install --upgrade pip 13 | - python3 -m pip install --user awscli 14 | - python3 -m pip install --user awsebcli 15 | - export PATH=$PATH:$HOME/.local/bin 16 | 17 | env: 18 | global: 19 | - PATH=/opt/python/3.7.1/bin:$PATH 20 | 21 | deploy: 22 | provider: script 23 | cleanup: true 24 | on: 25 | branch: main 26 | script: sh $TRAVIS_BUILD_DIR/scripts/deploy.sh 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.18.3 2 | WORKDIR /usr/src/app 3 | COPY . . 4 | RUN npm install && npm run build 5 | EXPOSE 3000 6 | ENTRYPOINT ["node", "./server/server.js"] -------------------------------------------------------------------------------- /Dockerfile-test: -------------------------------------------------------------------------------- 1 | FROM node:12.18.3 2 | RUN npm install --global jest 3 | WORKDIR /usr/src/app 4 | COPY . /usr/src/app 5 | RUN npm install && npm run build 6 | EXPOSE 3000 7 | ENTRYPOINT [ "npm", "test" ] -------------------------------------------------------------------------------- /Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSEBDockerrunVersion": "1", 3 | "Image": { 4 | "Name": "416156120607.dkr.ecr.us-east-2.amazonaws.com/draqla:", 5 | "Update": "true" 6 | }, 7 | "Ports": [{ 8 | "ContainerPort": "3000" 9 | }] 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Splash Image](./client/assets/splashgif.gif)
2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/oslabs-beta/DraQLa/blob/main/LICENSE) ![GitHub package.json version](https://img.shields.io/badge/version-v1.0.0-blue) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/oslabs-beta/DraQLa/issues) ![Stars](https://img.shields.io/github/stars/oslabs-beta/DraQLa?color=red) 3 | 4 | # DraQLa 5 | 6 | DraQLa is a GraphQL migration assistance tool that empowers developers to build GraphQL schemas by introspecting existing PostGreSQL databases, all without writing any code. 7 | 8 | 9 | Accelerated by [OS Labs](https://github.com/oslabs-beta/) 10 | 11 | ## Getting Started 12 | Visit [draqlabs.io](https://draqlabs.io) to sink your teeth into the tool. 13 | 14 | ## Features 15 | * Exportable custom GraphQL schema (including resolvers and mutations) 16 | * Visual interactive diagram of current PostgreSQL database 17 | * Advice Console that provides high level breakdown of the generated boilerplate 18 | * Temporary dummy server and sample query/mutation to test the personalized schema with GraphQL's Playground GUI 19 | * Encrypted URI on client and server side to keep your data private 20 | 21 | ## How does DraQLa work? 22 | First, you enter the database URI that you want to convert into a GraphQL API. Then, DraQLa encrypts your URI to ensure your privacy. Or feel free to test with our Sample Database!
23 | 24 | DraQLa will immediately start by extracting all of your database's tables and relationships, and will generate compatible GraphQL schemas, which consists of types and their corresponding resolvers.

25 | ![graph](./client/assets/modal.gif) 26 | 27 |
28 | DraQLa also features a user friendly visual representation that depicts the parts of your database that can now be queried and manipulated via GraphQL.

29 | 30 | ![graphgif](./client/assets/graphgif.gif) 31 | 32 | The Advice Console provides an overview on: 33 | * GraphQL 34 | * how you and your clients can access and manipulate your database 35 | * a sample query and mutation

36 | ![advice.gif](./client/assets/advice.gif) 37 | 38 |
39 | In addition to your new schema, DraQLa spins up a temporary GraphQL server to allow you to test out the sample query via GraphQL's Playground.

40 | 41 | ![playground.gif](./client/assets/playground.gif) 42 | 43 |
44 | When you're ready to adopt your schema, click "Export" to receive the code and further integration instructions. 45 |

46 | 47 | ## How To Contribute 48 | We would love for you to test our application and submit any issues you encouter. Please feel free to fork your own repository and submit your own pull requests. 49 | 50 | Things you can do to contribute: 51 | * Bug fixes 52 | * Implementing features 53 | * Submitting or resolving GitHub issues 54 | * Help market our application 55 |

56 | ## Developers 57 | Daniel Dolich || [LinkedIn](https://www.linkedin.com/in/danieldolich/) | [GitHub](https://github.com/danieldolich) 58 | 59 | Emily Krebs || [LinkedIn](https://www.linkedin.com/in/emilyrkrebs/) | [GitHub](https://github.com/emilykrebs) 60 | 61 | Heidi Kim || [LinkedIn](https://www.linkedin.com/in/heidiykim/) | [GitHub](https://github.com/heidiyoora) 62 | 63 | Ross Sarcona || [Linkedin](https://www.linkedin.com/in/rosssarcona/) | [GitHub](https://github.com/RossRSarc) 64 | 65 | Tommy Liang || [LinkedIn](https://www.linkedin.com/in/mrtommyliang/) | [GitHub](https://github.com/mrtommyliang) 66 | 67 | 68 | ## License 69 | This project is licensed under the [MIT License](https://github.com/oslabs-beta/DraQLa/blob/main/LICENSE) -------------------------------------------------------------------------------- /__mocks__/backendMockServer.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const path = require('path'); 4 | const router = require('../server/router.js'); 5 | const { graphqlHTTP } = require('express-graphql'); 6 | const schema = require('../server/graphQLServer/schema'); 7 | 8 | 9 | app.use(express.json()); 10 | 11 | /* route handlers */ 12 | app.use('/graphql', 13 | graphqlHTTP({ 14 | schema, 15 | graphiql: true, 16 | }) 17 | ); 18 | app.use('/', router); 19 | 20 | /* handles static files */ 21 | app.use('/dist', express.static(path.resolve(__dirname, '../dist'))); 22 | app.use('/assets', express.static(path.resolve(__dirname, '../client/assets'))); 23 | 24 | app.get('/*', (req, res) => { 25 | return res.status(200).sendFile(path.join(__dirname, '../client/index.html')); 26 | }); 27 | 28 | // catch all 29 | app.use('*', (req, res, next) => { 30 | return res.status(404).send('Sorry, wrong page! Try again! 🤪'); 31 | }); 32 | 33 | // global error handler 34 | app.use((err, req, res, next) => { 35 | const defaultErr = { 36 | log: 'Express error handler caught unknown middleware error', 37 | status: 400, 38 | message: { err: 'Interal Server Error' }, 39 | }; 40 | 41 | const errorObj = Object.assign(defaultErr, err); 42 | console.log(errorObj.log); 43 | return res.status(errorObj.status).send(errorObj.message); 44 | }); 45 | 46 | module.exports = app; 47 | -------------------------------------------------------------------------------- /__mocks__/mockCases.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {jest} from '@jest/globals'; 3 | 4 | // set up mockState with real state name as prop, and state properties passed in 5 | 6 | export const homeGenState = { 7 | generalState: { 8 | onHomePage: true, 9 | URImodal: false, 10 | helpModal: false, 11 | }, 12 | }; 13 | 14 | export const appGenState = { 15 | generalState: { 16 | onHomePage: false, 17 | URImodal: false, 18 | helpModal: false, 19 | generalDispatch: jest.fn(), 20 | }, 21 | }; -------------------------------------------------------------------------------- /__mocks__/mockServer.js: -------------------------------------------------------------------------------- 1 | // tests RESTful requests. there's also a GraphQL one 2 | import { rest } from 'msw'; 3 | import { setupServer } from 'msw/node'; 4 | import 'whatwg-fetch'; 5 | 6 | const server = setupServer( 7 | rest.get('/', (req, res, ctx) => { 8 | return res(ctx.status(200), ctx.json({ greeting: 'hello world' })); 9 | }) 10 | ); 11 | 12 | // establish API mocking before all tests 13 | beforeAll(() => server.listen()); 14 | // reset any request handlers that are declared as a part of our tests 15 | // (i.e. for testing one-time error scenarios) 16 | afterEach(() => server.resetHandlers()); 17 | // clean up once the tests are done 18 | afterAll(() => server.close()); 19 | 20 | async function onRoot() { 21 | const result = await fetch('/'); 22 | const data = await result.json(); 23 | return data; 24 | } 25 | 26 | export { server, rest }; 27 | -------------------------------------------------------------------------------- /__mocks__/setupTests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | import 'regenerator-runtime/runtime'; 3 | -------------------------------------------------------------------------------- /__tests__/backend/routes.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const { URI, secret } = require('../../server/controllers/testPSQL'); 3 | const CryptoJS = require('crypto-js'); 4 | const app = require('../../__mocks__/backendMockServer'); 5 | 6 | const sampleURI = CryptoJS.AES.encrypt(URI, secret).toString(); 7 | 8 | let server; 9 | 10 | beforeAll((done) => { 11 | server = app.listen(3001) 12 | done(); 13 | }) 14 | 15 | afterAll((done) => { 16 | server.close(done) 17 | }) 18 | 19 | describe('Route integration', () => { 20 | describe('/', () => { 21 | describe('GET -> Homepage', () => { 22 | it('responds with 200 status and text/html content type', (done) => { 23 | return request(server) 24 | .get('/') 25 | .expect('Content-Type', /text\/html/) 26 | .expect(200) 27 | .end(done); 28 | }); 29 | }); 30 | }) 31 | 32 | describe('/app', () => { 33 | describe('GET -> app page', () => { 34 | it('responds with 200 status and text/html content type', (done) => { 35 | return request(server) 36 | .get('/app') 37 | .expect('Content-Type', /text\/html/) 38 | .expect(200) 39 | .end(done); 40 | }); 41 | }); 42 | }) 43 | 44 | describe('/graphql', () => { 45 | describe('GET -> graphql playground', () => { 46 | it('responds with 200 status and application/json content type AFTER successful POST to /psql', (done) => { 47 | return request(server) 48 | .post('/psql') 49 | .send({ psqlURI: sampleURI }) 50 | .expect(200) 51 | .expect('Content-Type', /application\/json/) 52 | .end(() => { 53 | request(server) 54 | .get('/graphql') 55 | .expect('Content-Type', /application\/json/) 56 | .expect(200) 57 | .end(() => { 58 | return done(); 59 | }) 60 | }) 61 | }); 62 | }); 63 | }) 64 | 65 | describe('/psql', () => { 66 | describe('POST -> postgres uri', () => { 67 | it('responds with 200 status and application/json content type with psqlURI passed in req body', (done) => { 68 | return request(server) 69 | .post('/psql') 70 | .send({ psqlURI: sampleURI }) 71 | .expect('Content-type', /application\/json/) 72 | .expect(200) 73 | .end(done) 74 | }) 75 | 76 | it('responds with 200 status and application/json content type with sample passed in req body', (done) => { 77 | return request(server) 78 | .post('/psql') 79 | .send({ sample: true }) 80 | .expect('Content-Type', /application\/json/) 81 | .expect(200) 82 | .end(done) 83 | }) 84 | 85 | it('properties dbName, schema, advice, and d3Data are in body of response', (done) => { 86 | return request(server) 87 | .post('/psql') 88 | .send({ psqlURI: sampleURI }) 89 | .expect(200) 90 | .expect('Content-Type', /application\/json/) 91 | .then(({ body }) => { 92 | expect(body).toHaveProperty('dbName'); 93 | expect(body).toHaveProperty('schema'); 94 | expect(body).toHaveProperty('advice'); 95 | expect(body).toHaveProperty('d3Data'); 96 | return done(); 97 | }) 98 | }) 99 | 100 | it('responds with \'psql\' as dbName', (done) => { 101 | return request(server) 102 | .post('/psql') 103 | .send({ psqlURI: sampleURI }) 104 | .expect(200) 105 | .expect('Content-Type', /application\/json/) 106 | .then(({ body }) => { 107 | expect(body.dbName).toEqual('psql'); 108 | return done(); 109 | }) 110 | }) 111 | 112 | 113 | it('responds to invalid request with 400 status and error message in body', (done) => { 114 | return request(server) 115 | .post('/psql') 116 | .send({ psqlURI: 'not a URI' }) 117 | .expect(400) 118 | .expect('Content-Type', /application\/json/) 119 | .then(({ body }) => { 120 | expect(body).toHaveProperty('err'); 121 | return done(); 122 | }) 123 | }) 124 | }) 125 | }) 126 | 127 | }) -------------------------------------------------------------------------------- /__tests__/frontend/enzyme.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | import { shallow, configure, mount } from 'enzyme'; 4 | import { useLocation } from 'react-router-dom'; 5 | 6 | // import custom useContext 7 | import * as MockContexts from '../../client/state/contexts'; 8 | 9 | // import mock cases 10 | import { homeGenState, appGenState } from '../../__mocks__/mockCases'; 11 | 12 | // React Components 13 | import App from '../../client/App'; 14 | import NavBar from '../../client/components/navbar'; 15 | import PSQLGraph from '../../client/graphs/psqlGraph'; 16 | import AdviceGraph from '../../client/graphs/adviceGraph'; 17 | 18 | configure({ adapter: new Adapter() }); 19 | 20 | jest.mock("react-router-dom", () => ({ 21 | ...jest.requireActual('react-router-dom'), 22 | useLocation: () => ({ 23 | pathname: 'localhost:8080/' 24 | }) 25 | })); 26 | 27 | describe(' renders on the browser', () => { 28 | const wrapper = shallow(); 29 | 30 | it('renders correctly', () => { 31 | shallow(); 32 | }); 33 | 34 | xit('App contains Logo and Logotext', () => { 35 | expect(wrapper.find('img').length).toEqual(2); 36 | }); 37 | }); 38 | 39 | describe('Dynamic NavBar Displays', () => { 40 | it('NavBar renders initially', () => { 41 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx) 42 | // we pass in our mock state as context to the spy 43 | jest 44 | .spyOn(MockContexts, 'useGenContext') 45 | .mockImplementation(() => homeGenState); 46 | 47 | const wrapper = shallow(); 48 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer 49 | const confirm = wrapper.hasClass('NavBarContainer'); 50 | // expects confirm (boolean => true) to be true 51 | expect(confirm).toBe(true); 52 | }); 53 | 54 | it('Home Page Navbar renders', () => { 55 | jest 56 | .spyOn(MockContexts, 'useGenContext') 57 | .mockImplementation(() => appGenState); 58 | const wrapper = shallow(); 59 | //const playLink = wrapper.find('Link').childAt(3).text(); 60 | const playLink = wrapper.find('Link').text() 61 | expect(playLink).toEqual('Play'); 62 | }); 63 | 64 | it('App Page NavBar renders', () => { 65 | jest 66 | .spyOn(MockContexts, 'useGenContext') 67 | .mockImplementation(() => appGenState); 68 | 69 | const wrapper = shallow(); 70 | 71 | const homeLink = wrapper.find('Link').text(); 72 | const testLink = wrapper.find('.link').at(0) 73 | testLink.simulate('click'); 74 | expect(homeLink).toEqual('Home'); 75 | expect(location.pathname).toEqual('/'); 76 | }); 77 | }); 78 | 79 | 80 | describe('Rendering graph', () => { 81 | it('should render radial tree correctly', () => { 82 | const mockState = { 83 | psqlState: { 84 | d3Tables: { 85 | name: 'Star Wars', 86 | children: [ 87 | { 88 | name: 'People', 89 | }, 90 | ], 91 | }, 92 | }, 93 | }; 94 | 95 | jest 96 | .spyOn(MockContexts, 'usePSQLContext') 97 | .mockImplementation(() => mockState); 98 | 99 | const wrapper = shallow(); 100 | const confirm = wrapper.find('svg').length; // should be 1 101 | expect(confirm).toBe(1); 102 | }); 103 | 104 | it('should render advice graph correctly', () => { 105 | const mockState = { 106 | adviceState: { 107 | advice: [ 108 | { 109 | Type: 'Queries', 110 | Amount: 1, 111 | Description: 'string', 112 | Example: 'string', 113 | }, 114 | ], 115 | }, 116 | }; 117 | jest 118 | .spyOn(MockContexts, 'useAdviceContext') 119 | .mockImplementation(() => mockState); 120 | 121 | const wrapper = shallow(); 122 | const confirm = wrapper.hasClass('container'); 123 | expect(confirm).toBe(true); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /__tests__/frontend/jest.test.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import '../../__mocks__/setupTests'; 3 | import { render, fireEvent, screen } from '@testing-library/react'; 4 | import userEvent from '@testing-library/user-event'; 5 | import { createMemoryHistory } from 'history'; 6 | import { Router } from 'react-router-dom'; 7 | import { server, rest } from '../../__mocks__/mockServer'; 8 | // overall SPA 9 | import App from '../../client/App'; 10 | import NavBar from '../../client/components/navbar'; 11 | 12 | // Home Page 13 | import TeamContainer from '../../client/containers/teamContainer'; 14 | import DemoContainer from '../../client/containers/demoContainer'; 15 | import HomePage from '../../client/pages/homePage'; 16 | 17 | // App Page 18 | import AppPage from '../../client/pages/appPage'; 19 | import AdviceGraph from '../../client/graphs/adviceGraph'; 20 | 21 | // prep the mock contexts 22 | let realUseContext; 23 | let useContextMock; 24 | 25 | // establish API mocking before all tests 26 | beforeAll(() => server.listen()); 27 | // set up the mock context before each test 28 | beforeEach(() => { 29 | realUseContext = React.useContext; 30 | useContextMock = React.useContext = jest.fn(); 31 | }); 32 | 33 | // reset any request handlers that are declared as a part of our tests 34 | // (i.e. for testing one-time error scenarios) 35 | afterEach(() => { 36 | // clean up the mock context 37 | React.useContext = realUseContext; 38 | server.resetHandlers(); 39 | }); 40 | // clean up once the tests are done 41 | afterAll(() => server.close()); 42 | 43 | xdescribe('Renders website', () => { 44 | describe('Renders Home Page', () => { 45 | test('renders Home Page Component', async () => { 46 | const { findByText } = render(); 47 | const element = await findByText(/Team Members/i); 48 | expect(element).toBeInTheDocument(); 49 | }); 50 | 51 | test('renders TeamContainer', async () => { 52 | const { findByText } = render(); 53 | const element = await findByText(/Heidi/i); 54 | expect(element).toBeInTheDocument(); 55 | }); 56 | 57 | test('renders DemoContainer', async () => { 58 | const { findByText } = render(); 59 | // multiple instances of a word will break, need to search for something specific 60 | const element = await findByText(/GraphQL migration assistance/i); 61 | expect(element).toBeInTheDocument(); 62 | }); 63 | }); 64 | 65 | // test('testing the onRoot', async () => { 66 | // const ok = await onRoot(); 67 | // expect(ok).toEqual({ greeting: 'hello world' }); 68 | // }); 69 | }); 70 | -------------------------------------------------------------------------------- /client/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useReducer, lazy, Suspense, useEffect } from 'react'; 2 | import { Route, Switch, Link, useLocation } from 'react-router-dom'; 3 | import NavBar from './components/navbar'; 4 | 5 | // import pages for routes; 6 | const HomePage = lazy(() => import('./pages/homePage')); 7 | const AppPage = lazy(() => import('./pages/appPage')); 8 | 9 | // import context object 10 | import { GeneralContext } from './state/contexts'; 11 | import { initialGeneralState, generalReducer } from './state/reducers'; 12 | 13 | 14 | const App = () => { 15 | // to grab the current URL path 16 | const location = useLocation(); 17 | 18 | const [generalState, generalDispatch] = useReducer(generalReducer, initialGeneralState); 19 | 20 | useEffect(() => { 21 | const nav = document.getElementById('NavBarContainer').style; 22 | const html = document.querySelector('html').style; 23 | const appHeader = document.getElementById('appHeader').style; 24 | 25 | if (location.pathname === '/') { 26 | nav.position = 'fixed'; 27 | appHeader.justifyContent = 'flex-end'; 28 | } else { 29 | html.background = '$currentline'; 30 | nav.position = ''; 31 | appHeader.justifyContent = 'space-between'; 32 | } 33 | }); 34 | 35 | return ( 36 | 41 | 42 |
43 | {location.pathname === '/app' && 44 | 52 | 53 | } 54 | 55 |
56 | 57 | }> 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | ); 66 | }; 67 | 68 | export default App; 69 | -------------------------------------------------------------------------------- /client/assets/advice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/advice.gif -------------------------------------------------------------------------------- /client/assets/backsplash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/backsplash.png -------------------------------------------------------------------------------- /client/assets/daniel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/daniel.jpg -------------------------------------------------------------------------------- /client/assets/emily.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/emily.jpg -------------------------------------------------------------------------------- /client/assets/export.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/export.gif -------------------------------------------------------------------------------- /client/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/github.png -------------------------------------------------------------------------------- /client/assets/graphgif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/graphgif.gif -------------------------------------------------------------------------------- /client/assets/heidi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/heidi.jpg -------------------------------------------------------------------------------- /client/assets/idea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/idea.png -------------------------------------------------------------------------------- /client/assets/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/linkedin.png -------------------------------------------------------------------------------- /client/assets/logoclear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/logoclear.png -------------------------------------------------------------------------------- /client/assets/logodark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/logodark.png -------------------------------------------------------------------------------- /client/assets/logotext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/logotext.png -------------------------------------------------------------------------------- /client/assets/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/medium.png -------------------------------------------------------------------------------- /client/assets/modal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/modal.gif -------------------------------------------------------------------------------- /client/assets/playground.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/playground.gif -------------------------------------------------------------------------------- /client/assets/ross.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/ross.jpg -------------------------------------------------------------------------------- /client/assets/splashgif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/splashgif.gif -------------------------------------------------------------------------------- /client/assets/story.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/story.png -------------------------------------------------------------------------------- /client/assets/teamdraqlabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/teamdraqlabs.png -------------------------------------------------------------------------------- /client/assets/tommy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/tommy.jpg -------------------------------------------------------------------------------- /client/assets/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/twitter.png -------------------------------------------------------------------------------- /client/assets/types.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/FluxQL/9f46f89ad9105e2d103a0c22049fe2fb7d55f358/client/assets/types.gif -------------------------------------------------------------------------------- /client/components/URILink.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { dynamicText, staticText } from '../graphs/helperFunctions'; 3 | import { secret } from '../../server/controllers/testPSQL.js'; 4 | import CryptoJS from 'crypto-js'; 5 | 6 | // import the Context Objects 7 | import { GeneralContext, URIContext } from '../state/contexts'; 8 | 9 | export default function URILink({ databaseName }) { 10 | const { generalDispatch } = useContext(GeneralContext); 11 | 12 | const { 13 | codeDispatch, 14 | psqlDispatch, 15 | mongoDispatch, 16 | adviceDispatch, 17 | } = useContext(URIContext); 18 | 19 | return ( 20 |
21 |

{databaseName} Link

22 | 28 | 29 | 103 |
104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /client/components/adviceCodeBlock.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { UnControlled as CodeMirror } from 'react-codemirror2'; 3 | import 'codemirror/mode/javascript/javascript'; 4 | import { AdviceContext } from '../state/contexts'; 5 | 6 | export default function adviceCodeBlock() { 7 | const { adviceState } = useContext(AdviceContext); 8 | 9 | let copyButton; 10 | 11 | if ( 12 | adviceState.displayExample.includes('type') || 13 | adviceState.displayExample.includes('values') 14 | ) { 15 | copyButton = null; 16 | } else { 17 | copyButton = ( 18 | 27 | ); 28 | } 29 | 30 | return ( 31 |
32 | 42 | {copyButton} 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /client/components/codeBox.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { UnControlled as CodeMirror } from 'react-codemirror2'; 3 | import 'codemirror/mode/javascript/javascript'; 4 | 5 | // import context object 6 | import { CodeContext } from '../state/contexts'; 7 | 8 | export default function codeBox() { 9 | const { codeState } = useContext(CodeContext); 10 | 11 | let code; 12 | codeState.showSchema 13 | ? (code = codeState.schema) 14 | : (code = codeState.resolver); 15 | 16 | return ( 17 | <> 18 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /client/components/demoRow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function demoRow ({ index, gif, text, header }) { 4 | if (!(index % 2)) { 5 | return ( 6 |
7 | EVEN 8 |
9 |

{header}

10 |

{text}

11 |
12 |
13 | ); 14 | } else { 15 | return ( 16 |
17 |
18 |

{header}

19 |

{text}

20 |
21 | ODD 22 |
23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /client/components/exportButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import JSZip from 'jszip'; 3 | import FileSaver from 'file-saver'; 4 | import fileGenerator from '../zipFiles/zipFileHelpers'; 5 | 6 | // import schema & resolver context from state 7 | import { CodeContext } from '../state/contexts'; 8 | 9 | export default function exportButton() { 10 | const { codeState } = useContext(CodeContext); 11 | 12 | const handleExport = () => { 13 | const zip = new JSZip(); 14 | 15 | // creating a new zip folder, called 'DraQLa' 16 | const draqlaFolder = zip.folder('DraQLa'); 17 | 18 | // creating new files called 'schema.js' and generating text to put inside each file using helper functions 19 | draqlaFolder.file( 20 | 'schema.js', 21 | fileGenerator.schemaFile(codeState.schema, codeState.resolver) 22 | ); 23 | draqlaFolder.file('README.md', fileGenerator.readMeFile()); 24 | 25 | zip.generateAsync({ type: 'blob' }).then((content) => { 26 | FileSaver.saveAs(content, 'DraQLa.zip'); 27 | }); 28 | }; 29 | 30 | return ( 31 |
32 | Export 33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /client/components/helpButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function helpButton({ handleHelpModal }) { 4 | return ( 5 |
6 | Help 7 |
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /client/components/navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | // import the custom useContext 5 | import { useGenContext } from '../state/contexts'; 6 | 7 | export default function navbar({ location }) { 8 | // we invoke the custom useContext 9 | const { generalDispatch } = useGenContext(); 10 | 11 | let navbarDisplay; 12 | 13 | // conditional rendering depending on URL endpoint 14 | if (location === '/'){ 15 | navbarDisplay = ( 16 | <> 17 | 18 | About 19 | 20 | 21 | Contact Us 22 | 23 | 29 | GitHub 30 | 31 | { 35 | setTimeout(generalDispatch({ type: 'OPEN_URI_MODAL' }), 1000); 36 | }} 37 | > 38 | Play 39 | 40 | 41 | ); 42 | } else if (location === '/app'){ 43 | navbarDisplay = ( 44 | <> 45 | 48 | Home 49 | 50 | 51 | { 55 | // toggle state to pop up the modal 56 | generalDispatch({ type: 'OPEN_URI_MODAL' }); 57 | }} 58 | > 59 | DB URI 60 | 61 | 62 | { 66 | window.open('/graphql', '_blank'); 67 | }} 68 | > 69 | Playground 70 | 71 | 72 | ); 73 | } 74 | return ( 75 | 78 | ); 79 | } -------------------------------------------------------------------------------- /client/components/teamCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | export default function teamCard({ name, profilePic, github, linkedin }) { 5 | return ( 6 |
7 | profile picture 8 |

{name}

9 |
10 | clickme window.open(github)} /> 11 | clickme window.open(linkedin)} /> 12 |
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /client/components/undoButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function undoButton() { 4 | return ( 5 |
console.log('undo clicked')}> 6 | Undo 7 |
8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /client/containers/aboutContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function aboutContainer() { 4 | return ( 5 |
6 |
7 | 8 |

Your Story

9 |

You are seeking an upgrade from your REST API framework to GraphQL but the evolution seems daunting.

10 |
11 |
12 | 13 |

Our Vision

14 |

DraQLa is a GraphQL migration assistance tool that empowers developers to build GraphQL schemas, without writing any code.

15 |
16 |
17 | 18 |

Features

19 |

20 | Custom GraphQL schema

21 | Advice Overview

22 | Interactive Visualizer

23 | Testing Opportunities

24 |

25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /client/containers/adviceContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AdviceGraph from '../graphs/adviceGraph'; 3 | 4 | export default function adviceContainer() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /client/containers/codeContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import CodeBox from '../components/codeBox'; 3 | import ExportButton from '../components/exportButton'; 4 | 5 | // import Context Object 6 | import { CodeContext } from '../state/contexts'; 7 | 8 | const codeContainer = () => { 9 | const { codeState, codeDispatch } = useContext(CodeContext); 10 | 11 | const displayCodeBtns = ( 12 |
13 |
codeDispatch({ type: 'SHOW_SCHEMA' })} 16 | > 17 | schema.js 18 |
19 |
20 |
codeDispatch({ type: 'SHOW_RESOLVER' })} 23 | > 24 | resolver.js 25 |
26 |
27 | 28 |
29 | ); 30 | 31 | return ( 32 |
33 | {codeState.schema && displayCodeBtns} 34 | 35 |
36 | ); 37 | }; 38 | 39 | export default codeContainer; 40 | -------------------------------------------------------------------------------- /client/containers/contactContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function contactContainer() { 4 | return ( 5 |
6 |
7 |
8 |

Please feel free to submit

any comments or suggestions!

9 |
10 |
11 | 12 | 13 |