Skip to content

Commit

Permalink
Merge pull request #35 from Claudiohbsantos/query-view-design
Browse files Browse the repository at this point in the history
Query view basic design
  • Loading branch information
FarazA22 authored Feb 10, 2021
2 parents e303b7b + 0676d3f commit ac048be
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 62 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
// turned off because typescript, functional components and default props
// don't seem to be good friends. Decided to manually handle defaults for
// optional props instead
"react/require-default-props": "off"
"react/require-default-props": "off",
"react/jsx-props-no-spreading": "off"
},
"root": true
}
3 changes: 2 additions & 1 deletion frontend/GlobalStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
*/

import { createGlobalStyle } from 'styled-components';
import { bgColor, textColor } from './style-variables';
import { bgColor } from './style-variables';

const GlobalStyle = createGlobalStyle`
body {
font-size: 1.2em;
background: ${bgColor};
}
`;

Expand Down
3 changes: 2 additions & 1 deletion frontend/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from 'styled-components';
import { MuiThemeProvider } from '@material-ui/core/';
import { StylesProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { MuiTheme, bgColor, sidebarWidth } from '../style-variables';
import { MuiTheme, bgColor, sidebarWidth, defaultMargin } from '../style-variables';
import GlobalStyle from '../GlobalStyle';
import { AppState, CreateNewQuery, QueryData } from '../types';
import { createQuery, key } from '../lib/queries';
Expand All @@ -22,6 +22,7 @@ const Main = styled.main`
grid-area: main;
background: ${bgColor};
height: 100vh;
padding: ${defaultMargin};
`;

const App = () => {
Expand Down
11 changes: 6 additions & 5 deletions frontend/components/sidebar/ViewSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import React from 'react';
import { ButtonGroup, Button } from '@material-ui/core/';
import styled from 'styled-components';
import { AppState } from '../../types';
import { selectedColor, textColor, hoverColor } from '../../style-variables';
import { selectedColor, textColor, defaultMargin } from '../../style-variables';

interface ViewButtonProps {
isSelected: boolean;
}

const ViewBtnGroup = styled(ButtonGroup)`
margin: 20px 5px;
margin: ${defaultMargin} 5px;
`;

interface ViewButtonProps {
isSelected: boolean;
}

const ViewButton = styled(Button)`
background: ${({ isSelected }: ViewButtonProps) =>
isSelected ? selectedColor : textColor};
Expand Down
12 changes: 9 additions & 3 deletions frontend/components/views/QueryView/QueryDb.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React from 'react';
import { Select, MenuItem, InputLabel } from '@material-ui/core/';
import { Select, MenuItem, InputLabel, Box } from '@material-ui/core/';
import styled from 'styled-components';
import { defaultMargin } from '../../../style-variables';

const SpacedBox = styled(Box)`
margin-left: ${defaultMargin};
`;

interface QueryDbProps {
db: string;
Expand All @@ -8,7 +14,7 @@ interface QueryDbProps {
}

const QueryDb = ({ db, onChange, databases }: QueryDbProps) => (
<>
<SpacedBox>
<InputLabel id="queryView-db-label">Database</InputLabel>
<Select
value={db}
Expand All @@ -21,7 +27,7 @@ const QueryDb = ({ db, onChange, databases }: QueryDbProps) => (
</MenuItem>
))}
</Select>
</>
</SpacedBox>
);

export default QueryDb;
8 changes: 5 additions & 3 deletions frontend/components/views/QueryView/QueryLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React from 'react';
import { TextField } from '@material-ui/core/';
import { TextField, Box} from '@material-ui/core/';
// import styled from 'styled-components'


interface QueryLabelProps {
label?: string;
onChange: (newLabel: string) => void;
}

const QueryLabel = ({ label, onChange }: QueryLabelProps) => (
<>
<Box>
<TextField
label="Label"
value={label}
onChange={(evt) => onChange(evt.target.value)}
/>
</>
</Box>
);

export default QueryLabel;
119 changes: 118 additions & 1 deletion frontend/components/views/QueryView/QueryResults.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,129 @@
// TODO: Implement first/last buttons for pagination https://material-ui.com/components/tables/#custom-pagination-actions
// TODO: dark scrollbar for table
// TODO: line up right margin with codemirror margin

import React from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Paper,
} from '@material-ui/core';
import styled from 'styled-components';
import { QueryData } from '../../../types';
import {
greyDark,
greyPrimary,
defaultMargin,
sidebarWidth,
} from '../../../style-variables';

const tableWidth = `calc(100vw - (${defaultMargin} * 3) - ${sidebarWidth})`;

const StyledPaper = styled(({ ...other }) => (
<Paper elevation={8} {...other} />
))`
background: ${greyDark};
min-width: ${tableWidth};
width: ${tableWidth};
`;

const StyledCell = styled(TableCell)`
border-bottom: 1px solid ${greyPrimary};
`;

interface Column {
name: string;
align: 'left' | 'right';
}

// TODO: temporary. In future we should get data type from table schema
const isNumber = (val: unknown) => {
if (typeof val === 'number') return true;
const numberRgx = /^-?[0-9.]+$/;
if (numberRgx.test(val as string)) return true;
return false;
};

const buildColumns = (row: Record<string, unknown>): Column[] =>
Object.entries(row).map(([column, value]) => ({
name: column,
align: isNumber(value) ? 'right' : 'left',
}));

interface QueryResultsProps {
results: QueryData['returnedRows'];
}

const QueryResults = ({ results }: QueryResultsProps) => {
return <>{results?.toString() || 'results'}</>;
if (!results || !results.length) return null;

const [page, setPage] = React.useState(0);
const rowsPerPage = 10

// reset page to 1 if page is further than it could reasonable be. e.g. if
// user edits a query that had many pages of results while being in the last
// page and new query has a single page
if (page * rowsPerPage > results.length) setPage(0);

const columns = buildColumns(results[0]);

const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage);
};


// if there are performance issues, look into https://material-ui.com/components/tables/#virtualized-table
return (
<>
<TableContainer component={StyledPaper}>
<Table>
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell key={column.name} align={column.align}>
<strong>{column.name}</strong>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{results
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row) => (
// TODO: figure out react key
<TableRow hover role="checkbox" tabIndex={-1}>
{columns.map((column) => (
<StyledCell
align={column.align}
key={`${column.name}_${row[column.name]}`}
>
{/* // TODO: how to properly type this? */}
{(row[column.name] as any)?.toString()}
</StyledCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
// if there is only one option the dropdown is not displayed
rowsPerPageOptions={[10]}
component="div"
count={results.length}
rowsPerPage={rowsPerPage}
page={page}
onChangePage={handleChangePage}
/>
</>
);
};

export default QueryResults;
36 changes: 35 additions & 1 deletion frontend/components/views/QueryView/QuerySummary.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
import React from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
} from '@material-ui/core';
import styled from 'styled-components'
import { QueryData } from '../../../types';

const StyledTableCell = styled(TableCell)`
border:none;
`

interface QuerySummaryProps {
executionPlan: QueryData['executionPlan'];
}

const QuerySummary = ({ executionPlan }: QuerySummaryProps) => {
return <div>{executionPlan?.toString()}</div>;
const summaryData = {
'Planning Time': executionPlan?.['Planning Time'],
'Execution Time': executionPlan?.['Execution Time'],
'Actual Total Time': executionPlan?.Plan['Actual Total Time'],
};

if (!executionPlan) return null;
return (
<TableContainer>
<Table size="small">
<TableBody>
<TableRow>
{Object.entries(summaryData).map(([property, value]) => (
<StyledTableCell align="center">
<strong>{`${property}: `}</strong>
{value}
</StyledTableCell>
))}
</TableRow>
</TableBody>
</Table>
</TableContainer>
);
};

export default QuerySummary;
32 changes: 8 additions & 24 deletions frontend/components/views/QueryView/QueryTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
// TODO: Add Separator to divide top to bottom

import React, { useState } from 'react';
import { QueryData } from '../../../types';
import { Box } from '@material-ui/core';
import { QueryData, ValidTabs } from '../../../types';

import TabSelector from './TabSelector';
import QueryResults from './QueryResults';
import QueryPlan from './QueryPlan';

type ValidTabs = 'Results' | 'Execution Plan';

interface TabSelectorProps {
selectedTab: ValidTabs;
select: (tab: ValidTabs) => void;
}

const TabSelector = ({ selectedTab, select }: TabSelectorProps) => {
const tabs: ValidTabs[] = ['Results', 'Execution Plan'];

return (
<div>
{tabs.map((tab: ValidTabs) => (
<button type="button" onClick={() => select(tab)} key={`querytab_${tab}`}>
{`${tab}${selectedTab === tab ? ' <' : ''}`}
</button>
))}
</div>
);
};

interface QueryTabsProps {
results: QueryData['returnedRows'];
executionPlan: QueryData['executionPlan'];
Expand All @@ -33,18 +16,19 @@ interface QueryTabsProps {
const QueryTabs = ({ results, executionPlan }: QueryTabsProps) => {
const [selectedTab, setSelectedTab] = useState<ValidTabs>('Results');

if (!results && !executionPlan) return null
return (
<div>
<TabSelector
selectedTab={selectedTab}
select={(tab: ValidTabs) => setSelectedTab(tab)}
/>
<div>
<Box border={0}>
{selectedTab === 'Results' ? <QueryResults results={results} /> : null}
{selectedTab === 'Execution Plan' ? (
<QueryPlan executionPlan={executionPlan} />
) : null}
</div>
</Box>
</div>
);
};
Expand Down
16 changes: 11 additions & 5 deletions frontend/components/views/QueryView/QueryTopSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import React from 'react';
import styled from 'styled-components';

const Container = styled.div`
margin-left: auto;
`;

interface QueryTopSummaryProps {
rows: number;
totalTime: string;
rows: number | undefined;
totalTime: string | undefined;
}

const QueryTopSummary = ({ rows, totalTime }: QueryTopSummaryProps) => {
return <div>{`${rows} rows - ${totalTime}`}</div>;
};
const QueryTopSummary = ({ rows, totalTime }: QueryTopSummaryProps) => {
if (!rows || !totalTime) return null
return <Container>{`${rows} rows - ${totalTime}`}</Container>
};

export default QueryTopSummary;
Loading

0 comments on commit ac048be

Please sign in to comment.