mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-10-10 21:07:53 +00:00
replace old query builder
This commit is contained in:
parent
fa353faf72
commit
d8ac3251f6
31 changed files with 851 additions and 2247 deletions
|
|
@ -9,7 +9,7 @@ import { Nav } from 'shared/Nav';
|
|||
import { NotFound } from 'App/NotFound';
|
||||
import { MoleculaTablesContainer } from 'App/MoleculaTables';
|
||||
import { QueryContainer } from 'App/Query';
|
||||
import { QBuilderContainer } from 'App/QBuilder';
|
||||
import { QueryBuilderContainer } from 'App/QueryBuilder';
|
||||
import css from './App.module.scss';
|
||||
|
||||
const App = () => {
|
||||
|
|
@ -45,7 +45,7 @@ const App = () => {
|
|||
<Route exact path="/" component={Home} />
|
||||
<Route path="/tables/:id?" component={MoleculaTablesContainer} />
|
||||
<Route exact path="/query" component={QueryContainer} />
|
||||
<Route exact path="/querybuilder" component={QBuilderContainer} />
|
||||
<Route exact path="/querybuilder" component={QueryBuilderContainer} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
.sharedConfig {
|
||||
padding: 32px 32px 0;
|
||||
|
||||
.configSelector {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.textLink {
|
||||
color: var(--primary);
|
||||
font-size: 12px;
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.builder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.queryMetadata {
|
||||
border-bottom: 1px solid var(--divider);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.config {
|
||||
padding: 24px 32px 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.builderActions {
|
||||
padding: 16px 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.mainActions {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import ArrowBackIcon from '@material-ui/icons/ArrowBack';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Popover from '@material-ui/core/Popover';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {
|
||||
cleanupRows,
|
||||
stringifyCount,
|
||||
stringifyExtract,
|
||||
stringifyGroupBy
|
||||
} from './utils';
|
||||
import { CountBuilder } from './CountBuilder';
|
||||
import { ExtractBuilder } from './ExtractBuilder';
|
||||
import { GroupByBuilder } from './GroupByBuilder';
|
||||
import { Select } from 'shared/Select';
|
||||
import css from './QBuilder.module.scss';
|
||||
|
||||
type QBuilderProps = {
|
||||
tables: any[];
|
||||
savedQuery: number;
|
||||
onRun: (
|
||||
table: string,
|
||||
operation: string,
|
||||
query: string,
|
||||
countQuery?: string
|
||||
) => void;
|
||||
onClear: () => void;
|
||||
onExitEdit: () => void;
|
||||
onSaveQuery: () => void;
|
||||
};
|
||||
|
||||
export const QBuilder: FC<QBuilderProps> = ({
|
||||
tables,
|
||||
savedQuery,
|
||||
onRun,
|
||||
onClear,
|
||||
onExitEdit,
|
||||
onSaveQuery
|
||||
}) => {
|
||||
const [saveButtonEl, setSaveButtonEl] = useState<null | HTMLElement>(null);
|
||||
const queriesList = JSON.parse(localStorage.getItem('saved-queries') || '[]');
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const [selectedTable, setSelectedTable] = useState<any>(tables[0]);
|
||||
const [operation, setOperation] = useState<string>('Extract');
|
||||
const [query, setQuery] = useState<any>({
|
||||
table: tables[0].name,
|
||||
operation: 'Extract',
|
||||
columns: tables[0].fields.map((field) => field.name)
|
||||
});
|
||||
const [showInvalid, setShowInvalid] = useState<boolean>(false);
|
||||
const [updating, setUpdating] = useState<boolean>(false);
|
||||
const [queryNameIdx, setQueryNameIdx] = useState<number>(-1);
|
||||
|
||||
useEffect(() => {
|
||||
if (savedQuery > -1) {
|
||||
const updatedList = JSON.parse(
|
||||
localStorage.getItem('saved-queries') || '[]'
|
||||
);
|
||||
setQuery(updatedList[savedQuery]);
|
||||
setEditMode(true);
|
||||
setOperation(updatedList[savedQuery].operation);
|
||||
const table = tables.find(
|
||||
(table) => table.name === updatedList[savedQuery].table
|
||||
);
|
||||
setSelectedTable(table);
|
||||
}
|
||||
}, [savedQuery, tables]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query?.name) {
|
||||
setQueryNameIdx(queriesList.findIndex((q) => q.name === query.name));
|
||||
}
|
||||
}, [query, queriesList]);
|
||||
|
||||
const cleanRows = () => {
|
||||
const cleanRows = cleanupRows(query?.rowCalls);
|
||||
return {
|
||||
...query,
|
||||
rowCalls: cleanRows.cleanRowCalls,
|
||||
isInvalid: cleanRows.isInvalid
|
||||
};
|
||||
};
|
||||
|
||||
const runQuery = () => {
|
||||
setShowInvalid(true);
|
||||
let queryString = '';
|
||||
let countQuery = '';
|
||||
|
||||
switch (operation) {
|
||||
case 'Extract':
|
||||
const cleanExtractQuery = cleanRows();
|
||||
setQuery(cleanExtractQuery);
|
||||
const extract = stringifyExtract(cleanExtractQuery);
|
||||
if (!extract.error && !cleanExtractQuery.isInvalid) {
|
||||
queryString = extract.queryString;
|
||||
countQuery = extract.countQuery;
|
||||
}
|
||||
break;
|
||||
case 'GroupBy':
|
||||
queryString = stringifyGroupBy(query);
|
||||
break;
|
||||
case 'Count':
|
||||
const cleanCountQuery = cleanRows();
|
||||
setQuery(cleanCountQuery);
|
||||
const count = stringifyCount(cleanCountQuery);
|
||||
if (!count.error && !cleanCountQuery.isInvalid) {
|
||||
queryString = count.queryString;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (queryString && !query.isInvalid) {
|
||||
setShowInvalid(false);
|
||||
onRun(selectedTable.name, operation, queryString, countQuery);
|
||||
} else {
|
||||
setShowInvalid(true);
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setQuery({
|
||||
table: selectedTable.name,
|
||||
columns: selectedTable.fields.map(field => field.name),
|
||||
operation,
|
||||
isInvalid: false
|
||||
});
|
||||
setOperation('Extract');
|
||||
setShowInvalid(false);
|
||||
setEditMode(false);
|
||||
onExitEdit();
|
||||
onClear();
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
setUpdating(true);
|
||||
setTimeout(() => {
|
||||
setUpdating(false);
|
||||
}, 1500);
|
||||
const cleanQuery = cleanRows();
|
||||
let queries = [...queriesList];
|
||||
|
||||
if (editMode && queries.length > 0) {
|
||||
const clone = [...queries];
|
||||
clone.splice(savedQuery, 1, cleanQuery);
|
||||
localStorage.setItem('saved-queries', JSON.stringify(clone));
|
||||
} else {
|
||||
queries.push(cleanQuery);
|
||||
localStorage.setItem('saved-queries', JSON.stringify(queries));
|
||||
}
|
||||
onSaveQuery();
|
||||
};
|
||||
|
||||
const renderBuilder = () => {
|
||||
switch (operation) {
|
||||
case 'Extract':
|
||||
return (
|
||||
<ExtractBuilder
|
||||
table={selectedTable}
|
||||
showInvalid={showInvalid}
|
||||
query={query}
|
||||
onChange={(q) => setQuery(q)}
|
||||
/>
|
||||
);
|
||||
case 'GroupBy':
|
||||
return (
|
||||
<GroupByBuilder
|
||||
table={selectedTable}
|
||||
showInvalid={showInvalid}
|
||||
query={query}
|
||||
onChange={(q) => setQuery(q)}
|
||||
/>
|
||||
);
|
||||
case 'Count':
|
||||
return (
|
||||
<CountBuilder
|
||||
table={selectedTable}
|
||||
showInvalid={showInvalid}
|
||||
query={query}
|
||||
onChange={(q) => setQuery(q)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <div>Invalid Operation</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={css.builder}>
|
||||
<div className={css.sharedConfig}>
|
||||
{editMode ? (
|
||||
<span className={css.textLink} onClick={reset}>
|
||||
<ArrowBackIcon fontSize="inherit" className={css.icon} />
|
||||
Back to new query
|
||||
</span>
|
||||
) : null}
|
||||
<Typography variant="h5" color="textSecondary" paragraph>
|
||||
{editMode ? 'Edit Saved Query' : 'New Query'}
|
||||
</Typography>
|
||||
|
||||
{editMode ? (
|
||||
<div className={css.queryMetadata}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Query Name"
|
||||
placeholder="Unique name to identify query"
|
||||
value={query?.name}
|
||||
error={
|
||||
!query?.name ||
|
||||
(queryNameIdx >= 0 && queryNameIdx !== savedQuery)
|
||||
}
|
||||
helperText={
|
||||
queryNameIdx >= 0 && queryNameIdx !== savedQuery
|
||||
? 'Query name must be unique'
|
||||
: ''
|
||||
}
|
||||
onChange={(event) =>
|
||||
setQuery({ ...query, name: event.target.value })
|
||||
}
|
||||
margin="normal"
|
||||
size="small"
|
||||
required
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Description"
|
||||
value={query?.description}
|
||||
onChange={(event) =>
|
||||
setQuery({ ...query, description: event.target.value })
|
||||
}
|
||||
margin="normal"
|
||||
size="small"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Select
|
||||
className={css.configSelector}
|
||||
label="Table"
|
||||
value={selectedTable.name}
|
||||
options={tables.map((table) => {
|
||||
return { label: table.name, value: table.name };
|
||||
})}
|
||||
onChange={(value) => {
|
||||
const table = tables.find((t) => t.name === value);
|
||||
setSelectedTable(table);
|
||||
setShowInvalid(false);
|
||||
setQuery({
|
||||
table: table.name,
|
||||
columns: table.fields.map((field) => field.name),
|
||||
operation: query.operation,
|
||||
isInvalid: false
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<Select
|
||||
className={css.configSelector}
|
||||
label="Operation"
|
||||
value={operation}
|
||||
options={[
|
||||
{ label: 'Extract', value: 'Extract' },
|
||||
{ label: 'Count', value: 'Count' },
|
||||
{ label: 'GroupBy', value: 'GroupBy' }
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setOperation(value);
|
||||
setShowInvalid(false);
|
||||
setQuery({
|
||||
table: selectedTable.name,
|
||||
operation: value,
|
||||
isInvalid: false
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
</div>
|
||||
<div className={css.config}>{renderBuilder()}</div>
|
||||
<div className={css.builderActions}>
|
||||
<div className={css.mainActions}>
|
||||
<Button variant="contained" color="primary" onClick={runQuery}>
|
||||
Run
|
||||
</Button>
|
||||
{editMode ? (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="default"
|
||||
onClick={onSave}
|
||||
disabled={
|
||||
!query?.name || (savedQuery >= 0 && queryNameIdx !== savedQuery)
|
||||
}
|
||||
>
|
||||
{updating ? <CircularProgress size="0.875rem" /> : 'Update'}
|
||||
</Button>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="default"
|
||||
onClick={(event) => setSaveButtonEl(event.currentTarget)}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Popover
|
||||
open={!!saveButtonEl}
|
||||
anchorEl={saveButtonEl}
|
||||
onClose={() => setSaveButtonEl(null)}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
>
|
||||
<div className={css.savePopover}>
|
||||
<Typography variant="caption" paragraph>
|
||||
Give your query a unique name and a short description to
|
||||
help you identify it for future use.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Query Name"
|
||||
placeholder="Unique name to identify query"
|
||||
value={query?.name ? query.name : ''}
|
||||
error={queryNameIdx >= 0}
|
||||
helperText={
|
||||
queryNameIdx >= 0 ? 'Query name must be unique' : ''
|
||||
}
|
||||
onChange={(event) =>
|
||||
setQuery({ ...query, name: event.target.value })
|
||||
}
|
||||
margin="dense"
|
||||
required
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<TextField
|
||||
variant="outlined"
|
||||
label="Description"
|
||||
value={query?.description ? query.description : ''}
|
||||
onChange={(event) =>
|
||||
setQuery({ ...query, description: event.target.value })
|
||||
}
|
||||
margin="dense"
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<div className={css.popoverActions}>
|
||||
<div className={css.saveActions}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="default"
|
||||
onClick={() => {
|
||||
onSave();
|
||||
setSaveButtonEl(null);
|
||||
}}
|
||||
disabled={!query?.name || queryNameIdx >= 0}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
<Button
|
||||
color="default"
|
||||
onClick={() => setSaveButtonEl(null)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<Button onClick={reset}>Clear</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
.builderColumn,
|
||||
.results {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: scroll;
|
||||
position: relative;
|
||||
|
||||
.openClose {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.split {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.collapsedBuilder {
|
||||
border-right: 1px solid rgba(var(--contrast-rgb), 0.1);
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.resultsBlock {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.resultsHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.download {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.downloadInfo {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.infoMessage {
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.5);
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0 16px;
|
||||
}
|
||||
|
||||
.spacer{
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.resultsLoading {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.savedQueries {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.noTables {
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
|
@ -1,323 +0,0 @@
|
|||
import React, { Fragment, useState } from 'react';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import classNames from 'classnames';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import InfoIcon from '@material-ui/icons/Info';
|
||||
import moment, { Moment } from 'moment';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Split from 'react-split';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { Block } from 'shared/Block';
|
||||
import { formatDuration } from 'shared/utils/formatDuration';
|
||||
import { grpc } from '@improbable-eng/grpc-web';
|
||||
import { pilosa } from 'services/eventServices';
|
||||
import { QBuilder } from './QBuilder';
|
||||
import { queryPQL } from 'services/grpcServices';
|
||||
import { QueryResults } from 'App/Query/QueryResults';
|
||||
import { ResultType } from 'App/Query/QueryContainer';
|
||||
import { RowResponse } from 'proto/pilosa_pb';
|
||||
import { SavedQueries } from 'App/QueryBuilder/SavedQueries';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import css from './QBuilderContainer.module.scss';
|
||||
|
||||
let streamingResults: ResultType = {
|
||||
query: '',
|
||||
operation: '',
|
||||
type: 'PQL',
|
||||
headers: [],
|
||||
rows: [],
|
||||
roundtrip: 0,
|
||||
error: ''
|
||||
};
|
||||
|
||||
export const QBuilderContainer = () => {
|
||||
let startTime: Moment;
|
||||
let exportRows: any[] = [];
|
||||
const colSizes = JSON.parse(
|
||||
localStorage.getItem('builderColSizes') || '[25, 75]'
|
||||
);
|
||||
const [queriesList, setQueriesList] = useState(
|
||||
JSON.parse(localStorage.getItem('saved-queries') || '[]')
|
||||
);
|
||||
|
||||
const [tables, setTables] = useState<any[]>([]);
|
||||
const [showBuilder, setShowBuilder] = useState<boolean>(true);
|
||||
const [results, setResults] = useState<ResultType>();
|
||||
const [fullCount, setFullCount] = useState<number>();
|
||||
const [recordsCount, setRecordsCount] = useState<number>();
|
||||
const [errorResult, setErrorResult] = useState<ResultType>();
|
||||
const [error, setError] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [savedQuery, setSavedQuery] = useState<number>(-1);
|
||||
|
||||
useEffectOnce(() => {
|
||||
pilosa.get.schema().then((res) => {
|
||||
setTables(res.data.indexes);
|
||||
});
|
||||
});
|
||||
|
||||
const handleQueryMessages = (message: RowResponse) => {
|
||||
const response = message.toObject();
|
||||
if (response.headersList.length > 0 && response.duration > 0) {
|
||||
streamingResults.headers = response.headersList;
|
||||
streamingResults.duration = response.duration;
|
||||
}
|
||||
streamingResults.rows.push(response.columnsList);
|
||||
};
|
||||
|
||||
const handleQueryEnd = (status: grpc.Code, statusMessage: string) => {
|
||||
if (status !== grpc.Code.OK) {
|
||||
streamingResults.error = statusMessage;
|
||||
setErrorResult(streamingResults);
|
||||
} else {
|
||||
streamingResults.roundtrip = moment
|
||||
.duration(moment().diff(startTime))
|
||||
.as('milliseconds');
|
||||
setErrorResult(undefined);
|
||||
setResults(streamingResults);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleExternalLookup = (message: RowResponse) => {
|
||||
const response = message.toObject();
|
||||
let rowStr: string[] = [];
|
||||
if (exportRows.length === 0) {
|
||||
const headers = response.headersList.map((header) => header.name);
|
||||
exportRows.push(headers.join('\t'));
|
||||
}
|
||||
response.headersList.forEach((header, idx) =>
|
||||
rowStr.push(response.columnsList[idx][`${header.datatype}val`])
|
||||
);
|
||||
exportRows.push(rowStr.join('\t'));
|
||||
};
|
||||
|
||||
const handleExternalLookupEnd = (
|
||||
status: grpc.Code,
|
||||
statusMessage: string
|
||||
) => {
|
||||
if (status !== grpc.Code.OK) {
|
||||
setError(statusMessage);
|
||||
} else if (exportRows.length === 0) {
|
||||
setError('No record attributes for current query.');
|
||||
} else {
|
||||
const dateTime = moment().unix();
|
||||
const element = document.createElement('a');
|
||||
const file = new Blob([exportRows.join('\n')], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
element.href = URL.createObjectURL(file);
|
||||
element.download = `molecula-${results?.index}-${dateTime}.csv`;
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
exportRows = [];
|
||||
}
|
||||
};
|
||||
|
||||
const onRunQuery = (
|
||||
table: string,
|
||||
operation: string,
|
||||
query: string,
|
||||
countQuery?: string
|
||||
) => {
|
||||
streamingResults = {
|
||||
query,
|
||||
operation,
|
||||
type: 'PQL',
|
||||
headers: [],
|
||||
rows: [],
|
||||
index: table,
|
||||
roundtrip: 0,
|
||||
error: ''
|
||||
};
|
||||
startTime = moment();
|
||||
setLoading(true);
|
||||
|
||||
if (operation !== 'Count') {
|
||||
if (countQuery) {
|
||||
pilosa.post.query(table, countQuery).then((res) => {
|
||||
setFullCount(res.data.results[0]);
|
||||
});
|
||||
}
|
||||
|
||||
pilosa.post
|
||||
.query(table, `Count(All())`)
|
||||
.then((res) => setRecordsCount(res.data.results[0]));
|
||||
} else {
|
||||
setFullCount(undefined);
|
||||
}
|
||||
|
||||
queryPQL(table, query, handleQueryMessages, handleQueryEnd);
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
setResults(undefined);
|
||||
setFullCount(undefined);
|
||||
setErrorResult(undefined);
|
||||
};
|
||||
|
||||
const onRemoveQuery = (queryIdx: number) => {
|
||||
let queries = [...queriesList];
|
||||
queries.splice(queryIdx, 1);
|
||||
localStorage.setItem('saved-queries', JSON.stringify(queries));
|
||||
setQueriesList(queries);
|
||||
};
|
||||
|
||||
const onSaveQuery = () => {
|
||||
const updatedList = JSON.parse(
|
||||
localStorage.getItem('saved-queries') || '[]'
|
||||
);
|
||||
if (savedQuery < 0) {
|
||||
setSavedQuery(updatedList.length - 1);
|
||||
}
|
||||
setQueriesList(updatedList);
|
||||
};
|
||||
|
||||
const onExportLogs = () => {
|
||||
if (results) {
|
||||
const columns = results.rows.map((row) => row[0].uint64val);
|
||||
const table = results.index ? results.index : '';
|
||||
onExternalLookup(table, columns);
|
||||
}
|
||||
};
|
||||
|
||||
const onExternalLookup = (table: string, columns: number[]) => {
|
||||
const cols = JSON.stringify(columns);
|
||||
const query = `ExternalLookup(ConstRow(columns=${cols}), query='select id, "rawlog" from "${table}" where id = ANY($1)')`;
|
||||
queryPQL(table, query, handleExternalLookup, handleExternalLookupEnd);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{tables.length > 0 ? (
|
||||
<Split
|
||||
sizes={showBuilder ? colSizes : [0, 100]}
|
||||
cursor="col-resize"
|
||||
minSize={showBuilder ? 350 : 50}
|
||||
onDragEnd={(sizes) =>
|
||||
localStorage.setItem('builderColSizes', JSON.stringify(sizes))
|
||||
}
|
||||
gutter={(_index, direction) => {
|
||||
const gutter = document.createElement('div');
|
||||
gutter.className = `gutter gutter-${direction}`;
|
||||
const dragbars = document.createElement('div');
|
||||
dragbars.className = 'dragBar';
|
||||
gutter.appendChild(dragbars);
|
||||
return gutter;
|
||||
}}
|
||||
className={classNames(css.split, !showBuilder ? 'hide-gutter' : '')}
|
||||
>
|
||||
{showBuilder ? (
|
||||
<div className={css.builderColumn}>
|
||||
<div className={css.openClose}>
|
||||
<IconButton onClick={() => setShowBuilder(false)} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<QBuilder
|
||||
tables={tables}
|
||||
savedQuery={savedQuery}
|
||||
onRun={onRunQuery}
|
||||
onClear={onClear}
|
||||
onExitEdit={() => setSavedQuery(-1)}
|
||||
onSaveQuery={onSaveQuery}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={css.collapsedBuilder}>
|
||||
<IconButton onClick={() => setShowBuilder(true)} size="small">
|
||||
<ArrowForwardIosIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
<div className={css.results}>
|
||||
<Block className={css.resultsBlock}>
|
||||
<div className={css.resultsHeader}>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Results{' '}
|
||||
</Typography>
|
||||
{results?.query.includes('Extract(') ? (
|
||||
<div className={css.download}>
|
||||
<Tooltip
|
||||
className={css.downloadInfo}
|
||||
title="Download raw data from query results"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<InfoIcon fontSize="inherit" />
|
||||
</Tooltip>
|
||||
<Button onClick={onExportLogs}>Download</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{loading ? <div>Loading...</div> : null}
|
||||
{results && !loading ? (
|
||||
<Fragment>
|
||||
{fullCount && recordsCount ? (
|
||||
<div className={css.infoMessage}>
|
||||
{results.duration ? (
|
||||
<div>
|
||||
{recordsCount.toLocaleString()} records scanned in{' '}
|
||||
{formatDuration(results.duration, true)}.
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
Showing{' '}
|
||||
{fullCount > 1000 ? 'first 1,000 rows of' : 'all'}{' '}
|
||||
{fullCount.toLocaleString()} results.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<QueryResults results={results} />
|
||||
<div className={css.spacer} />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{errorResult && !loading ? <div>{errorResult.error}</div> : null}
|
||||
{!loading && !results && !error ? (
|
||||
<Fragment>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
Build a query to see results
|
||||
</Typography>
|
||||
{queriesList.length > 0 ? (
|
||||
<div className={css.savedQueries}>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Saved Queries
|
||||
</Typography>
|
||||
<SavedQueries
|
||||
queries={queriesList}
|
||||
tables={tables}
|
||||
onClickSaved={(queryIdx) => setSavedQuery(queryIdx)}
|
||||
onRemoveQuery={onRemoveQuery}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Block>
|
||||
</div>
|
||||
<Snackbar open={!!error}>
|
||||
<Alert severity="info" onClose={() => setError('')}>
|
||||
{error}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Split>
|
||||
) : (
|
||||
<Block>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Query Builder
|
||||
</Typography>
|
||||
<Paper className={css.noTables}>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
There are no tables to query.
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Block>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './helpers';
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from './QBuilderContainer';
|
||||
export * from './rowTypes';
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export type Operator = 'and' | 'or';
|
||||
|
||||
export const groupOperators: Operator[] = ['and', 'or'];
|
||||
|
||||
export type RowGrouping = {
|
||||
row: RowCallType[];
|
||||
isNot?: boolean;
|
||||
operator?: Operator;
|
||||
}
|
||||
|
||||
export type RowCallType = {
|
||||
field: string;
|
||||
rowOperator: string;
|
||||
value: string;
|
||||
type: string;
|
||||
keys?: boolean;
|
||||
};
|
||||
|
||||
export type RowsCallType = {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { FC } from 'react';
|
||||
import { RowCallBuilder } from 'App/QBuilder/RowCallBuilder';
|
||||
import { Operator, RowGrouping } from 'App/QueryBuilder/rowTypes';
|
||||
import { RowCallBuilder } from 'App/QueryBuilder/RowCallBuilder';
|
||||
|
||||
type CountBuilderProps = {
|
||||
table: any;
|
||||
|
|
@ -22,12 +23,11 @@ export const CountBuilder: FC<CountBuilderProps> = ({
|
|||
rowCalls={rowCalls}
|
||||
fields={table.fields}
|
||||
showInvalid={showInvalid}
|
||||
onChange={(rowCalls, operator, isInvalid) => {
|
||||
onChange={(rowCalls: RowGrouping[], operator?: Operator) => {
|
||||
onChange({
|
||||
...query,
|
||||
rowCalls,
|
||||
operator,
|
||||
isInvalid
|
||||
operator
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
@ -2,7 +2,8 @@ import React, { FC, useState } from 'react';
|
|||
import InfoIcon from '@material-ui/icons/Info';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import { ColumnSelector } from 'App/QueryBuilder/ColumnSelector';
|
||||
import { RowCallBuilder } from 'App/QBuilder/RowCallBuilder';
|
||||
import { Operator, RowGrouping } from 'App/QueryBuilder/rowTypes';
|
||||
import { RowCallBuilder } from 'App/QueryBuilder/RowCallBuilder';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import css from './ExtractBuilder.module.scss';
|
||||
|
||||
|
|
@ -78,12 +79,11 @@ export const ExtractBuilder: FC<ExtractBuilderProps> = ({
|
|||
rowCalls={rowCalls}
|
||||
fields={table.fields}
|
||||
showInvalid={showInvalid}
|
||||
onChange={(rowCalls, operator, isInvalid) => {
|
||||
onChange={(rowCalls: RowGrouping[], operator?: Operator) => {
|
||||
onChange({
|
||||
...query,
|
||||
rowCalls,
|
||||
operator,
|
||||
isInvalid
|
||||
operator
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
@ -5,7 +5,7 @@ import Typography from '@material-ui/core/Typography';
|
|||
import { GroupBySort } from 'App/QueryBuilder/GroupBySort';
|
||||
import { SavedQueries } from 'App/QueryBuilder/SavedQueries';
|
||||
import { Select } from 'shared/Select';
|
||||
import { stringifyRowData } from 'App/QueryBuilder/stringifyRowData';
|
||||
import { stringifyRowData } from 'App/QueryBuilder/utils';
|
||||
import css from './GroupByBuilder.module.scss';
|
||||
|
||||
type GroupByBuilderProps = {
|
||||
|
|
@ -131,7 +131,7 @@ export const GroupByBuilder: FC<GroupByBuilderProps> = ({
|
|||
const { rowCalls, operator } = filters[queryIdx];
|
||||
const res = stringifyRowData(rowCalls, operator);
|
||||
if (!res.error) {
|
||||
onChange({ ...query, filter: res.query });
|
||||
onChange({ ...query, filter: res.queryString });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
@ -1,154 +1,12 @@
|
|||
.builderColumn,
|
||||
.results {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: scroll;
|
||||
position: relative;
|
||||
|
||||
.openClose {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.resultsBlock {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.builderColumn {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
.results {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.resultsHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.download {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.downloadInfo {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsedBuilder {
|
||||
border-right: 1px solid rgba(var(--contrast-rgb), 0.1);
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.builderHeader {
|
||||
.sharedConfig {
|
||||
padding: 32px 32px 0;
|
||||
}
|
||||
|
||||
.builder {
|
||||
overflow-y: scroll;
|
||||
padding: 20px 32px;
|
||||
|
||||
.queryMetadata,
|
||||
.tableSelector,
|
||||
.operationSelector {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.columnsSelector {
|
||||
margin: 4px 14px;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
|
||||
.info {
|
||||
margin-left: 4px;
|
||||
fill: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.queryMetadata {
|
||||
border-bottom: 1px solid var(--divider);
|
||||
padding-bottom: 16px;
|
||||
.configSelector {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.builderActions {
|
||||
padding: 16px 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.mainActions {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.newGroup,
|
||||
.operator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.line {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
background: var(--divider);
|
||||
}
|
||||
|
||||
.addButton {
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.groupBy {
|
||||
.line {
|
||||
flex-grow: 1;
|
||||
height: 1px;
|
||||
background: var(--divider);
|
||||
}
|
||||
|
||||
.fieldSelector {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.filterHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.filterInfo {
|
||||
vertical-align: text-top;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.savePopover {
|
||||
width: 300px;
|
||||
|
||||
.popoverActions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.saveActions {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 8px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.menuPaper {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.link {
|
||||
.textLink {
|
||||
color: var(--primary);
|
||||
font-size: 12px;
|
||||
|
||||
|
|
@ -164,18 +22,31 @@
|
|||
}
|
||||
}
|
||||
|
||||
.infoMessage {
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.5);
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0 16px;
|
||||
.builder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.queryMetadata {
|
||||
border-bottom: 1px solid var(--divider);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.resultsLoading {
|
||||
margin-left: 8px;
|
||||
.config {
|
||||
padding: 24px 32px 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.savedQueries {
|
||||
margin-top: 48px;
|
||||
.builderActions {
|
||||
padding: 16px 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.mainActions {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,65 @@
|
|||
.builderColumn,
|
||||
.results {
|
||||
height: calc(100vh - 64px);
|
||||
overflow: scroll;
|
||||
position: relative;
|
||||
|
||||
.openClose {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.split {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.collapsedBuilder {
|
||||
border-right: 1px solid rgba(var(--contrast-rgb), 0.1);
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.resultsBlock {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.resultsHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.download {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.downloadInfo {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.infoMessage {
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(var(--primary-rgb), 0.5);
|
||||
background: rgba(var(--primary-rgb), 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 4px 0 16px;
|
||||
}
|
||||
|
||||
.spacer{
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.resultsLoading {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.savedQueries {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.noTables {
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
import React, { Fragment, useState } from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import classNames from 'classnames';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import InfoIcon from '@material-ui/icons/Info';
|
||||
import moment, { Moment } from 'moment';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Split from 'react-split';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { Block } from 'shared/Block';
|
||||
import { Operator, RowGrouping, RowsCallType } from './rowTypes';
|
||||
import { formatDuration } from 'shared/utils/formatDuration';
|
||||
import { grpc } from '@improbable-eng/grpc-web';
|
||||
import { pilosa } from 'services/eventServices';
|
||||
import { QueryBuilder } from './QueryBuilder';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import { ResultType } from 'App/Query/QueryContainer';
|
||||
import { queryPQL } from 'services/grpcServices';
|
||||
import { grpc } from '@improbable-eng/grpc-web';
|
||||
import { QueryResults } from 'App/Query/QueryResults';
|
||||
import { ResultType } from 'App/Query/QueryContainer';
|
||||
import { RowResponse } from 'proto/pilosa_pb';
|
||||
import { SortOption } from './GroupBySort';
|
||||
import { stringifyRowData } from './stringifyRowData';
|
||||
import { SavedQueries } from 'App/QueryBuilder/SavedQueries';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import css from './QueryBuilderContainer.module.scss';
|
||||
|
||||
let streamingResults: ResultType = {
|
||||
|
|
@ -30,13 +38,22 @@ let streamingResults: ResultType = {
|
|||
export const QueryBuilderContainer = () => {
|
||||
let startTime: Moment;
|
||||
let exportRows: any[] = [];
|
||||
const [tables, setTables] = useState<any[]>();
|
||||
const colSizes = JSON.parse(
|
||||
localStorage.getItem('builderColSizes') || '[25, 75]'
|
||||
);
|
||||
const [queriesList, setQueriesList] = useState(
|
||||
JSON.parse(localStorage.getItem('saved-queries') || '[]')
|
||||
);
|
||||
|
||||
const [tables, setTables] = useState<any[]>([]);
|
||||
const [showBuilder, setShowBuilder] = useState<boolean>(true);
|
||||
const [results, setResults] = useState<ResultType>();
|
||||
const [fullCount, setFullCount] = useState<number>();
|
||||
const [recordsCount, setRecordsCount] = useState<number>();
|
||||
const [errorResult, setErrorResult] = useState<ResultType>();
|
||||
const [error, setError] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [savedQuery, setSavedQuery] = useState<number>(-1);
|
||||
|
||||
useEffectOnce(() => {
|
||||
pilosa.get.schema().then((res) => {
|
||||
|
|
@ -67,62 +84,6 @@ export const QueryBuilderContainer = () => {
|
|||
setLoading(false);
|
||||
};
|
||||
|
||||
const onRunQuery = (
|
||||
table: any,
|
||||
operation: string,
|
||||
rowData: RowGrouping[],
|
||||
columnsList: string[],
|
||||
operator?: Operator
|
||||
) => {
|
||||
streamingResults = {
|
||||
query: '',
|
||||
operation,
|
||||
type: 'PQL',
|
||||
headers: [],
|
||||
rows: [],
|
||||
index: table.name,
|
||||
roundtrip: 0,
|
||||
error: ''
|
||||
};
|
||||
startTime = moment();
|
||||
setLoading(true);
|
||||
let query: string = '';
|
||||
if (rowData.length === 0) {
|
||||
query = 'All()';
|
||||
} else {
|
||||
const res = stringifyRowData(rowData, operator);
|
||||
if (res.error) {
|
||||
streamingResults.error = res.query;
|
||||
} else {
|
||||
query = res.query;
|
||||
}
|
||||
}
|
||||
|
||||
if (operation !== 'Count') {
|
||||
const countQuery = `Count(${query})`;
|
||||
pilosa.post
|
||||
.query(table.name, countQuery)
|
||||
.then((res) => setFullCount(res.data.results[0]));
|
||||
|
||||
pilosa.post
|
||||
.query(table.name, `Count(All())`)
|
||||
.then((res) => setRecordsCount(res.data.results[0]));
|
||||
} else {
|
||||
setFullCount(undefined);
|
||||
}
|
||||
|
||||
if (operation === 'Extract') {
|
||||
const fields = columnsList.map((field) => `Rows(${field})`);
|
||||
const allRows = fields.join(', ');
|
||||
query = `${operation}(Limit(${query}, limit=1000), ${allRows})`;
|
||||
} else {
|
||||
query = `${operation}(${query})`;
|
||||
}
|
||||
|
||||
streamingResults.query = query;
|
||||
queryPQL(table.name, query, handleQueryMessages, handleQueryEnd);
|
||||
};
|
||||
|
||||
const handleExternalLookup = (message: RowResponse) => {
|
||||
const response = message.toObject();
|
||||
let rowStr: string[] = [];
|
||||
|
|
@ -158,46 +119,71 @@ export const QueryBuilderContainer = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const onRunGroupBy = (
|
||||
table: any,
|
||||
rowsData: RowsCallType,
|
||||
sort: SortOption[],
|
||||
filter?: string
|
||||
const onRunQuery = (
|
||||
table: string,
|
||||
operation: string,
|
||||
query: string,
|
||||
countQuery?: string
|
||||
) => {
|
||||
streamingResults = {
|
||||
query: '',
|
||||
operation: 'GroupBy',
|
||||
query,
|
||||
operation,
|
||||
type: 'PQL',
|
||||
headers: [],
|
||||
rows: [],
|
||||
index: table.name,
|
||||
index: table,
|
||||
roundtrip: 0,
|
||||
error: ''
|
||||
};
|
||||
startTime = moment();
|
||||
setLoading(true);
|
||||
let sortString = '';
|
||||
let aggregateString = '';
|
||||
if (sort.length > 0) {
|
||||
sortString = sort[0].sortValue;
|
||||
if (sort[0].sortValue.includes('sum')) {
|
||||
aggregateString = `, aggregate=Sum(field=${sort[0].field})`;
|
||||
|
||||
if (operation !== 'Count') {
|
||||
if (countQuery) {
|
||||
pilosa.post.query(table, countQuery).then((res) => {
|
||||
setFullCount(res.data.results[0]);
|
||||
});
|
||||
}
|
||||
if (sort.length > 1) {
|
||||
sortString = `${sortString}, ${sort[1].sortValue}`;
|
||||
if (sort[1].sortValue.includes('sum')) {
|
||||
aggregateString = `, aggregate=Sum(field=${sort[1].field})`;
|
||||
}
|
||||
}
|
||||
sortString = `, sort="${sortString}"`;
|
||||
|
||||
pilosa.post
|
||||
.query(table, `Count(All())`)
|
||||
.then((res) => setRecordsCount(res.data.results[0]));
|
||||
} else {
|
||||
setFullCount(undefined);
|
||||
}
|
||||
|
||||
queryPQL(table, query, handleQueryMessages, handleQueryEnd);
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
setResults(undefined);
|
||||
setFullCount(undefined);
|
||||
setErrorResult(undefined);
|
||||
};
|
||||
|
||||
const onRemoveQuery = (queryIdx: number) => {
|
||||
let queries = [...queriesList];
|
||||
queries.splice(queryIdx, 1);
|
||||
localStorage.setItem('saved-queries', JSON.stringify(queries));
|
||||
setQueriesList(queries);
|
||||
};
|
||||
|
||||
const onSaveQuery = () => {
|
||||
const updatedList = JSON.parse(
|
||||
localStorage.getItem('saved-queries') || '[]'
|
||||
);
|
||||
if (savedQuery < 0) {
|
||||
setSavedQuery(updatedList.length - 1);
|
||||
}
|
||||
setQueriesList(updatedList);
|
||||
};
|
||||
|
||||
const onExportLogs = () => {
|
||||
if (results) {
|
||||
const columns = results.rows.map((row) => row[0].uint64val);
|
||||
const table = results.index ? results.index : '';
|
||||
onExternalLookup(table, columns);
|
||||
}
|
||||
const filterString =
|
||||
filter && filter.length > 0 ? `, filter=${filter}` : '';
|
||||
const query = rowsData.secondary
|
||||
? `GroupBy(Rows(${rowsData.primary}), Rows(${rowsData.secondary})${filterString}${sortString}${aggregateString})`
|
||||
: `GroupBy(Rows(${rowsData.primary})${filterString}${sortString}${aggregateString})`;
|
||||
streamingResults.query = query;
|
||||
queryPQL(table.name, query, handleQueryMessages, handleQueryEnd);
|
||||
};
|
||||
|
||||
const onExternalLookup = (table: string, columns: number[]) => {
|
||||
|
|
@ -206,40 +192,132 @@ export const QueryBuilderContainer = () => {
|
|||
queryPQL(table, query, handleExternalLookup, handleExternalLookupEnd);
|
||||
};
|
||||
|
||||
return tables && tables.length > 0 ? (
|
||||
return (
|
||||
<Fragment>
|
||||
<QueryBuilder
|
||||
tables={tables}
|
||||
results={results}
|
||||
fullResultsCount={fullCount}
|
||||
fullRecordsCount={recordsCount}
|
||||
error={errorResult}
|
||||
loading={loading}
|
||||
onQuery={onRunQuery}
|
||||
onRunGroupBy={onRunGroupBy}
|
||||
onExternalLookup={onExternalLookup}
|
||||
onClear={() => {
|
||||
setResults(undefined);
|
||||
setFullCount(undefined);
|
||||
setErrorResult(undefined);
|
||||
}}
|
||||
/>
|
||||
<Snackbar open={!!error}>
|
||||
<Alert severity="info" onClose={() => setError('')}>
|
||||
{error}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
{tables.length > 0 ? (
|
||||
<Split
|
||||
sizes={showBuilder ? colSizes : [0, 100]}
|
||||
cursor="col-resize"
|
||||
minSize={showBuilder ? 350 : 50}
|
||||
onDragEnd={(sizes) =>
|
||||
localStorage.setItem('builderColSizes', JSON.stringify(sizes))
|
||||
}
|
||||
gutter={(_index, direction) => {
|
||||
const gutter = document.createElement('div');
|
||||
gutter.className = `gutter gutter-${direction}`;
|
||||
const dragbars = document.createElement('div');
|
||||
dragbars.className = 'dragBar';
|
||||
gutter.appendChild(dragbars);
|
||||
return gutter;
|
||||
}}
|
||||
className={classNames(css.split, !showBuilder ? 'hide-gutter' : '')}
|
||||
>
|
||||
{showBuilder ? (
|
||||
<div className={css.builderColumn}>
|
||||
<div className={css.openClose}>
|
||||
<IconButton onClick={() => setShowBuilder(false)} size="small">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<QueryBuilder
|
||||
tables={tables}
|
||||
savedQuery={savedQuery}
|
||||
onRun={onRunQuery}
|
||||
onClear={onClear}
|
||||
onExitEdit={() => setSavedQuery(-1)}
|
||||
onSaveQuery={onSaveQuery}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={css.collapsedBuilder}>
|
||||
<IconButton onClick={() => setShowBuilder(true)} size="small">
|
||||
<ArrowForwardIosIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
<div className={css.results}>
|
||||
<Block className={css.resultsBlock}>
|
||||
<div className={css.resultsHeader}>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Results{' '}
|
||||
</Typography>
|
||||
{results?.query.includes('Extract(') ? (
|
||||
<div className={css.download}>
|
||||
<Tooltip
|
||||
className={css.downloadInfo}
|
||||
title="Download raw data from query results"
|
||||
placement="top"
|
||||
arrow
|
||||
>
|
||||
<InfoIcon fontSize="inherit" />
|
||||
</Tooltip>
|
||||
<Button onClick={onExportLogs}>Download</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{loading ? <div>Loading...</div> : null}
|
||||
{results && !loading ? (
|
||||
<Fragment>
|
||||
{fullCount && recordsCount ? (
|
||||
<div className={css.infoMessage}>
|
||||
{results.duration ? (
|
||||
<div>
|
||||
{recordsCount.toLocaleString()} records scanned in{' '}
|
||||
{formatDuration(results.duration, true)}.
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
Showing{' '}
|
||||
{fullCount > 1000 ? 'first 1,000 rows of' : 'all'}{' '}
|
||||
{fullCount.toLocaleString()} results.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<QueryResults results={results} />
|
||||
<div className={css.spacer} />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{errorResult && !loading ? <div>{errorResult.error}</div> : null}
|
||||
{!loading && !results && !error ? (
|
||||
<Fragment>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
Build a query to see results
|
||||
</Typography>
|
||||
{queriesList.length > 0 ? (
|
||||
<div className={css.savedQueries}>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Saved Queries
|
||||
</Typography>
|
||||
<SavedQueries
|
||||
queries={queriesList}
|
||||
tables={tables}
|
||||
onClickSaved={(queryIdx) => setSavedQuery(queryIdx)}
|
||||
onRemoveQuery={onRemoveQuery}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Block>
|
||||
</div>
|
||||
<Snackbar open={!!error}>
|
||||
<Alert severity="info" onClose={() => setError('')}>
|
||||
{error}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Split>
|
||||
) : (
|
||||
<Block>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Query Builder
|
||||
</Typography>
|
||||
<Paper className={css.noTables}>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
There are no tables to query.
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Block>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Block>
|
||||
<Typography variant="h5" color="textSecondary">
|
||||
Query Builder
|
||||
</Typography>
|
||||
<Paper className={css.noTables}>
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
There are no tables to query.
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
export const operators = {
|
||||
decimal: [
|
||||
{ label: '>', value: '>' },
|
||||
{ label: '<', value: '<' },
|
||||
{ label: '>=', value: '>=' },
|
||||
{ label: '<=', value: '<=' },
|
||||
{ label: '==', value: '=' },
|
||||
{ label: '!=', value: '!=' },
|
||||
],
|
||||
int: [
|
||||
{ label: '>', value: '>' },
|
||||
{ label: '<', value: '<' },
|
||||
{ label: '>=', value: '>=' },
|
||||
{ label: '<=', value: '<=' },
|
||||
{ label: '==', value: '=' },
|
||||
{ label: '!=', value: '!=' },
|
||||
],
|
||||
'mutex-id': [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' }
|
||||
],
|
||||
'mutex-keys': [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' },
|
||||
{ label: 'like', value: 'like' },
|
||||
{ label: 'CIDR', value: 'cidr' }
|
||||
],
|
||||
"set-id": [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' }
|
||||
],
|
||||
"set-keys": [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' },
|
||||
{ label: 'like', value: 'like' },
|
||||
{ label: 'CIDR', value: 'cidr' }
|
||||
],
|
||||
"time-id": [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' }
|
||||
],
|
||||
"time-keys": [
|
||||
{ label: 'is', value: '=' },
|
||||
{ label: 'is not', value: '!=' },
|
||||
{ label: 'like', value: 'like' }
|
||||
],
|
||||
timestamp: [
|
||||
{ label: 'is before', value: '<' },
|
||||
{ label: 'is after', value: '>' },
|
||||
{ label: 'is', value: '=' },
|
||||
]
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import TextField from '@material-ui/core/TextField';
|
|||
import Typography from '@material-ui/core/Typography';
|
||||
import { getIPRange } from 'get-ip-range';
|
||||
import { operators } from './helpers';
|
||||
import { groupOperators, Operator, RowCallType } from '../rowTypes';
|
||||
import { groupOperators, Operator, RowCallType } from 'App/QueryBuilder/rowTypes';
|
||||
import { Select } from 'shared/Select';
|
||||
import MomentUtils from '@date-io/moment';
|
||||
import { MuiPickersUtilsProvider, DateTimePicker } from '@material-ui/pickers';
|
||||
|
|
@ -10,18 +10,15 @@ import {
|
|||
RowCallType,
|
||||
RowGrouping
|
||||
} from 'App/QueryBuilder/rowTypes';
|
||||
import { RowCall } from 'App/QueryBuilder/RowCall';
|
||||
import { RowCall } from './RowCall';
|
||||
import css from './RowCallBuilder.module.scss';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type RowCallBuilderProps = {
|
||||
rowCalls: RowGrouping[];
|
||||
fields: any[];
|
||||
showInvalid: boolean;
|
||||
onChange: (
|
||||
rows: RowGrouping[],
|
||||
operator?: Operator,
|
||||
hasInvalid?: boolean
|
||||
) => void;
|
||||
onChange: (rows: RowGrouping[], operator?: Operator) => void;
|
||||
};
|
||||
|
||||
export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
||||
|
|
@ -34,7 +31,7 @@ export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
|||
const [operatorEl, setOperatorEl] = useState<null | HTMLElement>(null);
|
||||
const [operator, setOperator] = useState<Operator>();
|
||||
|
||||
const onNewGroup = () => {
|
||||
const onNewGroup = (op?: Operator) => {
|
||||
const newRow: RowCallType[] = [
|
||||
{
|
||||
field: '',
|
||||
|
|
@ -45,19 +42,20 @@ export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
|||
}
|
||||
];
|
||||
|
||||
onChange([...rowCalls, { row: newRow }]);
|
||||
onChange([...rowCalls, { row: newRow }], op ? op : operator);
|
||||
};
|
||||
|
||||
const onRemoveGroup = (groupIdx: number) => {
|
||||
if (groupIdx === 0) {
|
||||
onChange(rowCalls.slice(1));
|
||||
onChange(rowCalls.slice(1), operator);
|
||||
} else {
|
||||
const updatedRowCalls = [...rowCalls];
|
||||
updatedRowCalls.splice(groupIdx, 1);
|
||||
onChange(updatedRowCalls);
|
||||
onChange(updatedRowCalls, operator);
|
||||
|
||||
if (updatedRowCalls.length < 1) {
|
||||
setOperator(undefined);
|
||||
onChange(updatedRowCalls);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -65,7 +63,7 @@ export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
|||
const onUpdateRow = (
|
||||
groupIdx: number,
|
||||
newRowData?: RowCallType[],
|
||||
operator?: Operator,
|
||||
rowGroupOperator?: Operator,
|
||||
isNot?: boolean
|
||||
) => {
|
||||
const updatedRowCalls = [...rowCalls];
|
||||
|
|
@ -77,14 +75,14 @@ export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
|||
};
|
||||
if (newRowData.length <= 1) {
|
||||
delete clone.operator;
|
||||
} else if (operator) {
|
||||
clone.operator = operator;
|
||||
} else if (rowGroupOperator) {
|
||||
clone.operator = rowGroupOperator;
|
||||
}
|
||||
updatedRowCalls.splice(groupIdx, 1, clone);
|
||||
} else {
|
||||
updatedRowCalls.splice(groupIdx, 1);
|
||||
}
|
||||
onChange(updatedRowCalls);
|
||||
onChange(updatedRowCalls, operator);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -167,7 +165,7 @@ export const RowCallBuilder: FC<RowCallBuilderProps> = ({
|
|||
key={`operator-${op}`}
|
||||
onClick={() => {
|
||||
setOperator(op);
|
||||
onNewGroup();
|
||||
onNewGroup(op);
|
||||
setNewOperatorEl(null);
|
||||
}}
|
||||
>
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import { Operator, RowGrouping } from './rowTypes';
|
||||
import { getIPRange } from 'get-ip-range';
|
||||
|
||||
export const stringifyRowData = (rowData: RowGrouping[], operator?: Operator) => {
|
||||
let query = '';
|
||||
let rowsMap: string[][] = [];
|
||||
rowData.forEach((group, groupIdx) => {
|
||||
rowsMap.push([]);
|
||||
group.row.forEach((row) => {
|
||||
let rowString = '';
|
||||
const { field, rowOperator, value, type, keys } = row;
|
||||
const isNegatory = rowOperator === '!=';
|
||||
const isUnion =
|
||||
['=', '!='].includes(rowOperator) && value.split(',').length > 1;
|
||||
const operator = isNegatory ? '=' : rowOperator;
|
||||
if (isUnion) {
|
||||
const values = value.split(',');
|
||||
const unionRows = values
|
||||
.map((v) => keys ? `Row(${field}="${v.trim()}")` : `Row(${field}=${v.trim()})`)
|
||||
.join(', ');
|
||||
rowString = `Union(${unionRows})`;
|
||||
} else if (rowOperator === 'cidr') {
|
||||
try {
|
||||
const ipRange = getIPRange(value);
|
||||
const ipRows = ipRange
|
||||
.map((ip) => `Row(${field}="${ip}")`)
|
||||
.join(', ');
|
||||
rowString = `Union(${ipRows})`;
|
||||
} catch (error) {
|
||||
return { error: true, query: error.message };
|
||||
}
|
||||
} else if (rowOperator === 'like') {
|
||||
if (value.includes('%') || value.includes('_')) {
|
||||
rowString = `UnionRows(Rows(field=${field}, like="${value}"))`;
|
||||
} else {
|
||||
rowString = `UnionRows(Rows(field=${field}, like="%${value}%"))`;
|
||||
}
|
||||
} else {
|
||||
rowString = keys || type === 'timestamp'
|
||||
? `Row(${field}${operator}"${value}")`
|
||||
: `Row(${field}${operator}${value})`;
|
||||
}
|
||||
|
||||
if (isNegatory) {
|
||||
rowsMap[groupIdx].push(`Not(${rowString})`);
|
||||
} else {
|
||||
rowsMap[groupIdx].push(rowString);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
query = rowsMap
|
||||
.map((group, idx) => {
|
||||
let joined = '';
|
||||
if (group.length > 1) {
|
||||
joined = group.map((r) => r).join(', ');
|
||||
const operator = rowData[idx].operator;
|
||||
if (operator === 'and') {
|
||||
joined = `Intersect(${joined})`;
|
||||
} else if (operator === 'or') {
|
||||
joined = `Union(${joined})`;
|
||||
}
|
||||
} else {
|
||||
joined = group[0];
|
||||
}
|
||||
return rowData[idx].isNot ? `Not(${joined})` : joined;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
if (rowData.length > 1 && operator) {
|
||||
if (operator === 'and') {
|
||||
query = `Intersect(${query})`;
|
||||
} else if (operator === 'or') {
|
||||
query = `Union(${query})`;
|
||||
}
|
||||
}
|
||||
|
||||
return { error: false, query };
|
||||
}
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
import { stringifyRowData } from './stringifyRowData';
|
||||
import {
|
||||
stringifyRowData,
|
||||
stringifyExtract,
|
||||
stringifyCount,
|
||||
stringifyGroupBy
|
||||
} from './utils';
|
||||
|
||||
describe('decimal types', () => {
|
||||
it('stringifies = operator', () => {
|
||||
|
|
@ -10,7 +15,7 @@ describe('decimal types', () => {
|
|||
"type": "decimal",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName=0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName=0)' })
|
||||
});
|
||||
|
||||
it('stringifies != operator', () => {
|
||||
|
|
@ -22,7 +27,7 @@ describe('decimal types', () => {
|
|||
"type": "decimal",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=0))' })
|
||||
});
|
||||
|
||||
it('stringifies > operator', () => {
|
||||
|
|
@ -34,7 +39,7 @@ describe('decimal types', () => {
|
|||
"type": "decimal",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName>0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName>0)' })
|
||||
});
|
||||
|
||||
it('stringifies >= operator', () => {
|
||||
|
|
@ -46,7 +51,7 @@ describe('decimal types', () => {
|
|||
"type": "decimal",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName>=0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName>=0)' })
|
||||
});
|
||||
|
||||
it('stringifies negated operations', () => {
|
||||
|
|
@ -59,7 +64,7 @@ describe('decimal types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName>=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName>=0))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ describe('int types', () => {
|
|||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName=0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName=0)' })
|
||||
});
|
||||
|
||||
it('stringifies != operator', () => {
|
||||
|
|
@ -85,7 +90,7 @@ describe('int types', () => {
|
|||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=0))' })
|
||||
});
|
||||
|
||||
it('stringifies > operator', () => {
|
||||
|
|
@ -97,7 +102,7 @@ describe('int types', () => {
|
|||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName>0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName>0)' })
|
||||
});
|
||||
|
||||
it('stringifies >= operator', () => {
|
||||
|
|
@ -109,7 +114,7 @@ describe('int types', () => {
|
|||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName>=0)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName>=0)' })
|
||||
});
|
||||
|
||||
it('stringifies negated operations', () => {
|
||||
|
|
@ -122,7 +127,7 @@ describe('int types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName>=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName>=0))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -136,7 +141,7 @@ describe('mutex (id) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName=1234)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName=1234)' })
|
||||
});
|
||||
|
||||
it('stringifies is operator for multiple values', () => {
|
||||
|
|
@ -148,7 +153,7 @@ describe('mutex (id) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName=1234), Row(fieldName=5678))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName=1234), Row(fieldName=5678))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator', () => {
|
||||
|
|
@ -160,7 +165,7 @@ describe('mutex (id) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=1234))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator for multiple values', () => {
|
||||
|
|
@ -172,7 +177,7 @@ describe('mutex (id) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -186,7 +191,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName="value")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName="value")' })
|
||||
});
|
||||
|
||||
it('stringifies is operator for multiple values', () => {
|
||||
|
|
@ -198,7 +203,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName="one"), Row(fieldName="two"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName="one"), Row(fieldName="two"))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator', () => {
|
||||
|
|
@ -210,7 +215,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName="value"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName="value"))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator for multiple values', () => {
|
||||
|
|
@ -222,7 +227,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator with a single-codepoint key', () => {
|
||||
|
|
@ -234,7 +239,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="_alue"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="_alue"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator with a wildcard', () => {
|
||||
|
|
@ -246,7 +251,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="value%"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="value%"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator without a single-codepoint key or wildcard', () => {
|
||||
|
|
@ -258,7 +263,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%value%"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="%value%"))' })
|
||||
});
|
||||
|
||||
it('stringifies CIDR operator', () => {
|
||||
|
|
@ -270,7 +275,7 @@ describe('mutex (keys) types', () => {
|
|||
"type": "mutex",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' })
|
||||
});
|
||||
|
||||
it('stringifies negated operations', () => {
|
||||
|
|
@ -283,7 +288,7 @@ describe('mutex (keys) types', () => {
|
|||
"keys": true
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -297,7 +302,7 @@ describe('set (id) types', () => {
|
|||
"type": "set",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName=1234)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName=1234)' })
|
||||
});
|
||||
|
||||
it('stringifies is operator for multiple values', () => {
|
||||
|
|
@ -309,7 +314,7 @@ describe('set (id) types', () => {
|
|||
"type": "set",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName=1234), Row(fieldName=5678))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName=1234), Row(fieldName=5678))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator', () => {
|
||||
|
|
@ -321,7 +326,7 @@ describe('set (id) types', () => {
|
|||
"type": "set",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=1234))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator for multiple values', () => {
|
||||
|
|
@ -333,7 +338,7 @@ describe('set (id) types', () => {
|
|||
"type": "set",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -347,7 +352,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName="value")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName="value")' })
|
||||
});
|
||||
|
||||
it('stringifies is operator for multiple values', () => {
|
||||
|
|
@ -359,7 +364,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName="one"), Row(fieldName="two"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName="one"), Row(fieldName="two"))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator', () => {
|
||||
|
|
@ -371,7 +376,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName="value"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName="value"))' })
|
||||
});
|
||||
|
||||
it('stringifies is not operator for multiple values', () => {
|
||||
|
|
@ -383,7 +388,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator with a single-codepoint key', () => {
|
||||
|
|
@ -395,7 +400,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="_alue"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="_alue"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator with a wildcard', () => {
|
||||
|
|
@ -407,7 +412,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="value%"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="value%"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operator without a single-codepoint key or wildcard', () => {
|
||||
|
|
@ -419,7 +424,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%value%"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="%value%"))' })
|
||||
});
|
||||
|
||||
it('stringifies CIDR operator', () => {
|
||||
|
|
@ -431,7 +436,7 @@ describe('set (keys) types', () => {
|
|||
"type": "set",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' })
|
||||
});
|
||||
|
||||
it('stringifies negated operations', () => {
|
||||
|
|
@ -444,7 +449,7 @@ describe('set (keys) types', () => {
|
|||
"keys": true
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -458,7 +463,7 @@ describe('time (ID) types', () => {
|
|||
"type": "time",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName=1234)' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName=1234)' })
|
||||
});
|
||||
|
||||
it('stringifies is not operators', () => {
|
||||
|
|
@ -470,7 +475,7 @@ describe('time (ID) types', () => {
|
|||
"type": "time",
|
||||
"keys": undefined
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=1234))' })
|
||||
});
|
||||
|
||||
it('stringifies negated operators', () => {
|
||||
|
|
@ -483,7 +488,7 @@ describe('time (ID) types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName=1234))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -497,7 +502,7 @@ describe('time (keys) types', () => {
|
|||
"type": "time",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName="1234")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName="1234")' })
|
||||
});
|
||||
|
||||
it('stringifies is not operators', () => {
|
||||
|
|
@ -509,7 +514,7 @@ describe('time (keys) types', () => {
|
|||
"type": "time",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName="1234"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName="1234"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operators with a single-codepoint key', () => {
|
||||
|
|
@ -521,7 +526,7 @@ describe('time (keys) types', () => {
|
|||
"type": "time",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="123_"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="123_"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operators with a wildcard', () => {
|
||||
|
|
@ -533,7 +538,7 @@ describe('time (keys) types', () => {
|
|||
"type": "time",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%1234"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="%1234"))' })
|
||||
});
|
||||
|
||||
it('stringifies like operators without a single-codepoint key or wildcard', () => {
|
||||
|
|
@ -545,7 +550,7 @@ describe('time (keys) types', () => {
|
|||
"type": "time",
|
||||
"keys": true
|
||||
}]
|
||||
}])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%1234%"))' })
|
||||
}])).toEqual({ error: false, queryString: 'UnionRows(Rows(field=fieldName, like="%1234%"))' })
|
||||
});
|
||||
|
||||
it('stringifies negated operators', () => {
|
||||
|
|
@ -558,7 +563,7 @@ describe('time (keys) types', () => {
|
|||
"keys": true
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName="1234"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName="1234"))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -572,7 +577,7 @@ describe('timestamp types', () => {
|
|||
"type": "timestamp",
|
||||
"keys": undefined
|
||||
}],
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName="2021-06-04T12:00:00Z")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName="2021-06-04T12:00:00Z")' })
|
||||
});
|
||||
|
||||
it('stringifies is before operators', () => {
|
||||
|
|
@ -584,7 +589,7 @@ describe('timestamp types', () => {
|
|||
"type": "timestamp",
|
||||
"keys": undefined
|
||||
}],
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName<"2021-06-04T12:00:00Z")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName<"2021-06-04T12:00:00Z")' })
|
||||
});
|
||||
|
||||
it('stringifies is after operators', () => {
|
||||
|
|
@ -596,7 +601,7 @@ describe('timestamp types', () => {
|
|||
"type": "timestamp",
|
||||
"keys": undefined
|
||||
}],
|
||||
}])).toEqual({ error: false, query: 'Row(fieldName>"2021-06-04T12:00:00Z")' })
|
||||
}])).toEqual({ error: false, queryString: 'Row(fieldName>"2021-06-04T12:00:00Z")' })
|
||||
});
|
||||
|
||||
it('stringifies negated operators', () => {
|
||||
|
|
@ -609,7 +614,7 @@ describe('timestamp types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Row(fieldName="2021-06-04T12:00:00Z"))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Row(fieldName="2021-06-04T12:00:00Z"))' })
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -630,7 +635,7 @@ describe('mixed types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
operator: "and"
|
||||
}])).toEqual({ error: false, query: 'Intersect(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Intersect(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' })
|
||||
});
|
||||
|
||||
it('stringifies multiple or-ed rows', () => {
|
||||
|
|
@ -649,7 +654,7 @@ describe('mixed types', () => {
|
|||
"keys": undefined
|
||||
}],
|
||||
operator: "or"
|
||||
}])).toEqual({ error: false, query: 'Union(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' })
|
||||
}])).toEqual({ error: false, queryString: 'Union(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' })
|
||||
});
|
||||
|
||||
it('stringifies negated multiple rows', () => {
|
||||
|
|
@ -669,6 +674,129 @@ describe('mixed types', () => {
|
|||
}],
|
||||
operator: "or",
|
||||
isNot: true
|
||||
}])).toEqual({ error: false, query: 'Not(Union(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0)))' })
|
||||
}])).toEqual({ error: false, queryString: 'Not(Union(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0)))' })
|
||||
});
|
||||
})
|
||||
|
||||
describe('Query types', () => {
|
||||
it('stringifies Extract queries', () => {
|
||||
expect(stringifyExtract({
|
||||
columns: ['one', 'two', 'three'],
|
||||
rowCalls: [{
|
||||
row: [{
|
||||
"field": "stringType",
|
||||
"rowOperator": "like",
|
||||
"value": "value",
|
||||
"type": "set",
|
||||
"keys": true
|
||||
}, {
|
||||
"field": "stringType2",
|
||||
"rowOperator": "is",
|
||||
"value": "value2",
|
||||
"type": "set",
|
||||
"keys": true
|
||||
}],
|
||||
operator: 'or'
|
||||
}, {
|
||||
row: [{
|
||||
"field": "intType",
|
||||
"rowOperator": "=",
|
||||
"value": "0",
|
||||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}],
|
||||
operator: "and"
|
||||
})).toEqual({ error: false, queryString: 'Extract(Limit(Intersect(Union(UnionRows(Rows(field=stringType, like=\"%value%\")), Row(stringType2is\"value2\")), Row(intType=0)), limit=1000), Rows(one), Rows(two), Rows(three))', countQuery: 'Count(Intersect(Union(UnionRows(Rows(field=stringType, like=\"%value%\")), Row(stringType2is\"value2\")), Row(intType=0)))' })
|
||||
});
|
||||
|
||||
it('stringifies Count queries', () => {
|
||||
expect(stringifyCount({
|
||||
columns: ['one', 'two', 'three'],
|
||||
rowCalls: [{
|
||||
row: [{
|
||||
"field": "stringType",
|
||||
"rowOperator": "like",
|
||||
"value": "value",
|
||||
"type": "set",
|
||||
"keys": true
|
||||
}, {
|
||||
"field": "stringType2",
|
||||
"rowOperator": "is",
|
||||
"value": "value2",
|
||||
"type": "set",
|
||||
"keys": true
|
||||
}],
|
||||
operator: 'or'
|
||||
}, {
|
||||
row: [{
|
||||
"field": "intType",
|
||||
"rowOperator": "=",
|
||||
"value": "0",
|
||||
"type": "int",
|
||||
"keys": undefined
|
||||
}]
|
||||
}],
|
||||
operator: "and"
|
||||
})).toEqual({ error: false, queryString: 'Count(Intersect(Union(UnionRows(Rows(field=stringType, like=\"%value%\")), Row(stringType2is\"value2\")), Row(intType=0)))' })
|
||||
});
|
||||
|
||||
it('stringifies basic GroupBy queries', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary'
|
||||
}
|
||||
})).toEqual('GroupBy(Rows(primary))');
|
||||
});
|
||||
|
||||
it('stringifies GroupBy queries with secondary grouping', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary',
|
||||
secondary: 'secondary'
|
||||
}
|
||||
})).toEqual('GroupBy(Rows(primary), Rows(secondary))');
|
||||
});
|
||||
|
||||
it('stringifies GroupBy queries with a filter', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary'
|
||||
},
|
||||
filter: 'filterString'
|
||||
})).toEqual('GroupBy(Rows(primary), filter=filterString)');
|
||||
});
|
||||
|
||||
it('stringifies GroupBy queries with a sort', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary'
|
||||
},
|
||||
sort: [{ sortValue: 'count desc' }]
|
||||
})).toEqual('GroupBy(Rows(primary), sort=\"count desc\")');
|
||||
});
|
||||
|
||||
it('stringifies GroupBy queries with a secondary sum sort', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary'
|
||||
},
|
||||
sort: [
|
||||
{ sortValue: 'count desc' },
|
||||
{ sortValue: 'sum asc', field: 'field' }
|
||||
]
|
||||
})).toEqual('GroupBy(Rows(primary), sort=\"count desc, sum asc\", aggregate=Sum(field=field))');
|
||||
});
|
||||
|
||||
it('stringifies GroupBy queries with a filter and sort', () => {
|
||||
expect(stringifyGroupBy({
|
||||
groupByCall: {
|
||||
primary: 'primary'
|
||||
},
|
||||
filter: 'filterString',
|
||||
sort: [
|
||||
{ sortValue: 'sum asc', field: 'field' }
|
||||
]
|
||||
})).toEqual('GroupBy(Rows(primary), filter=filterString, sort=\"sum asc\", aggregate=Sum(field=field))');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { RowGrouping } from './rowTypes';
|
||||
import { Operator, RowGrouping } from './rowTypes';
|
||||
import { getIPRange } from 'get-ip-range';
|
||||
|
||||
export const stringifyExtract = (query: any) => {
|
||||
|
|
@ -29,8 +29,9 @@ export const stringifyCount = (query: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
const stringifyRowData = (rowCalls, operator) => {
|
||||
export const stringifyRowData = (rowCalls: RowGrouping[], operator?: Operator) => {
|
||||
let queryString = '';
|
||||
let error = false;
|
||||
if (rowCalls.length === 0) {
|
||||
queryString = 'All()';
|
||||
} else {
|
||||
|
|
@ -58,7 +59,7 @@ const stringifyRowData = (rowCalls, operator) => {
|
|||
.join(', ');
|
||||
rowString = `Union(${ipRows})`;
|
||||
} catch (error) {
|
||||
return { error: true, query: error.message };
|
||||
return { error: true, queryString: error.message };
|
||||
}
|
||||
} else if (rowOperator === 'like') {
|
||||
if (value.includes('%') || value.includes('_')) {
|
||||
|
|
@ -80,34 +81,36 @@ const stringifyRowData = (rowCalls, operator) => {
|
|||
});
|
||||
});
|
||||
|
||||
queryString = rowsMap
|
||||
.map((group, idx) => {
|
||||
let joined = '';
|
||||
if (group.length > 1) {
|
||||
joined = group.map((r) => r).join(', ');
|
||||
const operator = rowCalls[idx].operator;
|
||||
if (operator === 'and') {
|
||||
joined = `Intersect(${joined})`;
|
||||
} else if (operator === 'or') {
|
||||
joined = `Union(${joined})`;
|
||||
if (!error) {
|
||||
queryString = rowsMap
|
||||
.map((group, idx) => {
|
||||
let joined = '';
|
||||
if (group.length > 1) {
|
||||
joined = group.map((r) => r).join(', ');
|
||||
const operator = rowCalls[idx].operator;
|
||||
if (operator === 'and') {
|
||||
joined = `Intersect(${joined})`;
|
||||
} else if (operator === 'or') {
|
||||
joined = `Union(${joined})`;
|
||||
}
|
||||
} else {
|
||||
joined = group[0];
|
||||
}
|
||||
} else {
|
||||
joined = group[0];
|
||||
}
|
||||
return rowCalls[idx].isNot ? `Not(${joined})` : joined;
|
||||
})
|
||||
.join(', ');
|
||||
return rowCalls[idx].isNot ? `Not(${joined})` : joined;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
if (rowCalls.length > 1 && operator) {
|
||||
if (operator === 'and') {
|
||||
queryString = `Intersect(${queryString})`;
|
||||
} else if (operator === 'or') {
|
||||
queryString = `Union(${queryString})`;
|
||||
if (rowCalls.length > 1 && operator) {
|
||||
if (operator === 'and') {
|
||||
queryString = `Intersect(${queryString})`;
|
||||
} else if (operator === 'or') {
|
||||
queryString = `Union(${queryString})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { error: false, queryString };
|
||||
return { error, queryString };
|
||||
}
|
||||
|
||||
export const stringifyGroupBy = (query: any) => {
|
||||
Loading…
Add table
Reference in a new issue