ai-query对接新版freesun-agent接口的分支
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

797 lines
29 KiB

'use strict';
const { previewDataSourceQuery } = require('./dataSourceQuery');
const PREVIEW_GROUPED_X_ALIAS = '__preview_x_group_array__';
const PREVIEW_GROUPED_Y_ALIAS = '__preview_y_group_array__';
const MAX_STORED_PREVIEW_POINTS = 100000;
const normalizeOptionalString = value => {
const normalized = String(value || '').trim();
return normalized || null;
};
const ensureObject = value => {
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
};
const deepCloneJson = value => {
if (value === null || value === undefined) return value;
return JSON.parse(JSON.stringify(value));
};
const normalizeFilterSignature = filter => {
const field = normalizeOptionalString(filter?.field);
if (!field) return null;
const operator = normalizeOptionalString(filter?.operator) || '=';
const value = Object.prototype.hasOwnProperty.call(filter || {}, 'value')
? deepCloneJson(filter.value)
: null;
return JSON.stringify({ field, operator, value });
};
const mergeFilterLists = (...filterLists) => {
const result = [];
const seen = new Set();
filterLists.forEach(filters => {
(Array.isArray(filters) ? filters : []).forEach(filter => {
const signature = normalizeFilterSignature(filter);
if (!signature || seen.has(signature)) return;
seen.add(signature);
result.push(deepCloneJson(filter));
});
});
return result;
};
const collectCommonMetricFilters = metricList => {
const safeMetrics = Array.isArray(metricList) ? metricList : [];
if (!safeMetrics.length) return [];
const firstFilters = Array.isArray(safeMetrics[0]?.filters) ? safeMetrics[0].filters : [];
return firstFilters.filter(filter => {
const signature = normalizeFilterSignature(filter);
if (!signature) return false;
return safeMetrics.every(metric => {
const currentFilters = Array.isArray(metric?.filters) ? metric.filters : [];
return currentFilters.some(item => normalizeFilterSignature(item) === signature);
});
}).map(filter => deepCloneJson(filter));
};
const normalizeTimeRangeSignature = value => {
const timeRange = ensureObject(value);
const field = normalizeOptionalString(timeRange?.field);
const start = normalizeOptionalString(timeRange?.start);
const end = normalizeOptionalString(timeRange?.end);
if (!field || !start || !end) return null;
return JSON.stringify({ field, start, end });
};
const collectCommonMetricTimeRange = metricList => {
const safeMetrics = Array.isArray(metricList) ? metricList : [];
if (!safeMetrics.length) return null;
const firstRange = ensureObject(safeMetrics[0]?.timeRange);
const firstSignature = normalizeTimeRangeSignature(firstRange);
if (!firstSignature) return null;
const isCommon = safeMetrics.every(metric => (
normalizeTimeRangeSignature(metric?.timeRange) === firstSignature
));
return isCommon ? deepCloneJson(firstRange) : null;
};
const uniqueStringList = values => {
const result = [];
const seen = new Set();
(Array.isArray(values) ? values : []).forEach(value => {
const normalized = normalizeOptionalString(value);
if (!normalized || seen.has(normalized)) return;
seen.add(normalized);
result.push(normalized);
});
return result;
};
const normalizeChartPoints = (points = []) => {
return (Array.isArray(points) ? points : [])
.map((item, index) => {
const yValue = Number(item?.y);
if (!Number.isFinite(yValue)) return null;
const xValue = item?.x == null ? `${index + 1}` : String(item.x);
return { x: xValue, y: yValue };
})
.filter(Boolean)
.slice(0, MAX_STORED_PREVIEW_POINTS);
};
const normalizeChartSeries = (seriesList = []) => {
return (Array.isArray(seriesList) ? seriesList : [])
.map((series, index) => {
const points = normalizeChartPoints(series?.points || []);
if (!points.length) return null;
return {
name: normalizeOptionalString(series?.name) || `系列${index + 1}`,
points,
};
})
.filter(Boolean);
};
const normalizeChartContentSnapshot = snapshot => {
const safeSnapshot = ensureObject(snapshot) || {};
const seriesList = normalizeChartSeries(
safeSnapshot.seriesList
|| safeSnapshot.previewSeries
|| []
);
const points = normalizeChartPoints(
safeSnapshot.points
|| safeSnapshot.previewPoints
|| seriesList[0]?.points
|| []
);
if (!seriesList.length) return null;
return {
kind: 'chart',
seriesList,
points: points.length ? points : (seriesList[0]?.points || []),
};
};
const buildGroupedChartSeries = (rows = [], xAlias, yAlias, groupByField) => {
const seriesList = (Array.isArray(rows) ? rows : [])
.map((row, rowIndex) => {
const xArray = Array.isArray(row?.[xAlias]) ? row[xAlias] : [];
const yArray = Array.isArray(row?.[yAlias]) ? row[yAlias] : [];
const maxLength = Math.min(xArray.length, yArray.length);
if (!maxLength) return null;
const points = Array.from({ length: maxLength })
.map((_, index) => {
const xRaw = xArray[index];
const yValue = Number(yArray[index]);
if (!Number.isFinite(yValue)) return null;
return {
x: xRaw == null ? `${index + 1}` : String(xRaw),
y: yValue,
};
})
.filter(Boolean);
if (!points.length) return null;
const rawName = row?.[groupByField];
const name = normalizeOptionalString(rawName) || `分组${rowIndex + 1}`;
return { name, points };
})
.filter(Boolean);
return normalizeChartSeries(seriesList);
};
const buildDetailChartSeries = (rows = [], xField, yField) => {
const points = (Array.isArray(rows) ? rows : [])
.map((row, rowIndex) => {
const yValue = Number(row?.[yField]);
if (!Number.isFinite(yValue)) return null;
const xRaw = row?.[xField];
return {
x: xRaw == null ? `${rowIndex + 1}` : String(xRaw),
y: yValue,
};
})
.filter(Boolean);
if (!points.length) return [];
return normalizeChartSeries([{ name: '数据', points }]);
};
const deriveTableId = config => {
return normalizeOptionalString(
config?.previewQuery?.table
|| config?.query?.table
|| config?.query?.tableId
|| config?.query?.tableName
|| config?.table
|| config?.tableId
|| config?.tableName
);
};
const deriveGroupByField = config => {
const direct = normalizeOptionalString(config?.groupByField);
if (direct) return direct;
const groupByList = uniqueStringList(config?.previewQuery?.groupBy || config?.groupBy || config?.query?.groupBy);
return groupByList[0] || null;
};
const normalizeTableColumns = (columns = []) => {
return (Array.isArray(columns) ? columns : [])
.map((item, index) => {
const field = normalizeOptionalString(item?.field || item?.sourceField);
const title = normalizeOptionalString(item?.title || item?.label);
const calcMode = normalizeOptionalString(item?.calcMode || item?.aggregation) || 'none';
if (!field && !title) return null;
return {
id: normalizeOptionalString(item?.id) || `table-col-${index + 1}`,
title: title || (field === '__index__' ? '序号' : field),
field,
calcMode,
};
})
.filter(Boolean);
};
const buildTablePreviewDslFromConfig = config => {
const previewQuery = ensureObject(config?.previewQuery);
if (previewQuery) return deepCloneJson(previewQuery);
const baseQuery = ensureObject(config?.query) || ensureObject(config) || {};
const table = deriveTableId(config);
if (!table) throw '表格块缺少预览查询表/数据集';
const tableColumns = normalizeTableColumns(
config?.tableColumns || config?.previewColumns || []
);
const selectFieldSet = new Set(
uniqueStringList(baseQuery.selectFields || config?.selectFields || config?.fields)
);
tableColumns.forEach(item => {
const field = normalizeOptionalString(item?.field);
if (field && field !== '__index__') selectFieldSet.add(field);
});
const filters = Array.isArray(baseQuery.filters)
? deepCloneJson(baseQuery.filters)
: Array.isArray(config?.filters)
? deepCloneJson(config.filters)
: [];
const timeRange = ensureObject(baseQuery.timeRange) || ensureObject(config?.timeRange);
filters.forEach(item => {
const field = normalizeOptionalString(item?.field);
if (field) selectFieldSet.add(field);
});
if (timeRange?.field) {
const timeField = normalizeOptionalString(timeRange.field);
if (timeField) selectFieldSet.add(timeField);
}
const limit = Number(
baseQuery.limit || config?.limit || 20
);
const dsl = {
table,
selectFields: Array.from(selectFieldSet).filter(Boolean),
limit: Number.isInteger(limit) && limit > 0 ? limit : 20,
};
if (!dsl.selectFields.length) throw '表格块缺少预览查询字段配置';
if (Array.isArray(baseQuery.orderBy) && baseQuery.orderBy.length) {
dsl.orderBy = deepCloneJson(baseQuery.orderBy);
}
if (filters.length) dsl.filters = filters;
if (timeRange) dsl.timeRange = deepCloneJson(timeRange);
return dsl;
};
const buildKpiPreviewDslFromConfig = config => {
const previewQuery = ensureObject(config?.previewQuery);
const baseQuery = previewQuery || ensureObject(config?.query) || ensureObject(config) || {};
const table = deriveTableId(config);
if (!table) throw 'KPI 块缺少预览查询表/数据集';
const metricList = Array.isArray(config?.kpiMetrics) ? config.kpiMetrics : [];
const selectFieldSet = new Set(
uniqueStringList(baseQuery.selectFields || config?.selectFields || config?.fields)
);
const filters = mergeFilterLists(
baseQuery.filters,
config?.filters,
collectCommonMetricFilters(metricList),
);
const timeRange = (
ensureObject(baseQuery.timeRange)
|| ensureObject(config?.timeRange)
|| collectCommonMetricTimeRange(metricList)
);
metricList.forEach(metric => {
const field = normalizeOptionalString(metric?.field);
if (field) selectFieldSet.add(field);
(Array.isArray(metric?.filters) ? metric.filters : []).forEach(item => {
const filterField = normalizeOptionalString(item?.field);
if (filterField) selectFieldSet.add(filterField);
});
const metricTimeRange = ensureObject(metric?.timeRange);
if (metricTimeRange?.field) {
const timeField = normalizeOptionalString(metricTimeRange.field);
if (timeField) selectFieldSet.add(timeField);
}
});
filters.forEach(item => {
const field = normalizeOptionalString(item?.field);
if (field) selectFieldSet.add(field);
});
if (timeRange?.field) {
const timeField = normalizeOptionalString(timeRange.field);
if (timeField) selectFieldSet.add(timeField);
}
const limit = Number(baseQuery.limit || config?.limit || 100000);
const dsl = {
table,
selectFields: Array.from(selectFieldSet).filter(Boolean),
limit: Number.isInteger(limit) && limit > 0 ? limit : 100000,
};
if (!dsl.selectFields.length) throw 'KPI 块缺少预览查询字段配置';
if (Array.isArray(baseQuery.orderBy) && baseQuery.orderBy.length) {
dsl.orderBy = deepCloneJson(baseQuery.orderBy);
}
if (filters.length) dsl.filters = filters;
if (timeRange) dsl.timeRange = deepCloneJson(timeRange);
return dsl;
};
const buildKpiMetricPreviewDslFromConfig = (config, metric = {}) => {
const previewQuery = ensureObject(config?.previewQuery);
const baseQuery = previewQuery || ensureObject(config?.query) || ensureObject(config) || {};
const table = deriveTableId(config);
if (!table) throw 'KPI 块缺少预览查询表/数据集';
const field = normalizeOptionalString(metric?.field);
if (!field) throw 'KPI 指标缺少字段配置';
const selectFieldSet = new Set(
uniqueStringList(baseQuery.selectFields || config?.selectFields || config?.fields)
);
selectFieldSet.add(field);
const filters = mergeFilterLists(baseQuery.filters, config?.filters, metric?.filters);
filters.forEach(item => {
const filterField = normalizeOptionalString(item?.field);
if (filterField) selectFieldSet.add(filterField);
});
const baseTimeRange = ensureObject(baseQuery.timeRange) || ensureObject(config?.timeRange);
const metricTimeRange = ensureObject(metric?.timeRange);
const previewTimeRange = baseTimeRange || metricTimeRange || null;
[baseTimeRange, metricTimeRange].forEach(item => {
const timeField = normalizeOptionalString(item?.field);
if (timeField) selectFieldSet.add(timeField);
});
const limit = Number(baseQuery.limit || config?.limit || 100000);
const dsl = {
table,
selectFields: Array.from(selectFieldSet).filter(Boolean),
limit: Number.isInteger(limit) && limit > 0 ? limit : 100000,
};
if (!dsl.selectFields.length) throw 'KPI 块缺少预览查询字段配置';
if (Array.isArray(baseQuery.orderBy) && baseQuery.orderBy.length) {
dsl.orderBy = deepCloneJson(baseQuery.orderBy);
}
if (filters.length) dsl.filters = filters;
if (previewTimeRange) dsl.timeRange = deepCloneJson(previewTimeRange);
return dsl;
};
const buildPreviewDslFromConfig = config => {
const previewQuery = ensureObject(config?.previewQuery);
if (previewQuery) return deepCloneJson(previewQuery);
const baseQuery = ensureObject(config?.query) || ensureObject(config) || {};
const table = deriveTableId(config);
if (!table) throw '图表块缺少预览查询表/数据集';
const xField = normalizeOptionalString(config?.xField);
const yField = normalizeOptionalString(config?.yField);
if (!yField) throw '图表块缺少 Y 轴字段配置';
const filters = Array.isArray(baseQuery.filters)
? deepCloneJson(baseQuery.filters)
: Array.isArray(config?.filters)
? deepCloneJson(config.filters)
: [];
const timeRange = ensureObject(baseQuery.timeRange) || ensureObject(config?.timeRange);
const groupByField = deriveGroupByField(config);
const limit = Number(baseQuery.limit || config?.limit || 100000);
const dsl = {
table,
selectFields: [],
limit: Number.isInteger(limit) && limit > 0 ? limit : 100000,
};
if (groupByField) {
if (!xField) throw '图表块缺少 X 轴字段配置';
dsl.selectFields = [groupByField];
dsl.groupBy = [groupByField];
dsl.aggregations = [
{ func: 'groupArray', field: xField, alias: PREVIEW_GROUPED_X_ALIAS },
{ func: 'groupArray', field: yField, alias: PREVIEW_GROUPED_Y_ALIAS },
];
dsl.sourceOrderBy = [{ field: groupByField, direction: 'ASC' }];
if (xField !== groupByField) {
dsl.sourceOrderBy.push({ field: xField, direction: 'ASC' });
}
dsl.orderBy = [{ field: groupByField, direction: 'ASC' }];
} else {
const selectFieldSet = new Set(uniqueStringList(baseQuery.selectFields || config?.selectFields || config?.fields));
if (xField) selectFieldSet.add(xField);
selectFieldSet.add(yField);
filters.forEach(item => {
const field = normalizeOptionalString(item?.field);
if (field) selectFieldSet.add(field);
});
if (timeRange?.field) {
const timeField = normalizeOptionalString(timeRange.field);
if (timeField) selectFieldSet.add(timeField);
}
dsl.selectFields = Array.from(selectFieldSet);
if (xField || yField) {
dsl.orderBy = [{ field: xField || yField, direction: 'ASC' }];
}
}
if (!dsl.selectFields.length) throw '图表块缺少预览查询字段配置';
if (filters.length) dsl.filters = filters;
if (timeRange) dsl.timeRange = deepCloneJson(timeRange);
return dsl;
};
const buildChartSeries = (rows = [], config, previewQuery) => {
const xField = normalizeOptionalString(config?.xField);
const yField = normalizeOptionalString(config?.yField);
if (!yField) throw '图表块缺少 Y 轴字段配置';
const aggregationList = Array.isArray(previewQuery?.aggregations)
? previewQuery.aggregations
: [];
const hasGroupedAggregation =
aggregationList.some(item => normalizeOptionalString(item?.alias) === PREVIEW_GROUPED_X_ALIAS)
&& aggregationList.some(item => normalizeOptionalString(item?.alias) === PREVIEW_GROUPED_Y_ALIAS);
if (hasGroupedAggregation) {
const groupByField = deriveGroupByField(config);
if (!groupByField) throw '图表块缺少分组字段配置';
return buildGroupedChartSeries(
rows,
PREVIEW_GROUPED_X_ALIAS,
PREVIEW_GROUPED_Y_ALIAS,
groupByField
);
}
return buildDetailChartSeries(rows, xField, yField);
};
const sanitizeQueryBlockConfig = rawConfig => {
const config = ensureObject(rawConfig);
if (!config) return {};
const nextConfig = deepCloneJson(config) || {};
delete nextConfig.previewSeries;
delete nextConfig.previewPoints;
delete nextConfig.previewRows;
delete nextConfig.previewColumns;
delete nextConfig.previewTotal;
return nextConfig;
};
const computeAggregateValue = (rows = [], field = '', calcMode = 'none') => {
const normalizedField = normalizeOptionalString(field);
if (!normalizedField || calcMode === 'none') return null;
const values = (Array.isArray(rows) ? rows : [])
.map(row => Number(row?.[normalizedField]))
.filter(value => Number.isFinite(value));
if (!values.length) return null;
if (calcMode === 'sum') {
return values.reduce((sum, value) => sum + value, 0);
}
if (calcMode === 'avg') {
return values.reduce((sum, value) => sum + value, 0) / values.length;
}
if (calcMode === 'max') return Math.max(...values);
if (calcMode === 'min') return Math.min(...values);
if (calcMode === 'count') return values.length;
if (calcMode === 'latest') return values[values.length - 1];
return null;
};
const buildTableSummary = (columns = [], rows = []) => {
const safeColumns = Array.isArray(columns) ? columns : [];
if (!safeColumns.length) return null;
let hasComputedCell = false;
const cells = safeColumns.map((column, index) => {
const field = normalizeOptionalString(column?.field || column?.sourceField);
const calcMode = normalizeOptionalString(column?.calcMode || column?.aggregation) || 'none';
if (index === 0 && field === '__index__') return '汇总';
if (!field || field === '__index__' || calcMode === 'none') return null;
const value = computeAggregateValue(rows, field, calcMode);
if (value === null) return null;
hasComputedCell = true;
return value;
});
if (!hasComputedCell) return null;
return {
label: '汇总',
cells,
};
};
const normalizeStoredTime = value => {
const normalized = normalizeOptionalString(value);
if (!normalized) return null;
const parsed = new Date(normalized);
if (!Number.isNaN(parsed.getTime())) return parsed.getTime();
const numericValue = Number(normalized);
if (Number.isFinite(numericValue)) return numericValue;
return null;
};
const normalizeStoredFilterValue = (operator, value) => {
if (operator === 'IN') {
if (Array.isArray(value)) {
return value.map(item => String(item)).filter(Boolean);
}
return String(value || '')
.split(',')
.map(item => item.trim())
.filter(Boolean);
}
if (Array.isArray(value)) return value;
return value;
};
const matchStoredFilter = (row, filter) => {
if (!filter?.field) return true;
const operator = normalizeOptionalString(filter?.operator) || '=';
const rowValue = row?.[filter.field];
if (operator === 'BETWEEN') {
const rawRange = Array.isArray(filter?.value)
? filter.value
: Array.isArray(filter?.dateRange)
? filter.dateRange
: [];
if (rawRange.length < 2) return true;
const rowTime = normalizeStoredTime(rowValue);
const rangeStart = normalizeStoredTime(rawRange[0]);
const rangeEnd = normalizeStoredTime(rawRange[1]);
if (rowTime !== null && rangeStart !== null && rangeEnd !== null) {
return rowTime >= rangeStart && rowTime <= rangeEnd;
}
const numericValue = Number(rowValue);
const numericStart = Number(rawRange[0]);
const numericEnd = Number(rawRange[1]);
if (
Number.isFinite(numericValue)
&& Number.isFinite(numericStart)
&& Number.isFinite(numericEnd)
) {
return numericValue >= numericStart && numericValue <= numericEnd;
}
const textValue = String(rowValue ?? '');
return textValue >= String(rawRange[0]) && textValue <= String(rawRange[1]);
}
const normalizedValue = normalizeStoredFilterValue(operator, filter?.value);
if (
normalizedValue === null
|| normalizedValue === undefined
|| (typeof normalizedValue === 'string' && !normalizedValue.trim())
|| (Array.isArray(normalizedValue) && !normalizedValue.length)
) {
return true;
}
const rowText = String(rowValue ?? '');
const compareText = String(normalizedValue ?? '');
if (operator === '=') return rowText === compareText;
if (operator === '!=') return rowText !== compareText;
if (operator === '>') return Number(rowValue) > Number(normalizedValue);
if (operator === '>=') return Number(rowValue) >= Number(normalizedValue);
if (operator === '<') return Number(rowValue) < Number(normalizedValue);
if (operator === '<=') return Number(rowValue) <= Number(normalizedValue);
if (operator === 'IN') {
const valueList = Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue];
return valueList.map(item => String(item)).includes(rowText);
}
if (operator === 'LIKE') return rowText.includes(compareText);
return true;
};
const applyStoredTimeRange = (rows = [], timeRange = null) => {
if (!timeRange?.field || !timeRange?.start || !timeRange?.end) return Array.isArray(rows) ? rows : [];
const startValue = normalizeStoredTime(timeRange.start);
const endValue = normalizeStoredTime(timeRange.end);
if (startValue === null || endValue === null) return Array.isArray(rows) ? rows : [];
return (Array.isArray(rows) ? rows : []).filter(row => {
const rowValue = normalizeStoredTime(row?.[timeRange.field]);
if (rowValue === null) return false;
return rowValue >= startValue && rowValue <= endValue;
});
};
const buildKpiMetricsSnapshot = (rows = [], kpiMetrics = []) => {
const safeMetrics = Array.isArray(kpiMetrics) ? kpiMetrics : [];
return safeMetrics
.map(metric => {
const field = normalizeOptionalString(metric?.field);
if (!field) return null;
let metricRows = Array.isArray(rows) ? rows : [];
(Array.isArray(metric?.filters) ? metric.filters : []).forEach(filter => {
metricRows = metricRows.filter(row => matchStoredFilter(row, filter));
});
metricRows = applyStoredTimeRange(metricRows, ensureObject(metric?.timeRange));
const calcMode = normalizeOptionalString(metric?.calcMode) || 'avg';
const value = computeAggregateValue(metricRows, field, calcMode);
if (value === null) return null;
return {
id: metric?.id,
title: normalizeOptionalString(metric?.title) || field || '指标',
field,
calcMode,
value,
};
})
.filter(Boolean);
};
const buildSingleKpiMetricSnapshot = (rows = [], metric = {}, baseConfig = {}) => {
const field = normalizeOptionalString(metric?.field);
if (!field) return null;
let metricRows = Array.isArray(rows) ? rows : [];
const baseFilters = mergeFilterLists(baseConfig?.previewQuery?.filters, baseConfig?.filters);
baseFilters.forEach(filter => {
metricRows = metricRows.filter(row => matchStoredFilter(row, filter));
});
(Array.isArray(metric?.filters) ? metric.filters : []).forEach(filter => {
metricRows = metricRows.filter(row => matchStoredFilter(row, filter));
});
metricRows = applyStoredTimeRange(
metricRows,
ensureObject(baseConfig?.previewQuery?.timeRange) || ensureObject(baseConfig?.timeRange)
);
metricRows = applyStoredTimeRange(metricRows, ensureObject(metric?.timeRange));
const calcMode = normalizeOptionalString(metric?.calcMode) || 'avg';
const value = computeAggregateValue(metricRows, field, calcMode);
if (value === null) return null;
return {
id: metric?.id,
title: normalizeOptionalString(metric?.title) || field || '指标',
field,
calcMode,
value,
};
};
const buildChartContentSnapshotFromSeries = seriesList => {
const normalized = normalizeChartSeries(seriesList);
if (!normalized.length) {
return {
kind: 'chart',
seriesList: [],
points: [],
};
}
return {
kind: 'chart',
seriesList: normalized,
points: normalized[0]?.points || [],
};
};
const buildChartSnapshotPayload = async (
ctx,
dataSourceId,
rawConfig,
transaction = null,
rawContentSnapshot = null
) => {
const config = ensureObject(rawConfig);
if (!config) throw '图表块配置无效';
const previewQuery = buildPreviewDslFromConfig(config);
const previewPayload = await previewDataSourceQuery(ctx, dataSourceId, previewQuery, transaction);
const previewRows = Array.isArray(previewPayload?.rows) ? previewPayload.rows : [];
const previewSeries = buildChartSeries(previewRows, config, previewQuery);
const computedSnapshot = buildChartContentSnapshotFromSeries(previewSeries);
const fallbackSnapshot =
normalizeChartContentSnapshot(rawContentSnapshot)
|| normalizeChartContentSnapshot(config);
const resolvedSnapshot = computedSnapshot?.seriesList?.length
? computedSnapshot
: (fallbackSnapshot || computedSnapshot);
return {
config: {
...sanitizeQueryBlockConfig(config),
previewQuery,
},
contentSnapshot: resolvedSnapshot,
};
};
const buildTableSnapshotPayload = async (ctx, dataSourceId, rawConfig, transaction = null) => {
const config = ensureObject(rawConfig);
if (!config) throw '表格块配置无效';
const sanitizedConfig = sanitizeQueryBlockConfig(config);
const previewQuery = buildTablePreviewDslFromConfig(sanitizedConfig);
const previewPayload = await previewDataSourceQuery(ctx, dataSourceId, previewQuery, transaction);
const tableColumns = normalizeTableColumns(
sanitizedConfig.tableColumns
|| sanitizedConfig.previewColumns
|| []
);
return {
config: {
...sanitizedConfig,
previewQuery,
},
contentSnapshot: {
kind: 'table',
columns: tableColumns.length
? tableColumns
: Array.isArray(previewPayload?.columns)
? previewPayload.columns
: [],
rows: Array.isArray(previewPayload?.rows) ? previewPayload.rows : [],
total: previewPayload?.total ?? null,
summary: buildTableSummary(
tableColumns.length
? tableColumns
: Array.isArray(previewPayload?.columns)
? previewPayload.columns
: [],
Array.isArray(previewPayload?.rows) ? previewPayload.rows : []
),
},
};
};
const buildKpiSnapshotPayload = async (ctx, dataSourceId, rawConfig, transaction = null) => {
const config = ensureObject(rawConfig);
if (!config) throw 'KPI 块配置无效';
const sanitizedConfig = sanitizeQueryBlockConfig(config);
const metricList = Array.isArray(sanitizedConfig?.kpiMetrics) ? sanitizedConfig.kpiMetrics : [];
const previewQuery = buildKpiPreviewDslFromConfig(sanitizedConfig);
const metrics = [];
for (const metric of metricList) {
const metricPreviewQuery = buildKpiMetricPreviewDslFromConfig(sanitizedConfig, metric);
const previewPayload = await previewDataSourceQuery(ctx, dataSourceId, metricPreviewQuery, transaction);
const previewRows = Array.isArray(previewPayload?.rows) ? previewPayload.rows : [];
const metricSnapshot = buildSingleKpiMetricSnapshot(previewRows, metric, {
...sanitizedConfig,
previewQuery: metricPreviewQuery,
});
if (metricSnapshot) metrics.push(metricSnapshot);
}
return {
config: {
...sanitizedConfig,
previewQuery,
},
contentSnapshot: {
kind: 'kpi',
metrics,
},
};
};
module.exports = {
sanitizeQueryBlockConfig,
normalizeChartContentSnapshot,
buildChartSnapshotPayload,
buildTableSnapshotPayload,
buildKpiSnapshotPayload,
};