├── .DS_Store ├── .babelrc ├── .gitignore ├── README.md ├── __mocks__ ├── MockCases.js ├── MockServer.js ├── SetupTests.js ├── electronMock.js ├── fileMock.js └── styleMock.js ├── __tests__ └── Enzyme.test.js ├── app ├── electron.js ├── server │ └── schema.js └── src │ ├── App.js │ ├── Components │ ├── AutoUpdate.js │ ├── CheckBox.js │ ├── DownloadBtn.js │ ├── DropDownMenu.js │ ├── Loader.js │ ├── MongoDBURI.js │ ├── MongoSchemaIDE.js │ ├── PlaygroundButton.js │ ├── ResetButton.js │ ├── Ribbon.js │ └── TreeGraph.js │ ├── containers │ └── Container.js │ ├── index.js │ └── public │ ├── codemirror.css │ ├── custom-tree.css │ ├── index.css │ ├── loader.css │ └── material.css ├── build-res └── icon.png ├── electron-builder.json ├── gif ├── pastingURI.gif ├── playground.gif ├── qlens.gif └── reset.gif ├── jest-setup.js ├── jest-teardown.js ├── jest.config.js ├── package-lock.json ├── package.json ├── webpack.build.config.js └── webpack.dev.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/.DS_Store -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | electron-builder.json 4 | .env 5 | notarize.js 6 | entitlements.plist 7 | build-res/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/open-source-labs/Swell/blob/master/LICENSE.txt) 6 | [![Build Status](https://travis-ci.org/open-source-labs/Swell.svg?branch=master)](https://travis-ci.org/open-source-labs/Swell) 7 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/open-source-labs/Swell?color=blue) 8 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/getswell/getswell/issues) 9 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Swell-%20For%20all%20your%20streaming%20API%20testing%20needs&url=https://www.getswell.io&hashtags=SSE,WebSocket,HTTP,API,developers) 10 | 11 | 12 | 13 | # QLens 14 | 15 | Using GraphQL with MongoDB can cause data mismatch and schema duplication requiring developers to write similar code multiple times. This can significantly slow down development time. In addition, there aren't many libraries that tackle MongoDB conversion to GraphQL schemas. Most libraries tackling this issue are focused on relational databases. QLens solves that problem. 16 | 17 | QLens — Open source tool to extract metadata from your MongoDB database to generate GraphQL schemas, resolvers and server setup. 18 | 19 | Accelerated by [OS Labs](https://github.com/oslabs-beta/) 20 | 21 | ## Getting Started 22 | 1. Globally install extract mongo schema on your machine: npm -g install extract-mongo-schema 23 | 2. Download QLens onto your desktop by going to [www.qlensapp.com](http://qlensapp.com) 24 | 25 | ## Features 26 | * GraphQL schema (including resolvers, and mutations) 27 | * Visual interactive diagram of Hierarchy Display 28 | * Download Schemas 29 | * GraphQL's Playground GUI 30 | 31 | ## How does QLens work? 32 | ![QLens.gif](./gif/qlens.gif) 33 | 34 | ## 1. Enter URI 35 | Simply enter your non-relational database (MongoDB) URI in the input form at the top of the screen and press enter. You will see a dropdown menu on the left hand side of the screen revealing all of your database schemas. Click the schemas you would like converted to GraphQL and then click the ‘Add Selected Schemas’ button. 36 | 37 | ![QLens.gif](./gif/pastingURI.gif) 38 | 39 | Please see below gif to reset the URI: 40 | 41 | ![QLens.gif](./gif/reset.gif) 42 | 43 | ## 2. Select GraphQL Schemas 44 | On the right side of the screen is where your life just got a whole lot easier. You will find your GraphQL schema boilerplate already entirely formatted for your project complete with resolvers and mutations. At the top of the text editor, you will see a tab to toggle to see your MongoDB schemas in their own editor. Here is where you can either copy your GraphQL schemas and paste right into your code editor, or click the download button.

45 | 46 | ## 3. Download Schemas 47 | Click the Download Schemas button at the bottom of the code editor and the GraphQL schema code will download into a folder on your desktop. Drag the folder into your preferred code editor and voilà! Formatted GraphQL schemas right there in your code editor! It’s that easy!

48 | 49 | ## 4. Playground: 50 | Back in QLens, you can see your GraphQL schemas in action by clicking on the Playground button at the top right-hand side of the screen which will take you to GraphQL’s very own integrated development environment, GraphiQL where you can make queries and test your code. 51 | 52 | ![QLens.gif](./gif/playground.gif) 53 | 54 | ## How To Contribute 55 | We would love for you to test our application and submit any issues you encouter. Please feel free to fork your own repository to and submit your own pull requests. 56 | 57 | ## Built With 58 | 59 | - Electron 60 | - React 61 | - Codemirror 62 | - Jest 63 | - Node.js 64 | - Express 65 | - Graphiql 66 | - Graphql 67 | - Lodash 68 | - Babel 69 | - Webpack 70 | - Enzyme 71 | - React-Testing-Library 72 | - Spectron 73 | 74 | How you can contribute: 75 | * Bug fixes 76 | * Implementing features 77 | * Submitting or resolving GitHub issues 78 | * Help market our application 79 | ## Developers 80 | * [Steven LaBrie](https://github.com/stevenlabrie) 81 | * [Judy Tan](https://github.com/Judanator) 82 | * [Cho Yee Win Aung](https://github.com/choyeewinag) 83 | * [Jake Diorio](https://github.com/jdiorio2393) 84 | 85 | ## License 86 | This project is licensed under the MIT License 87 | 88 | ## Upcoming Features 89 | Currently QLens does not support playground functionality and has limited functionality for creating resolvers. Currently we are working on implementing the following features: 90 | * Playground functionality for testing queries and mutations 91 | * Generating mutations for updating 92 | * Generating resolvers for Mongo documents with "ref" or foreign keys 93 | * Allowing for conversion of SQL databases 94 | -------------------------------------------------------------------------------- /__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 schemaDataState = { 7 | schemaDataState: {}, 8 | }; 9 | 10 | export const schemaDataSetState = { 11 | generalState: { 12 | setSchemaData: jest.fn(), 13 | }, 14 | }; 15 | 16 | export const uriIdState = { 17 | uriIdState: {}, 18 | }; 19 | 20 | export const uriIdSetState = { 21 | uriIdState: { 22 | setUriId: jest.fn(), 23 | }, 24 | }; 25 | 26 | export const selectedSchemaDataState = { 27 | selectedSchemaDataState: {}, 28 | }; 29 | 30 | export const selectedSchemaDataSetState = { 31 | selectedSchemaDataState: { 32 | setSelectedSchemaData: jest.fn(), 33 | }, 34 | }; 35 | 36 | export const clickedState = { 37 | clicked: {}, 38 | }; 39 | 40 | export const clickedSetState = { 41 | clicked: { 42 | setClicked: jest.fn(), 43 | }, 44 | }; 45 | 46 | 47 | export const graphQLSchemaState = { 48 | graphQLSchema: {}, 49 | }; 50 | 51 | export const graphQLSchemaSetState = { 52 | graphQLSchema: { 53 | setGraphQLSchema: jest.fn(), 54 | }, 55 | }; 56 | 57 | // export const homeGenState = { 58 | // generalState: { 59 | // onHomePage: true, 60 | // URImodal: false, 61 | // helpModal: false, 62 | // }, 63 | // }; 64 | 65 | // export const appGenState = { 66 | // generalState: { 67 | // onHomePage: false, 68 | // URImodal: false, 69 | // helpModal: false, 70 | // generalDispatch: jest.fn(), 71 | // }, 72 | // }; -------------------------------------------------------------------------------- /__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 }; -------------------------------------------------------------------------------- /__mocks__/SetupTests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | import 'regenerator-runtime/runtime'; 3 | -------------------------------------------------------------------------------- /__mocks__/electronMock.js: -------------------------------------------------------------------------------- 1 | export const ipcRenderer = { 2 | on: jest.fn(), 3 | }; -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = "test-file-stub" -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | 3 | -------------------------------------------------------------------------------- /__tests__/Enzyme.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | import { shallow, configure } from 'enzyme'; 4 | 5 | // import custom useContext 6 | import * as MockContexts from '../app/src/state/Contexts'; 7 | 8 | // import mock cases 9 | import { schemaDataState, schemaDataSetState } from '../__mocks__/MockCases'; 10 | import { uriIdState, uriIdSetState } from '../__mocks__/MockCases'; 11 | import { selectedSchemaDataState, selectedSchemaDataSetState } from '../__mocks__/MockCases'; 12 | import { clickedState, clickedSetState } from '../__mocks__/MockCases'; 13 | import { graphQLSchema, graphQLSetSchema } from '../__mocks__/MockCases'; 14 | 15 | // import React Components 16 | import CheckBox from '../app/src/Components/CheckBox'; 17 | import DropDownMenu from '../app/src/Components/DropDownMenu'; 18 | import MongoDBURI from '../app/src/Components/MongoDBURI'; 19 | import MongoSchemaIDE from '../app/src/Components/MongoSchemaIDE'; 20 | import PlaygroundButton from '../app/src/Components/PlaygroundButton'; 21 | import TreeGraph from '../app/src/Components/TreeGraph'; 22 | import App from '../app/src/App'; 23 | 24 | configure({ adapter: new Adapter() }); 25 | 26 | describe(' renders on electron app', () => { 27 | const wrapper = shallow(); 28 | 29 | it('App contains Logo', () => { 30 | expect(wrapper.find('img')).toBeTruthy(); 31 | }); 32 | }) 33 | 34 | describe('MongoDBURI Input Box Displays', () => { 35 | it('MongoDBURI Input Box renders initially', () => { 36 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx) 37 | // we pass in our mock state as context to the spy 38 | jest 39 | .spyOn(MockContexts, 'useGenContext') 40 | // .mockImplementation(() => uriIdState); 41 | 42 | const wrapper = shallow(); 43 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer 44 | const confirm = wrapper.hasClass('formContainer'); 45 | // expects confirm (boolean => true) to be true 46 | expect(confirm).toBe(true); 47 | }); 48 | }) 49 | 50 | describe('PlaygroundButton Displays', () => { 51 | it('PlaygroundButton renders initially', () => { 52 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx) 53 | // we pass in our mock state as context to the spy 54 | jest 55 | .spyOn(MockContexts, 'useGenContext') 56 | // .mockImplementation(() => schemaDataState); 57 | 58 | const wrapper = shallow(); 59 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer 60 | const confirm = wrapper.hasClass('playgroundButton'); 61 | // expects confirm (boolean => true) to be true 62 | expect(confirm).toBe(true); 63 | }); 64 | }) 65 | 66 | describe('DropDownMenu Displays', () => { 67 | it('DropDownMenu renders initially', () => { 68 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx) 69 | // we pass in our mock state as context to the spy 70 | jest 71 | .spyOn(MockContexts, 'useGenContext') 72 | // .mockImplementation(() => schemaDataState); 73 | 74 | const wrapper = shallow(); 75 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer 76 | const confirm = wrapper.hasClass('dropDown'); 77 | // expects confirm (boolean => true) to be true 78 | expect(confirm).toBe(true); 79 | }); 80 | }) 81 | 82 | 83 | describe('TreeGraph Displays', () => { 84 | it('TreeGraph renders initially', () => { 85 | // jest spyOn can only spy on functions, which is why we created our custom useContext (clients/state/context.jsx) 86 | // we pass in our mock state as context to the spy 87 | jest 88 | .spyOn(MockContexts, 'useGenContext') 89 | // .mockImplementation(() => schemaDataState); 90 | 91 | const wrapper = shallow(); 92 | // create a variable that equal holds the boolean value of whether wrapper has a class of NavBarContainer 93 | const confirm = wrapper.hasClass('tree'); 94 | // expects confirm (boolean => true) to be true 95 | expect(confirm).toBe(true); 96 | }); 97 | }) 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/electron.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import parts of electron to use 3 | const { app, BrowserWindow } = require('electron'); 4 | const path = require('path'); 5 | const url = require('url'); 6 | const { ipcMain } = require('electron'); 7 | const process = require('process'); 8 | const server = require('./server/schema'); 9 | const fs = require('fs'); 10 | const os = require('os'); 11 | const fixPath = require('fix-path'); 12 | // Connect to mongodb 13 | const MongoClient = require('mongodb').MongoClient; 14 | // Executing terminal commands using JS 15 | const { exec } = require('child_process'); 16 | const { autoUpdater } = require('electron-updater'); 17 | 18 | // Add React extension for development 19 | const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer'); 20 | 21 | // Keep a global reference of the window object, if you don't, the window will 22 | // be closed automatically when the JavaScript object is garbage collected. 23 | let mainWindow; 24 | 25 | // Keep a reference for dev mode 26 | let dev = false; 27 | 28 | // Determine the mode (dev or production) 29 | if (process.defaultApp || /[\\/]electron-prebuilt[\\/]/.test(process.execPath) || /[\\/]electron[\\/]/.test(process.execPath)) { 30 | dev = true; 31 | } 32 | 33 | // Temporary fix for broken high-dpi scale factor on Windows (125% scaling) 34 | // info: https://github.com/electron/electron/issues/9691 35 | if (process.platform === 'win32') { 36 | app.commandLine.appendSwitch('high-dpi-support', 'true') 37 | app.commandLine.appendSwitch('force-device-scale-factor', '1') 38 | } 39 | 40 | function createWindow() { 41 | // Create the browser window. 42 | mainWindow = new BrowserWindow({ 43 | width: 1350, // width of the window default 1024 44 | height: 768, // height of the window 45 | show: false, // don't show until window is ready 46 | webPreferences: { 47 | nodeIntegration: true 48 | } 49 | }) 50 | 51 | // and load the index.html of the app. 52 | let indexPath; 53 | 54 | // Determine the correct index.html file 55 | // (created by webpack) to load in dev and production 56 | if (dev && process.argv.indexOf('--noDevServer') === -1) { 57 | indexPath = url.format({ 58 | protocol: 'http:', 59 | host: 'localhost:8080', 60 | pathname: 'index.html', 61 | slashes: true 62 | }) 63 | } else { 64 | indexPath = url.format({ 65 | protocol: 'file:', 66 | pathname: path.join(__dirname, '../dist/index.html'), 67 | slashes: true 68 | }) 69 | } 70 | 71 | // Load the index.html 72 | mainWindow.loadURL(indexPath) 73 | 74 | // Don't show the app window until it is ready and loaded 75 | mainWindow.once('ready-to-show', () => { 76 | mainWindow.show() 77 | 78 | // Open the DevTools automatically if developing 79 | if (dev) { 80 | installExtension(REACT_DEVELOPER_TOOLS) 81 | .catch(err => console.log('Error loading React DevTools: ', err)) 82 | mainWindow.webContents.openDevTools() 83 | } 84 | }) 85 | 86 | // Emitted when the window is closed. 87 | mainWindow.on('closed', function() { 88 | // Dereference the window object, usually you would store windows 89 | // in an array if your app supports multi windows, this is the time 90 | // when you should delete the corresponding element. 91 | mainWindow = null 92 | }) 93 | mainWindow.once('ready-to-show', () => { 94 | autoUpdater.checkForUpdatesAndNotify(); 95 | }); 96 | } 97 | 98 | // This method will be called when Electron has finished 99 | // initialization and is ready to create browser windows. 100 | // Some APIs can only be used after this event occurs. 101 | app.on('ready', () => { 102 | createWindow() 103 | // otherWindow() 104 | }) 105 | 106 | // Quit when all windows are closed. 107 | app.on('window-all-closed', () => { 108 | // On macOS it is common for applications and their menu bar 109 | // to stay active until the user quits explicitly with Cmd + Q 110 | if (process.platform !== 'darwin') { 111 | app.quit() 112 | } 113 | }) 114 | 115 | app.on('activate', () => { 116 | // On macOS it's common to re-create a window in the app when the 117 | // dock icon is clicked and there are no other windows open. 118 | if (mainWindow === null) { 119 | createWindow() 120 | } 121 | }) 122 | 123 | if (!fs.existsSync(path.join(process.resourcesPath, "/schemafiles/"))) { 124 | fs.mkdirSync(path.join(process.resourcesPath, "/schemafiles/")); 125 | } 126 | let testpath = path.join(process.resourcesPath, "/schemafiles/qlens.json") 127 | 128 | if (process.resourcesPath !== 'win32') fixPath(); 129 | 130 | ipcMain.on('URI', (event, arg) => { 131 | 132 | // Connect to mongodb 133 | MongoClient.connect(arg).then(() => { 134 | const query = `extract-mongo-schema -d "${arg}" -o ${testpath}`; 135 | event.sender.send('console', process.platform) 136 | //Using exec to run extract-mongo-schema package in terminal 137 | exec(query, (error, stdout, stderr) => { 138 | event.sender.send('URI-reply', path.join(process.resourcesPath)) 139 | }) 140 | }) 141 | 142 | let filepath = path.join(process.resourcesPath, "/schemafiles") 143 | let file; 144 | let extractedSchemas; 145 | // Watching for changes in root directory. (The adding of json file with schema) 146 | const watcher = fs.watch(filepath, (events, trigger) => { 147 | // console.log(`there was a ${event} at ${trigger}`); 148 | // Once change happens (file is added) read schema.json file 149 | file = fs.readFileSync(path.join(process.resourcesPath, "/schemafiles/qlens.json")); 150 | // console.log('SERVER.JS =========> ', Buffer.from(file).toString()) 151 | extractedSchemas = Buffer.from(file).toString(); 152 | if (extractedSchemas) { 153 | // send locals data to client, close this watch function 154 | watcher.close(); 155 | if (fs.existsSync(path.join(process.resourcesPath, "/schemafiles/qlens.json"))) { 156 | fs.unlinkSync(path.join(process.resourcesPath, "/schemafiles/qlens.json")); 157 | console.log('file Deleted'); 158 | } 159 | event.sender.send('URI-reply', extractedSchemas) 160 | } 161 | }) 162 | }); 163 | 164 | autoUpdater.on('update-available', () => { 165 | mainWindow.webContents.send('update_available'); 166 | }); 167 | autoUpdater.on('update-downloaded', () => { 168 | mainWindow.webContents.send('update_downloaded'); 169 | }); 170 | 171 | ipcMain.on('restart_app', () => { 172 | autoUpdater.quitAndInstall(); 173 | }); 174 | -------------------------------------------------------------------------------- /app/server/schema.js: -------------------------------------------------------------------------------- 1 | const graphql = require('graphql'); 2 | var cloneDeep = require('lodash.clonedeep'); 3 | const MongoClient = require('mongodb').MongoClient; 4 | var pluralize = require('pluralize'); 5 | const { ipcMain } = require('electron') 6 | const { graphqlHTTP } = require('express-graphql'); 7 | const express = require('express'); 8 | const app = express(); 9 | 10 | const { 11 | GraphQLObjectType, 12 | GraphQLString, 13 | GraphQLSchema, 14 | GraphQLID, 15 | GraphQLInt, 16 | GraphQLList, 17 | GraphQLNonNull, 18 | } = graphql; 19 | 20 | const converter = {}; 21 | let rootQueryObj = {}; 22 | 23 | function addWhiteSpace(number) { 24 | let whiteSpace = ' '; 25 | for (let i = 0; i < number; i += 1) { 26 | whiteSpace += ' '; 27 | } 28 | return whiteSpace; 29 | } 30 | 31 | ipcMain.on('selectedSchemas', (event, arg) => { 32 | const url = arg.uriId; 33 | const data = arg.selectedSchemas; 34 | console.log('IS THIS IT????', data); 35 | // Function for capitalization 36 | const capitalize = (s) => { 37 | if (typeof s !== 'string') return ''; 38 | return s.charAt(0).toUpperCase() + s.slice(1); 39 | }; 40 | const getGraphQlType = (key, value) => { 41 | switch (true) { 42 | case key.includes('__v'): 43 | break; 44 | case key.includes('_id'): 45 | fieldsObj[key] = { type: GraphQLID }; 46 | strFields += `${addWhiteSpace(4)}${key}: { type: GraphQLID },|`; 47 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace( 48 | 4 49 | )}${JSON.stringify(value)},|`; 50 | mutationToString += `${addWhiteSpace(8)}${key}: ${ 51 | value.required === false 52 | ? '{ type: GraphQLID }' 53 | : '{ type: new GraphQLNonNull(GraphQLID)}' 54 | },|`; 55 | break; 56 | case value.type.includes('string'): 57 | fieldsObj[key] = { type: GraphQLString }; 58 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLString) }; 59 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLString },|`; 60 | mutationToString += `${addWhiteSpace(8)}${key}: ${ 61 | value.required === false 62 | ? '{ type: GraphQLString }' 63 | : '{ type: new GraphQLNonNull(GraphQLString)}' 64 | },|`; 65 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace( 66 | 4 67 | )}${JSON.stringify(value)},|`; 68 | break; 69 | case value.type.includes('Array'): 70 | fieldsObj[key] = { type: new GraphQLList(GraphQLString) }; 71 | mutationObj[key] = { 72 | type: new GraphQLNonNull(new GraphQLList(GraphQLString)), 73 | }; 74 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLString },|`; 75 | mutationToString += `${addWhiteSpace(8)}${key}: ${ 76 | value.required === false 77 | ? '{ type: new GraphQLList(GraphQLString) }' 78 | : '{ type: new GraphQLNonNull(new GraphQLList(GraphQLString)) }' 79 | },|`; 80 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace( 81 | 4 82 | )}${JSON.stringify(value)},|`; 83 | break; 84 | case value.type.includes('number'): 85 | fieldsObj[key] = { type: GraphQLInt }; 86 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLInt) }; 87 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLInt },|`; 88 | mutationToString += `${addWhiteSpace(8)}${key}: ${ 89 | value.required === false 90 | ? '{ type: GraphQLInt }' 91 | : '{ type: new GraphQLNonNull(GraphQLInt) }' 92 | },|`; 93 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace( 94 | 4 95 | )}${JSON.stringify(value)},|`; 96 | break; 97 | case value.type.includes('Object'): 98 | fieldsObj[key] = { type: GraphQLObjectType }; 99 | mutationObj[key] = { type: new GraphQLNonNull(GraphQLObjectType) }; 100 | strFields += `${addWhiteSpace(4)}${key}:{ type: GraphQLObjectType },|`; 101 | mutationToString += `${addWhiteSpace(8)}${key}: ${ 102 | value.required === false 103 | ? '{ type: GraphQLObjectType }' 104 | : '{ type: new GraphQLNonNull(GraphQLObjectType) }' 105 | },|`; 106 | mongoSchemaStr += `${addWhiteSpace(2)}${key}: |${addWhiteSpace( 107 | 4 108 | )}${JSON.stringify(value)},|`; 109 | 110 | break; 111 | default: 112 | console.log(value, 'Nothing Triggered-----'); 113 | break; 114 | } 115 | }; 116 | 117 | // Storing graphql object types 118 | let obj = {}; 119 | // Storing properties of each mongo db schema 120 | let fieldsObj = {}; 121 | let strFields = ''; 122 | let stringObj = {}; 123 | let rootQueryStr = ''; 124 | let sendRootQueryObj = {}; 125 | let mutationObj = {}; 126 | let mutationObjStr = ''; 127 | let mutationToString = ''; 128 | let mutationSchema; 129 | let buildMongoStr = ''; 130 | let mongoSchemaStr = ''; 131 | 132 | for (const property in data) { 133 | for (const [key, value] of Object.entries(data[property])) { 134 | getGraphQlType(key, value); 135 | } 136 | 137 | 138 | // Building Selected schemas string to be sent to front end display (codemirror) 139 | 140 | buildMongoStr += 141 | `const ${property} = new Schema({|` + `${mongoSchemaStr}` + `});||`; 142 | 143 | // Function will be added to rootQuery resolver. Returns documents 144 | async function run(id) { 145 | let dataArr = []; 146 | const client = new MongoClient(url, { useUnifiedTopology: true }); 147 | const regex = /\/(\w+)\?/g; 148 | const databaseName = url.match(regex); 149 | const databaseString = databaseName 150 | .join('') 151 | .slice(1, databaseName.join('').length - 1); 152 | try { 153 | await client.connect(); 154 | const database = client.db(databaseString); 155 | const collection = database.collection(property); 156 | if (id) { 157 | const query = { _id: id }; 158 | const cursor = await collection.findOne(query); 159 | dataArr.push(cursor); 160 | } else { 161 | const cursor = collection.find({}); 162 | // print a message if no documents were found 163 | if ((await cursor.count()) === 0) { 164 | console.log('No documents found!'); 165 | } 166 | await cursor.forEach((data) => { 167 | dataArr.push(data); 168 | }); 169 | } 170 | } finally { 171 | await client.close(); 172 | } 173 | // return !id ? dataArr : dataArr.join(''); 174 | return dataArr; 175 | } 176 | const deep = cloneDeep(fieldsObj); 177 | 178 | // Dynamically creating graphql object types 179 | obj[capitalize(`${property}Type`)] = new GraphQLObjectType({ 180 | name: capitalize(property), 181 | fields: () => deep, 182 | }); 183 | 184 | // formats the graphQL schema to send to the front end 185 | stringObj[property] = 186 | `const ${capitalize(`${property}Type`)} = new GraphQLObjectType({\n` + 187 | `${addWhiteSpace(2)}name: '${capitalize(property)}',\n` + 188 | `${addWhiteSpace(2)}fields: () => ({\n` + 189 | `${strFields}` + 190 | `${addWhiteSpace(2)}})${addWhiteSpace(1)}\n` + 191 | `});${addWhiteSpace(1)}\n`; 192 | // dynamically creating fields object in rootQueryType 193 | rootQueryObj[property] = { 194 | type: new GraphQLList(obj[capitalize(`${property}Type`)]), 195 | resolve: function resolve(parent, args) { 196 | // replace with Find() method Based on your file structure/imports 197 | return run(); 198 | }, 199 | }; 200 | 201 | //======================= CREATING MUTATION ======================= 202 | 203 | let listOfProperties = ''; 204 | Object.keys(data[property]) 205 | .filter((key) => key !== '_id' && key !== '__v') 206 | .forEach((el) => { 207 | listOfProperties += `${el}: args.${el},|${addWhiteSpace(10)}`; 208 | }); 209 | 210 | mutationObjStr += 211 | ` add${capitalize(property)} : {|` + 212 | ` type: ${capitalize(`${property}Type`)},|` + 213 | ` args: {|` + 214 | `${mutationToString}` + 215 | `${addWhiteSpace(6)}},|` + 216 | `${addWhiteSpace(6)}resolve(parent, args) {|` + 217 | `${addWhiteSpace(8)}let ${property} = new ${capitalize(property)} ({|` + 218 | `${addWhiteSpace(10)}${JSON.stringify(listOfProperties)}` + 219 | `});|` + 220 | `${addWhiteSpace(8)}return ${property}.save(); |` + 221 | ` } |` + 222 | ` }, |` + 223 | ` delete${pluralize.singular(capitalize(property))} : {|` + 224 | ` type: ${capitalize(`${property}Type`)},|` + 225 | ` args: {|` + 226 | `${mutationToString}` + 227 | `${addWhiteSpace(6)}},|` + 228 | `${addWhiteSpace(6)}resolve(parent, args) {|` + 229 | `${addWhiteSpace(8)} return ${pluralize.singular( 230 | capitalize(`${property}`) 231 | )}.findOneAndDelete({id: args.id})` + 232 | ` } |` + 233 | ` }, |`; 234 | 235 | mutationSchema = 236 | `const Mutation = new GraphQLObjectType({|` + 237 | `${addWhiteSpace(2)}name: 'Mutation', |` + 238 | `${addWhiteSpace(2)}fields: {|${mutationObjStr}|}|});`; 239 | 240 | //===================================================================== 241 | 242 | rootQueryStr += 243 | ` ${pluralize.singular(property)}: {|` + 244 | `${addWhiteSpace(6)}type: ${capitalize(`${property}Type`)},|` + 245 | `${addWhiteSpace(6)}args: { id: { type: GraphQLID }},|` + 246 | `${addWhiteSpace(6)}resolve(parent, args) {|` + 247 | `${addWhiteSpace(8)}return ${capitalize( 248 | pluralize.singular(property) 249 | )}.findById(args.id);|` + 250 | `${addWhiteSpace(6)}} |` + 251 | `${addWhiteSpace(4)}},|`; 252 | 253 | rootQueryStr += 254 | `${`${addWhiteSpace(4)}${property}`}: {|` + 255 | `${addWhiteSpace(6)}type: new GraphQLList(${capitalize( 256 | `${property}Type` 257 | )}),|` + 258 | `${addWhiteSpace(6)}resolve(parent, args) {|` + 259 | `${addWhiteSpace(8)}return ${capitalize( 260 | pluralize.singular(property) 261 | )}.find({});|` + 262 | `${addWhiteSpace(6)}}|` + 263 | `${addWhiteSpace(4)}},|`; 264 | 265 | // resetting the fieldsObject 266 | fieldsObj = {}; 267 | strFields = ''; 268 | mutationToString = ''; 269 | mongoSchemaStr = ''; 270 | } 271 | 272 | sendRootQueryObj.queries = 273 | `const RootQuery = new GraphQLObjectType({|` + 274 | ` name: "RootQueryType",|` + 275 | ` fields: { | ${rootQueryStr} }|});|`; 276 | 277 | const RootQuery = new GraphQLObjectType({ 278 | name: 'RootQueryType', 279 | fields: rootQueryObj, 280 | }); 281 | 282 | const Schema = new GraphQLSchema({ 283 | query: RootQuery, 284 | }) 285 | 286 | app.use( 287 | '/graphql', 288 | graphqlHTTP({ schema: Schema, graphiql: true }) 289 | ); 290 | 291 | event.sender.send("returnedSchemas", {types: stringObj, queries: sendRootQueryObj, mutations: mutationSchema, mongoSchema: buildMongoStr}) 292 | }) 293 | 294 | const RootQuery = new GraphQLObjectType({ 295 | name: 'RootQueryType', 296 | fields: rootQueryObj, 297 | }); 298 | 299 | app.listen(3000, () => console.log('listening on port 3000')); 300 | 301 | module.exports = { 302 | converter, 303 | schema: new GraphQLSchema({ 304 | query: RootQuery, 305 | }), 306 | }; 307 | -------------------------------------------------------------------------------- /app/src/App.js: -------------------------------------------------------------------------------- 1 | // Import dependencies 2 | import React from "react"; 3 | import Container from "./containers/Container"; 4 | import AutoUpdate from "./Components/AutoUpdate"; 5 | import "./public/index.css"; 6 | 7 | // Create main App component 8 | const App = () => { 9 | return ( 10 |
11 | 12 | 13 |
14 | ) 15 | } 16 | // Export the App component 17 | export default App; 18 | -------------------------------------------------------------------------------- /app/src/Components/AutoUpdate.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import "../public/index.css"; 3 | const { ipcRenderer } = require('electron') 4 | 5 | const AutoUpdate = () => { 6 | const notification = document.getElementById('notification'); 7 | const message = document.getElementById('message'); 8 | const restartButton = document.getElementById('restart-button'); 9 | 10 | ipcRenderer.on('update_available', () => { 11 | ipcRenderer.removeAllListeners('update_available'); 12 | message.innerText = 'A new update is available. Downloading now...'; 13 | notification.classList.remove('hidden'); 14 | }); 15 | ipcRenderer.on('update_downloaded', () => { 16 | ipcRenderer.removeAllListeners('update_downloaded'); 17 | message.innerText = 'Update Downloaded. It will be installed on restart. Restart now?'; 18 | restartButton.classList.remove('hidden'); 19 | notification.classList.remove('hidden'); 20 | }); 21 | 22 | function closeNotification() { 23 | notification.classList.add('hidden'); 24 | }; 25 | function restartApp() { 26 | ipcRenderer.send('restart_app'); 27 | }; 28 | 29 | return ( 30 |
31 |

32 | 35 | 38 |
39 | ) 40 | } 41 | 42 | export default AutoUpdate 43 | -------------------------------------------------------------------------------- /app/src/Components/CheckBox.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | // import MongoSchemaIDE from 'MongoSchemaIDE'; 3 | 4 | const CheckBox = (props) => { 5 | return ( 6 |
7 |
8 | 12 |
13 |
14 | ) 15 | } 16 | 17 | export default CheckBox; -------------------------------------------------------------------------------- /app/src/Components/DownloadBtn.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import JSZip from "jszip"; 3 | import saveAs from "file-saver"; 4 | 5 | const DownloadBtn = ({ graphSchema }) => { 6 | console.log(graphSchema); 7 | const download = () => { 8 | let zip = new JSZip(); 9 | 10 | 11 | var schema = zip.folder("schemas"); 12 | schema.file("graphQLSchema.js", graphSchema); 13 | schema.file("readMe.md", "hi there"); 14 | 15 | zip.generateAsync({ type: "blob" }).then(function (content) { 16 | // see FileSaver.js 17 | saveAs(content, "QLens.zip"); 18 | }); 19 | }; 20 | return ( 21 | 24 | ); 25 | }; 26 | 27 | export default DownloadBtn; 28 | -------------------------------------------------------------------------------- /app/src/Components/DropDownMenu.js: -------------------------------------------------------------------------------- 1 | // create a button to send schema data to the backend to receive schema data back 2 | // research code mirror to hold the schemas 3 | // todo: create functionality so that when a checkbox is clicked, the schema appears on the page 4 | 5 | import React from 'react'; 6 | import CheckBox from './CheckBox'; 7 | import {ipcRenderer} from "electron"; 8 | import ResetButton from './ResetButton'; 9 | 10 | const DropDownMenu = ({schemaData, uriData, sendSchemas, addCheckmark, toggleBtn, toggleCheckbox, resetBtn, resetButton}) => { 11 | const checkBoxComponents = []; 12 | 13 | for (let key in schemaData) { 14 | checkBoxComponents.push( 15 | 16 | ) 17 | } 18 | 19 | return( 20 |
21 |
22 | {checkBoxComponents} 23 |
24 |
25 | 26 | 27 |
28 |
29 | ) 30 | } 31 | 32 | export default DropDownMenu; 33 | 34 | -------------------------------------------------------------------------------- /app/src/Components/Loader.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import '../public/loader.css'; 3 | 4 | const Loader = () => { 5 | return ( 6 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |

Loading...

18 |
19 | ); 20 | }; 21 | 22 | export default Loader; 23 | -------------------------------------------------------------------------------- /app/src/Components/MongoDBURI.js: -------------------------------------------------------------------------------- 1 | import React, {useState,useEffect} from "react"; // userEffect to fetch instead of component mount 2 | import '../public/index.css'; 3 | 4 | // input field for the mongoDB uri 5 | const MongoDBURI = ({submitbtn, geturi, toggleInput}) => { 6 | return ( 7 |
8 |
9 |
10 | 11 | 12 | 13 |
14 |
15 |
16 | ) 17 | } 18 | 19 | export default MongoDBURI; 20 | -------------------------------------------------------------------------------- /app/src/Components/MongoSchemaIDE.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Codemirror from 'codemirror'; 3 | import {UnControlled as CodeMirror} from 'react-codemirror2'; 4 | import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; 5 | import 'react-tabs/style/react-tabs.css'; 6 | require('codemirror/mode/javascript/javascript'); 7 | import '../public/codemirror.css'; 8 | import '../../../node_modules/codemirror/lib/codemirror.css'; 9 | import '../../../node_modules/codemirror/theme/dracula.css'; 10 | import DownloadBtn from './DownloadBtn' 11 | const _ = require('lodash'); 12 | const MongoSchemaIDE = ({schemaData, selectedSchemaData, graphQLSchema}) => { 13 | const [data, setData] = useState([]); 14 | const [graphData, setGraphData] = useState({}); 15 | // iterates over the graphQLSchema and removes the double quotes 16 | const eliminateQuotes = (obj) => { 17 | let str = ''; 18 | for (let key in obj) { 19 | str += obj[key].replace(/["]+/g, ''); 20 | } 21 | return str; 22 | } 23 | // function creates a new line to format the schemas 24 | const newLinePillar = (str) => { 25 | if (str === undefined) return; 26 | let newStr = ''; 27 | let array = str.split(''); 28 | for (let i = 0; i < array.length; i+=1) { 29 | if (array[i] === '|') { 30 | array[i] = '\n'; 31 | newStr += array[i]; 32 | } else if (array[i] === '{' && array[i - 1] === '{') { 33 | continue; 34 | } else { 35 | newStr += array[i]; 36 | } 37 | } 38 | return newStr; 39 | } 40 | // adds a new line where there is a comma 41 | const newLineComma = (str) => { 42 | let newStr = ''; 43 | let array = str.split(''); 44 | for (let i = 0; i < array.length; i+=1) { 45 | if (array[i] === ',') { 46 | newStr += array[i]; 47 | newStr += '\n '; 48 | } else { 49 | newStr += array[i]; 50 | } 51 | } 52 | return newStr; 53 | } 54 | // mongoDB Schema 55 | const noQuotes = eliminateQuotes(JSON.stringify(selectedSchemaData)); 56 | const format = newLineComma(noQuotes); 57 | // const mSchema = newLineComma(graphQLSchema.mongoSchema); 58 | const newLineMongo = newLinePillar(graphQLSchema.mongoSchema); 59 | const mongoSchemaWithoutQuotes = eliminateQuotes(newLineMongo); 60 | const formattedMongo = newLineComma(mongoSchemaWithoutQuotes); 61 | // graphQL Schema 62 | const newTypes = eliminateQuotes(graphQLSchema.types); 63 | const newQueries = eliminateQuotes(graphQLSchema.queries); 64 | const commaLessMutation = eliminateQuotes(graphQLSchema.mutation); 65 | const rootQ = newLinePillar(newQueries); 66 | const formattedTypes = newLinePillar(newTypes); 67 | const rootM = newLinePillar(commaLessMutation); 68 | const combineQueries = (query1, query2, query3) => { 69 | return query1 + query2 + query3; 70 | } 71 | const combined = combineQueries(formattedTypes, rootQ, rootM); 72 | return( 73 |
74 | 75 | 76 | GraphQL Schemas 77 | MongoDB Schemas 78 | 79 | 80 |
81 | 100 |
101 |
102 | 103 |
104 | 121 |
122 |
123 |
124 | 125 |
126 | ) 127 | } 128 | export default MongoSchemaIDE;{/* */} -------------------------------------------------------------------------------- /app/src/Components/PlaygroundButton.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import '../public/index.css'; 3 | 4 | const PlaygroundButton = () => { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | export default PlaygroundButton; -------------------------------------------------------------------------------- /app/src/Components/ResetButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ResetButton = ({resetBtn}) => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default ResetButton; 12 | -------------------------------------------------------------------------------- /app/src/Components/Ribbon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Ribbon = () => { 4 | return ; 5 | }; 6 | 7 | export default Ribbon; -------------------------------------------------------------------------------- /app/src/Components/TreeGraph.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tree from 'react-tree-graph'; 3 | import 'react-tree-graph/dist/style.css' 4 | import '../public/index.css'; 5 | 6 | const TreeGraph = ({ schemaChart }) => { 7 | return( 8 |
9 | 17 |
18 | ) 19 | } 20 | 21 | export default TreeGraph; -------------------------------------------------------------------------------- /app/src/containers/Container.js: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | import React, { useState, useEffect, Fragment } from 'react'; 3 | import MongoDBURI from '../Components/MongoDBURI'; 4 | import MongoSchemaIDE from '../Components/MongoSchemaIDE'; 5 | import DropDownMenu from '../Components/DropDownMenu'; 6 | import PlaygroundButton from '../Components/PlaygroundButton'; 7 | // import Tree from '../Components/Tree'; 8 | // import TreeChart from '../Components/TreeChart'; 9 | import TreeGraph from '../Components/TreeGraph'; 10 | import Loader from '../Components/Loader'; 11 | import Ribbon from '../Components/Ribbon'; 12 | import fetch from 'electron-fetch' 13 | import {ipcRenderer} from "electron"; 14 | import ResetButton from '../Components/ResetButton' 15 | 16 | const Container = () => { 17 | const [schemaData, setSchemaData] = useState({}); 18 | const [uriId, setUriId] = useState(''); 19 | const [selectedSchemaData, setSelectedSchemaData] = useState([]); 20 | const [clicked, setClicked] = useState([]); 21 | const [graphQLSchema, setGraphQLSchema] = useState({}); 22 | const [loading, setLoading] = useState(false); 23 | 24 | const [toggleASSBtn, setToggleASSBtn] = useState(false) 25 | const [toggleInput, setToggleInput] = useState(false) 26 | const [toggleCheckbox, setToggleCheckbox] = useState(false) 27 | 28 | // enter MongoDBURI to receive schemas 29 | // submit function fetches schemas from backend when Submit button is clicked 30 | 31 | const submit = (e) => { 32 | e.preventDefault(); 33 | setLoading(true); 34 | ipcRenderer.send('URI', uriId) 35 | setToggleASSBtn(!toggleASSBtn) 36 | setToggleInput(!toggleInput) 37 | }; 38 | 39 | const resetButton = () => { 40 | window.location.reload(true); 41 | } 42 | 43 | useEffect(() => { 44 | ipcRenderer.on('URI-reply', (event, arg) => { 45 | setSchemaData(JSON.parse(arg)); 46 | setLoading(false); 47 | }) 48 | ipcRenderer.on('returnedSchemas', (event, arg) => { 49 | setGraphQLSchema(arg) 50 | setClicked([]); 51 | }) 52 | }, []) 53 | 54 | 55 | // updating state with the MongoDBRUI from input field 56 | const getUri = (e) => { 57 | setUriId(e.target.value); 58 | console.log(uriId); 59 | }; 60 | 61 | const addCheckmark = (item) => { 62 | let clickedSchema = item.target.name; 63 | if (clicked.includes(clickedSchema)) { 64 | setClicked( 65 | clicked.filter((tool) => { 66 | return tool !== clickedSchema; 67 | }) 68 | ); 69 | } else { 70 | setClicked([...clicked, clickedSchema]); 71 | } 72 | }; 73 | // sendSchema function builds the selectedSchemas object with the schemas that are selected in the DropDownMenu 74 | // sends the selectedSchemas to the backend for migration 75 | const sendSchemas = (e) => { 76 | let selectedSchemas = {}; 77 | for (let i = 0; i < clicked.length; i += 1) { 78 | selectedSchemas[clicked[i]] = schemaData[clicked[i]]; 79 | } 80 | setSelectedSchemaData([selectedSchemas]); 81 | setClicked([]) 82 | setToggleASSBtn(!toggleASSBtn) 83 | setToggleCheckbox(!toggleCheckbox) 84 | ipcRenderer.send('selectedSchemas', {selectedSchemas, uriId}) 85 | }; 86 | 87 | 88 | 89 | // creating formatted object for d3 graph 90 | let schemaChart = {}; 91 | if (selectedSchemaData[0]) { 92 | schemaChart = { 93 | name: 'Database Schema', 94 | children: [], 95 | }; 96 | let i = 0; 97 | let childArr = []; 98 | for (let key in selectedSchemaData[0]) { 99 | console.log(selectedSchemaData[0][key]); 100 | for (let prop in selectedSchemaData[0][key]) { 101 | childArr.push({ name: prop }); 102 | } 103 | console.log(childArr); 104 | schemaChart.children[i] = { 105 | name: key, 106 | children: childArr, 107 | }; 108 | childArr = []; 109 | i++; 110 | } 111 | } 112 | 113 | return ( 114 | 115 | {loading ? : null} 116 |
117 |
118 | QLens-logo 124 | 133 | 134 |
135 |
136 | 145 | {Object.keys(schemaChart).length > 0 ? ( 146 | 147 | ) : null} 148 | 152 |
153 | 154 | {/* */} 157 |
158 |
159 | ); 160 | }; 161 | 162 | export default Container; 163 | -------------------------------------------------------------------------------- /app/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | // Import dependencies 3 | import React from 'react' 4 | import { render } from 'react-dom' 5 | 6 | // Import main App component 7 | import App from './App.js' 8 | 9 | // // Import CSS stylesheet 10 | // import './assets/css/app.css' 11 | 12 | // Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it 13 | let root = document.createElement('div') 14 | 15 | // Append root div to body 16 | root.id = 'root' 17 | document.body.appendChild(root) 18 | 19 | // Render the app into the root div 20 | render(, document.getElementById('root')) 21 | // Export the App component 22 | export default App -------------------------------------------------------------------------------- /app/src/public/codemirror.css: -------------------------------------------------------------------------------- 1 | 2 | .codebox { 3 | border: 1px solid black; 4 | } 5 | 6 | .codebox2 { 7 | border: 2px solid black; 8 | } 9 | 10 | .CodeMirror { 11 | height: 75vh !important; 12 | } -------------------------------------------------------------------------------- /app/src/public/custom-tree.css: -------------------------------------------------------------------------------- 1 | .node__root > circle { 2 | fill: red; 3 | } 4 | 5 | .node__branch > circle { 6 | fill: yellow; 7 | stroke: white; 8 | fill: #5EAFC6; 9 | } 10 | 11 | .node__leaf > circle { 12 | fill: green; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/public/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); 2 | * { 3 | outline: none; 4 | } 5 | .logo { 6 | grid-area: logo; 7 | width: 65%; 8 | align-self: center; 9 | } 10 | 11 | body { 12 | background-color: #111212; 13 | font-family: monospace; 14 | } 15 | 16 | .URIForm { 17 | font-family: monospace; 18 | font-size: 2.3vh; 19 | width: 100%; 20 | padding: 1%; 21 | border: none; 22 | margin-bottom: 2%; 23 | border-radius: 20px; 24 | margin-left: 20px; 25 | } 26 | 27 | .formClass { 28 | text-align: center; 29 | } 30 | 31 | .node__root { 32 | color: red; 33 | } 34 | 35 | /* ================================ CHECKBOX BUTTON ================================ */ 36 | 37 | .checkboxLabel { 38 | color: #0D2F63; 39 | font-family: monospace; 40 | font-size: 1.7vh; 41 | padding: 1.3%; 42 | } 43 | 44 | /* .checkbox { 45 | appearance: none; 46 | } */ 47 | 48 | .checkboxContainer { 49 | background-color: white; 50 | margin-bottom: 2px; 51 | padding: 1.3%; 52 | width: 100%; 53 | margin: 2px auto; 54 | } 55 | 56 | #box { 57 | border: 4px solid #8be9fd; 58 | border-radius: 3px; 59 | margin: auto; 60 | margin-top: 2%; 61 | max-width: 95%; 62 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); 63 | transition: all 0.3s cubic-bezier(.25,.8,.25,1); 64 | } 65 | 66 | #box:hover { 67 | animation-duration: 0.25s; 68 | border-left: 8px solid #8be9fd; 69 | box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); 70 | } 71 | 72 | .buttonContainer { 73 | display: inline-block; 74 | } 75 | 76 | .addSchemaBtn { 77 | text-align: center; 78 | } 79 | 80 | .disableAddSelected { 81 | display: block; 82 | width: 100%; 83 | height: 40px; 84 | line-height: 40px; 85 | font-size: 13px; 86 | font-family: monospace; 87 | text-decoration: none; 88 | /* background-color: red; */ 89 | color: #333; 90 | letter-spacing: 2px; 91 | text-align: center; 92 | position: relative; 93 | transition: all .35s; 94 | border-radius: 5px; 95 | } 96 | 97 | .disableAddSelected:disabled { 98 | /* background-color: blue; */ 99 | } 100 | 101 | .container { 102 | display: grid; 103 | grid-template-columns: 0.7fr 1.6fr 0.7fr; 104 | grid-template-rows: 1fr; 105 | gap: 0px 0px; 106 | grid-template-areas: 107 | "logo formContainer playgroundButton"; 108 | } 109 | 110 | .formContainer { 111 | grid-area: formContainer; 112 | } 113 | 114 | .grid-container { 115 | display: grid; 116 | margin-top: 2rem; 117 | grid-template-columns: 0.4fr 1.4fr 1fr; 118 | grid-template-rows: 1fr; 119 | gap: 0px 0px; 120 | grid-template-areas: 121 | "dropDown tree codeboxContainer"; 122 | } 123 | 124 | .dropDown { 125 | grid-area: dropDown; 126 | display: block; 127 | width: 100%; 128 | } 129 | 130 | .menuItems { 131 | line-height: 40px; 132 | font-size: 13px; 133 | font-family: monospace; 134 | text-decoration: none; 135 | color: #333; 136 | letter-spacing: 2px; 137 | /* text-align: center; */ 138 | position: relative; 139 | transition: all .35s; 140 | /* margin-left: 37px; 141 | margin-top: 2%; */ 142 | } 143 | 144 | .checkboxForm { 145 | display:block; 146 | width: 100%; 147 | 148 | } 149 | 150 | .tree { 151 | text-align: center; 152 | 153 | } 154 | 155 | .codeboxContainer { 156 | grid-area: codeboxContainer; 157 | } 158 | 159 | .playgroundButton { 160 | grid-area: playgroundButton; 161 | align-self: center; 162 | } 163 | 164 | .URISubmitButton:hover { 165 | cursor: pointer; 166 | background-color: rgb(116, 115, 124); 167 | } 168 | 169 | .playgroundButton:hover{ 170 | cursor: pointer; 171 | } 172 | 173 | .react-tabs__tab { 174 | color: white; 175 | } 176 | 177 | .react-tabs__tab--selected { 178 | background: #fff; 179 | border-color: #aaa; 180 | color: black; 181 | border-radius: 5px 5px 0 0; 182 | } 183 | 184 | .circle { 185 | background-color: black; 186 | } 187 | .node { 188 | background-color: black; 189 | } 190 | 191 | div.custom-container { 192 | background-color: #242424; 193 | } 194 | 195 | svg.custom .node circle { 196 | fill: #f4f4f4; 197 | stroke: #2593B8; 198 | stroke-width: 2px; 199 | } 200 | 201 | svg.custom .node text { 202 | font-size: 18px; 203 | background-color: #444; 204 | fill: #f4f4f4; 205 | text-shadow: 0 1px 4px black; 206 | } 207 | 208 | svg.custom .node { 209 | cursor: pointer; 210 | } 211 | 212 | svg.custom path.link { 213 | fill: none; 214 | stroke: #32a8cf; 215 | stroke-width: 2px; 216 | } 217 | 218 | /* ================================ ADD SELECTED SCHEMA BUTTON ================================ */ 219 | 220 | 221 | .AddSelectedSchemasButton{ 222 | display: block; 223 | width: 100%; 224 | height: 40px; 225 | line-height: 40px; 226 | font-size: 13px; 227 | font-family: monospace; 228 | text-decoration: none; 229 | color: #333; 230 | border: 2px solid #333; 231 | letter-spacing: 2px; 232 | text-align: center; 233 | position: relative; 234 | transition: all .35s; 235 | margin-top: 6%; 236 | margin-bottom: 3%; 237 | border-radius: 5px; 238 | } 239 | 240 | .AddSelectedSchemasButton span { 241 | font-family: monospace; 242 | font-weight: 700; 243 | cursor: pointer; 244 | position: relative; 245 | z-index: 2; 246 | } 247 | 248 | .AddSelectedSchemasButton:after { 249 | position: absolute; 250 | content: ""; 251 | top: 0; 252 | left: 0; 253 | width: 0; 254 | height: 100%; 255 | background: #8be9fd; 256 | transition: all .35s; 257 | } 258 | 259 | .AddSelectedSchemasButton:hover{ 260 | color: #0D2F63; 261 | } 262 | 263 | .AddSelectedSchemasButton:hover:after{ 264 | width: 100%; 265 | } 266 | 267 | /* ================================ INPUT FIELD ================================ */ 268 | .form__group { 269 | position: relative; 270 | padding: 15px 0 0; 271 | margin-top: 10px; 272 | width: 100%; 273 | 274 | } 275 | 276 | .formContainer { 277 | width: 75%; 278 | justify-self: center; 279 | align-self: center; 280 | } 281 | 282 | .form__field { 283 | font-family: inherit; 284 | width: 100%; 285 | border: 0; 286 | border-bottom: 2px solid #9b9b9b; 287 | outline: 0; 288 | font-size: 1.3rem; 289 | color: #fff; 290 | padding: 7px 0; 291 | background: transparent; 292 | transition: border-color 0.2s; 293 | } 294 | .form__field::placeholder { 295 | color: transparent; 296 | } 297 | .form__field:placeholder-shown ~ .form__label { 298 | font-size: 1.3rem; 299 | cursor: text; 300 | top: 20px; 301 | } 302 | .form__label { 303 | position: absolute; 304 | top: 0; 305 | display: block; 306 | transition: 0.2s; 307 | font-size: 1rem; 308 | color: #9b9b9b; 309 | } 310 | .form__field:focus { 311 | padding-bottom: 6px; 312 | font-weight: 700; 313 | border-width: 3px; 314 | border-image: linear-gradient(to right, #11998e, #38ef7d); 315 | border-image-slice: 1; 316 | } 317 | .form__field:focus ~ .form__label { 318 | position: absolute; 319 | top: 0; 320 | display: block; 321 | transition: 0.2s; 322 | font-size: 1rem; 323 | color: #11998e; 324 | font-weight: 700; 325 | } 326 | /* reset input */ 327 | .form__field:required, .form__field:invalid { 328 | box-shadow: none; 329 | } 330 | 331 | /* ================================ PLAYGROUND BUTTON ================================ */ 332 | .draw-border { 333 | font-family: monospace; 334 | position: relative; 335 | padding: 4%; 336 | width: 50%; 337 | background-color: #61dbfb; 338 | font-size: 17px; 339 | } 340 | 341 | .draw-border a { 342 | text-decoration: none; 343 | color: black; 344 | } 345 | .draw-border::before, .draw-border::after { 346 | border: 0 solid transparent; 347 | box-sizing: border-box; 348 | content: ''; 349 | pointer-events: none; 350 | position: absolute; 351 | width: 0; 352 | height: 0; 353 | bottom: 0; 354 | right: 0; 355 | } 356 | .draw-border::before { 357 | border-bottom-width: 4px; 358 | border-left-width: 4px; 359 | } 360 | .draw-border::after { 361 | border-top-width: 4px; 362 | border-right-width: 4px; 363 | } 364 | .draw-border:hover { 365 | color: #ffe593; 366 | } 367 | .draw-border:hover::before, .draw-border:hover::after { 368 | border-color: #0D2F63; 369 | transition: border-color 0s, width 0.25s, height 0.25s; 370 | width: 100%; 371 | height: 100%; 372 | } 373 | .draw-border:hover::before { 374 | transition-delay: 0s, 0s, 0.25s; 375 | } 376 | 377 | .draw-border:hover::after { 378 | transition-delay: 0s, 0.25s, 0s; 379 | } 380 | 381 | /* ================================ DOWNLOAD BUTTON ================================ */ 382 | .downloadBtn { 383 | border-radius: 4px; 384 | background-color: #61dbfb; 385 | border: none; 386 | text-align: center; 387 | font-size: 18px; 388 | padding: 8px; 389 | width: 200px; 390 | transition: all 0.5s; 391 | cursor: pointer; 392 | margin: 5px; 393 | font-family: monospace; 394 | font-weight: 600; 395 | } 396 | 397 | .downloadBtn span { 398 | cursor: pointer; 399 | display: inline-block; 400 | position: relative; 401 | transition: 0.5s; 402 | } 403 | 404 | .downloadBtn span:after { 405 | content: '\00bb'; 406 | position: absolute; 407 | opacity: 0; 408 | top: 0; 409 | right: -20px; 410 | transition: 0.5s; 411 | } 412 | 413 | .downloadBtn:hover span { 414 | padding-right: 25px; 415 | } 416 | 417 | .downloadBtn:hover span:after { 418 | opacity: 1; 419 | right: 0; 420 | } 421 | 422 | /* ================================ RIBBON BUTTON ================================ */ 423 | 424 | .corner-ribbon{ 425 | width: 200px; 426 | background: #F49274; 427 | position: absolute; 428 | font-weight: 700; 429 | font-size: 11.5px; 430 | top: 25px; 431 | left: -50px; 432 | text-align: center; 433 | line-height: 35px; 434 | letter-spacing: 1px; 435 | color: black; 436 | transform: rotate(-45deg); 437 | -webkit-transform: rotate(-45deg); 438 | } 439 | 440 | .corner-ribbon a { 441 | text-decoration-line: none; 442 | color: black; 443 | } 444 | 445 | .corner-ribbon.sticky{ 446 | position: fixed; 447 | } 448 | 449 | .corner-ribbon.top-right{ 450 | top: 25px; 451 | right: -50px; 452 | left: auto; 453 | transform: rotate(45deg); 454 | -webkit-transform: rotate(45deg); 455 | } 456 | 457 | /* ================================ RESET BUTTON ================================ */ 458 | 459 | .resetButton { 460 | font-size: 15px; 461 | letter-spacing: 2px; 462 | text-transform: uppercase; 463 | display: inline-block; 464 | text-align: center; 465 | width: 100%; 466 | height: 40px; 467 | font-weight: bold; 468 | padding: 5px 0px; 469 | /* border: 3px solid #61dbfb; */ 470 | border-radius: 2px; 471 | position: relative; 472 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.1); 473 | z-index: 2; 474 | border-radius: 5px; 475 | } 476 | .resetButton:before { 477 | -webkit-transition: 0.5s all ease; 478 | transition: 0.5s all ease; 479 | position: absolute; 480 | top: 0; 481 | left: 50%; 482 | right: 50%; 483 | bottom: 0; 484 | opacity: 0; 485 | content: ''; 486 | background-color: #61dbfb; 487 | z-index: -1; 488 | } 489 | .resetButton:hover:before { 490 | -webkit-transition: 0.5s all ease; 491 | transition: 0.5s all ease; 492 | left: 0; 493 | right: 0; 494 | opacity: 1; 495 | } 496 | .resetButton:focus:before { 497 | -webkit-transition: 0.5s all ease; 498 | transition: 0.5s all ease; 499 | left: 0; 500 | right: 0; 501 | opacity: 1; 502 | } 503 | /* ================================ AUTO UPDATE ================================ */ 504 | 505 | #notification { 506 | position: fixed; 507 | bottom: 20px; 508 | left: 20px; 509 | width: 200px; 510 | padding: 20px; 511 | border-radius: 5px; 512 | background-color: white; 513 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); 514 | } 515 | .hidden { 516 | display: none; 517 | } 518 | -------------------------------------------------------------------------------- /app/src/public/loader.css: -------------------------------------------------------------------------------- 1 | .lds-roller { 2 | display: inline-block; 3 | position: absolute; 4 | top: 50%; 5 | left: 50%; 6 | margin-top: -50px; 7 | margin-left: -50px; 8 | width: 150px; 9 | height: 150px; 10 | } 11 | 12 | .loading-text { 13 | position: absolute; 14 | top: 60%; 15 | left: 49%; 16 | margin-top: -50px; 17 | margin-left: -50px; 18 | color: white; 19 | } 20 | 21 | .lds-roller div { 22 | animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 23 | transform-origin: 40px 40px; 24 | } 25 | .lds-roller div:after { 26 | content: ' '; 27 | display: block; 28 | position: absolute; 29 | width: 7px; 30 | height: 7px; 31 | border-radius: 50%; 32 | background: #fff; 33 | margin: -4px 0 0 -4px; 34 | } 35 | .lds-roller div:nth-child(1) { 36 | animation-delay: -0.036s; 37 | } 38 | .lds-roller div:nth-child(1):after { 39 | top: 63px; 40 | left: 63px; 41 | } 42 | .lds-roller div:nth-child(2) { 43 | animation-delay: -0.072s; 44 | } 45 | .lds-roller div:nth-child(2):after { 46 | top: 68px; 47 | left: 56px; 48 | } 49 | .lds-roller div:nth-child(3) { 50 | animation-delay: -0.108s; 51 | } 52 | .lds-roller div:nth-child(3):after { 53 | top: 71px; 54 | left: 48px; 55 | } 56 | .lds-roller div:nth-child(4) { 57 | animation-delay: -0.144s; 58 | } 59 | .lds-roller div:nth-child(4):after { 60 | top: 72px; 61 | left: 40px; 62 | } 63 | .lds-roller div:nth-child(5) { 64 | animation-delay: -0.18s; 65 | } 66 | .lds-roller div:nth-child(5):after { 67 | top: 71px; 68 | left: 32px; 69 | } 70 | .lds-roller div:nth-child(6) { 71 | animation-delay: -0.216s; 72 | } 73 | .lds-roller div:nth-child(6):after { 74 | top: 68px; 75 | left: 24px; 76 | } 77 | .lds-roller div:nth-child(7) { 78 | animation-delay: -0.252s; 79 | } 80 | .lds-roller div:nth-child(7):after { 81 | top: 63px; 82 | left: 17px; 83 | } 84 | .lds-roller div:nth-child(8) { 85 | animation-delay: -0.288s; 86 | } 87 | .lds-roller div:nth-child(8):after { 88 | top: 56px; 89 | left: 12px; 90 | } 91 | @keyframes lds-roller { 92 | 0% { 93 | transform: rotate(0deg); 94 | } 95 | 100% { 96 | transform: rotate(360deg); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/public/material.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/app/src/public/material.css -------------------------------------------------------------------------------- /build-res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/build-res/icon.png -------------------------------------------------------------------------------- /electron-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "com.QLens.app", 3 | "afterSign": "notarize.js", 4 | "publish": [ 5 | { 6 | "provider": "github", 7 | "owner": "oslabs-beta", 8 | "repo": "QLens", 9 | "token": "680c09ac62533c95f7cf3c593e0474363be3eff8" 10 | } 11 | ], 12 | "asar": true, 13 | "productName": "QLens", 14 | "directories": { 15 | "app": ".", 16 | "output": "dist", 17 | "buildResources": "build-res" 18 | }, 19 | "files": [ 20 | "package.json", 21 | "app/package.json", 22 | "app/**/*", 23 | "node_modules/**/*", 24 | "app/node_modules/**/*", 25 | "dist/index.html", 26 | "dist/bundle.css", 27 | "dist/main.js", 28 | "app/qlens.json", 29 | "build-res/icon.png" 30 | ], 31 | "extraFiles": [ 32 | "credentials" 33 | ], 34 | "extraResources": ["./extraResources/**"], 35 | "dmg": { 36 | "sign": false, 37 | "background": null, 38 | "backgroundColor": "#ffffff", 39 | "window": { 40 | "width": "400", 41 | "height": "300" 42 | }, 43 | "contents": [ 44 | { 45 | "x": 100, 46 | "y": 100 47 | }, 48 | { 49 | "x": 300, 50 | "y": 100, 51 | "type": "link", 52 | "path": "/Applications" 53 | } 54 | ] 55 | }, 56 | "mac": { 57 | "target": "dmg", 58 | "category": "public.app-category.utilities", 59 | "hardenedRuntime": true, 60 | "gatekeeperAssess": false, 61 | "entitlements": "entitlements.plist", 62 | "entitlementsInherit": "entitlements.plist" 63 | }, 64 | "win": { 65 | "target": "nsis" 66 | }, 67 | "linux": { 68 | "target": "AppImage", 69 | "category": "Utility" 70 | } 71 | } -------------------------------------------------------------------------------- /gif/pastingURI.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/gif/pastingURI.gif -------------------------------------------------------------------------------- /gif/playground.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/gif/playground.gif -------------------------------------------------------------------------------- /gif/qlens.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/gif/qlens.gif -------------------------------------------------------------------------------- /gif/reset.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/QLens/2d8a462113697ddcc918295a5ad7da6226826144/gif/reset.gif -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | global.testServer = await require('./server'); 3 | }; -------------------------------------------------------------------------------- /jest-teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async (globalConfig) => { 2 | testServer.close(); 3 | }; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const {defaults} = require('jest-config'); 2 | 3 | module.exports = { 4 | verbose: true, 5 | // preset: 'ts-jest', 6 | // transform: { 7 | // '^.+\\.tsx?$': 'babel-jest', 8 | // }, 9 | moduleNameMapper: { 10 | "\\.(css|less|scss|sss|styl)$": "/node_modules/jest-css-modules" 11 | } 12 | }; 13 | 14 | // module.exports = { 15 | // verbose: true, 16 | // runner: "@jest-runner/electron", 17 | // testEnvironment: "@jest-runner/electron/environment", 18 | // moduleNameMapper: { 19 | // // "collectCoverage": true, 20 | // electron: "/__mocks__/electronMock.js", 21 | // "\\.(css|less|sass|scss)$": "/__mocks__/styleMocks.js", 22 | // "\\.(gif|ttf|eot|svg|png)$": "/__mocks__/fileMock.js", 23 | // }, 24 | // resolver: null,}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "QLens", 3 | "version": "1.1.0", 4 | "description": "Open source tool to extract metadata from your MongoDB database to generate GraphQL schemas, resolvers and server setup, enhancing developer's productivity.", 5 | "main": "app/electron.js", 6 | "homepage": "./", 7 | "scripts": { 8 | "prod": "webpack --mode production --config ./webpack.build.config.js && electron --noDevServer .", 9 | "start": "webpack serve --hot --host localhost --config=./webpack.dev.config.js --mode development", 10 | "build": "webpack --config ./webpack.build.config.js --mode production", 11 | "package": "npm run build", 12 | "postpackage": "electron-packager ./ --out=./builds", 13 | "postinstall": "electron-builder install-app-deps", 14 | "pack": "npm run build && electron-builder -w", 15 | "pack-mac": "npm run build && electron-builder -m", 16 | "dist": "electron-builder", 17 | "test": "jest --verbose", 18 | "test:watch": "npm run test -- --watch", 19 | "publish-mac": "npm run build && electron-builder -m --publish always", 20 | "publish-win": "npm run build && electron-builder -w --publish always", 21 | "publish-lin": "npm run build && electron-builder -l --publish always", 22 | "deploy": "npm run build && electron-builder -mwl --publish always" 23 | }, 24 | "repository": "github:oslabs-beta/QLens", 25 | "keywords": [], 26 | "author": { 27 | "name": "Judy Tan, Jake Diorio, Steven LaBrie, Cho Yee Win Aung", 28 | "email": "team@qlensapp.com", 29 | "url": "https://qlensapp.com/" 30 | }, 31 | "contributors": [ 32 | { 33 | "name": "Cho Yee Win Aung", 34 | "email": "choyeewinag@gmail.com", 35 | "url": "https://github.com/choyeewinag/" 36 | }, 37 | { 38 | "name": "Judy Tan", 39 | "email": "judytan864@gmail.com", 40 | "url": "https://github.com/Judanator" 41 | }, 42 | { 43 | "name": "Jake Diorio", 44 | "email": "jdiorio2393@gmail.com", 45 | "url": "https://github.com/jdiorio2393" 46 | }, 47 | { 48 | "name": "Steven LaBrie", 49 | "email": "steven.labrie.arias@outlook.com", 50 | "url": "https://github.com/stevenlabrie" 51 | } 52 | ], 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/stevenlabrie/QLens/issues" 56 | }, 57 | "devDependencies": { 58 | "@babel/core": "^7.12.10", 59 | "@babel/preset-env": "^7.12.11", 60 | "@babel/preset-react": "^7.12.13", 61 | "babel-loader": "^8.2.2", 62 | "babili-webpack-plugin": "^0.1.2", 63 | "concurrently": "^5.3.0", 64 | "css-loader": "^5.0.1", 65 | "electron": "^11.3.0", 66 | "electron-builder": "^22.9.1", 67 | "electron-packager": "^15.2.0", 68 | "file-loader": "^6.2.0", 69 | "html-loader": "^1.3.2", 70 | "html-webpack-plugin": "^4.5.1", 71 | "jest": "^26.6.3", 72 | "mini-css-extract-plugin": "^1.3.5", 73 | "nodemon": "^2.0.7", 74 | "resize-observer-polyfill": "^1.5.1", 75 | "style-loader": "^2.0.0", 76 | "use-resize-observer": "^7.0.0", 77 | "webpack": "^5.20.0", 78 | "webpack-cli": "^4.5.0", 79 | "webpack-dev-server": "^3.11.2" 80 | }, 81 | "dependencies": { 82 | "@testing-library/jest-dom": "^5.11.9", 83 | "@testing-library/react": "^11.2.5", 84 | "@testing-library/user-event": "^12.7.3", 85 | "ace-builds": "^1.4.12", 86 | "babel-preset-es2015": "^6.24.1", 87 | "babel-preset-react": "^6.24.1", 88 | "codemirror": "^5.59.2", 89 | "cors": "^2.8.5", 90 | "d3": "^6.5.0", 91 | "dotenv": "^8.2.0", 92 | "electron-devtools-installer": "^3.1.1", 93 | "electron-fetch": "^1.7.3", 94 | "electron-is-dev": "^1.2.0", 95 | "electron-notarize": "^1.0.0", 96 | "electron-updater": "^4.3.5", 97 | "enzyme": "^3.11.0", 98 | "enzyme-adapter-react-16": "^1.15.6", 99 | "express": "^4.17.1", 100 | "express-graphql": "^0.12.0", 101 | "extract-mongo-schema": "^0.2.10", 102 | "file-saver": "^2.0.5", 103 | "fix-path": "^3.0.0", 104 | "graphiql": "^1.3.2", 105 | "graphql": "^15.5.0", 106 | "graphql-log": "^0.1.3", 107 | "jest-config": "^26.6.3", 108 | "jest-css-modules": "^2.1.0", 109 | "jszip": "^3.6.0", 110 | "lodash": "^4.17.20", 111 | "lodash.clonedeep": "^4.5.0", 112 | "mongodb": "^3.6.4", 113 | "mongoose": "^5.11.14", 114 | "msw": "^0.26.2", 115 | "pluralize": "^8.0.0", 116 | "react": "^17.0.1", 117 | "react-ace": "^9.3.0", 118 | "react-codemirror2": "^7.2.1", 119 | "react-d3-tree": "^2.0.1", 120 | "react-dom": "^17.0.1", 121 | "react-router-dom": "^5.2.0", 122 | "react-tabs": "^3.2.0", 123 | "react-tree-graph": "^6.0.0", 124 | "spectron": "^13.0.0", 125 | "svg-url-loader": "^7.1.1", 126 | "wait-on": "^5.2.1" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /webpack.build.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | // const BabiliPlugin = require('babili-webpack-plugin') 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 6 | 7 | // Any directories you will be adding code/files into, need to be added to this array so webpack will pick them up 8 | const defaultInclude = path.resolve(__dirname, './app/src'); 9 | 10 | module.exports = { 11 | entry: './app/src', 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.css$/, 16 | use: [ 17 | MiniCssExtractPlugin.loader, 18 | 'css-loader' 19 | ], 20 | include: [defaultInclude, path.resolve(__dirname, 'node_modules/codemirror/lib/codemirror.css'), path.resolve(__dirname, 'node_modules/codemirror/theme/dracula.css'), path.resolve(__dirname, './node_modules/react-tabs/style/react-tabs.css'), path.resolve(__dirname, './node_modules/react-tree-graph/dist/style.css')] 21 | }, 22 | { 23 | test: /\.js|\.jsx$/, 24 | use: { loader: 'babel-loader', options: {presets: ['@babel/preset-env', '@babel/preset-react']} }, 25 | include: defaultInclude, 26 | exclude: '/node_modules/' 27 | }, 28 | { 29 | test: /\.(jpe?g|png|gif)$/, 30 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], 31 | include: defaultInclude 32 | }, 33 | { 34 | test: /\.(eot|svg|ttf|woff|woff2)$/, 35 | use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }], 36 | include: defaultInclude 37 | } 38 | ] 39 | }, 40 | resolve: { 41 | extensions: ['*', '.js', '.jsx'] 42 | }, 43 | target: 'electron-renderer', 44 | plugins: [ 45 | new HtmlWebpackPlugin(), 46 | new MiniCssExtractPlugin({ 47 | // Options similar to the same options in webpackOptions.output 48 | // both options are optional 49 | filename: 'bundle.css', 50 | chunkFilename: '[id].css' 51 | }), 52 | new webpack.DefinePlugin({ 53 | 'process.env.NODE_ENV': JSON.stringify('production') 54 | }), 55 | // new BabiliPlugin() 56 | ], 57 | stats: { 58 | colors: true, 59 | children: false, 60 | chunks: false, 61 | modules: false 62 | } 63 | } -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const { spawn } = require('child_process') 5 | 6 | // Any directories you will be adding code/files into, need to be added to this array so webpack will pick them up 7 | const defaultInclude = path.resolve(__dirname, './app/src'); 8 | 9 | module.exports = { 10 | entry: './app/src', 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | //use: [{ loader: 'style-loader' }, { loader: 'css-loader' }], 16 | use: ["style-loader", "css-loader"], 17 | include: [defaultInclude, 18 | path.resolve(__dirname, 'node_modules/codemirror/lib/codemirror.css'), path.resolve(__dirname, 'node_modules/codemirror/theme/dracula.css'), path.resolve(__dirname, './node_modules/react-tabs/style/react-tabs.css'), path.resolve(__dirname, './node_modules/react-tree-graph/dist/style.css')] 19 | }, 20 | { 21 | test: /\.jsx?$/, 22 | use: [{ loader: 'babel-loader' }], 23 | include: defaultInclude 24 | }, 25 | { 26 | test: /\.(jpe?g|png|gif)$/, 27 | use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], 28 | include: defaultInclude 29 | }, 30 | { 31 | test: /\.(eot|ttf|woff|woff2)$/, 32 | use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }], 33 | include: defaultInclude 34 | }, 35 | { 36 | test: /\.svg$/, 37 | use: [ 38 | { 39 | loader: 'svg-url-loader', 40 | options: { 41 | limit: 10000, 42 | }, 43 | }, 44 | ], 45 | }, 46 | ] 47 | }, 48 | target: 'electron-renderer', 49 | plugins: [ 50 | new HtmlWebpackPlugin(), 51 | new webpack.DefinePlugin({ 52 | 'process.env.NODE_ENV': JSON.stringify('development') 53 | }) 54 | ], 55 | devtool: 'cheap-source-map', 56 | devServer: { 57 | proxy: { 58 | '/': 'localhost:3000', 59 | }, 60 | contentBase: path.resolve(__dirname, 'dist'), 61 | stats: { 62 | colors: true, 63 | chunks: false, 64 | children: false 65 | }, 66 | before() { 67 | spawn( 68 | 'electron', 69 | ['.'], 70 | { shell: true, env: process.env, stdio: 'inherit' } 71 | ) 72 | .on('close', code => process.exit(0)) 73 | .on('error', spawnError => console.error(spawnError)) 74 | } 75 | } 76 | } --------------------------------------------------------------------------------