featurebase/lattice/src/App/Query/Query.tsx

253 lines
7.9 KiB
TypeScript

import React, {
createRef,
FC,
Fragment,
useCallback,
useEffect,
useState
} from 'react';
import CircularProgress from '@material-ui/core/CircularProgress';
import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Paper from '@material-ui/core/Paper';
import Split from 'react-split';
import Typography from '@material-ui/core/Typography';
import { Block } from 'shared/Block';
import { Console } from './Console';
import { motion } from 'framer-motion';
import { QueryResults } from './QueryResults';
import { RecentQueries } from './RecentQueries';
import { ResultType } from './QueryContainer';
import './splitjs.scss';
import css from './Query.module.scss';
type QueryProps = {
indexList: any[];
results: ResultType[];
error?: ResultType;
loading: boolean;
onRemoveResult: (resultIdx: number) => void;
onClear: () => void;
onQuery: (query: string, type: 'PQL' | 'SQL', index?: string) => void;
};
export const Query: FC<QueryProps> = ({
indexList = [],
results,
error,
loading,
onRemoveResult,
onClear,
onQuery
}) => {
const [index, setIndex] = useState<any>();
const [helperText, setHelperText] = useState<string[]>([]);
const [showRecent, setShowRecent] = useState<boolean>(false);
const colSizes = JSON.parse(localStorage.getItem('colSizes') || '[80, 20]');
const inputRef = createRef<HTMLInputElement>();
const queries = JSON.parse(localStorage.getItem('recent-queries') || '[]');
const hasRecentQueries = queries.length > 0;
useEffect(() => {
if (!error) {
setIndex(undefined);
setHelperText([]);
}
}, [results, error]);
const onSetIndex = useCallback(
(queryIndex: string | undefined) => {
const matchIndex = indexList.find((i) => i.name === queryIndex);
if (matchIndex) {
setIndex(matchIndex);
} else {
setIndex(undefined);
}
},
[setIndex, indexList]
);
const onReRunQuery = (query: string) => {
const queryPattern = query.match(/\[(.*?)\]/);
if (query[0] === '[' || (queryPattern && queryPattern.index === 0)) {
const indexStart = query.indexOf('[') + 1;
const indexEnd = query.indexOf(']');
const index = query.substring(indexStart, indexEnd);
const pqlQuery = query.substring(indexEnd + 1);
onQuery(pqlQuery, 'PQL', index);
} else {
onQuery(query, 'SQL');
}
setIndex(undefined);
};
return (
<Fragment>
<Block>
<Typography variant="h5" color="textSecondary">
Query
</Typography>
<Split
sizes={colSizes}
cursor="col-resize"
minSize={215}
onDragEnd={(sizes) =>
localStorage.setItem('colSizes', JSON.stringify(sizes))
}
gutter={(_index, direction) => {
const gutter = document.createElement('div');
gutter.className = `gutter gutter-margin-top gutter-${direction}`;
const dragbars = document.createElement('div');
dragbars.className = 'dragBars';
gutter.appendChild(dragbars);
return gutter;
}}
style={{ display: 'flex' }}
>
<div className={css.consoleCol}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.7, delay: 0.3 }}
>
<div className={css.consoleLayout}>
<div className={css.consoleContent}>
<Console
ref={inputRef}
onQuery={(
query: string,
type: 'PQL' | 'SQL',
index?: string
) => onQuery(query, type, index)}
error={error}
onHelperTextChange={setHelperText}
onSetIndex={onSetIndex}
loading={loading}
indexList={indexList}
previousQueries={queries}
/>
<Typography
className={css.syntaxHelper}
color="textSecondary"
variant="caption"
component="div"
>
{helperText
? helperText.map((txt) => <div key={txt}>{txt}</div>)
: null}
</Typography>
</div>
</div>
</motion.div>
{results.length > 0 ? (
<div className={css.clearResults}>
<span
className={css.recentLink}
onClick={() => setShowRecent(true)}
>
Show Recent Queries
</span>
<span className={css.divider}>|</span>
<span className={css.clearLink} onClick={onClear}>
Clear Results
</span>
</div>
) : null}
{!results.length && hasRecentQueries ? (
<Fragment>
<Typography color="textPrimary" variant="overline">
Recent Queries
</Typography>
<RecentQueries onQueryClick={(query) => onReRunQuery(query)} />
</Fragment>
) : null}
{loading && (
<Paper className={css.loadingContainer}>
<CircularProgress size={20} />
</Paper>
)}
{results.map((result, idx) => (
<Paper key={`query-result-${idx}`} className={css.results}>
<QueryResults
collapsibleQuery={false}
results={result}
onRemoveResult={() => onRemoveResult(idx)}
/>
</Paper>
))}
</div>
{index ? (
<div className={css.fieldsCol}>
<Typography color="textPrimary" variant="overline">
Available Fields
</Typography>
<Typography
className={css.fieldList}
color="textSecondary"
variant="caption"
component="div"
>
{index.fields.map((field) => (
<div
key={field.name}
title={`${field.name} (${field.options.type})`}
>
{field.name} ({field.options.type})
</div>
))}
</Typography>
</div>
) : (
<div className={css.fieldsCol}>
<Typography color="textPrimary" variant="overline">
Available Indexes
</Typography>
{indexList ? (
<Typography
className={css.fieldList}
color="textSecondary"
variant="caption"
component="div"
>
{indexList.map((i) => (
<div key={i.name} title={i.name}>
{i.name}
</div>
))}
</Typography>
) : (
<Typography
color="textSecondary"
variant="caption"
component="div"
>
No indexes available
</Typography>
)}
</div>
)}
</Split>
</Block>
<Dialog
open={showRecent}
onClose={() => setShowRecent(false)}
maxWidth="md"
>
<DialogTitle>Recent Queries</DialogTitle>
<DialogContent>
<div className={css.list}>
<RecentQueries
onQueryClick={(query) => {
setShowRecent(false);
onReRunQuery(query);
}}
/>
</div>
</DialogContent>
</Dialog>
</Fragment>
);
};