Skip to content

Commit

Permalink
feat(query-history):Add query history (#71)
Browse files Browse the repository at this point in the history
Co-authored-by: Broknloop <[email protected]>
  • Loading branch information
broknloop and joaomcclain authored Mar 17, 2024
1 parent 6f147bb commit 9dff0b9
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 16 deletions.
30 changes: 30 additions & 0 deletions src/main/js/client/history-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export default class HistoryClient {
fetchHistory() {
const items = this.getAllFromLocalStorage();
return this.sort(items)
}

getAllFromLocalStorage() {
let items = []
for (let i = 0; i < localStorage.length; i++) {
// set iteration key name
const key = localStorage.key(i);
// use key name to retrieve the corresponding value
const value = localStorage.getItem(key);
items.push(JSON.parse(value))
}
return items
}

sort(array) {
return array.sort(function (a, b) {
if (a.timestamp < b.timestamp) {
return 1;
}
if (a.timestamp > b.timestamp) {
return -1;
}
return 0;
})
}
}
13 changes: 12 additions & 1 deletion src/main/js/components/query-result/query-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ function QueryResult({containerUrl, vispanaClient, query, showResults, schema, r
background: {
default: '#1f2a40',
},
highlightOnHover: {
default: '#3b4f77',
text: '#fff',
},
striped: {
default: '#2c3c5a',
text: '#fff',
},
context: {
text: '#facc15',
},
Expand Down Expand Up @@ -185,6 +193,9 @@ function QueryResult({containerUrl, vispanaClient, query, showResults, schema, r
paginationTotalRows={totalRows}
onChangeRowsPerPage={handlePerRowsChange}
onChangePage={handlePageChange}
responsive
striped
highlightOnHover
noDataComponent={<NoDataConst/>}
/>
)
Expand Down Expand Up @@ -220,7 +231,7 @@ function QueryResult({containerUrl, vispanaClient, query, showResults, schema, r
}

return (
<TabView tabs={tabs} />
<TabView tabs={tabs}/>
)
}

Expand Down
12 changes: 9 additions & 3 deletions src/main/js/components/tabs/tab-view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React from 'react'
import React, {useState} from 'react'

import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';

function TabView({ tabs }) {
function TabView({ tabs, currentTab, tabSelector }) {
if(currentTab === undefined || !tabSelector === undefined ) {
const [tabIndex, setTabIndex] = useState(0);
currentTab = tabIndex;
tabSelector = setTabIndex;
}

tabs.forEach(tab => {
if(! tab.hasOwnProperty('header') || !tab.hasOwnProperty('content')) {
throw new Error(`Expected 'header' and 'content' properties in TabView. Got: ${tab}`)
Expand All @@ -11,7 +17,7 @@ function TabView({ tabs }) {

return (
<div className="w-full max-w-full min-w-full">
<Tabs>
<Tabs selectedIndex={currentTab} onSelect={(index) => tabSelector(index)}>
<TabList className="flex justify-left items-left component-no-scrollbar overflow-x-scroll">
{ tabs
.map((tab, index) => (
Expand Down
157 changes: 157 additions & 0 deletions src/main/js/routes/schema/query-history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React from "react";
import DataTable, {createTheme} from "react-data-table-component";
import SyntaxHighlighter from "react-syntax-highlighter";
import {androidstudio} from "react-syntax-highlighter/dist/cjs/styles/hljs";
import HistoryClient from "../../client/history-client";
import {queryFieldFromSearchParam} from "./query";

const FilterComponent = ({ filterText, onFilter}) => (
<>
<div style={{ display: 'flex', alignItems: 'left', minWidth: '30%' }}>
<div className="form-control w-full">
<div className="font-search">
<input id="search"
className="border text-sm rounded-lg block w-full p-1.5 bg-standout-blue border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500 text-center input-bordered"
value={filterText}
onChange={onFilter}
type="text"
placeholder="Filter"
aria-label="Search Input"
/>
<i className="fa fa-search"></i>
</div>
</div>
</div>
</>
);

function QueryHistory({schema, tabSelector, searchParams, setSearchParams}) {
const [filterText, setFilterText] = React.useState('');
const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false);

const historyClient = new HistoryClient()
const queryHistory = historyClient.fetchHistory()
const filteredItems = queryHistory.filter(item => {
return item.query && item.query.toLowerCase().includes(filterText.toLowerCase())
});
const subHeaderComponentMemo = React.useMemo(() => {
return <FilterComponent onFilter={e => setFilterText(e.target.value)} filterText={filterText} />;
}, [filterText, resetPaginationToggle]);

const columns = [
{
name: '',
selector: row => row.query,
maxWidth: '150px',
cell: row => (
<button id="search"
className="border text-xs rounded-lg block w-28 p-1 bg-standout-blue text-yellow-400 border-yellow-400 placeholder-gray-400 focus:ring-blue-500 focus:border-blue-500 text-center input-bordered"
onClick={() => {
searchParams.set(queryFieldFromSearchParam(schema), row.query)
setSearchParams(searchParams)
tabSelector(0)
}}
type="text"
placeholder="Filter"
aria-label="Search Input"> Query </button>
)
},
{
name: 'Time',
selector: row => row.timestamp,
sortable: true,
maxWidth: '200px',
},
{
name: 'Vespa instance',
selector: row => row.vespaInstance,
maxWidth: '200px',
sortable: true,
},
{
name: 'Query',
selector: row => row.query,
},
]

// customize theme
createTheme('dark', {
text: {
// primary: '#facc15',
primary: '#fff',
secondary: '#fff',
},
background: {
default: '#1f2a40',
},
highlightOnHover: {
default: '#3b4f77',
text: '#fff',
},
striped: {
default: '#2c3c5a',
text: '#fff',
},
context: {
text: '#facc15',
},
divider: {
default: 'rgb(20 27 45 / var(--tw-bg-opacity))',
},
});

const NoDataConst = props => {
return <><span className="text-yellow-400 m-8">There are no records to display</span></>
}

return (
<DataTable
theme="dark"
customStyles={{
head: {
style: {
color: '#facc15'
}
},
subHeader: {
style: {
minHeight: '52px'
},
}
}}
columns={columns}
data={filteredItems}
fixedHeader

pagination
paginationResetDefaultPage={resetPaginationToggle} //a hook to reset pagination to
// page 1
responsive

striped
highlightOnHover
pointerOnHover

subHeader
subHeaderAlign="right"
subHeaderWrap
subHeaderComponent={subHeaderComponentMemo}

expandableRows
expandOnRowClicked
expandableRowsComponent={ExpandedComponent}

noDataComponent={<NoDataConst/>}
/>
)
}

const ExpandedComponent = ({data}) => {
const cloneData = { ...data };
cloneData.query = JSON.parse(cloneData.query)
return <SyntaxHighlighter language="json" style={androidstudio}>
{JSON.stringify(cloneData, null, 2)}
</SyntaxHighlighter>
};

export default QueryHistory;
21 changes: 15 additions & 6 deletions src/main/js/routes/schema/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@ import {v4 as uuidv4} from 'uuid';
import {Tooltip} from "react-tooltip";
import {useSearchParams} from "react-router-dom";

function Query({containerUrl, schema}) {
function Query({containerUrl, schema, searchParams, setSearchParams}) {
function runQuery() {
searchParams.set(queryFieldFromSearchParam(schema), query)
setSearchParams(searchParams)
setShowResults(true)
setRefreshQuery(uuidv4())

// Save the query to local storage
// create an object similar to the sample object in history-client.js
// and save it to local storage
const itemToHistory = {
timestamp: new Date().toISOString(),
vespaInstance: containerUrl,
query: query
}
localStorage.setItem(uuidv4(), JSON.stringify(itemToHistory))
}

function handleClick(event) {
Expand All @@ -26,10 +36,6 @@ function Query({containerUrl, schema}) {
}
}

function queryFieldFromSearchParam(schema) {
return `${schema}Query`
}

function addTrace() {
try {
const parsed = JSON.parse(query)
Expand All @@ -49,7 +55,6 @@ function Query({containerUrl, schema}) {
const [query, setQuery] = useState(defaultQuery)
const [showResults, setShowResults] = useState(false)
const [refreshQuery, setRefreshQuery] = useState(uuidv4())
const [searchParams, setSearchParams] = useSearchParams();

useEffect(() => {
const queryField = queryFieldFromSearchParam(schema)
Expand Down Expand Up @@ -120,4 +125,8 @@ export function defaultQuery(schema) {
yql: `SELECT * from ${schema} where true` }, null, 2);
}

export function queryFieldFromSearchParam(schema) {
return `${schema}Query`
}

export default Query
20 changes: 14 additions & 6 deletions src/main/js/routes/schema/schema.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react'
import {useOutletContext, useParams} from "react-router-dom";
import React, {useState} from 'react'
import {useOutletContext, useParams, useSearchParams} from "react-router-dom";
import TabView from "../../components/tabs/tab-view";
import Preview from "./preview";
import Query from "./query";
import Query, {defaultQuery} from "./query";
import QueryHistory from "./query-history";
import SyntaxHighlighter from "react-syntax-highlighter";
import {androidstudio} from "react-syntax-highlighter/dist/cjs/styles/hljs";

Expand All @@ -12,12 +13,19 @@ function Schema() {
const containerUrl = getQueryableContainer(vespaState)
const schema = params.schema
const schemaDetails = findSchemaDetails(vespaState, schema)
const [tabIndex, setTabIndex] = useState(0);

const [searchParams, setSearchParams] = useSearchParams();

return (<>
<TabView tabs={[
{
"header": "Query",
"content": <Query containerUrl={containerUrl} schema={schema}/>
"content": <Query containerUrl={containerUrl} schema={schema} searchParams={searchParams} setSearchParams= {setSearchParams}/>
},
{
"header": "Query history",
"content": <QueryHistory schema={schema} tabSelector={setTabIndex} searchParams={searchParams} setSearchParams= {setSearchParams}/>
},
{
"header": "Data preview",
Expand All @@ -28,8 +36,8 @@ function Schema() {
"content": <SyntaxHighlighter language="yaml" style={androidstudio}>
{schemaDetails}
</SyntaxHighlighter>
}
]} />
},
]} currentTab={tabIndex} tabSelector={setTabIndex} />
</>)

/* finds a valid container to issue the query */
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/static/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,14 @@ div.service_unknown {
* {
color: rgb(229 231 235 / var(--tw-text-opacity))
}

.font-search {
position: relative;
}

.font-search i{
position: absolute;
left: 15px;
top: 10px;
color: rgb(128, 128, 128);
}

0 comments on commit 9dff0b9

Please sign in to comment.