diff --git a/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.module.scss b/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.module.scss new file mode 100644 index 000000000..c164ead96 --- /dev/null +++ b/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.module.scss @@ -0,0 +1,8 @@ +.sortSelectRow { + margin-top: 15px; + display: flex; + + .fieldSelect { + margin-left: 15px; + } +} diff --git a/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.tsx b/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.tsx new file mode 100644 index 000000000..1b5f5b85c --- /dev/null +++ b/lattice/src/App/QueryBuilder/GroupBySort/GroupBySort.tsx @@ -0,0 +1,118 @@ +import React, { FC } from 'react'; +import { Select } from 'shared/Select'; +import css from './GroupBySort.module.scss'; + +export type SortOption = { + sortValue: string; + field?: string; +}; + +type GroupBySortProps = { + sort: SortOption[]; + fields: { label: string; value: string }[]; + showErrors: boolean; + onUpdate: (sort: SortOption[]) => void; +}; + +export const GroupBySort: FC = ({ + sort, + fields, + showErrors, + onUpdate +}) => { + const hasPrimary = sort.length > 0; + const primary = hasPrimary ? sort[0].sortValue.split(' ')[0] : ''; + const hasSecondary = sort.length > 1; + const secondary = hasSecondary ? sort[1].sortValue.split(' ')[0] : ''; + const sortOptions = [ + { label: 'Count (desc)', value: 'count desc' }, + { label: 'Count (asc)', value: 'count asc' }, + { label: 'Sum (desc)', value: 'sum desc' }, + { label: 'Sum (asc)', value: 'sum asc' } + ]; + + const onPrimaryChange = (value: string) => { + const split = value.split(' '); + const isSum = split[0] === 'sum'; + const fieldValue = hasPrimary ? sort[0].field : undefined; + + if (!hasSecondary) { + onUpdate([{ sortValue: value, field: isSum ? fieldValue : undefined }]); + } else if (hasSecondary && sort[1].sortValue.includes(split[0])) { + onUpdate([{ sortValue: value, field: isSum ? fieldValue : undefined }]); + } else { + onUpdate([ + { sortValue: value, field: isSum ? fieldValue : undefined }, + sort[1] + ]); + } + }; + const onSecondaryChange = (value: string) => { + let sortOp = { sortValue: value }; + + if (hasSecondary) { + sortOp = { ...sort[1], ...sortOp }; + } + onUpdate([sort[0], sortOp]); + }; + + const onUpdateSumField = (isPrimary: boolean, value: string) => { + let clone = [...sort]; + + if (isPrimary) { + clone[0].field = value; + } else { + clone[1].field = value; + } + + onUpdate(clone); + }; + + return ( +
+
+ onUpdateSumField(true, value)} + error={showErrors ? !sort[0].field : false} + /> + ) : null} +
+ + {hasPrimary ? ( +
+ onUpdateSumField(false, value)} + error={showErrors ? !sort[1].field : false} + /> + ) : null} +
+ ) : null} +
+ ); +}; diff --git a/lattice/src/App/QueryBuilder/GroupBySort/index.ts b/lattice/src/App/QueryBuilder/GroupBySort/index.ts new file mode 100644 index 000000000..499ca54a8 --- /dev/null +++ b/lattice/src/App/QueryBuilder/GroupBySort/index.ts @@ -0,0 +1 @@ +export * from './GroupBySort'; diff --git a/lattice/src/App/QueryBuilder/QueryBuilder.tsx b/lattice/src/App/QueryBuilder/QueryBuilder.tsx index 1f309787a..9560d1e85 100644 --- a/lattice/src/App/QueryBuilder/QueryBuilder.tsx +++ b/lattice/src/App/QueryBuilder/QueryBuilder.tsx @@ -30,6 +30,7 @@ import { ResultType } from 'App/Query/QueryContainer'; import { RowCall } from './RowCall'; import { SavedQueries } from './SavedQueries'; import { Select } from 'shared/Select'; +import { GroupBySort, SortOption } from './GroupBySort'; import { stringifyRowData } from './stringifyRowData'; import css from './QueryBuilder.module.scss'; @@ -47,7 +48,12 @@ type QueryBuilderProps = { columns: string[], operator?: Operator ) => void; - onRunGroupBy: (table: any, rowsData: RowsCallType, filter?: string) => void; + onRunGroupBy: ( + table: any, + rowsData: RowsCallType, + sort: SortOption[], + filter?: string + ) => void; onExternalLookup: (table: string, columns: number[]) => void; onClear: () => void; }; @@ -95,6 +101,7 @@ export const QueryBuilder: FC = ({ ); const [groupByFilters, setGroupByFilters] = useState([]); const [filter, setFilter] = useState(); + const [sort, setSort] = useState([]); const colSizes = JSON.parse( localStorage.getItem('builderColSizes') || '[25, 75]' ); @@ -231,10 +238,25 @@ export const QueryBuilder: FC = ({ const onRunClick = () => { if (operation === 'GroupBy') { - const isInvalid = groupByCall.primary ? false : true; + let isInvalid = false; + if (!groupByCall.primary) { + isInvalid = true; + } else if ( + sort.length > 0 && + sort[0].sortValue.includes('sum') && + !sort[0].field + ) { + isInvalid = true; + } else if ( + sort.length > 1 && + sort[1].sortValue.includes('sum') && + !sort[1].field + ) { + isInvalid = true; + } setHasInvalid(isInvalid); if (!isInvalid) { - onRunGroupBy(selectedTable, groupByCall, filter); + onRunGroupBy(selectedTable, groupByCall, sort, filter); } } else { const { cleanRowCalls, isInvalid } = cleanupRows(); @@ -328,7 +350,7 @@ export const QueryBuilder: FC = ({ if (!isInvalid) { if (operation === 'GroupBy') { - onRunGroupBy(tableDetails, groupByCall, filter); + onRunGroupBy(tableDetails, groupByCall, sort, filter); } else { onQuery(tableDetails, operation, rowCalls, columns, operator); } @@ -659,6 +681,22 @@ export const QueryBuilder: FC = ({ for {selectedTable.name} with at least one field constraint. )} + +
+ + Sort (optional) + + field.options.type === 'int') + .map((field) => { + return { label: field.name, value: field.name }; + })} + showErrors={hasInvalid} + /> +
) : (
diff --git a/lattice/src/App/QueryBuilder/QueryBuilderContainer.tsx b/lattice/src/App/QueryBuilder/QueryBuilderContainer.tsx index 0456943ec..40e42fb8c 100644 --- a/lattice/src/App/QueryBuilder/QueryBuilderContainer.tsx +++ b/lattice/src/App/QueryBuilder/QueryBuilderContainer.tsx @@ -13,6 +13,7 @@ import { ResultType } from 'App/Query/QueryContainer'; import { queryPQL } from 'services/grpcServices'; import { grpc } from '@improbable-eng/grpc-web'; import { RowResponse } from 'proto/pilosa_pb'; +import { SortOption } from './GroupBySort'; import { stringifyRowData } from './stringifyRowData'; import css from './QueryBuilderContainer.module.scss'; @@ -160,6 +161,7 @@ export const QueryBuilderContainer = () => { const onRunGroupBy = ( table: any, rowsData: RowsCallType, + sort: SortOption[], filter?: string ) => { streamingResults = { @@ -174,11 +176,26 @@ export const QueryBuilderContainer = () => { }; 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 (sort.length > 1) { + sortString = `${sortString}, ${sort[1].sortValue}`; + if (sort[1].sortValue.includes('sum')) { + aggregateString = `, aggregate=Sum(field=${sort[1].field})`; + } + } + sortString = `, sort="${sortString}"`; + } const filterString = filter && filter.length > 0 ? `, filter=${filter}` : ''; const query = rowsData.secondary - ? `GroupBy(Rows(${rowsData.primary}), Rows(${rowsData.secondary})${filterString})` - : `GroupBy(Rows(${rowsData.primary})${filterString})`; + ? `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); };