import { ColumnsType } from 'antd/es/table'; import { ColumnType } from 'antd/lib/table'; import { FORMULA_REGEXP } from 'constants/regExp'; import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; import { toCapitalize } from 'lib/toCapitalize'; import { ReactNode } from 'react'; import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData'; import { ListItem, QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery'; import { v4 as uuid } from 'uuid'; type CreateTableDataFromQueryParams = Pick< QueryTableProps, 'queryTableData' | 'query' | 'renderActionCell' >; export type RowData = { timestamp: number; key: string; [key: string]: string | number; }; type DynamicColumn = { key: keyof RowData; data: (string | number)[]; type: 'field' | 'operator'; sortable: boolean; }; type DynamicColumns = DynamicColumn[]; type CreateTableDataFromQuery = ( params: CreateTableDataFromQueryParams, ) => { columns: ColumnsType; 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; type SeriesItemLabels = SeriesItem['labels']; const isFormula = (queryName: string): boolean => FORMULA_REGEXP.test(queryName); const isColumnExist = ( columnName: string, columns: DynamicColumns, ): boolean => { const columnKeys = columns.map((item) => item.key); return columnKeys.includes(columnName); }; const prepareColumnTitle = (title: string): string => { const haveUnderscore = title.includes('_'); if (haveUnderscore) { return title .split('_') .map((str) => toCapitalize(str)) .join(' '); } return toCapitalize(title); }; const getQueryOperator = ( queryData: IBuilderQuery[], currentQueryName: string, ): string => { const builderQuery = queryData.find((q) => q.queryName === currentQueryName); return builderQuery ? builderQuery.aggregateOperator : ''; }; const createLabels = ( labels: T, label: keyof T, dynamicColumns: DynamicColumns, ): void => { if (isColumnExist(label as string, dynamicColumns)) return; const labelValue = labels[label]; const isNumber = !Number.isNaN(parseFloat(String(labelValue))); const fieldObj: DynamicColumn = { key: label as string, data: [], type: 'field', sortable: isNumber, }; dynamicColumns.push(fieldObj); }; const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { const dynamicColumns: DynamicColumns = []; queryTableData.forEach((currentQuery) => { if (currentQuery.list) { currentQuery.list.forEach((listItem) => { Object.keys(listItem.data).forEach((label) => { createLabels( listItem.data, label as ListItemKey, dynamicColumns, ); }); }); } if (currentQuery.series) { if (!isColumnExist('timestamp', dynamicColumns)) { dynamicColumns.push({ key: 'timestamp', data: [], type: 'field', sortable: true, }); } currentQuery.series.forEach((seria) => { Object.keys(seria.labels).forEach((label) => { createLabels(seria.labels, label, dynamicColumns); }); }); const operator = getQueryOperator( query.builder.queryData, currentQuery.queryName, ); if (operator === '' || isColumnExist(operator, dynamicColumns)) return; const operatorColumn: DynamicColumn = { key: operator, data: [], type: 'operator', sortable: true, }; dynamicColumns.push(operatorColumn); } }); return dynamicColumns; }; const fillEmptyRowCells = ( unusedColumnsKeys: Set, sourceColumns: DynamicColumns, currentColumn: DynamicColumn, ): void => { unusedColumnsKeys.forEach((key) => { if (key === currentColumn.key) { const unusedCol = sourceColumns.find((item) => item.key === key); if (unusedCol) { unusedCol.data.push('N/A'); unusedColumnsKeys.delete(key); } } }); }; const fillDataFromSeria = ( seria: SeriesItem, columns: DynamicColumns, currentQueryName: string, ): void => { const labelEntries = Object.entries(seria.labels); seria.values.forEach((value) => { const unusedColumnsKeys = new Set( columns.map((item) => item.key), ); columns.forEach((column) => { if (column.key === 'timestamp') { column.data.push(value.timestamp); unusedColumnsKeys.delete('timestamp'); return; } if (currentQueryName === column.key) { column.data.push(parseFloat(value.value).toFixed(2)); unusedColumnsKeys.delete(column.key); return; } labelEntries.forEach(([key, currentValue]) => { if (column.key === key) { column.data.push(currentValue); unusedColumnsKeys.delete(key); } }); fillEmptyRowCells(unusedColumnsKeys, columns, column); }); }); }; const fillDataFromList = ( listItem: ListItem, columns: DynamicColumns, ): void => { columns.forEach((column) => { if (isFormula(column.key as string)) return; Object.keys(listItem.data).forEach((label) => { if (column.key === label) { if (listItem.data[label as ListItemKey]) { column.data.push(listItem.data[label as ListItemKey] as string | number); } 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 resultColumns = [...fields, ...operators]; queryTableData.forEach((currentQuery) => { // const currentOperator = getQueryOperator( // query.builder.queryData, // currentQuery.queryName, // ); if (currentQuery.series) { currentQuery.series.forEach((seria) => { fillDataFromSeria(seria, resultColumns, currentQuery.queryName); }); } if (currentQuery.list) { currentQuery.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 { key } = item; acc[key] = item.data[i]; acc.key = uuid(); return acc; }, {} as RowData); data.push(rowData); } return data; }; const generateTableColumns = ( dynamicColumns: DynamicColumns, ): ColumnsType => { const columns: ColumnsType = dynamicColumns.reduce< ColumnsType >((acc, item) => { const column: ColumnType = { dataIndex: item.key, key: item.key, title: prepareColumnTitle(item.key as string), sorter: item.sortable ? (a: RowData, b: RowData): number => (a[item.key] as number) - (b[item.key] as number) : false, }; return [...acc, column]; }, []); return columns; }; export const createTableColumnsFromQuery: CreateTableDataFromQuery = ({ query, queryTableData, renderActionCell, }) => { const dynamicColumns = getDynamicColumns(queryTableData, query); const { filledDynamicColumns, rowsLength } = fillColumnsData( queryTableData, dynamicColumns, ); const dataSource = generateData(filledDynamicColumns, rowsLength); const columns = generateTableColumns(filledDynamicColumns); const actionsCell: ColumnType | null = renderActionCell ? { key: 'actions', title: 'Actions', render: (_, record): ReactNode => renderActionCell(record), } : null; if (actionsCell && dataSource.length > 0) { columns.push(actionsCell); } return { columns, dataSource, rowsLength }; };