('')
5 | const { fields, makeDBRequest} = props
6 | return(
7 |
8 | {fields}
9 | setURIinput(event.target.value)} placeholder="postgres://" />
10 | makeDBRequest(URIinput)}>Submit
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 | {changeDataRender(true, i)}}
18 | >
19 | {title}
20 |
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 | {title}
11 | )
12 | }
13 |
14 |
15 | return (
16 |
17 | {
18 | selectionChoicesFunction(ev, num)
19 | filterSelectBarElements()
20 | }}>
21 | {options}
22 |
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 | Copy / paste your PostgreSQL database link and click 'Generate' to view your data.
29 |
30 | Below the table visualizer, you can test various SQL queries to compare two different tables without pinging your database over and over again
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 | {joinType}
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({searchField[i][j]} )
147 | }
148 | }
149 |
150 | useEffect(()=>{
151 | setGenerateSearchField(true);
152 | }, [generateSearchField])
153 |
154 | return (
155 |
156 |
162 |
163 |
164 |
165 |
SELECT
166 |
167 | {
168 | const selectConditions = [];
169 | for (let i = 0; i < ev.length; i++) {
170 | selectConditions.push(ev[i]['value']);
171 | }
172 | setSelectionField(selectConditions)
173 | }}/>
174 |
175 |
FROM {tableNames[tableTargets[0]]}
176 |
177 |
178 | {
179 | setJoinCondition(JOIN[ev.target.selectedIndex]);
180 | }}>
181 | {joinOptions}
182 |
183 | JOIN {tableNames[tableTargets[1]]} ON
184 | {
185 | if (!warning) setListIndex([ev.target.selectedIndex, listIndex[1]]);
186 | }}>
187 | {onOptions[0]}
188 |
189 | =
190 | {
191 | if (!warning) setListIndex([listIndex[0], ev.target.selectedIndex]);
192 | }}>
193 | {onOptions[1]}
194 |
195 |
196 |
197 | {
198 | // changeDataRender(false, tableTargets[0], tableTargets[1])
199 | const reqBody:databaseConnection = {
200 | //array or arrays
201 | tables: [queryDataSet[tableTargets[0]], queryDataSet[tableTargets[1]]],
202 | //array of strings of length 2
203 | on: onCondition,
204 | //string not empty and 'INNER', 'LEFT', 'RIGHT', 'OUTER'
205 | how: joinCondition,
206 | //array of strings or empty array
207 | columns: selectionField,
208 | //array of strings of length 2
209 | tableNames: [tableNames[tableTargets[0]], tableNames[tableTargets[1]]]
210 | }
211 | console.log(reqBody)
212 | queryDFRequest(reqBody)
213 | }}>Generate
214 |
215 |
216 |
217 | )
218 | }
219 |
220 | export default QueryGenerator
221 |
--------------------------------------------------------------------------------