Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Dashboard from './dashboard/Dashboard';
// user context
import {userContext} from './userContext';

function App() {
document.title = 'QueryBooster';

const [user, setUser] = React.useState({"id": 1, "email": "alice@ics.uci.edu"});

return (
<div className="App">
{/* <header className="App-header">
Expand All @@ -20,7 +26,9 @@ function App() {
Learn React
</a>
</header> */}
<Dashboard />
<userContext.Provider value={user}>
<Dashboard />
</userContext.Provider>
</div>
);
}
Expand Down
85 changes: 85 additions & 0 deletions client/src/dashboard/ApplicationSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState, useCallback } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
import NiceModal, { useModal } from '@ebay/nice-modal-react';
import axios from 'axios';
import defaultApplicationsData from '../mock-api/listApplications';

const ApplicationSelect = NiceModal.create(({ user }) => {
const modal = useModal();
// Set up a state for list of applications
const [applications, setApplications] = React.useState([]);
// Set up a state for selected application Id
const [selectedAppId, setSelectedAppId] = useState(-1);

const handleSelectChange = (event) => {
setSelectedAppId(event.target.value);
};

// initial loading applications for the current user from server
const listApplications = (user) => {
console.log('[/listApplications] -> request:');
console.log(' user_id: ' + user.id);
// post listApplications request to server
axios.post('/listApplications', { 'user_id': user.id })
.then(function (response) {
console.log('[/listApplications] -> response:');
console.log(response);
// update the state for list of applications
setApplications(response.data);
})
.catch(function (error) {
console.log('[/listApplications] -> error:');
console.log(error);
// mock the result
console.log(defaultApplicationsData);
setApplications(defaultApplicationsData);
});
};

// call listApplications() only once after initial rendering
React.useEffect(() => { listApplications(user) }, []);

const handleSubmit = useCallback(() => {
const selectedApplication = applications.find((app) => app.id == selectedAppId);
console.log("[ApplicationSelect] selectedAppId = " + selectedAppId);
console.log("[ApplicationSelect] applications = ");
console.log(applications);
console.log("[ApplicationSelect] find selected application = ");
console.log(selectedApplication);
modal.resolve(selectedApplication);
modal.hide();
}, [modal]);

return (
<Dialog
open={modal.visible}
onClose={() => modal.hide()}
TransitionProps={{
onExited: () => modal.remove(),
}}
maxWidth={'sm'}
>
<DialogTitle>Enable Rule for Application</DialogTitle>
<DialogContent>
<Box>
<select value={selectedAppId} onChange={handleSelectChange}>
<option value={-1} key={-1}>Select...</option>
{applications.map((app) => (
<option value={app.id} key={app.id}>{app.name}</option>
))}
</select>
</Box>
</DialogContent>
<DialogActions>
<Button type="submit" variant="contained" color="primary" onClick={handleSubmit}>Confirm</Button>
</DialogActions>
</Dialog>
);
});

export default ApplicationSelect;
78 changes: 78 additions & 0 deletions client/src/dashboard/ApplicationTag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react';
import axios from 'axios';
import { useModal } from '@ebay/nice-modal-react';
import ApplicationSelect from './ApplicationSelect';
import {userContext} from '../userContext';

function AppTagCell({ruleId: initialRuleId, tags: initialApps }) {
const [ruleId, setRule] = React.useState(initialRuleId);
const [apps, setApps] = React.useState(initialApps);
// Set up a state for providing forceUpdate function
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

const applicationSelectModal = useModal(ApplicationSelect);

const user = React.useContext(userContext);

function handleSelect(selectedApplication) {
if (selectedApplication) {
// post enableRule request to server
axios.post('/enableRule', {'rule': {'id': ruleId}, 'app': selectedApplication})
.then(function (response) {
console.log('[/enableRule] -> response:');
console.log(response);
setApps([...apps, {'app_id': selectedApplication.id, 'app_name': selectedApplication.name}]);
forceUpdate();
})
.catch(function (error) {
console.log('[/enableRule] -> error:');
console.log(error);
// TODO - alter the entered application name doest not exist
});
}
}

const handleAddApp = React.useCallback(() => {
applicationSelectModal.show({user}).then((selectedApplication) => {
console.log("[ApplicationTag] selectedApplication = ");
console.log(selectedApplication);
handleSelect(selectedApplication);
});
}, [applicationSelectModal]);

function handleRemoveApp(app) {
// post disableRule request to server
axios.post('/disableRule', {'rule': {'id': ruleId}, 'app': {'id': app.app_id, 'name': app.app_name}})
.then(function (response) {
console.log('[/disableRule] -> response:');
console.log(response);
const updatedApps = apps.filter((a) => a !== app);
setApps(updatedApps);
forceUpdate();
})
.catch(function (error) {
console.log('[/disableRule] -> error:');
console.log(error);
});
}

return (
<div>
{apps.map((app) => (
<span key={app.app_id} className="tag">
{app.app_name}
<button
className="delete-button"
onClick={() => handleRemoveApp(app)}
>
x
</button>
</span>
))}
<button onClick={handleAddApp}>+</button>
</div>
);
}

export default AppTagCell;
9 changes: 7 additions & 2 deletions client/src/dashboard/QueryLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import defaultQueriesData from '../mock-api/listQueries';
import QueryRewritingPath from './QueryRewritingPath';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { vs } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import {userContext} from '../userContext';


export default function QueryLogs() {
Expand All @@ -21,10 +22,12 @@ export default function QueryLogs() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

const user = React.useContext(userContext);

// initial loading queries from server
const listQueries = (_page) => {
// post listQueries request to server
axios.post('/listQueries', {page: _page})
axios.post('/listQueries', {page: _page, 'user_id': user.id})
.then(function (response) {
console.log('[/listQueries] -> response:');
console.log(response);
Expand Down Expand Up @@ -57,8 +60,9 @@ export default function QueryLogs() {
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>App</TableCell>
<TableCell>Timestamp</TableCell>
<TableCell>Boosted</TableCell>
<TableCell>Rewritten</TableCell>
<TableCell>Before Latency(s)</TableCell>
<TableCell>After Latency(s)</TableCell>
<TableCell>SQL</TableCell>
Expand All @@ -70,6 +74,7 @@ export default function QueryLogs() {
{queries.map((query) => (
<TableRow key={query.id} onClick={() => selectQuery(query)}>
<TableCell>{query.id}</TableCell>
<TableCell>{query.app_name}</TableCell>
<TableCell>{query.timestamp}</TableCell>
<TableCell>{query.boosted}</TableCell>
<TableCell>{query.before_latency/1000}</TableCell>
Expand Down
16 changes: 11 additions & 5 deletions client/src/dashboard/RewritingRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Switch from '@mui/material/Switch';
import Title from './Title';
import defaultRulesData from '../mock-api/listRules';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { vs } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import { Box } from '@mui/material';
import AddRewritingRule from './AddRewritingRule';
import AppTagCell from './ApplicationTag';
import {userContext} from '../userContext';


export default function RewrittingRules() {
Expand All @@ -26,10 +27,14 @@ export default function RewrittingRules() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

const user = React.useContext(userContext);

// initial loading rules from server
const listRules = () => {
console.log('[/listRules] -> request:');
console.log(' user_id: ' + user.id);
// post listRules request to server
axios.post('/listRules', {})
axios.post('/listRules', {'user_id': user.id})
.then(function (response) {
console.log('[/listRules] -> response:');
console.log(response);
Expand Down Expand Up @@ -119,7 +124,7 @@ export default function RewrittingRules() {
<TableCell>Name</TableCell>
<TableCell>Pattern</TableCell>
<TableCell>Rewrite</TableCell>
<TableCell align="right">Enabled</TableCell>
<TableCell align="right">Enabled Apps</TableCell>
<TableCell align="center">Delete</TableCell>
</TableRow>
</TableHead>
Expand All @@ -139,10 +144,11 @@ export default function RewrittingRules() {
</SyntaxHighlighter>
</TableCell>
<TableCell align="right">
<Switch
{/* <Switch
checked={rule.enabled}
onChange={(event) => handleChange(event, rule)}
inputProps={{ 'aria-label': 'controlled' }} />
inputProps={{ 'aria-label': 'controlled' }} /> */}
<AppTagCell ruleId={rule.id} tags={rule.enabled_apps} />
</TableCell>
<TableCell align="center">
<Button variant="outlined" color="error" onClick={() => handleDelete(rule)} >Delete</Button>
Expand Down
16 changes: 16 additions & 0 deletions client/src/mock-api/listApplications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const defaultApplicationsData = [
{
"id": 1,
"name": "TwitterPg"
},
{
"id": 2,
"name": "TpchPg"
},
{
"id": 3,
"name": "TwitterMySQL"
}
];

export default defaultApplicationsData;
30 changes: 14 additions & 16 deletions client/src/mock-api/listQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,34 @@ const defaultQueriesData = [
{
"id": 1,
"timestamp": "2022-10-12 16:36:03",
"latency": 3,
"original_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
"rewritten": 'YES',
"before_latency": 35000,
"after_latency": 3200,
"sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
CAST("tweets"."state_name" AS TEXT) AS "state_name"
FROM "public"."tweets" "tweets"
WHERE ((CAST(DATE_TRUNC('QUARTER', CAST("tweets"."created_at" AS DATE)) AS DATE) IN ((TIMESTAMP '2016-04-01 00:00:00.000'), (TIMESTAMP '2016-07-01 00:00:00.000'), (TIMESTAMP '2016-10-01 00:00:00.000'), (TIMESTAMP '2017-01-01 00:00:00.000')))
AND (STRPOS(CAST(LOWER(CAST(CAST("tweets"."text" AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0))
GROUP BY 2`,
"rewritten_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
tweets.state_name AS state_name
FROM public.tweets AS tweets
WHERE DATE_TRUNC('QUARTER', tweets.created_at) IN ((TIMESTAMP '2016-04-01 00:00:00.000'), (TIMESTAMP '2016-07-01 00:00:00.000'), (TIMESTAMP '2016-10-01 00:00:00.000'), (TIMESTAMP '2017-01-01 00:00:00.000'))
AND tweets.text ILIKE '%iphone%'
GROUP BY 2`
"suggestion": "NO",
"suggested_latency": -1000,
"app_name": "TwitterPg"
},
{
"id": 0,
"timestamp": "2022-10-12 16:31:42",
"latency": 34,
"original_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
"rewritten": 'YES',
"before_latency": 32000,
"after_latency": 2800,
"sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
CAST("tweets"."state_name" AS TEXT) AS "state_name"
FROM "public"."tweets" "tweets"
WHERE ((CAST(DATE_TRUNC('QUARTER', CAST("tweets"."created_at" AS DATE)) AS DATE) IN ((TIMESTAMP '2017-10-01 00:00:00.000'), (TIMESTAMP '2018-01-01 00:00:00.000'), (TIMESTAMP '2018-04-01 00:00:00.000')))
AND (STRPOS(CAST(LOWER(CAST(CAST("tweets"."text" AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0))
GROUP BY 2`,
"rewritten_sql": `SELECT SUM(1) AS "cnt:tweets_5460F7F804494E7CB9FD188E329004C1:ok",
CAST(tweets.state_name AS TEXT) AS state_name
FROM public.tweets AS tweets
WHERE CAST(DATE_TRUNC('QUARTER', CAST(tweets.created_at AS DATE)) AS DATE) IN ((TIMESTAMP '2017-10-01 00:00:00.000'), (TIMESTAMP '2018-01-01 00:00:00.000'), (TIMESTAMP '2018-04-01 00:00:00.000'))
AND STRPOS(CAST(LOWER(CAST(CAST(tweets.text AS TEXT) AS TEXT)) AS TEXT), CAST('iphone' AS TEXT)) > 0
GROUP BY 2`
"suggestion": "NO",
"suggested_latency": -1000,
"app_name": "TwitterPg"
}
];

Expand Down
Loading