diff --git a/README.md b/README.md index 2f76b662..796c6f34 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Github stars](https://img.shields.io/github/stars/oslabs-beta/SeeQR?style=social)](https://github.com/oslabs-beta/SeeQR) [![Tests](https://github.com/open-source-labs/SeeQR/actions/workflows/test.yml/badge.svg)](https://github.com/open-source-labs/SeeQR/actions/workflows/test.yml) -[theSeeQR.io](http://www.theseeqr.io) +[theSeeQR.com](http://www.theseeqr.com)

SeeQR: A database analytic tool to compare the efficiency of different schemas and queries on a granular level so that developers/architects can make better informed architectural decisions regarding SQL databases at various scales.

@@ -56,7 +56,7 @@ To get started on contributing to this project: - The application connects to the local instance of PostgreSQL using the role 'postgres', so all databases that 'postgres' has access to are available - Besides using the existing databases, the application also provides various options to create new databases: - Importing `.sql` or `.tar` files - - Manually running `CREATE DATABASE` queries in SeeQR + - Navigating to the `Create Database` view at bottom of sidebar - Copying an existing database (with or without original data) - Users can toggle between the 'DATABASES' view and the 'QUERIES' view @@ -81,7 +81,19 @@ To get started on contributing to this project:
+- Create/Edit Database + - Users can create a new database from scratch by clicking the `Create New Database` button at the bottom of the sidebar + - Once a the database is given a name, htiting the `Initialize Database` button will create new database on the users PostgreSQL instance + - Users can then input SQL commands and click `Update Database` to create and drop tables in the database + - Users have the option to alter any existing databases as well by selecting the database on the sidebar and running any SQL commands they would like. + - The `Export` button will write a .sql file on the user's desktop of the selected database + + +
+
+ +
- Queries - In the 'QUERIES' view, the main panel is where the query input text field is located, utilizing CodeMirror. The paint button in the top right corner of the panel auto-formats the inputted query @@ -139,7 +151,7 @@ We've released SeeQR because it's a useful tool to help optimize SQL databases. ## Core Team -[Casey Escovedo](https://github.com/caseyescovedo) | [Casey Walker](https://github.com/cwalker3011) | [Catherine Chiu](https://github.com/catherinechiu) | [Chris Akinrinade](https://github.com/chrisakinrinade) | [Cindy Chau](https://github.com/cindychau) | [Claudio Santos](https://github.com/Claudiohbsantos) | [Faraz Akhtar](https://github.com/faraza22) | [Frank Norton](https://github.com/FrankNorton32) | [James Kolotouros](https://github.com/dkolotouros) | [Jennifer Courtner](https://github.com/jcourtner) | [Justin Dury-Agri](https://github.com/justinD-A) | [Katie Klochan](https://github.com/kklochan) | [Mercer Stronck](https://github.com/mercerstronck) | [Muhammad Trad](https://github.com/muhammadtrad) | [Richard Lam](https://github.com/rlam108) | [Sam Frakes](https://github.com/frakes413) | [Serena Kuo](https://github.com/serenackuo) +[Allison Le](https://github.com/allisonle1) | [Brandon Lee](https://github.com/BrandonW-Lee) | [Casey Escovedo](https://github.com/caseyescovedo) | [Casey Walker](https://github.com/cwalker3011) | [Catherine Chiu](https://github.com/catherinechiu) | [Chris Akinrinade](https://github.com/chrisakinrinade) | [Cindy Chau](https://github.com/cindychau) | [Claudio Santos](https://github.com/Claudiohbsantos) | [Faraz Akhtar](https://github.com/faraza22) | [Frank Norton](https://github.com/FrankNorton32) | [Harrison Nam](https://github.com/harrynam07) | [James Kolotouros](https://github.com/dkolotouros) | [Jennifer Courtner](https://github.com/jcourtner) | [Justin Dury-Agri](https://github.com/justinD-A) | [Katie Klochan](https://github.com/kklochan) | [Mercer Stronck](https://github.com/mercerstronck) | [Muhammad Trad](https://github.com/muhammadtrad) | [Richard Guo](https://github.com/richardguoo) | [Richard Lam](https://github.com/rlam108) | [Sam Frakes](https://github.com/frakes413) | [Serena Kuo](https://github.com/serenackuo) | [Timothy Sin](https://github.com/timothysin) ## License diff --git a/assets/readmeImages/gifs/create_db.gif b/assets/readmeImages/gifs/create_db.gif new file mode 100644 index 00000000..abbf3f99 Binary files /dev/null and b/assets/readmeImages/gifs/create_db.gif differ diff --git a/assets/readmeImages/gifs/dummy_data.gif b/assets/readmeImages/gifs/dummy_data.gif index 05a3cbe7..ed7b5254 100644 Binary files a/assets/readmeImages/gifs/dummy_data.gif and b/assets/readmeImages/gifs/dummy_data.gif differ diff --git a/assets/readmeImages/gifs/quick_start.gif b/assets/readmeImages/gifs/quick_start.gif index 4ecb6d43..3034a0ba 100644 Binary files a/assets/readmeImages/gifs/quick_start.gif and b/assets/readmeImages/gifs/quick_start.gif differ diff --git a/frontend/components/App.tsx b/frontend/components/App.tsx index aa3a22ba..2300c871 100644 --- a/frontend/components/App.tsx +++ b/frontend/components/App.tsx @@ -18,6 +18,7 @@ import QueryView from './views/QueryView/QueryView'; import DbView from './views/DbView/DbView'; import CompareView from './views/CompareView/CompareView'; import QuickStartView from './views/QuickStartView'; +import NewSchemaView from './views/NewSchemaView/NewSchemaView'; import FeedbackModal from './modal/FeedbackModal'; import Spinner from './modal/Spinner'; @@ -84,6 +85,9 @@ const App = () => { } shownView = 'queryView'; break; + case 'newSchemaView': + shownView = 'newSchemaView'; + break; case 'quickStartView': default: shownView = 'quickStartView'; @@ -128,6 +132,14 @@ const App = () => { show={shownView === 'queryView'} /> + diff --git a/frontend/components/sidebar/BottomButtons.tsx b/frontend/components/sidebar/BottomButtons.tsx new file mode 100644 index 00000000..ed9703a5 --- /dev/null +++ b/frontend/components/sidebar/BottomButtons.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { ButtonGroup, Button } from '@material-ui/core/'; +import styled from 'styled-components'; +import { ipcRenderer } from 'electron'; +import { AppState } from '../../types'; +import { selectedColor, textColor, defaultMargin } from '../../style-variables'; +import { sendFeedback } from '../../lib/utils'; + +const ViewBtnGroup = styled(ButtonGroup)` + margin: ${defaultMargin} 5px; + position: fixed; + bottom: 0px; + width: 300px; +`; + +interface ViewButtonProps { + $isSelected: boolean; +} + +const ViewButton = styled(Button)` + background: ${({ $isSelected }: ViewButtonProps) => + $isSelected ? selectedColor : textColor}; + +`; + + +type ViewSelectorProps = Pick; + +/** + * Selector for view on sidebar. Updates App state with selected view + */ +const BottomButtons = ({ selectedView, setSelectedView, setSelectedDb, selectedDb}: ViewSelectorProps) => ( + + { + setSelectedView('newSchemaView'); + setSelectedDb(''); + + ipcRenderer + .invoke('select-db', '') + .catch(() => + sendFeedback({ + type: 'error', + message: `Database connection error` + }) + ) + }} + $isSelected={ + selectedView === 'newSchemaView' || selectedView === 'compareView' + } + > + Create New Database + + +); +export default BottomButtons; diff --git a/frontend/components/sidebar/DbList.tsx b/frontend/components/sidebar/DbList.tsx index 5a458742..4ef1c38b 100644 --- a/frontend/components/sidebar/DbList.tsx +++ b/frontend/components/sidebar/DbList.tsx @@ -68,7 +68,7 @@ const DbList = ({ }; const selectHandler = (dbName: string) => { - setSelectedView('dbView'); + // setSelectedView('dbView'); if (dbName === selectedDb) return; ipcRenderer .invoke('select-db', dbName) @@ -109,7 +109,7 @@ const DbList = ({ databases={databases} /> ) : null} - + { + const toggleOpen = () => setSidebarHidden(!sidebarIsHidden); /** * Show empty query view for user to create new query. @@ -87,7 +90,7 @@ const Sidebar = ({ - + + {/* */} ); diff --git a/frontend/components/views/DbView/DbView.tsx b/frontend/components/views/DbView/DbView.tsx index c79f62d9..48ab4bce 100644 --- a/frontend/components/views/DbView/DbView.tsx +++ b/frontend/components/views/DbView/DbView.tsx @@ -61,6 +61,7 @@ const DbView = ({ selectedDb, show }: DbViewProps) => {
setSelectedTable(table)} selectedTable={selectedTable} diff --git a/frontend/components/views/DbView/TablesTabBar.tsx b/frontend/components/views/DbView/TablesTabBar.tsx index 9851cdb6..d5692012 100644 --- a/frontend/components/views/DbView/TablesTabBar.tsx +++ b/frontend/components/views/DbView/TablesTabBar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect }from 'react'; import { Tabs, Tab } from '@material-ui/core'; import styled from 'styled-components'; import TableDetails from './TableDetails'; @@ -33,7 +33,8 @@ const a11yProps = (index: any) => ({ 'aria-controls': `scrollable-auto-tabpanel-${index}`, }); -interface TablesTabBarProps { +interface TablesTabBarProps { + tables: TableInfo[]; selectTable: (table: TableInfo) => void; selectedTable: TableInfo | undefined; @@ -44,6 +45,10 @@ const TablesTabs = ({ selectTable, selectedTable, }: TablesTabBarProps) => { + + + + const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => { selectTable(tables[newValue]); }; diff --git a/frontend/components/views/NewSchemaView/NewSchemaView.tsx b/frontend/components/views/NewSchemaView/NewSchemaView.tsx new file mode 100644 index 00000000..7f4c8005 --- /dev/null +++ b/frontend/components/views/NewSchemaView/NewSchemaView.tsx @@ -0,0 +1,219 @@ +import { IpcRendererEvent, ipcRenderer } from 'electron'; +import React, { useEffect, useState } from 'react'; +import { Box, Button, Typography } from '@material-ui/core/'; +import styled from 'styled-components'; +import { + QueryData, + CreateNewQuery, + AppState, + TableInfo, + DatabaseInfo, + isDbLists +} from '../../../types'; +import { defaultMargin } from '../../../style-variables'; + +// not sure what this is yet...seems necessary for error message listeners +import { once, sendFeedback } from '../../../lib/utils'; + +// import child components below +import SchemaName from './SchemaName'; +import TablesTabs from '../DbView/TablesTabBar'; +import SchemaSqlInput from './SchemaSqlInput'; + +// emitting with no payload requests backend to send back a db-lists event with list of dbs +const requestDbListOnce = once(() => ipcRenderer.send('return-db-list')); + +// top row container +const TopRow = styled(Box)` + display: flex; + align-items: flex-end; + margin: ${defaultMargin} 0; +`; + +// Container +const Container = styled.a` + display: flex; + justify-content: flex-start; + padding-top: 0px; +`; + +// button elements +const CenterButton = styled(Box)` + display: flex; + justify-content: center; + padding-bottom: 0px; +`; + +const RunButton = styled(Button)` + margin: ${defaultMargin} auto; +`; + +const InitButton = styled(Button)` + margin-left: 2%; + height: 30px; + width: 160px; + font-size: 12px; +`; + +const ExportButton = styled(Button)` + margin-left: 44%; + height: 30px; + width: 160px; + font-size: 12px; +`; + +// view container +const NewSchemaViewContainer = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +`; + +// props interface +interface NewSchemaViewProps { + query?: AppState['workingQuery']; + setQuery: AppState['setWorkingQuery']; + createNewQuery: CreateNewQuery; + setSelectedDb: AppState['setSelectedDb']; + selectedDb: AppState['selectedDb']; + show: boolean; +} + +const NewSchemaView = ({ + query, + setQuery, + createNewQuery, + setSelectedDb, + selectedDb, + show, +}: NewSchemaViewProps) => { + // additional local state properties using hooks + const [dbTables, setTables] = useState([]); + const [selectedTable, setSelectedTable] = useState(); + const [currentSql, setCurrentSql] = useState(''); + const [databases, setDatabases] = useState([]); + const [open, setOpen] = useState(false); + + + const defaultQuery: QueryData = { + label: '', // required by QueryData interface, but not necessary for this view + db: '', // name that user inputs in SchemaName.tsx + sqlString: '', // sql string that user inputs in SchemaSqlInput.tsx + }; + + const localQuery = { ...defaultQuery, ...query }; + + useEffect(() => { + + // Listen to backend for updates to list of tables on current db + const tablesFromBackend = (evt: IpcRendererEvent, dbLists: unknown) => { + + if (isDbLists(dbLists)) { + setDatabases(dbLists.databaseList); + setTables(dbLists.tableList); + setSelectedTable(selectedTable || dbLists.tableList[0]); + } + }; + ipcRenderer.on('db-lists', tablesFromBackend); + requestDbListOnce(); + // return cleanup function + return () => { + ipcRenderer.removeListener('db-lists', tablesFromBackend); + }; + }); + + // handles naming of schema + const onNameChange = (newName: string) => { + setQuery({ ...localQuery, db: newName }); + setSelectedDb(newName); + }; + + // handles sql string input + const onSqlChange = (newSql: string) => { + // because App's workingQuery changes ref + setCurrentSql(newSql); + setQuery({ ...localQuery, sqlString: newSql }); + }; + + // handle intializing new schema + const onInitialize = () => { + + ipcRenderer.invoke( + 'initialize-db', { + newDbName: localQuery.db, + }) + .catch((err) => { + sendFeedback({ + type: 'error', + message: err ?? 'Failed to initialize db', + }); + }); + } + + // handle exporting + const onExport = () => { + ipcRenderer.invoke( + 'export-db', { + sourceDb: selectedDb + }) + .catch((err) => { + sendFeedback({ + type: 'error', + message: err ?? 'Failed to export db', + }); + }); + } + + + // onRun function to handle when user submits sql string to update schema + const onRun = () => { + + setSelectedDb(localQuery.db); + // // request backend to run query + ipcRenderer + .invoke('update-db', { + sqlString: localQuery.sqlString, + selectedDb + }) + .then(() => {setCurrentSql('');}) + .catch((err) => { + sendFeedback({ + type: 'error', + message: err ?? 'Failed to Update Schema', + }); + }); + }; + + +if (!show) return null; +return ( + + + + Initialize Database + Export + + + + + Update Database + + + + {`${selectedDb}`} + + setSelectedTable(table)} + selectedTable={selectedTable} + /> + +); +}; +export default NewSchemaView; \ No newline at end of file diff --git a/frontend/components/views/NewSchemaView/SchemaExport.tsx b/frontend/components/views/NewSchemaView/SchemaExport.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/components/views/NewSchemaView/SchemaName.tsx b/frontend/components/views/NewSchemaView/SchemaName.tsx new file mode 100644 index 00000000..ec3307f4 --- /dev/null +++ b/frontend/components/views/NewSchemaView/SchemaName.tsx @@ -0,0 +1,20 @@ +import React, { useState } from 'react'; +import { TextField, Box } from '@material-ui/core/'; + +interface SchemaNameProps { + name? : string; + onChange: (newName: string) => void; +} + +const SchemaName = ({ name, onChange }: SchemaNameProps) => ( + + + onChange(evt.target.value)} + /> + +); + +export default SchemaName; diff --git a/frontend/components/views/NewSchemaView/SchemaSqlInput.tsx b/frontend/components/views/NewSchemaView/SchemaSqlInput.tsx new file mode 100644 index 00000000..6bd294e2 --- /dev/null +++ b/frontend/components/views/NewSchemaView/SchemaSqlInput.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import FormatPaintIcon from '@material-ui/icons/FormatPaint'; +import { ButtonGroup, Button, Tooltip } from '@material-ui/core'; +import styled from 'styled-components'; +import { format } from 'sql-formatter'; + +import 'codemirror/lib/codemirror.css'; // Styline +import 'codemirror/mode/sql/sql'; // Language (Syntax Highlighting) +import 'codemirror/theme/lesser-dark.css'; // Theme +import CodeMirror from '@skidding/react-codemirror'; + +const Container = styled.div` + position: relative; +`; + +const SquareBtn = styled(Button)` + padding: 5px; +`; + +const Toolbar = styled.div` + position: absolute; + z-index: 1000; + top: 5px; + right: 23px; + opacity: 0.3; + transition: opacity 0.3s ease; + + &:hover { + opacity: 1; + } +`; + + + +interface SchemaSqlInputProps { + sql: string; + onChange: (newSql: string) => void; + runQuery: () => void; + } + + const SchemaSqlInput = ({ sql, onChange, runQuery }: SchemaSqlInputProps) => { + const formatQuery = () => { + const formatted = format(sql, { language: 'postgresql', uppercase: true }); + onChange(formatted); + }; + + // Codemirror module configuration options + const options = { + lineNumbers: true, + mode: 'sql', + theme: 'lesser-dark', + extraKeys: { + 'Ctrl-Enter': runQuery, + 'Ctrl-F': formatQuery, + }, + }; + return ( + + + + + + + + + + + + + ); + }; + + export default SchemaSqlInput; + \ No newline at end of file diff --git a/frontend/components/views/NewSchemaView/SchemaSummary.tsx b/frontend/components/views/NewSchemaView/SchemaSummary.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/components/views/QueryView/QuerySqlInput.tsx b/frontend/components/views/QueryView/QuerySqlInput.tsx index 49c676a7..af8f1067 100644 --- a/frontend/components/views/QueryView/QuerySqlInput.tsx +++ b/frontend/components/views/QueryView/QuerySqlInput.tsx @@ -63,7 +63,7 @@ const QuerySqlInput = ({ sql, onChange, runQuery }: QuerySqlInputProps) => { - + ); }; diff --git a/frontend/components/views/QueryView/QueryView.tsx b/frontend/components/views/QueryView/QueryView.tsx index b667b396..b1f5ec9e 100644 --- a/frontend/components/views/QueryView/QueryView.tsx +++ b/frontend/components/views/QueryView/QueryView.tsx @@ -142,6 +142,9 @@ const QueryView = ({ }; createNewQuery(transformedData); }) + .then(() => { + localQuery.sqlString = ''; + }) .catch((err) => { sendFeedback({ type: 'error', @@ -167,6 +170,7 @@ const QueryView = ({ diff --git a/frontend/components/views/QuickStartView.tsx b/frontend/components/views/QuickStartView.tsx index 742b9f12..22378e13 100644 --- a/frontend/components/views/QuickStartView.tsx +++ b/frontend/components/views/QuickStartView.tsx @@ -253,7 +253,7 @@ const QuickStartView = ({ show }: QuickStartViewProps) => { return ( - Welcome to SeeQr + Welcome to SeeQR Logo diff --git a/frontend/types.ts b/frontend/types.ts index 04771df8..066b81c0 100644 --- a/frontend/types.ts +++ b/frontend/types.ts @@ -2,7 +2,7 @@ * This file contains common types that need to be used across the frontend */ -type ViewName = 'compareView' | 'dbView' | 'queryView' | 'quickStartView'; +type ViewName = 'compareView' | 'dbView' | 'queryView' | 'quickStartView' | 'newSchemaView'; export interface AppState { selectedView: ViewName;