feat: aggreagate drilldown refactor to use for tables and other panels alike

This commit is contained in:
Aditya Singh 2025-07-15 19:29:26 +05:30
parent 4a98c54e78
commit a9ac3b7e15
6 changed files with 94 additions and 123 deletions

View File

@ -8,11 +8,8 @@ import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { getAggregateColumnHeader } from './drilldownUtils';
import { AGGREGATE_OPTIONS, SUPPORTED_OPERATORS } from './menuOptions';
import {
getBreakoutQuery,
getFiltersToAdd,
getQueryData,
} from './tableDrilldownUtils';
import { getBreakoutQuery, getQueryData } from './tableDrilldownUtils';
import { AggregateData } from './useAggregateDrilldown';
export type ContextMenuItem = ReactNode;
@ -45,7 +42,7 @@ export interface BreakoutOptionsProps {
onColumnClick: (groupBy: BaseAutocompleteData) => void;
}
function getGroupContextMenuConfig({
export function getGroupContextMenuConfig({
query,
clickedData,
panelType,
@ -88,26 +85,32 @@ function getGroupContextMenuConfig({
return {};
}
function getAggregateContextMenuConfig({
export function getAggregateContextMenuConfig({
subMenu,
query,
clickedData,
onColumnClick,
}: Omit<ContextMenuConfigParams, 'configType'>): AggregateContextMenuConfig {
console.log('getAggregateContextMenuConfig', { clickedData, query });
aggregateData,
}: {
subMenu?: string;
query: Query;
onColumnClick: (key: string, query?: Query) => void;
aggregateData: AggregateData | null;
}): AggregateContextMenuConfig {
console.log('getAggregateContextMenuConfig', { query, aggregateData });
if (subMenu === 'breakout') {
const queryData = getQueryData(query, clickedData);
const queryData = getQueryData(query, aggregateData);
return {
header: 'Breakout by',
items: (
<BreakoutOptions
queryData={queryData}
onColumnClick={(groupBy: BaseAutocompleteData): void => {
const filtersToAdd = getFiltersToAdd(query, clickedData);
// Use aggregateData.filters
const filtersToAdd = aggregateData?.filters || [];
const breakoutQuery = getBreakoutQuery(
query,
clickedData,
aggregateData,
groupBy,
filtersToAdd,
);
@ -118,11 +121,16 @@ function getAggregateContextMenuConfig({
};
}
// Use aggregateData.queryName
const queryName = aggregateData?.queryName;
const { dataSource, aggregations } = getAggregateColumnHeader(
query,
clickedData?.column?.dataIndex as string,
queryName as string,
);
console.log('dataSource', dataSource);
console.log('aggregations', aggregations);
return {
header: (
<div>
@ -141,36 +149,3 @@ function getAggregateContextMenuConfig({
)),
};
}
export function getContextMenuConfig({
subMenu,
configType,
query,
clickedData,
panelType,
onColumnClick,
}: ContextMenuConfigParams): {
header?: string | ReactNode;
items?: ContextMenuItem;
} {
if (configType === ConfigType.GROUP) {
return getGroupContextMenuConfig({
query,
clickedData,
panelType,
onColumnClick,
});
}
if (configType === ConfigType.AGGREGATE) {
return getAggregateContextMenuConfig({
subMenu,
query,
clickedData,
panelType,
onColumnClick,
});
}
return {};
}

View File

@ -122,7 +122,8 @@ export const getAggregateColumnHeader = (
return { dataSource: '', aggregations: '' };
}
const { dataSource, aggregations } = queryStep;
console.log('queryStep', queryStep);
const { dataSource, aggregations } = queryStep; // TODO: check if this is correct
// Extract aggregation expressions based on data source type
let aggregationExpressions: string[] = [];

View File

@ -13,43 +13,25 @@ import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
import { getBaseMeta } from './drilldownUtils';
import { AggregateData } from './useAggregateDrilldown';
/**
* Gets the query data that matches the clicked column's dataIndex
* Gets the query data that matches the aggregate data's queryName
*/
export const getQueryData = (query: Query, clickedData: any): IBuilderQuery => {
if (!clickedData) {
console.warn('clickedData is null in getQueryData');
export const getQueryData = (
query: Query,
aggregateData: AggregateData | null,
): IBuilderQuery => {
if (!aggregateData) {
console.warn('aggregateData is null in getQueryData');
return initialQueryBuilderFormValuesMap.logs;
}
const queryData = query?.builder?.queryData?.filter(
(item: IBuilderQuery) => item.queryName === clickedData.column.dataIndex,
(item: IBuilderQuery) => item.queryName === aggregateData.queryName,
);
return queryData[0];
};
/**
* Creates filters to add to the query from columns which are part of the query.builder.queryData[].groupBy
*/
export const getFiltersToAdd = (
query: Query,
clickedData: any,
): FilterData[] => {
if (!clickedData) {
console.warn('clickedData is null in getFiltersToAdd');
return [];
}
const queryData = getQueryData(query, clickedData);
const { groupBy } = queryData;
return groupBy.map((item: BaseAutocompleteData) => ({
filterKey: item.key,
filterValue: clickedData.record[item.key],
operator: OPERATORS['='],
}));
};
export const isEmptyFilterValue = (value: any): boolean =>
value === '' || value === null || value === undefined || value === 'n/a';
@ -138,29 +120,29 @@ export const getViewQuery = (
*/
export const getBreakoutQuery = (
query: Query,
clickedData: any,
aggregateData: AggregateData | null,
groupBy: BaseAutocompleteData,
filtersToAdd: FilterData[],
): Query => {
if (!clickedData) {
console.warn('clickedData is null in getBreakoutQuery');
if (!aggregateData) {
console.warn('aggregateData is null in getBreakoutQuery');
return query;
}
console.log('>> groupBy', groupBy);
console.log('>> clickedData', clickedData);
console.log('>> aggregateData', aggregateData);
console.log('>> query', query);
const queryWithFilters = addFilterToSelectedQuery(
query,
filtersToAdd,
clickedData.column.dataIndex,
aggregateData.queryName,
);
const newQuery = cloneDeep(queryWithFilters);
newQuery.builder.queryData = newQuery.builder.queryData.map(
(item: IBuilderQuery) => {
if (item.queryName === clickedData.column.dataIndex) {
if (item.queryName === aggregateData.queryName) {
return {
...item,
groupBy: [groupBy],

View File

@ -1,22 +1,24 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import {
getFiltersToAddToView,
getViewQuery,
} from 'container/QueryTable/tableDrilldownUtils';
import { FilterData } from 'container/QueryTable/drilldownUtils';
import { getViewQuery } from 'container/QueryTable/tableDrilldownUtils';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import createQueryParams from 'lib/createQueryParams';
import { ClickedData } from 'periscope/components/ContextMenu/types';
import { useCallback, useMemo } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import {
ConfigType,
ContextMenuItem,
getContextMenuConfig,
getAggregateContextMenuConfig,
} from './contextConfig';
// Type for aggregate data
export interface AggregateData {
queryName: string;
filters: FilterData[];
}
const getRoute = (key: string): string => {
switch (key) {
case 'view_logs':
@ -33,17 +35,17 @@ const getRoute = (key: string): string => {
const useAggregateDrilldown = ({
query,
widgetId,
clickedData,
onClose,
subMenu,
setSubMenu,
aggregateData,
}: {
query: Query;
widgetId: string;
clickedData: ClickedData | null;
onClose: () => void;
subMenu: string;
setSubMenu: (subMenu: string) => void;
aggregateData: AggregateData | null;
}): {
aggregateDrilldownConfig: {
header?: string | React.ReactNode;
@ -56,7 +58,7 @@ const useAggregateDrilldown = ({
(query: Query): void => {
redirectWithQueryBuilderData(
query,
{ [QueryParams.expandedWidgetId]: widgetId },
{ [QueryParams.expandedWidgetId]: widgetId }, // add only if view mode
undefined,
true,
);
@ -67,7 +69,7 @@ const useAggregateDrilldown = ({
const handleAggregateDrilldown = useCallback(
(key: string, drilldownQuery?: Query): void => {
console.log('Aggregate drilldown:', { clickedData, widgetId, query, key });
console.log('Aggregate drilldown:', { widgetId, query, key, aggregateData });
if (key === 'breakout') {
if (!drilldownQuery) {
@ -80,7 +82,7 @@ const useAggregateDrilldown = ({
}
const route = getRoute(key);
const filtersToAdd = clickedData ? getFiltersToAddToView(clickedData) : [];
const filtersToAdd = aggregateData?.filters || [];
const viewQuery = getViewQuery(query, filtersToAdd, key);
let queryParams = {
@ -105,30 +107,28 @@ const useAggregateDrilldown = ({
onClose();
},
[
clickedData,
query,
widgetId,
safeNavigate,
onClose,
redirectToViewMode,
setSubMenu,
aggregateData,
],
);
const aggregateDrilldownConfig = useMemo(() => {
if (!clickedData) {
console.warn('clickedData is null in aggregateDrilldownConfig');
if (!aggregateData) {
console.warn('aggregateData is null in aggregateDrilldownConfig');
return {};
}
return getContextMenuConfig({
return getAggregateContextMenuConfig({
subMenu,
configType: ConfigType.AGGREGATE,
query,
clickedData,
panelType: 'table',
onColumnClick: handleAggregateDrilldown,
aggregateData,
});
}, [handleAggregateDrilldown, clickedData, query, subMenu]);
}, [handleAggregateDrilldown, query, subMenu, aggregateData]);
return { aggregateDrilldownConfig };
};

View File

@ -2,9 +2,11 @@ import { QueryParams } from 'constants/query';
import { addFilterToQuery } from 'container/QueryTable/drilldownUtils';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ClickedData } from 'periscope/components/ContextMenu/types';
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { getGroupContextMenuConfig } from './contextConfig';
const useFilterDrilldown = ({
query,
widgetId,
@ -15,7 +17,12 @@ const useFilterDrilldown = ({
widgetId: string;
clickedData: ClickedData | null;
onClose: () => void;
}): { handleFilterDrilldown: (operator: string) => void } => {
}): {
filterDrilldownConfig: {
header?: string | React.ReactNode;
items?: React.ReactNode;
};
} => {
const { redirectWithQueryBuilderData } = useQueryBuilder();
const redirectToViewMode = useCallback(
@ -47,8 +54,21 @@ const useFilterDrilldown = ({
[onClose, clickedData, query, redirectToViewMode],
);
const filterDrilldownConfig = useMemo(() => {
if (!clickedData) {
console.warn('clickedData is null in filterDrilldownConfig');
return {};
}
return getGroupContextMenuConfig({
query,
clickedData,
panelType: 'table',
onColumnClick: handleFilterDrilldown,
});
}, [handleFilterDrilldown, clickedData, query]);
return {
handleFilterDrilldown,
filterDrilldownConfig,
};
};

View File

@ -1,7 +1,5 @@
import {
ConfigType,
getContextMenuConfig,
} from 'container/QueryTable/contextConfig';
import { ConfigType } from 'container/QueryTable/contextConfig';
import { getFiltersToAddToView } from 'container/QueryTable/tableDrilldownUtils';
import useAggregateDrilldown from 'container/QueryTable/useAggregateDrilldown';
import useFilterDrilldown from 'container/QueryTable/useFilterDrilldown';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
@ -34,34 +32,29 @@ export function useTableContextMenu({
};
} {
const drilldownQuery = useGetCompositeQueryParam() || query;
const { handleFilterDrilldown } = useFilterDrilldown({
const { filterDrilldownConfig } = useFilterDrilldown({
query: drilldownQuery,
widgetId,
clickedData,
onClose,
});
const filterDrilldownConfig = useMemo(() => {
if (!clickedData) {
console.warn('clickedData is null in filterDrilldownConfig');
return {};
}
return getContextMenuConfig({
configType: ConfigType.GROUP,
query,
clickedData,
panelType: 'table',
onColumnClick: handleFilterDrilldown,
});
}, [handleFilterDrilldown, clickedData, query]);
const aggregateData = useMemo(() => {
if (!clickedData?.column?.isValueColumn) return null;
return {
queryName: String(clickedData.column.dataIndex || ''),
filters: getFiltersToAddToView(clickedData) || [],
};
}, [clickedData]);
const { aggregateDrilldownConfig } = useAggregateDrilldown({
query: drilldownQuery,
widgetId,
clickedData,
onClose,
subMenu,
setSubMenu,
aggregateData,
});
const menuItemsConfig = useMemo(() => {