├── git ├── .gitignore ├── src ├── pictures │ ├── logo.png │ ├── logo2.png │ ├── preQL1.gif │ ├── preQL2.gif │ ├── triangles.jpg │ ├── errormessage.png │ ├── logo-cropped.png │ ├── preql-website.png │ └── securitysettings.png ├── About │ └── AboutMain.tsx ├── index.html ├── Home │ ├── Warning.tsx │ ├── UserInput.tsx │ ├── TableSelector.tsx │ ├── SelectButtons.tsx │ ├── Tables.tsx │ ├── SelectionChoices.tsx │ ├── HomeMain.tsx │ └── QueryGenerator.tsx ├── navbar.tsx ├── index.js ├── App.tsx ├── electron.js └── styles.scss ├── import-png.d.ts ├── .vscode └── settings.json ├── tsconfig.json ├── server ├── apiRouter.js ├── server.js └── controller.js ├── index.js ├── README.md ├── webpack.config.js └── package.json /git: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out/ -------------------------------------------------------------------------------- /src/pictures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/logo.png -------------------------------------------------------------------------------- /src/pictures/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/logo2.png -------------------------------------------------------------------------------- /import-png.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" { 2 | const value: any; 3 | export default value; 4 | } 5 | -------------------------------------------------------------------------------- /src/pictures/preQL1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/preQL1.gif -------------------------------------------------------------------------------- /src/pictures/preQL2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/preQL2.gif -------------------------------------------------------------------------------- /src/pictures/triangles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/triangles.jpg -------------------------------------------------------------------------------- /src/pictures/errormessage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/errormessage.png -------------------------------------------------------------------------------- /src/pictures/logo-cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/logo-cropped.png -------------------------------------------------------------------------------- /src/pictures/preql-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/preql-website.png -------------------------------------------------------------------------------- /src/pictures/securitysettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/preql/HEAD/src/pictures/securitysettings.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontFamily": "Consolas, 'Courier New', monospace", 3 | "editor.fontLigatures": null, 4 | "typescript.tsdk": "node_modules/typescript/lib" 5 | } 6 | -------------------------------------------------------------------------------- /src/About/AboutMain.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function About() { 4 | return( 5 |
6 | BANANA! 7 |
8 | ) 9 | } 10 | 11 | export default About 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "allowJs": true, 9 | "allowSyntheticDefaultImports": true, 10 | "moduleResolution": "node", 11 | }, 12 | "include": ["src", "import-png.d.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 |
Test
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Home/Warning.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | 3 | const Warning = (props: any) => { 4 | const { warning} = props; 5 | const [alert, setAlert] = useState('You must pick from two different tables*') 6 | 7 | if(warning) { 8 | return( 9 |
10 | {alert} 11 |
12 | ) 13 | } else { 14 | return( 15 |
16 | {/* {alert} */} 17 |
18 | ) 19 | } 20 | } 21 | 22 | export default Warning; 23 | -------------------------------------------------------------------------------- /src/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Link } from 'react-router-dom' 4 | import Logo from './pictures/logo.png'; 5 | 6 | const Navbar = () => { 7 | return ( 8 |
9 |
10 | 11 |
12 |
13 | Home 14 | About 15 |
16 |
17 | ); 18 | } 19 | 20 | export default Navbar 21 | -------------------------------------------------------------------------------- /server/apiRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const controller = require('./controller'); 3 | const apiRouter = express.Router(); 4 | 5 | //Connection Route 6 | apiRouter.post('/connect', 7 | controller.getTableNames, 8 | controller.getTableData, 9 | (req, res) => { 10 | res.status(200).send(res.locals.returnData); 11 | }); 12 | 13 | apiRouter.post('/join', 14 | controller.getJoinTable, 15 | (req, res) => { 16 | res.status(200).send(res.locals.returnJoinData); 17 | }); 18 | 19 | module.exports = apiRouter; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // import path from 'path/posix'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import App from './App' 6 | import styles from './styles.scss' 7 | //import triangles from './pictures/triangles.jpg' 8 | 9 | // testing 10 | 11 | render( 12 |
13 | 14 |
, 15 | document.getElementById('app'), 16 | ) 17 | 18 | // original script for start line 47 19 | //"start": "nodemon server/server.js", 20 | 21 | // original main js script on line 5 22 | // "main": "index.js", 23 | -------------------------------------------------------------------------------- /src/Home/UserInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, Component } from 'react'; 2 | 3 | const UserInput = (props: any) => { 4 | const [URIinput, setURIinput] = useState('') 5 | const { fields, makeDBRequest} = props 6 | return( 7 |
8 | 9 | setURIinput(event.target.value)} placeholder="postgres://" />

10 | 11 |
12 | ) 13 | } 14 | 15 | export default UserInput 16 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, Component} from 'react'; 2 | import { BrowserRouter as Router, Route, Switch} from 'react-router-dom'; 3 | 4 | import Navbar from './navbar'; 5 | import HomeMain from './Home/HomeMain'; 6 | import About from './About/AboutMain'; 7 | 8 | const App = () => { 9 | return ( 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | 26 | export default App 27 | -------------------------------------------------------------------------------- /src/Home/TableSelector.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, Component} from 'react'; 2 | 3 | 4 | const TableSelector = (props: any) => { 5 | const { changeDataRender, dataSet, tableNames, visualizerData } = props 6 | 7 | const amountOfTables = [] 8 | if (dataSet) { 9 | for (let i = 0; i < dataSet.length; i++) { 10 | let title; 11 | if (tableNames[i]) title = tableNames[i].toUpperCase(); 12 | amountOfTables.push( 13 | 21 | ) 22 | } 23 | } 24 | 25 | return ( 26 |
27 | {amountOfTables} 28 |
29 | ); 30 | } 31 | 32 | export default TableSelector 33 | -------------------------------------------------------------------------------- /src/Home/SelectButtons.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import SelectionChoices from './SelectionChoices' 3 | 4 | const SelectButton = (props: any) => { 5 | const { tableNames, tables, selectionChoicesFunction, filterSelectBarElements } = props; 6 | 7 | const selectors: any = [] 8 | for (let i = 0; i < tables.length; i++) { 9 | selectors.push( 10 | 17 | ) 18 | } 19 | 20 | return ( 21 |
22 | {selectors} 23 |
24 | ) 25 | } 26 | 27 | export default SelectButton 28 | 29 | // function searchFieldsChanger(nameOfTable: string, dataFromTable: object[], index: number) { 30 | // const array:string[] = [] 31 | // if (dataFromTable !== undefined) { 32 | // for (const key in dataFromTable[0]) { 33 | // const str: string = nameOfTable + '.' + key; 34 | // array.push(str) 35 | // } 36 | // } 37 | // searchField[index] = array; 38 | // setSearchField(searchField); 39 | // } 40 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const express = require("express"); 3 | const app = express(); 4 | const apiRouter = require('./apiRouter'); 5 | 6 | 7 | const PORT = 3000; 8 | 9 | 10 | app.use(express.json()); 11 | app.use(express.urlencoded({ extended: true })); 12 | 13 | app.use(express.static(path.join(__dirname, "..src/pictures"))) 14 | //app.use("/about", (req, res) => res.status(200).send('ok')); 15 | //change later, just for test 16 | if (true) { 17 | app.get("/", (req, res) => { 18 | return res.status(200).sendFile(path.join(__dirname, "../src/index.html")); 19 | }); 20 | app.use("/build", express.static(path.join(__dirname, "../build"))); 21 | } 22 | 23 | else { 24 | app.get("/", (req, res) => { 25 | return res.status(200).sendFile(path.join(__dirname, "../src/index.html")); 26 | }); 27 | } 28 | 29 | app.use('/api', apiRouter); 30 | 31 | 32 | // catch-all Error 404 33 | app.use((req, res) => res.status(404).send("

404 Route Not Found

")); 34 | 35 | //Global Error handler 36 | app.use((err, req, res, next) => { 37 | const defaultErr = { 38 | log: "Express error handler caught unknown middleware error", 39 | status: 500, 40 | message: { err: "An error occurred" }, 41 | }; 42 | const errorObj = Object.assign({}, defaultErr, err); 43 | console.log(errorObj.log); 44 | return res.status(errorObj.status).json(errorObj.message); 45 | }); 46 | 47 | app.listen(PORT, () => { 48 | console.log(`Server listening on port: ${PORT}...`); 49 | }); 50 | 51 | module.exports = app; 52 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const url = require('url'); 3 | const path = require('path') 4 | 5 | const { app, BrowserWindow } = electron 6 | // electron in development mode, might have to install this dependency 7 | const isDev = require('electron-is-dev') 8 | 9 | let mainWindow; 10 | 11 | // function for creating the window 12 | const createWindow = () => { 13 | mainWindow = new BrowserWindow({ width: 800, height: 600, frame: false }); 14 | const appURL = isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, './src/index.html')}` 15 | mainWindow.loadURL(appURL); 16 | mainWindow.maximize(); 17 | mainWindow.setFullScreen(true); 18 | mainWindow.on('closed', () => mainWindow = null); 19 | } 20 | 21 | app.on('ready', createWindow); 22 | app.on('window-all-closed', () => { 23 | // Follow OS convention on whether to quit app when 24 | // all windows are closed. 25 | if (process.platform !== 'darwin') { app.quit() } 26 | }) 27 | app.on('activate', () => { 28 | // If the app is still open, but no windows are open, 29 | // create one when the app comes into focus. 30 | if (mainWindow === null) { createWindow() } 31 | }) 32 | 33 | 34 | // // listen for app to be ready 35 | // app.on('ready', function () { 36 | // // create a new window 37 | // mainWindow = new BrowserWindow({}) 38 | // // load HTML into window 39 | // mainWindow.loadURL( 40 | // url.format({ 41 | // pathname: path.join(__dirname, './src/index.html'), 42 | // protocol: 'file', 43 | // slashes: true, 44 | // } 45 | // )); // file://dirname/index.html 46 | // }); 47 | 48 | // mainWindow.show(); 49 | -------------------------------------------------------------------------------- /src/Home/Tables.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, Component, FC } from 'react'; 2 | // import ReactCSSTransitionGroup from 'react-transition-group'; 3 | 4 | 5 | const Tables = (props: any) => { 6 | const headers = []; 7 | const dataObj = []; 8 | const rowDataObj = []; 9 | const table = props.dataSet; 10 | // console.log('dis data', table) 11 | // const table2 = props.dataSet[props.displayData[1]]; 12 | 13 | if (table) { 14 | if (table.length != 0) { 15 | const columns = Object.keys(table[0]); 16 | for (let i = 0; i < columns.length; i++) { 17 | headers.push({columns[i]}) 18 | }; 19 | 20 | for (let i = 0; i < table.length; i++) { 21 | dataObj.push([]) 22 | for (let key in table[i]) { 23 | dataObj[i].push({table[i][key]}) 24 | } 25 | }; 26 | for (let i = 0; i < dataObj.length; i++) { 27 | rowDataObj.push( 28 | 29 | {dataObj[i]} 30 | 31 | ) 32 | }; 33 | } 34 | else { 35 | rowDataObj.push( 36 | 37 | 38 | 39 | ) 40 | }; 41 | 42 | } 43 | 44 | if (table) { 45 | return ( 46 |
47 | 48 | 49 | 50 | {headers} 51 | 52 | 53 | 54 | {rowDataObj} 55 | 56 |
57 |
58 | ) 59 | } else { 60 | return ( 61 |
62 | ) 63 | } 64 | 65 | } 66 | 67 | export default Tables 68 | -------------------------------------------------------------------------------- /src/Home/SelectionChoices.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | 3 | const SelectionChoices = (props: any) => { 4 | const { tableNames, num, selectionChoicesFunction, filterSelectBarElements } = props 5 | 6 | const options: any = []; //need to ensure input being pushed inside array will be a string 7 | for (let i = 0; i < tableNames.length; i++) { 8 | let title = tableNames[i]; 9 | options.push( 10 | 11 | ) 12 | } 13 | 14 | 15 | return ( 16 |
17 | 23 |
24 | ) 25 | } 26 | 27 | export default SelectionChoices 28 | 29 | // const nameOfTable = ev.target.value; 30 | // const index = ev.target.selectedIndex; 31 | // //the table we create is always one length longer than the one we are comparing too 32 | // // so we minus one on lines 22 and 24 33 | // tableTargets[num] = index - 1; 34 | // setTableTargets(tableTargets); 35 | // tables[num] = nameOfTable 36 | // setTables(tables) 37 | 38 | // if (tableTargets[0] !== null && tableTargets[1] !== null && tableTargets[0] !== tableTargets[1]) { 39 | // setWarning(false) 40 | // } 41 | // else setWarning(true) 42 | 43 | // const dataFromTable = queryDataSet[index - 1]; 44 | // searchFieldsChanger(nameOfTable, dataFromTable, num); 45 | // setGenerateSearchField(false); 46 | -------------------------------------------------------------------------------- /src/electron.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron'); 2 | const path = require('path'); 3 | const server = require('../server/server'); //your express app 4 | 5 | // Handle creating/removing shortcuts on Windows when installing/uninstalling. 6 | if (require('electron-squirrel-startup')) { // eslint-disable-line global-require 7 | app.quit(); 8 | } 9 | 10 | const createWindow = () => { 11 | // Create the browser window. 12 | const mainWindow = new BrowserWindow({ 13 | width: 1280, 14 | height: 720, 15 | autoHideMenuBar: true, 16 | useContentSize: true, 17 | resizable: false, 18 | }); 19 | 20 | // and load the index.html of the app. 21 | // mainWindow.loadFile(path.join(__dirname, 'index.html')); 22 | mainWindow.loadURL('http://localhost:3000/'); 23 | mainWindow.focus(); 24 | // Open the DevTools. 25 | mainWindow.webContents.openDevTools(); 26 | }; 27 | 28 | // This method will be called when Electron has finished 29 | // initialization and is ready to create browser windows. 30 | // Some APIs can only be used after this event occurs. 31 | app.on('ready', createWindow); 32 | 33 | // Quit when all windows are closed, except on macOS. There, it's common 34 | // for applications and their menu bar to stay active until the user quits 35 | // explicitly with Cmd + Q. 36 | app.on('window-all-closed', () => { 37 | if (process.platform !== 'darwin') { 38 | app.quit(); 39 | } 40 | }); 41 | 42 | app.on('activate', () => { 43 | // On OS X it's common to re-create a window in the app when the 44 | // dock icon is clicked and there are no other windows open. 45 | if (BrowserWindow.getAllWindows().length === 0) { 46 | createWindow(); 47 | } 48 | }); 49 | 50 | // In this file you can include the rest of your app's specific main process 51 | // code. You can also put them in separate files and import them here. 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PreQL 2 |

3 | 4 |

5 | 6 |

Table of Contents

7 | 13 | 14 |

What is PreQL?

15 |

PreQL is an interactive PostgreSQL visualization tool that provides developers with the ability to easily visualize their databases with cached, query-less requests to the backend and seamlessly generate SQL queries.

16 | 17 |

Installation

18 |

Depending on your operating system, download for MacOS or Windows.

19 |

Once downloaded, navigate to the application and double-click to install.

20 |

MacOS Users: You may need to adjust your security settings to allow the application to run.

21 |

If you encounter the error below when running the application:

22 | 23 |

Go to your security settings and click 'Open Anyway'. You should now be able to use the application as intended.

24 | 25 | 26 |

How to use PreQL

27 |
    28 |
  1. Copy / paste your PostgreSQL database link and click 'Generate' to view your data.
  2. 29 | 30 |
  3. Below the table visualizer, you can test various SQL queries to compare two different tables without pinging your database over and over again
  4. 31 | 32 |
33 | 34 | 35 |

PreQL Team

36 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var HtmlWebPackPlugin = require("html-webpack-plugin"); 3 | 4 | module.exports = { 5 | entry: "./src/index.js", 6 | output: { 7 | path: path.join(__dirname, "build"), 8 | publicPath: '/build', 9 | filename: "index_bundle.js" 10 | }, 11 | mode: process.env.NODE_ENV, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.tsx?/, 16 | use: 'ts-loader', 17 | exclude: /node_modules/, 18 | }, 19 | { 20 | test: /\.jsx?/, 21 | use: { 22 | loader: 'babel-loader', 23 | options: { 24 | presets: ['@babel/preset-env', '@babel/preset-react'], 25 | plugins: ["@babel/plugin-syntax-jsx"] 26 | }, 27 | }, 28 | exclude: /npm_modules/ 29 | }, 30 | { 31 | //npm install -D sass-loader css-loader style-loader webpack 32 | // /\.s[ac]ss$/i 33 | // /\.css / 34 | test: /\.s?css/, 35 | use: ["style-loader", "css-loader", "sass-loader" 36 | ], 37 | }, 38 | { 39 | // Now we apply rule for images 40 | test: /\.(png|jpg|gif|svg)$/, 41 | use: [ 42 | { 43 | // Using file-loader for these files 44 | loader: "file-loader", 45 | // loader: "url-loader", 46 | // In options we can set different things like format 47 | // and directory to save 48 | options: { 49 | outputPath: '/images' 50 | } 51 | } 52 | ] 53 | }, 54 | // { 55 | // test: /\.(png|jpg)$/, 56 | // loader: 'url-loader' 57 | // }, 58 | ] 59 | }, 60 | resolve: { 61 | // Enable importing JS / JSX files without specifying their extension 62 | extensions: [".js", ".jsx", ".tsx", ".ts"], 63 | }, 64 | devServer: { 65 | static: { 66 | directory: path.join(__dirname, '/src'), 67 | }, 68 | proxy: { 69 | '/': 'http://localhost:3000' 70 | }, 71 | compress: true, 72 | port: 8080, 73 | }, 74 | }; 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "preql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/electron.js", 6 | "dependencies": { 7 | "@babel/core": "^7.15.5", 8 | "@babel/plugin-syntax-jsx": "^7.14.5", 9 | "@babel/plugin-transform-react-jsx": "^7.14.9", 10 | "@babel/preset-env": "^7.15.6", 11 | "@babel/preset-react": "^7.14.5", 12 | "@babel/preset-typescript": "^7.15.0", 13 | "@types/react-transition-group": "^4.4.3", 14 | "babel-loader": "^8.2.2", 15 | "babel-polyfill": "^6.26.0", 16 | "dataframe-js": "^1.4.4", 17 | "electron-is-dev": "^2.0.0", 18 | "express": "^4.17.1", 19 | "file-loader": "^6.2.0", 20 | "html-webpack-plugin": "^5.3.2", 21 | "nodemon": "^2.0.13", 22 | "pandas-js": "^0.2.4", 23 | "path": "^0.12.7", 24 | "pg": "^8.7.1", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "react-router-dom": "^5.3.0", 28 | "react-select": "^5.1.0", 29 | "regenerator-runtime": "^0.13.9", 30 | "request": "^2.79.0", 31 | "sass": "^1.42.1", 32 | "ts-loader": "^9.2.6", 33 | "webpack": "^5.58.1", 34 | "webpack-cli": "^4.9.0", 35 | "@electron-forge/cli": "^6.0.0-beta.61", 36 | "@electron-forge/maker-deb": "^6.0.0-beta.61", 37 | "@electron-forge/maker-rpm": "^6.0.0-beta.61", 38 | "@electron-forge/maker-squirrel": "^6.0.0-beta.61", 39 | "@electron-forge/maker-zip": "^6.0.0-beta.61", 40 | "electron": "15.3.0", 41 | "@types/react-dom": "^17.0.9", 42 | "@types/react-router-dom": "^5.3.1", 43 | "css-loader": "^6.3.0", 44 | "sass-loader": "^12.1.0", 45 | "style-loader": "^3.3.0", 46 | "typescript": "^4.4.3", 47 | "url-loader": "^4.1.1", 48 | "webpack-dev-server": "^4.3.1", 49 | "concurrently": "^6.3.0", 50 | "cross-env": "^7.0.3", 51 | "electron-builder": "^22.13.1", 52 | "wait-on": "^6.0.0" 53 | }, 54 | "devDependencies": { 55 | "@types/react-dom": "^17.0.9", 56 | "@types/react-router-dom": "^5.3.1", 57 | "concurrently": "^6.3.0", 58 | "cross-env": "^7.0.3", 59 | "css-loader": "^6.3.0", 60 | "electron": "^15.3.0", 61 | "electron-builder": "^22.13.1", 62 | "sass-loader": "^12.1.0", 63 | "style-loader": "^3.3.0", 64 | "typescript": "^4.4.3", 65 | "url-loader": "^4.1.1", 66 | "wait-on": "^6.0.0", 67 | "webpack-dev-server": "^4.3.1", 68 | "electron-squirrel-startup": "^1.0.0" 69 | }, 70 | "config": { 71 | "forge": { 72 | "packagerConfig": {}, 73 | "makers": [ 74 | { 75 | "name": "@electron-forge/maker-squirrel", 76 | "config": { 77 | "name": "preql" 78 | } 79 | }, 80 | { 81 | "name": "@electron-forge/maker-zip", 82 | "platforms": [ 83 | "darwin" 84 | ] 85 | }, 86 | { 87 | "name": "@electron-forge/maker-deb", 88 | "config": {} 89 | }, 90 | { 91 | "name": "@electron-forge/maker-rpm", 92 | "config": {} 93 | } 94 | ] 95 | } 96 | }, 97 | "scripts": { 98 | "start": "electron-forge start", 99 | "build": "NODE_ENV=production webpack", 100 | "test": "echo \"Error: no test specified\" && exit 1", 101 | "dev": "NODE_ENV=development webpack server --open & nodemon server/server.js", 102 | "preelectron-package": "npm run build", 103 | "package": "electron-forge package", 104 | "make": "electron-forge make", 105 | "publish": "electron-forge publish", 106 | "lint": "echo \"No linting configured\"" 107 | }, 108 | "repository": { 109 | "type": "git", 110 | "url": "git+https://github.com/oslabs-beta/preql.git" 111 | }, 112 | "keywords": [], 113 | "author": "", 114 | "license": "ISC", 115 | "bugs": { 116 | "url": "https://github.com/oslabs-beta/preql/issues" 117 | }, 118 | "build": { 119 | "appId": "preql", 120 | "files": [ 121 | "build/**/*", 122 | "node_modules/**/*" 123 | ], 124 | "directories": { 125 | "buildResources": "assets" 126 | } 127 | }, 128 | "homepage": "./" 129 | } 130 | -------------------------------------------------------------------------------- /server/controller.js: -------------------------------------------------------------------------------- 1 | const controller = {}; 2 | const { Pool } = require('pg'); 3 | const { DataFrame } = require('dataframe-js'); 4 | 5 | 6 | controller.getTableNames = async (req, res, next) => { 7 | let PSQL_URI = req.body.link; 8 | let db = new Pool({ connectionString: PSQL_URI}); 9 | const GET_TABLE_QUERY = 'SELECT conrelid::regclass AS table_name\n'+ 10 | 'FROM pg_constraint\n'+ 11 | 'WHERE contype = \'p\' AND connamespace = \'public\'::regnamespace'; 12 | try{ 13 | const results = await db.query(GET_TABLE_QUERY); 14 | res.locals.tableNames = []; 15 | for (let i = 0; i < results.rows.length; i++){ 16 | res.locals.tableNames.push(results.rows[i]['table_name']) 17 | } 18 | next(); 19 | } 20 | catch (error) { 21 | next({ 22 | log: "Error in Get Table Names", 23 | status: 400, 24 | message: { err: "Error in Get Table Names" }, 25 | }); 26 | } 27 | } 28 | 29 | 30 | controller.getTableData = async (req, res, next) => { 31 | let PSQL_URI = req.body.link; 32 | let db = new Pool({ connectionString: PSQL_URI}); 33 | 34 | res.locals.tableData = [] 35 | let result; 36 | try{ 37 | for (let i = 0; i < res.locals.tableNames.length; i++){ 38 | result = await db.query('SELECT * FROM '+ res.locals.tableNames[i]); 39 | res.locals.tableData.push(result.rows); 40 | } 41 | res.locals.returnData = {'tableNames':res.locals.tableNames, 'tableData':res.locals.tableData} 42 | next(); 43 | } 44 | catch (error) { 45 | next({ 46 | log: "Error in Get Table Data", 47 | status: 400, 48 | message: { err: "Error in Get Table Data" }, 49 | }); 50 | } 51 | } 52 | 53 | controller.getJoinTable = async (req, res, next) => { 54 | 55 | const tables = req.body.query.tables; 56 | const tableOne = req.body.query.tables[0]; 57 | const tableTwo = req.body.query.tables[1]; 58 | const joinHow = req.body.query.how; 59 | const on = req.body.query.on; 60 | const columns = req.body.query.columns; 61 | const tableNames = req.body.query.tableNames; 62 | const columnNames = []; 63 | const qureynames = []; 64 | console.log(on) 65 | try{ 66 | for (let i = 0; i < tableNames.length; i++){ 67 | columnNames.push([]) 68 | for (let name in tables[i][0]){ 69 | columnNames[i].push(`${tableNames[i]}.${name}`) 70 | qureynames.push(`${tableNames[i]}.${name} as \"${tableNames[i]}.${name}\"`) 71 | } 72 | 73 | } 74 | 75 | dfOne = new DataFrame(tableOne); 76 | dfOne = dfOne.renameAll(columnNames[0]); 77 | 78 | dfTwo = new DataFrame(tableTwo); 79 | dfTwo = dfTwo.renameAll(columnNames[1]); 80 | 81 | for (let i = 0; i < columnNames.length; i++){ 82 | columnNames[i] = [...columnNames[i], 'merge']; 83 | } 84 | console.log("on ", on) 85 | dfOne = dfOne.restructure(columnNames[0]) 86 | dfTwo = dfTwo.restructure(columnNames[1]) 87 | dfOne = dfOne.map(row => row.set('merge', row.get(`${on[0]}`))).cast('merge', String); 88 | // console.log('df1 ',dfOne.head(10).toCollection());//[0]['merge'] 89 | dfTwo = dfTwo.map(row => row.set('merge', row.get(`${on[1]}`))).cast('merge', String); 90 | // console.log('df2 ',dfTwo.head(10).toCollection()) 91 | dfJoin = dfOne.join(dfTwo, 'merge', joinHow.toLowerCase()); 92 | dfJoin = dfJoin.drop('merge') 93 | console.log('join ', dfJoin.head(10).toCollection()); 94 | if (columns[0]) dfJoin = dfJoin.restructure(columns); 95 | res.locals.returnJoinData = dfJoin.toCollection(); 96 | 97 | next(); 98 | } 99 | catch (error) { 100 | next({ 101 | log: "Error in TEST Table Data", 102 | status: 400, 103 | message: { err: "Error in TEST Table Data" }, 104 | }); 105 | } 106 | } 107 | 108 | //Get table names by URL 109 | 110 | module.exports = controller; 111 | -------------------------------------------------------------------------------- /src/Home/HomeMain.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, Component, FC} from 'react'; 2 | import Tables from './Tables'; 3 | import TableSelector from './TableSelector'; 4 | import QueryGenerator from './QueryGenerator'; 5 | import UserInput from './UserInput' 6 | 7 | 8 | function Home() { 9 | 10 | const [fields, setFields] = useState([ 11 | ['Database Link'] 12 | ]); 13 | const [textField, setTextField] = useState(''); 14 | const [dataSet, setDataSet] = useState(''); 15 | const [visualizerData, setVisualizerData] = useState([0]); 16 | const [tableNames, setTableNames] = useState(''); //this kind of syntax allows functionality of changing state all in one function 17 | const [queryDataSet, setQueryDataSet] = useState(''); 18 | const [queryDisplayData, setQueryDisplayData] = useState([null, null]); 19 | 20 | const [queryTable, setQueryTable] = useState(''); 21 | 22 | 23 | function makeDBRequest(link: string) { 24 | fetch('/api/connect', { 25 | method: 'POST', 26 | headers: { 'Content-Type': 'application/json' }, 27 | body: JSON.stringify({ link: link }) 28 | }) 29 | .then(function(response) { 30 | // console.log(response.status); // Will show you the status 31 | if (!response.ok) { 32 | throw new Error("HTTP status " + response.status); 33 | } 34 | return response.json(); 35 | }) 36 | .then(data => { 37 | setTextField(link); 38 | setDataSet(data['tableData']); 39 | setQueryDataSet(data['tableData']); 40 | setTableNames(data['tableNames']); 41 | }) 42 | .catch((err) => { 43 | console.log('Error:', err)//YO CT,it is because the value of the first table is 0 so when you if(!value[1]) it alwasy false 44 | }); //once you change the sencond drop down to something else, it works proper 45 | } 46 | 47 | let fieldsArray = []; 48 | for (let i = 0; i < fields.length; i++) { 49 | fieldsArray.push() 50 | } 51 | 52 | function changeDataRender(visualizer: boolean, value: number, value2: number) { 53 | //hello! this ternary is kinda confusing. its bascially saying that it if its not the visualizer table, 54 | // then change the other table instead, then check if they values are the same 55 | // if they are, then just print it once 56 | return visualizer === true ? setVisualizerData([value]) : 57 | (value !== value2) ? 58 | setQueryDisplayData([value, value2]) : 59 | setQueryDisplayData([value, null]) 60 | } 61 | 62 | if (!dataSet) { 63 | return( //replaces "render" 64 |
65 |

"We love merge conflicts" - The clinically insane

66 | {fieldsArray} 67 |
68 | ) 69 | } else { 70 | return ( 71 | // we have two tables: one carries the visualizer state, the other is for the query state 72 | // if you look inside their boxes you will notice the small difference. so don't be confused by the naming :) 73 |
74 |

"If the solution has to be N^2, do it in style" - CT

75 | {/* {dataObjects} */} 76 | 83 | 89 | 99 | 105 |
106 | ); 107 | } 108 | } 109 | 110 | export default Home 111 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | $triangle: url('./pictures/triangles.jpg'); 2 | 3 | body { 4 | font-family: monospace; 5 | width: 100%; 6 | height: 100%; 7 | //postgres://zhocexop:Ipv9EKas6bU6z9ehDXZQRorjITIXijGv@ziggy.db.elephantsql.com:5432/zhocexop 8 | // background-color: #EDE7E3; 9 | margin: 0px; 10 | background-color: #35363a; 11 | // background-image: $triangle; 12 | 13 | // #app { 14 | // background-image: $triangle; 15 | // } 16 | 17 | ::-webkit-scrollbar { 18 | height: 7px; 19 | width: 7px; 20 | color: rgba(94,23,235,255); 21 | } 22 | ::-webkit-scrollbar-thumb { 23 | background: #8F8F8F; 24 | border-radius: 10px; 25 | } 26 | ::-webkit-scrollbar-thumb:hover { 27 | background: #8955F1; 28 | } 29 | ::-webkit-scrollbar-corner { 30 | background: rgba(0,0,0,0); 31 | } 32 | 33 | .Navbar { 34 | background-color: rgba(94,23,235,255); 35 | color: white; 36 | display: flex; 37 | align-items: center; 38 | justify-content: space-between; 39 | 40 | .links { 41 | 42 | a { 43 | font-family: 'Walter'; 44 | text-decoration: none; 45 | color: white; 46 | margin: 10px 50px 10px 10px; 47 | } 48 | } 49 | 50 | .logo { 51 | max-width: 150px; 52 | margin: 10px 10px 10px 50px 53 | } 54 | } 55 | .routes{ 56 | width: 70%; 57 | align-content: center; 58 | margin: auto; 59 | .homeContainer{ 60 | //border: 2px solid rgba(94,23,235,255); 61 | h1, label{ 62 | color: grey; 63 | text-align: center; 64 | } 65 | .UserInput { 66 | display: flex; 67 | align-items: center; 68 | justify-content: space-evenly; 69 | text-align: center; 70 | flex-direction: column; 71 | .URIinput { 72 | border: 2px solid rgba(94,23,235,255); 73 | border-radius: 5px; 74 | } 75 | } 76 | .tableSelector { 77 | display: flex; 78 | align-items: center; 79 | justify-content: flex-start; 80 | border: 2px solid rgba(94,23,235,255); 81 | border-radius: 5px; 82 | margin-bottom: 15px; 83 | overflow-x: auto; 84 | .tableButtonSelectors { 85 | border: none; 86 | display: block; 87 | padding: 10px; 88 | width: 100%; 89 | background-color: #EDE7E3; 90 | color: rgba(94,23,235,255);; 91 | &:hover { 92 | transition: 0.3s; 93 | background-color: rgba(94,23,235,255); 94 | color: #EDE7E3; 95 | cursor: pointer; 96 | } 97 | 98 | } 99 | .active { 100 | border: none; 101 | display: block; 102 | padding: 10px; 103 | width: 100%; 104 | background-color: rgba(94,23,235,255); 105 | color: #EDE7E3; 106 | } 107 | } 108 | .container { 109 | //border: 2px solid rgba(94,23,235,255) 110 | border-radius: 5px; 111 | display: flex; 112 | overflow-x: scroll; 113 | overflow-y: scroll; 114 | max-height: 400px; 115 | margin-bottom: 50px; 116 | table { 117 | border: 1px solid rgba(94,23,235,255); 118 | border-radius: 15px; 119 | border-collapse: collapse; 120 | display: flex; 121 | flex-direction: column; 122 | border-style: solid; 123 | width: 100%; 124 | // &:hover { 125 | // transform: rotate(360deg); 126 | // transition: 2s 127 | // } 128 | tr{ 129 | display: flex; 130 | justify-content: space-between; 131 | width: 100%; 132 | } 133 | 134 | thead > tr > th{ 135 | background-color: #F7F4F3; 136 | border: solid black 1px; 137 | overflow-x: hidden; 138 | min-width: 100px; 139 | width: 100%; 140 | } 141 | td { 142 | background-color: #F7F4F3; 143 | border: solid black 1px; 144 | overflow-x: hidden; 145 | overflow-y: hidden; 146 | min-width: 100px; 147 | width: 100%; 148 | max-height: 150px 149 | } 150 | } 151 | } 152 | .queryContainer{ 153 | display: flex; 154 | justify-content: center; 155 | flex-wrap: wrap; 156 | flex-direction: column; 157 | .selectButtons{ 158 | display: flex; 159 | justify-content: center; 160 | margin: 15px; 161 | align-items: center; 162 | font-size: larger; 163 | .tableDropdown{ 164 | border: 2px solid rgba(94,23,235,255); 165 | border-radius: 4px; 166 | margin: 0px 15px; 167 | } 168 | } 169 | .buttonContainer { 170 | display: flex; 171 | border-radius: 2px; 172 | align-items: center; 173 | justify-content: center; 174 | margin: auto; 175 | button { 176 | min-width: 50px; 177 | } 178 | } 179 | .warning{ 180 | display: flex; 181 | justify-content: center; 182 | font-size: larger; 183 | color: red; 184 | min-height: 25px; 185 | } 186 | .queryGenerator{ 187 | flex-wrap: wrap; 188 | .tableButtons{ 189 | display: flex; 190 | justify-content: center; 191 | margin: 15px; 192 | align-items: center; 193 | font-size: larger; 194 | .multiSelect{ 195 | justify-content: flex-start; 196 | div{ 197 | justify-content: flex-start; 198 | } 199 | } 200 | .tableDropdown{ 201 | border: 2px solid rgba(94,23,235,255); 202 | border-radius: 4px; 203 | margin: 0px 15px; 204 | } 205 | .okayButton { 206 | border: 2px solid rgba(94,23,235,255); 207 | border-radius: 4px; 208 | } 209 | } 210 | div { 211 | display: flex; 212 | justify-content: center; 213 | margin: 15px; 214 | .generateButton { 215 | border: 2px solid rgba(94,23,235,255); 216 | border-radius: 4px; 217 | } 218 | } 219 | } 220 | } 221 | } 222 | } 223 | .aboutContainer { 224 | text-align: center; 225 | } 226 | } 227 | // .active { 228 | // background-color: black; 229 | // } 230 | -------------------------------------------------------------------------------- /src/Home/QueryGenerator.tsx: -------------------------------------------------------------------------------- 1 | import { join } from 'path/posix'; 2 | import React, {useLayoutEffect, useState, FC, useRef, useEffect } from 'react'; 3 | import ReactDOM from'react-dom' 4 | import Select from 'react-select'; 5 | import SelectButtons from './SelectButtons'; 6 | import Warning from './Warning'; 7 | 8 | function QueryGenerator(props: any) { 9 | 10 | const { setQueryTable, queryDataSet, tableNames, changeDataRender, setQueryDataSet } = props; 11 | 12 | 13 | const [tableTargets, setTableTargets] = useState([-1, -1]); 14 | const [tables, setTables] = useState(['', '']) 15 | const [searchField, setSearchField] = useState([[''], ['']]); 16 | const [listIndex, setListIndex] = useState([0, 0]); 17 | 18 | const [warning, setWarning] = useState(false); 19 | const [selectionField, setSelectionField] = useState([]) 20 | 21 | const [generateSearchField, setGenerateSearchField] = useState(false); 22 | 23 | const [joinCondition, setJoinCondition] = useState('INNER'); 24 | let onCondition = [searchField[0][listIndex[0]], searchField[1][listIndex[1]]] 25 | 26 | const selectBarElements = useRef(null); 27 | 28 | interface selectionType { 29 | value: string, 30 | label: string 31 | } 32 | 33 | function filterSelectBarElements () { 34 | let { value } = selectBarElements.current.props 35 | const { setValue } = selectBarElements.current 36 | 37 | function helper(string: string) { 38 | const tableOneRegex = (tables[0] !== '') ? new RegExp(`^${tables[0]}[\.]`) : new RegExp(/^(?![\s\S])/g) 39 | const tableTwoRegex = (tables[1] !== '') ? new RegExp(`^${tables[1]}[\.]`) : new RegExp(/^(?![\s\S])/g) 40 | 41 | if (tableOneRegex && (tableOneRegex.test(string))) return true 42 | else if (tableTwoRegex && (tableTwoRegex.test(string))) return true 43 | else return false 44 | } 45 | 46 | if (value) { 47 | const result = value.filter((obj: selectionType) => 48 | helper(obj.value) 49 | ) 50 | setValue(result) 51 | setSelectionField(result) 52 | return value 53 | } 54 | } 55 | 56 | // do we need to move this fetch request to another component? 57 | function queryDFRequest(query: databaseConnection) { 58 | fetch('/api/join', { 59 | method: 'POST', 60 | headers: { 'Content-Type': 'application/json'}, 61 | body: JSON.stringify({ query }) 62 | }) 63 | .then(function(response) { 64 | if (!response.ok) { 65 | throw new Error("HTTP status " + response.status); 66 | } 67 | return response.json(); 68 | }) 69 | .then(data => { 70 | //this is where Adi and I need to communicate how the information is 71 | //given back so we can display it in a graph 72 | // set state for the table below the query generator 73 | setQueryTable(data); 74 | }) 75 | .catch((err) => { 76 | console.log('Error:', err); 77 | 78 | }) 79 | }; 80 | 81 | function selectionChoicesFunction(ev: any, num: number){ 82 | const nameOfTable = ev.target.value; 83 | const index = ev.target.selectedIndex; 84 | //the table we create is always one length longer than the one we are comparing too 85 | // so we minus one on lines 22 and 24 86 | tableTargets[num] = index - 1; 87 | setTableTargets(tableTargets); 88 | tables[num] = nameOfTable 89 | setTables(tables) 90 | 91 | if (tableTargets[0] !== null && tableTargets[1] !== null && tableTargets[0] !== tableTargets[1]) { 92 | setWarning(false) 93 | } 94 | else setWarning(true) 95 | 96 | const dataFromTable = queryDataSet[index - 1]; 97 | searchFieldsChanger(nameOfTable, dataFromTable, num); 98 | setGenerateSearchField(false); 99 | } 100 | 101 | function searchFieldsChanger(nameOfTable: string, dataFromTable: object[], index: number) { 102 | const array: string[] = [] 103 | if (dataFromTable !== undefined) { 104 | for (const key in dataFromTable[0]) { 105 | const str: string = nameOfTable + '.' + key; 106 | array.push(str) 107 | } 108 | } 109 | searchField[index] = array; 110 | setSearchField(searchField); 111 | } 112 | 113 | 114 | type arrayOfArrays = [string[], string[]] // will have strings within those arrays 115 | type stringArray = [string, string] 116 | type optionalStrings = [string?, string?] 117 | 118 | interface databaseConnection { 119 | tables: arrayOfArrays; 120 | on: string[]; 121 | how: string; 122 | columns: string[]; 123 | tableNames: stringArray; 124 | } 125 | 126 | const JOIN: string[] = ['INNER', 'LEFT', 'RIGHT', 'OUTER']; 127 | const joinOptions: any = []; 128 | 129 | for (let i = 0; i < JOIN.length; i++) { 130 | let joinType = JOIN[i]; 131 | joinOptions.push( 132 | 133 | ) 134 | } 135 | 136 | let listOfOptions = []; 137 | let onOptions=[]; 138 | for (let i = 0; i < searchField.length; i++) { 139 | onOptions.push([]); 140 | if (tableTargets[0] === tableTargets[1]) break; 141 | for (let j = 0; j < searchField[i].length; j++) { 142 | let temp: any = {}; 143 | temp['value'] = `${searchField[i][j]}` // {} 144 | temp['label'] = `${searchField[i][j]}` 145 | listOfOptions.push(temp) 146 | onOptions[i].push() 147 | } 148 | } 149 | 150 | useEffect(()=>{ 151 | setGenerateSearchField(true); 152 | }, [generateSearchField]) 153 | 154 | return ( 155 |
156 | 162 | 163 |
164 |
165 | 166 |
167 | { 179 | setJoinCondition(JOIN[ev.target.selectedIndex]); 180 | }}> 181 | {joinOptions} 182 | 183 | 184 | 189 | 190 | 195 |
196 |
197 | 214 |
215 |
216 |
217 | ) 218 | } 219 | 220 | export default QueryGenerator 221 | --------------------------------------------------------------------------------