import {
getPropertyId,
formatPropertyPath,
executeReport,
type RunReportOptions,
} from "../../client.js";
import { resolveDate } from "../../utils/date.js";
import type { RunReportInput, RunReportOutput, ReportRow } from "../../types.js";
/**
* GA4 レポートを実行
*/
export async function runReport(input: RunReportInput): Promise<RunReportOutput> {
const propertyId = getPropertyId(input.propertyId);
const property = formatPropertyPath(propertyId);
// 日付を解決
const startDate = resolveDate(input.startDate);
const endDate = resolveDate(input.endDate);
// リクエストオプションを構築
const options: RunReportOptions = {
property,
dateRanges: [{ startDate, endDate }],
dimensions: input.dimensions.map((name) => ({ name })),
metrics: input.metrics.map((name) => ({ name })),
limit: input.limit || 10,
};
// フィルターの設定
if (input.dimensionFilter) {
options.dimensionFilter = {
filter: {
fieldName: input.dimensionFilter.fieldName,
stringFilter: {
matchType: input.dimensionFilter.stringFilter.matchType,
value: input.dimensionFilter.stringFilter.value,
caseSensitive: input.dimensionFilter.stringFilter.caseSensitive,
},
},
};
}
// ソートの設定
if (input.orderBy) {
options.orderBys = [];
if (input.orderBy.metric) {
options.orderBys.push({
metric: { metricName: input.orderBy.metric },
desc: input.orderBy.desc ?? true,
});
} else if (input.orderBy.dimension) {
options.orderBys.push({
dimension: { dimensionName: input.orderBy.dimension },
desc: input.orderBy.desc ?? false,
});
}
}
// レポート実行
const response = await executeReport(options);
// 結果を整形
const rows: ReportRow[] = [];
const dimensionHeaders = response.dimensionHeaders || [];
const metricHeaders = response.metricHeaders || [];
for (const row of response.rows || []) {
const dimensions: Record<string, string> = {};
const metrics: Record<string, number | string> = {};
// ディメンション値を取得
(row.dimensionValues || []).forEach((value, index) => {
const header = dimensionHeaders[index];
if (header?.name) {
dimensions[header.name] = value.value || "";
}
});
// メトリクス値を取得
(row.metricValues || []).forEach((value, index) => {
const header = metricHeaders[index];
if (header?.name) {
const rawValue = value.value || "0";
// 数値に変換可能なら数値に、そうでなければ文字列のまま
const numValue = parseFloat(rawValue);
metrics[header.name] = isNaN(numValue) ? rawValue : numValue;
}
});
rows.push({ dimensions, metrics });
}
// 合計値の取得
let totals: Record<string, number | string> | undefined;
if (response.totals && response.totals.length > 0) {
totals = {};
const totalRow = response.totals[0];
(totalRow.metricValues || []).forEach((value, index) => {
const header = metricHeaders[index];
if (header?.name) {
const rawValue = value.value || "0";
const numValue = parseFloat(rawValue);
totals![header.name] = isNaN(numValue) ? rawValue : numValue;
}
});
}
return {
rows,
rowCount: rows.length,
totals,
metadata: {
dataLossFromOtherRow: response.metadata?.dataLossFromOtherRow || false,
},
};
}