From 51aebe11b0c0bdb5189cf5b4e95c8e0c1a079bea Mon Sep 17 00:00:00 2001 From: Marius Andra Date: Thu, 28 Sep 2023 22:00:55 +0200 Subject: [PATCH] feat(datatable): explicitly enable features per query (#17667) --- .../src/queries/nodes/DataTable/DataTable.tsx | 101 +++++++++++------- .../queries/nodes/DataTable/dataTableLogic.ts | 21 +++- .../queries/nodes/DataTable/queryFeatures.ts | 44 ++++++++ 3 files changed, 120 insertions(+), 46 deletions(-) create mode 100644 frontend/src/queries/nodes/DataTable/queryFeatures.ts diff --git a/frontend/src/queries/nodes/DataTable/DataTable.tsx b/frontend/src/queries/nodes/DataTable/DataTable.tsx index 5687f2b424205..435922fa671ba 100644 --- a/frontend/src/queries/nodes/DataTable/DataTable.tsx +++ b/frontend/src/queries/nodes/DataTable/DataTable.tsx @@ -28,7 +28,7 @@ import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import clsx from 'clsx' import { SessionPlayerModal } from 'scenes/session-recordings/player/modal/SessionPlayerModal' import { OpenEditorButton } from '~/queries/nodes/Node/OpenEditorButton' -import { isEventsQuery, isHogQlAggregation, isHogQLQuery, isPersonsNode, taxonomicFilterToHogQl } from '~/queries/utils' +import { isEventsQuery, isHogQlAggregation, isHogQLQuery, taxonomicFilterToHogQl } from '~/queries/utils' import { PersonPropertyFilters } from '~/queries/nodes/PersonsNode/PersonPropertyFilters' import { PersonsSearch } from '~/queries/nodes/PersonsNode/PersonsSearch' import { PersonDeleteModal } from 'scenes/persons/PersonDeleteModal' @@ -42,6 +42,7 @@ import { InsightEmptyState, InsightErrorState } from 'scenes/insights/EmptyState import { EventType } from '~/types' import { SavedQueries } from '~/queries/nodes/DataTable/SavedQueries' import { HogQLQueryEditor } from '~/queries/nodes/HogQLQuery/HogQLQueryEditor' +import { QueryFeature } from '~/queries/nodes/DataTable/queryFeatures' interface DataTableProps { uniqueKey?: string | number @@ -88,7 +89,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } } = useValues(builtDataNodeLogic) const dataTableLogicProps: DataTableLogicProps = { query, vizKey: vizKey, dataKey: dataKey, context } - const { dataTableRows, columnsInQuery, columnsInResponse, queryWithDefaults, canSort } = useValues( + const { dataTableRows, columnsInQuery, columnsInResponse, queryWithDefaults, canSort, sourceFeatures } = useValues( dataTableLogic(dataTableLogicProps) ) @@ -114,8 +115,11 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } const isReadOnly = setQuery === undefined - const actionsColumnShown = showActions && isEventsQuery(query.source) && columnsInResponse?.includes('*') - const columnsInLemonTable = isHogQLQuery(query.source) ? columnsInResponse ?? columnsInQuery : columnsInQuery + const eventActionsColumnShown = + showActions && sourceFeatures.has(QueryFeature.eventActionsColumn) && columnsInResponse?.includes('*') + const columnsInLemonTable = sourceFeatures.has(QueryFeature.columnsInResponse) + ? columnsInResponse ?? columnsInQuery + : columnsInQuery const lemonColumns: LemonTableColumn[] = [ ...columnsInLemonTable.map((key, index) => ({ @@ -126,13 +130,13 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } if (index === (expandable ? 1 : 0)) { return { children: label, - props: { colSpan: columnsInLemonTable.length + (actionsColumnShown ? 1 : 0) }, + props: { colSpan: columnsInLemonTable.length + (eventActionsColumnShown ? 1 : 0) }, } } else { return { props: { colSpan: 0 } } } } else if (result) { - if (isEventsQuery(query.source) || isHogQLQuery(query.source)) { + if (sourceFeatures.has(QueryFeature.resultIsArrayOfArrays)) { return renderColumn(key, result[index], result, query, setQuery, context) } return renderColumn(key, result[key], result, query, setQuery, context) @@ -140,7 +144,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } }, sorter: undefined, // using custom sorting code more: - !isReadOnly && showActions && isEventsQuery(query.source) ? ( + !isReadOnly && showActions && sourceFeatures.has(QueryFeature.eventActionsColumn) ? ( <>
{extractExpressionComment(key)}
@@ -157,21 +161,24 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } fullWidth onChange={(v, g) => { const hogQl = taxonomicFilterToHogQl(g, v) - if (hogQl && isEventsQuery(query.source)) { + if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { + // Typecasting to a query type with select and order_by fields. + // The actual query may or may not be an events query. + const source = query.source as EventsQuery const isAggregation = isHogQlAggregation(hogQl) - const isOrderBy = query.source?.orderBy?.[0] === key - const isDescOrderBy = query.source?.orderBy?.[0] === `${key} DESC` - setQuery?.({ + const isOrderBy = source.orderBy?.[0] === key + const isDescOrderBy = source.orderBy?.[0] === `${key} DESC` + setQuery({ ...query, source: { - ...query.source, - select: query.source.select + ...source, + select: source.select .map((s, i) => (i === index ? hogQl : s)) .filter((c) => (isAggregation ? c !== '*' : true)), orderBy: isOrderBy || isDescOrderBy ? [isDescOrderBy ? `${hogQl} DESC` : hogQl] - : query.source?.orderBy, + : source.orderBy, }, }) } @@ -183,7 +190,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } <> { setQuery?.({ @@ -199,7 +206,11 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } { setQuery?.({ @@ -225,16 +236,17 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } fullWidth onChange={(v, g) => { const hogQl = taxonomicFilterToHogQl(g, v) - if (hogQl && isEventsQuery(query.source)) { + if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { const isAggregation = isHogQlAggregation(hogQl) - setQuery?.({ + const source = query.source as EventsQuery + setQuery({ ...query, source: { - ...query.source, + ...source, select: [ - ...(query.source.select || []).slice(0, index), + ...(source.select || []).slice(0, index), hogQl, - ...(query.source.select || []).slice(index), + ...(source.select || []).slice(index), ].filter((c) => (isAggregation ? c !== '*' : true)), } as EventsQuery, }) @@ -251,16 +263,17 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } fullWidth onChange={(v, g) => { const hogQl = taxonomicFilterToHogQl(g, v) - if (hogQl && isEventsQuery(query.source)) { + if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { const isAggregation = isHogQlAggregation(hogQl) + const source = query.source as EventsQuery setQuery?.({ ...query, source: { - ...query.source, + ...source, select: [ - ...(query.source.select || []).slice(0, index + 1), + ...(source.select || []).slice(0, index + 1), hogQl, - ...(query.source.select || []).slice(index + 1), + ...(source.select || []).slice(index + 1), ].filter((c) => (isAggregation ? c !== '*' : true)), } as EventsQuery, }) @@ -302,7 +315,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } ) : undefined, })), - ...(actionsColumnShown + ...(eventActionsColumnShown ? [ { dataIndex: '__more' as any, @@ -311,7 +324,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } if (label) { return { props: { colSpan: 0 } } } - if (result && isEventsQuery(query.source) && columnsInResponse?.includes('*')) { + if (result && columnsInResponse?.includes('*')) { return } return null @@ -328,25 +341,27 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } ) const firstRowLeft = [ - showDateRange && (isEventsQuery(query.source) || isHogQLQuery(query.source)) ? ( + showDateRange && sourceFeatures.has(QueryFeature.dateRangePicker) ? ( ) : null, - showEventFilter && isEventsQuery(query.source) ? ( - + showEventFilter && sourceFeatures.has(QueryFeature.eventNameFilter) ? ( + ) : null, - showSearch && isPersonsNode(query.source) ? ( - + showSearch && sourceFeatures.has(QueryFeature.personsSearch) ? ( + ) : null, - showPropertyFilter && (isEventsQuery(query.source) || isHogQLQuery(query.source)) ? ( - + showPropertyFilter && sourceFeatures.has(QueryFeature.eventPropertyFilters) ? ( + ) : null, - showPropertyFilter && isPersonsNode(query.source) ? ( - + showPropertyFilter && sourceFeatures.has(QueryFeature.personPropertyFilters) ? ( + ) : null, ].filter((x) => !!x) const firstRowRight = [ - showSavedQueries && isEventsQuery(query.source) ? : null, + showSavedQueries && sourceFeatures.has(QueryFeature.savedEventsQueries) ? ( + + ) : null, ].filter((x) => !!x) const secondRowLeft = [ @@ -356,7 +371,8 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } ].filter((x) => !!x) const secondRowRight = [ - (showColumnConfigurator || showPersistentColumnConfigurator) && isEventsQuery(query.source) ? ( + (showColumnConfigurator || showPersistentColumnConfigurator) && + sourceFeatures.has(QueryFeature.columnConfigurator) ? ( ) : null, showExport ? : null, @@ -413,7 +429,10 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } dataSource={(dataTableRows ?? []) as DataTableRow[]} rowKey={({ result }: DataTableRow, rowIndex) => { if (result) { - if (isEventsQuery(query.source)) { + if ( + sourceFeatures.has(QueryFeature.resultIsArrayOfArrays) && + sourceFeatures.has(QueryFeature.columnsInResponse) + ) { if (columnsInResponse?.includes('*')) { return result[columnsInResponse.indexOf('*')].uuid } else if (columnsInResponse?.includes('uuid')) { @@ -434,7 +453,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } useURLForSorting={false} emptyState={ responseError ? ( - isHogQLQuery(query.source) || isEventsQuery(query.source) ? ( + sourceFeatures.has(QueryFeature.displayResponseError) ? ( ([ })), selectors({ sourceKind: [(_, p) => [p.query], (query): NodeKind | null => query.source?.kind], + sourceFeatures: [(_, p) => [p.query], (query): Set => getQueryFeatures(query.source)], orderBy: [ - (_, p) => [p.query], - (query): string[] | null => - isEventsQuery(query.source) ? query.source.orderBy || ['timestamp DESC'] : null, + (s, p) => [p.query, s.sourceFeatures], + (query, sourceFeatures): string[] | null => + sourceFeatures.has(QueryFeature.selectAndOrderByColumns) + ? 'orderBy' in query.source // might not be EventsQuery, but something else with orderBy + ? (query.source as EventsQuery).orderBy ?? null + : isEventsQuery(query.source) + ? ['timestamp DESC'] + : null + : null, { resultEqualityCheck: objectsEqual }, ], columnsInResponse: [ @@ -150,6 +158,8 @@ export const dataTableLogic = kea([ ...sortedKeys({ ...rest, full: query.full ?? false, + + // The settings under features.tsx override some of these expandable: query.expandable ?? true, embedded: query.embedded ?? false, propertiesViaUrl: query.propertiesViaUrl ?? false, @@ -180,8 +190,9 @@ export const dataTableLogic = kea([ }, ], canSort: [ - (s) => [s.queryWithDefaults], - (query: DataTableNode): boolean => isEventsQuery(query.source) && !!query.allowSorting, + (s) => [s.queryWithDefaults, s.sourceFeatures], + (query: DataTableNode, sourceFeatures): boolean => + sourceFeatures.has(QueryFeature.selectAndOrderByColumns) && !!query.allowSorting, ], }), propsChanged(({ actions, props }, oldProps) => { diff --git a/frontend/src/queries/nodes/DataTable/queryFeatures.ts b/frontend/src/queries/nodes/DataTable/queryFeatures.ts new file mode 100644 index 0000000000000..d296e38a23e57 --- /dev/null +++ b/frontend/src/queries/nodes/DataTable/queryFeatures.ts @@ -0,0 +1,44 @@ +import { isEventsQuery, isHogQLQuery, isPersonsNode } from '~/queries/utils' +import { Node } from '~/queries/schema' + +export enum QueryFeature { + columnsInResponse, + eventActionsColumn, + dateRangePicker, + eventNameFilter, + eventPropertyFilters, + personPropertyFilters, + personsSearch, + savedEventsQueries, + columnConfigurator, + resultIsArrayOfArrays, + selectAndOrderByColumns, + displayResponseError, +} + +export function getQueryFeatures(query: Node): Set { + const features = new Set() + + if (isHogQLQuery(query) || isEventsQuery(query)) { + features.add(QueryFeature.dateRangePicker) + features.add(QueryFeature.columnsInResponse) + features.add(QueryFeature.eventPropertyFilters) + features.add(QueryFeature.resultIsArrayOfArrays) + features.add(QueryFeature.displayResponseError) + } + + if (isEventsQuery(query)) { + features.add(QueryFeature.eventActionsColumn) + features.add(QueryFeature.eventNameFilter) + features.add(QueryFeature.savedEventsQueries) + features.add(QueryFeature.columnConfigurator) + features.add(QueryFeature.selectAndOrderByColumns) + } + + if (isPersonsNode(query)) { + features.add(QueryFeature.personPropertyFilters) + features.add(QueryFeature.personsSearch) + } + + return features +}