signoz/frontend/src/lib/query/createTableColumnsFromQuery.ts

509 lines
12 KiB
TypeScript
Raw Normal View History

import { ColumnsType } from 'antd/es/table';
import { ColumnType } from 'antd/lib/table';
import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
} from 'constants/queryBuilder';
import { FORMULA_REGEXP } from 'constants/regExp';
2023-07-28 19:14:07 +03:00
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
Logs explorer design update (#4352) * feat: logs explorer - new design * feat: update styles * feat: added new toolbar for logs explorer (#4336) * feat: logs list view changes (#4348) * feat: logs list view changes * fix: list view and toolbar styles * feat: side btns * feat: added auto refresh handler * feat: handle popover close for btn click date time * feat: extract the common log actions btn component * feat: update the button for log line actions * fix: event propagation from context button * feat: use styles from ui-library * Query builder design update (#4359) * feat: QB design update * fix: add functionality and light mode styles * fix: ts issues * fix: update all css color variables to correct names * fix: lint errors * feat: new table view for logs explorer list section (#4353) * feat: table view changes for logs list * feat: code refactor to support log line actions * feat: code refactor to support log line actions * fix: the positioning of the btns * feat: fix the table onclick * fix: header issue * fix: on hover * fix: type issue * fix: eslint error * fix: type errors (#4360) * feat: handle light theme for logs explorer design changes (#4363) * feat: handle light theme for list tables and dateTime selection * feat: handle light theme for popover * fix: address review comments * feat: date time custom time modal to render inside the new popover (#4366) * feat: single calender for range picker * fix: edgecases * feat: integrate date time selector across app * fix: remove dangling border after element removal * feat: handle qb design changes across the application * feat: handle light theme * feat: handle light theme * fix: virtuoso scroll refresh issue * feat: handle new typing changes for date time picker v2 (#4386) Co-authored-by: Yunus M <myounis.ar@live.com> * chore: styles improvement across new design (#4389) * fix: improve date time styles * feat: table view changes according to new design * fix: button visibility in clickhouse and promQL headers (#4390) * feat: change the tabs to new design buttons for query builder * Settings theme change (#4368) * feat: settings theme change * [Refactor]: New design for Log details page (#4362) New design for Log details page Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com> Co-authored-by: Yunus M <myounis.ar@live.com> * feat: save view for new design (#4392) * feat: save view for new design * refactor: done with save view * feat: update styles for logs detail view (#4407) * feat: update styles for logs detail view * feat: update styles for logs detail view * feat: add raw view attributes in the logs list view (#4422) * feat: add raw view attributes in the logs list view * feat: add raw view attributes in the logs list view * fix: raw attributes * fix: logs UI improvements (#4426) * fix: remove fixed times from the date time picker v2 * fix: added old logs explorer CTA in new designs * feat: handle active logs indicator update * fix: address review comments * fix: old logs explorer page * fix: remove info text and add relative time buttons * fix: update logs explorer tab designs * fix: update logs explorer tab designs * fix: update logs explorer tab designs * refactor: New design for Save views. (#4435) * feat: [GH-4436]: date range enhancements (#4448) * feat: [GH-4436]: when selecting custom time range it should be from start of day to end of date * fix: custom time width and refresh text visibility issues (#4428) --------- Co-authored-by: Yunus M <myounis.ar@live.com> * feat: update ui (#4449) * feat: added loading and error states for logs design (#4452) * feat: added loading and error states for logs design * feat: added error states for table view and time series view * feat: handle error and loading states * feat: loading states * [Refactor]: Tab Switch deplay issue and UI improvement for Clickhouse (#4409) * fix: switching between logs display tabs (#4457) * [Feat]: View in Traces (#4450) * refactor: datetime selector beside run query removed add to dashboard * refactor: added tab for traces view details page * refactor: done with the save view in traces * fix: the gittery effect when navigatigating from views * refactor: view tab view title light mode support * refactor: removed console * fix: gittery effect when switch view from views tabs * refactor: separate traces routes * refactor: remove query params * chore: fix tsc issues * fix: jest config issues * fix: update TODO and remove extra braces * feat: handle loading states and incorporate ui feedback (#4479) * UI feedback updates (#4482) * feat: handle loading and fix ui issues * feat: ui updates * fix: logs explorer issues (#4483) * fix: logs explorer issues * fix: jest test cases * feat: support custom times unique to pages new design changes (#4485) * fix: loading states for list log view (#4486) * fix: logs search view query fix, logs details view - attribute tags alignment fix (#4489) * fix: delete empty file * fix: chart loading when scrolling logs (#4495) * fix: chart should not load when scrolling the logs as it is already fetched * fix: make the search bar as default rather than advanced options * fix: rename show context to show in context * fix: query range api not triggering on default select first load (#4498) * Refactor: Log Explorer UI changes. (#4502) * refactor: used selected view enum * refactor: updated hight of switch old button and tab border * refactor: import fixes * refactor: query builder border and button groups * refactor: removed hypen from refreshed * refactor: show delete button only when there is more than one query * refactor: sqaure up the query build button groups * refactor: updated css * fix: additional filter color button shadow * refactor: removed commented code and used selected panel enum * refactor: updated typecheck script * refactor: used enum selected view (#4504) * fix: retain the current query on date time change (#4510) * feat: added new icon for promQL and added tooltips for dashboards and alerts (#4512) * feat: added new icon for promQL and added tooltips for dashboards and alerts * fix: styles at 1440 px zoom * fix: rename clickhouse to clickHouse --------- Co-authored-by: Vikrant Gupta <54737045+Vikrant2520@users.noreply.github.com> Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com> Co-authored-by: Rajat Dabade <rajat@signoz.io>
2024-02-12 00:23:19 +05:30
import { isObject } from 'lodash-es';
import { ReactNode } from 'react';
import {
IBuilderFormula,
IBuilderQuery,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { ListItem, QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery';
import { QueryBuilderData } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
type CreateTableDataFromQueryParams = Pick<
QueryTableProps,
'queryTableData' | 'query' | 'renderActionCell' | 'renderColumnCell'
>;
export type RowData = {
timestamp: number;
key: string;
[key: string]: string | number;
};
export type DynamicColumn = {
query: IBuilderQuery | IBuilderFormula;
field: string;
dataIndex: string;
title: string;
data: (string | number)[];
type: 'field' | 'operator' | 'formula';
};
type DynamicColumns = DynamicColumn[];
type CreateTableDataFromQuery = (
params: CreateTableDataFromQueryParams,
) => {
columns: ColumnsType<RowData>;
dataSource: RowData[];
rowsLength: number;
};
type FillColumnData = (
queryTableData: QueryDataV3[],
dynamicColumns: DynamicColumns,
) => { filledDynamicColumns: DynamicColumns; rowsLength: number };
type GetDynamicColumns = (
queryTableData: QueryDataV3[],
query: Query,
) => DynamicColumns;
type ListItemData = ListItem['data'];
type ListItemKey = keyof ListItemData;
const isFormula = (queryName: string): boolean =>
FORMULA_REGEXP.test(queryName);
const isValueExist = (
field: keyof DynamicColumn,
value: string,
columns: DynamicColumns,
): boolean => {
const existColumns = columns.find((item) => item[field] === value);
return !!existColumns;
};
const getQueryByName = <T extends keyof QueryBuilderData>(
builder: QueryBuilderData,
currentQueryName: string,
type: T,
): T extends 'queryData' ? IBuilderQuery : IBuilderFormula => {
const queryArray = builder[type];
const defaultValue =
type === 'queryData'
? initialQueryBuilderFormValues
: initialFormulaBuilderFormValues;
const currentQuery =
queryArray.find((q) => q.queryName === currentQueryName) || defaultValue;
return currentQuery as T extends 'queryData' ? IBuilderQuery : IBuilderFormula;
};
const addLabels = (
query: IBuilderQuery | IBuilderFormula,
label: string,
dynamicColumns: DynamicColumns,
): void => {
if (isValueExist('dataIndex', label, dynamicColumns)) return;
const fieldObj: DynamicColumn = {
query,
field: label as string,
dataIndex: label,
title: label,
data: [],
type: 'field',
};
dynamicColumns.push(fieldObj);
};
const addOperatorFormulaColumns = (
query: IBuilderFormula | IBuilderQuery,
dynamicColumns: DynamicColumns,
customLabel?: string,
): void => {
if (isFormula(query.queryName)) {
const formulaQuery = query as IBuilderFormula;
let formulaLabel = `${formulaQuery.queryName}(${formulaQuery.expression})`;
if (formulaQuery.legend) {
formulaLabel = formulaQuery.legend;
}
const formulaColumn: DynamicColumn = {
query,
field: formulaQuery.queryName,
dataIndex: formulaQuery.queryName,
title: customLabel || formulaLabel,
data: [],
type: 'formula',
};
dynamicColumns.push(formulaColumn);
return;
}
const currentQueryData = query as IBuilderQuery;
let operatorLabel = `${currentQueryData.aggregateOperator}`;
if (currentQueryData.aggregateAttribute.key) {
operatorLabel += `(${currentQueryData.aggregateAttribute.key})`;
}
if (currentQueryData.legend) {
operatorLabel = currentQueryData.legend;
}
const operatorColumn: DynamicColumn = {
query,
field: currentQueryData.queryName,
dataIndex: currentQueryData.queryName,
title: customLabel || operatorLabel,
data: [],
type: 'operator',
};
dynamicColumns.push(operatorColumn);
};
const transformColumnTitles = (
dynamicColumns: DynamicColumns,
): DynamicColumns =>
dynamicColumns.map((item) => {
if (isFormula(item.field as string)) {
return item;
}
const sameValues = dynamicColumns.filter(
(column) => column.title === item.title,
);
if (sameValues.length > 1) {
return {
...item,
dataIndex: `${item.title} - ${item.query.queryName}`,
title: `${item.title} - ${item.query.queryName}`,
};
}
return item;
});
const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => {
const dynamicColumns: DynamicColumns = [];
queryTableData.forEach((currentQuery) => {
const { series, queryName, list } = currentQuery;
const currentStagedQuery = getQueryByName(
query.builder,
queryName,
isFormula(queryName) ? 'queryFormulas' : 'queryData',
);
if (list) {
list.forEach((listItem) => {
Object.keys(listItem.data).forEach((label) => {
addLabels(currentStagedQuery, label, dynamicColumns);
});
});
}
if (series) {
const isValuesColumnExist = series.some((item) => item.values.length > 0);
const isEveryValuesExist = series.every((item) => item.values.length > 0);
if (isValuesColumnExist) {
addOperatorFormulaColumns(
currentStagedQuery,
dynamicColumns,
isEveryValuesExist ? undefined : currentStagedQuery.queryName,
);
}
series.forEach((seria) => {
Object.keys(seria.labels).forEach((label) => {
if (label === currentQuery?.queryName) return;
addLabels(currentStagedQuery, label, dynamicColumns);
});
});
}
});
return transformColumnTitles(dynamicColumns);
};
const fillEmptyRowCells = (
unusedColumnsKeys: Set<keyof RowData>,
sourceColumns: DynamicColumns,
currentColumn: DynamicColumn,
): void => {
unusedColumnsKeys.forEach((key) => {
if (key === currentColumn.field) {
const unusedCol = sourceColumns.find((item) => item.field === key);
if (unusedCol) {
unusedCol.data.push('N/A');
unusedColumnsKeys.delete(key);
}
}
});
};
const findSeriaValueFromAnotherQuery = (
currentLabels: Record<string, string>,
nextQuery: QueryDataV3 | null,
): SeriesItem | null => {
if (!nextQuery || !nextQuery.series) return null;
let value = null;
const labelEntries = Object.entries(currentLabels);
nextQuery.series.forEach((seria) => {
const localLabelEntries = Object.entries(seria.labels);
if (localLabelEntries.length !== labelEntries.length) return;
const isExistLabels = localLabelEntries.find(([key, value]) =>
labelEntries.find(
([currentKey, currentValue]) =>
currentKey === key && currentValue === value,
),
);
if (isExistLabels) {
value = seria;
}
});
return value;
};
const isEqualQueriesByLabel = (
equalQueries: string[],
queryName: string,
): boolean => equalQueries.includes(queryName);
const fillAggregationData = (
column: DynamicColumn,
value: string,
unusedColumnsKeys: Set<keyof RowData>,
): void => {
column.data.push(parseFloat(value).toFixed(2));
unusedColumnsKeys.delete(column.field);
};
const fillRestAggregationData = (
column: DynamicColumn,
queryTableData: QueryDataV3[],
seria: SeriesItem,
equalQueriesByLabels: string[],
): void => {
const nextQueryData =
queryTableData.find((q) => q.queryName === column.field) || null;
const targetSeria = findSeriaValueFromAnotherQuery(
seria.labels,
nextQueryData,
);
if (targetSeria) {
const isEqual = isEqualQueriesByLabel(equalQueriesByLabels, column.field);
if (!isEqual) {
equalQueriesByLabels.push(column.field);
}
column.data.push(parseFloat(targetSeria.values[0].value).toFixed(2));
} else {
column.data.push('N/A');
}
};
const fillLabelsData = (
column: DynamicColumn,
seria: SeriesItem,
unusedColumnsKeys: Set<keyof RowData>,
): void => {
const labelEntries = Object.entries(seria.labels);
labelEntries.forEach(([key, currentValue]) => {
if (column.field === key) {
column.data.push(currentValue);
unusedColumnsKeys.delete(key);
}
});
};
const fillDataFromSeries = (
currentQuery: QueryDataV3,
queryTableData: QueryDataV3[],
columns: DynamicColumns,
equalQueriesByLabels: string[],
): void => {
const { series, queryName } = currentQuery;
const isEqualQuery = isEqualQueriesByLabel(equalQueriesByLabels, queryName);
if (!series) return;
series.forEach((seria) => {
const unusedColumnsKeys = new Set<keyof RowData>(
columns.map((item) => item.field),
);
columns.forEach((column) => {
if (queryName === column.field) {
if (seria.values.length === 0) return;
fillAggregationData(
column,
parseFloat(seria.values[0].value).toFixed(2),
unusedColumnsKeys,
);
return;
}
if (column.type !== 'field' && column.field !== queryName) {
fillRestAggregationData(
column,
queryTableData,
seria,
equalQueriesByLabels,
);
return;
}
if (isEqualQuery) return;
fillLabelsData(column, seria, unusedColumnsKeys);
fillEmptyRowCells(unusedColumnsKeys, columns, column);
});
});
};
const fillDataFromList = (
listItem: ListItem,
columns: DynamicColumns,
): void => {
columns.forEach((column) => {
if (isFormula(column.field)) return;
Object.keys(listItem.data).forEach((label) => {
if (column.dataIndex === label) {
if (listItem.data[label as ListItemKey] !== '') {
Logs explorer design update (#4352) * feat: logs explorer - new design * feat: update styles * feat: added new toolbar for logs explorer (#4336) * feat: logs list view changes (#4348) * feat: logs list view changes * fix: list view and toolbar styles * feat: side btns * feat: added auto refresh handler * feat: handle popover close for btn click date time * feat: extract the common log actions btn component * feat: update the button for log line actions * fix: event propagation from context button * feat: use styles from ui-library * Query builder design update (#4359) * feat: QB design update * fix: add functionality and light mode styles * fix: ts issues * fix: update all css color variables to correct names * fix: lint errors * feat: new table view for logs explorer list section (#4353) * feat: table view changes for logs list * feat: code refactor to support log line actions * feat: code refactor to support log line actions * fix: the positioning of the btns * feat: fix the table onclick * fix: header issue * fix: on hover * fix: type issue * fix: eslint error * fix: type errors (#4360) * feat: handle light theme for logs explorer design changes (#4363) * feat: handle light theme for list tables and dateTime selection * feat: handle light theme for popover * fix: address review comments * feat: date time custom time modal to render inside the new popover (#4366) * feat: single calender for range picker * fix: edgecases * feat: integrate date time selector across app * fix: remove dangling border after element removal * feat: handle qb design changes across the application * feat: handle light theme * feat: handle light theme * fix: virtuoso scroll refresh issue * feat: handle new typing changes for date time picker v2 (#4386) Co-authored-by: Yunus M <myounis.ar@live.com> * chore: styles improvement across new design (#4389) * fix: improve date time styles * feat: table view changes according to new design * fix: button visibility in clickhouse and promQL headers (#4390) * feat: change the tabs to new design buttons for query builder * Settings theme change (#4368) * feat: settings theme change * [Refactor]: New design for Log details page (#4362) New design for Log details page Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com> Co-authored-by: Yunus M <myounis.ar@live.com> * feat: save view for new design (#4392) * feat: save view for new design * refactor: done with save view * feat: update styles for logs detail view (#4407) * feat: update styles for logs detail view * feat: update styles for logs detail view * feat: add raw view attributes in the logs list view (#4422) * feat: add raw view attributes in the logs list view * feat: add raw view attributes in the logs list view * fix: raw attributes * fix: logs UI improvements (#4426) * fix: remove fixed times from the date time picker v2 * fix: added old logs explorer CTA in new designs * feat: handle active logs indicator update * fix: address review comments * fix: old logs explorer page * fix: remove info text and add relative time buttons * fix: update logs explorer tab designs * fix: update logs explorer tab designs * fix: update logs explorer tab designs * refactor: New design for Save views. (#4435) * feat: [GH-4436]: date range enhancements (#4448) * feat: [GH-4436]: when selecting custom time range it should be from start of day to end of date * fix: custom time width and refresh text visibility issues (#4428) --------- Co-authored-by: Yunus M <myounis.ar@live.com> * feat: update ui (#4449) * feat: added loading and error states for logs design (#4452) * feat: added loading and error states for logs design * feat: added error states for table view and time series view * feat: handle error and loading states * feat: loading states * [Refactor]: Tab Switch deplay issue and UI improvement for Clickhouse (#4409) * fix: switching between logs display tabs (#4457) * [Feat]: View in Traces (#4450) * refactor: datetime selector beside run query removed add to dashboard * refactor: added tab for traces view details page * refactor: done with the save view in traces * fix: the gittery effect when navigatigating from views * refactor: view tab view title light mode support * refactor: removed console * fix: gittery effect when switch view from views tabs * refactor: separate traces routes * refactor: remove query params * chore: fix tsc issues * fix: jest config issues * fix: update TODO and remove extra braces * feat: handle loading states and incorporate ui feedback (#4479) * UI feedback updates (#4482) * feat: handle loading and fix ui issues * feat: ui updates * fix: logs explorer issues (#4483) * fix: logs explorer issues * fix: jest test cases * feat: support custom times unique to pages new design changes (#4485) * fix: loading states for list log view (#4486) * fix: logs search view query fix, logs details view - attribute tags alignment fix (#4489) * fix: delete empty file * fix: chart loading when scrolling logs (#4495) * fix: chart should not load when scrolling the logs as it is already fetched * fix: make the search bar as default rather than advanced options * fix: rename show context to show in context * fix: query range api not triggering on default select first load (#4498) * Refactor: Log Explorer UI changes. (#4502) * refactor: used selected view enum * refactor: updated hight of switch old button and tab border * refactor: import fixes * refactor: query builder border and button groups * refactor: removed hypen from refreshed * refactor: show delete button only when there is more than one query * refactor: sqaure up the query build button groups * refactor: updated css * fix: additional filter color button shadow * refactor: removed commented code and used selected panel enum * refactor: updated typecheck script * refactor: used enum selected view (#4504) * fix: retain the current query on date time change (#4510) * feat: added new icon for promQL and added tooltips for dashboards and alerts (#4512) * feat: added new icon for promQL and added tooltips for dashboards and alerts * fix: styles at 1440 px zoom * fix: rename clickhouse to clickHouse --------- Co-authored-by: Vikrant Gupta <54737045+Vikrant2520@users.noreply.github.com> Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com> Co-authored-by: Rajat Dabade <rajat@signoz.io>
2024-02-12 00:23:19 +05:30
if (isObject(listItem.data[label as ListItemKey])) {
column.data.push(JSON.stringify(listItem.data[label as ListItemKey]));
} else {
column.data.push(listItem.data[label as ListItemKey].toString());
}
} else {
column.data.push('N/A');
}
}
});
});
};
const fillColumnsData: FillColumnData = (queryTableData, cols) => {
const fields = cols.filter((item) => item.type === 'field');
const operators = cols.filter((item) => item.type === 'operator');
const formulas = cols.filter((item) => item.type === 'formula');
const resultColumns = [...fields, ...operators, ...formulas];
const equalQueriesByLabels: string[] = [];
queryTableData.forEach((currentQuery) => {
const { list } = currentQuery;
fillDataFromSeries(
currentQuery,
queryTableData,
resultColumns,
equalQueriesByLabels,
);
if (list) {
list.forEach((listItem) => {
fillDataFromList(listItem, resultColumns);
});
}
});
const rowsLength = resultColumns.length > 0 ? resultColumns[0].data.length : 0;
return { filledDynamicColumns: resultColumns, rowsLength };
};
const generateData = (
dynamicColumns: DynamicColumns,
rowsLength: number,
): RowData[] => {
const data: RowData[] = [];
for (let i = 0; i < rowsLength; i += 1) {
const rowData: RowData = dynamicColumns.reduce((acc, item) => {
const { dataIndex } = item;
acc[dataIndex] = item.data[i];
acc.key = uuid();
return acc;
}, {} as RowData);
data.push(rowData);
}
return data;
};
const generateTableColumns = (
dynamicColumns: DynamicColumns,
renderColumnCell?: QueryTableProps['renderColumnCell'],
): ColumnsType<RowData> => {
const columns: ColumnsType<RowData> = dynamicColumns.reduce<
ColumnsType<RowData>
>((acc, item) => {
const column: ColumnType<RowData> = {
dataIndex: item.dataIndex,
title: item.title,
2023-07-28 19:14:07 +03:00
width: QUERY_TABLE_CONFIG.width,
render: renderColumnCell && renderColumnCell[item.dataIndex],
};
return [...acc, column];
}, []);
return columns;
};
export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({
query,
queryTableData,
renderActionCell,
renderColumnCell,
}) => {
const sortedQueryTableData = queryTableData.sort((a, b) =>
a.queryName < b.queryName ? -1 : 1,
);
const dynamicColumns = getDynamicColumns(sortedQueryTableData, query);
const { filledDynamicColumns, rowsLength } = fillColumnsData(
sortedQueryTableData,
dynamicColumns,
);
const dataSource = generateData(filledDynamicColumns, rowsLength);
const columns = generateTableColumns(filledDynamicColumns, renderColumnCell);
const actionsCell: ColumnType<RowData> | null = renderActionCell
? {
key: 'actions',
title: 'Actions',
render: (_, record): ReactNode => renderActionCell(record),
}
: null;
if (actionsCell && dataSource.length > 0) {
columns.push(actionsCell);
}
return { columns, dataSource, rowsLength };
};