mirror of
https://github.com/stfrigerio/sqliteDB.git
synced 2026-07-22 05:38:02 +00:00
adapted sqlchart block to remote db
This commit is contained in:
parent
466b239153
commit
6ca1207333
5 changed files with 222 additions and 138 deletions
|
|
@ -5,7 +5,7 @@ import { writeFile } from "fs/promises";
|
|||
import { SQLiteDBSettings } from "./types";
|
||||
|
||||
export class DBService {
|
||||
private mode: "local" | "remote" = "local";
|
||||
public mode: "local" | "remote" = "local";
|
||||
private db: Database | null = null;
|
||||
private SQL: SqlJsStatic | null = null; // We still need to store the initialized library instance
|
||||
private app: App;
|
||||
|
|
|
|||
|
|
@ -1,56 +1,72 @@
|
|||
import { ChartConfig } from "./parseChartParams";
|
||||
import { ChartConfig, PieChartConfig } from "./parseChartParams";
|
||||
|
||||
function isPieConfig(config: ChartConfig): config is PieChartConfig {
|
||||
return config.chartType === "pie";
|
||||
}
|
||||
|
||||
export function buildSqlQuery(config: ChartConfig) {
|
||||
let query, params = [];
|
||||
|
||||
switch (config.chartType) {
|
||||
case 'pie':
|
||||
query = `
|
||||
let query, params = [];
|
||||
|
||||
// Check for special case: Time table and duration field
|
||||
const isTimeDuration =
|
||||
config.table.toLowerCase() === "time" &&
|
||||
config.chartType === "pie" &&
|
||||
config.valueColumn === "duration";
|
||||
|
||||
const valueExpr = isTimeDuration
|
||||
? `SUM(strftime('%s', '1970-01-01T' || ${config.valueColumn}) - strftime('%s', '1970-01-01T00:00:00'))`
|
||||
: isPieConfig(config)
|
||||
? `SUM(${config.valueColumn})`
|
||||
: "";
|
||||
|
||||
switch (config.chartType) {
|
||||
case 'pie':
|
||||
query = `
|
||||
SELECT
|
||||
${config.categoryColumn},
|
||||
SUM(${config.valueColumn}) as value
|
||||
${valueExpr} as value
|
||||
FROM "${config.table}"
|
||||
`;
|
||||
|
||||
if (config.startDate && config.endDate) {
|
||||
query += ` WHERE ${config.dateColumn} BETWEEN ? AND ?`;
|
||||
params.push(config.startDate, config.endDate);
|
||||
}
|
||||
|
||||
query += ` GROUP BY ${config.categoryColumn}`;
|
||||
query += ` ORDER BY value DESC`;
|
||||
break;
|
||||
|
||||
case 'line':
|
||||
case 'bar':
|
||||
if (config.categoryColumn) {
|
||||
query = `
|
||||
if (config.startDate && config.endDate) {
|
||||
query += ` WHERE ${config.dateColumn} BETWEEN ? AND ?`;
|
||||
params.push(config.startDate, config.endDate);
|
||||
}
|
||||
|
||||
query += ` GROUP BY ${config.categoryColumn}`;
|
||||
query += ` ORDER BY value DESC`;
|
||||
break;
|
||||
|
||||
case 'line':
|
||||
case 'bar':
|
||||
if (config.categoryColumn) {
|
||||
query = `
|
||||
SELECT
|
||||
${config.xColumn},
|
||||
${config.categoryColumn},
|
||||
${config.yColumns.join(', ')}
|
||||
FROM "${config.table}"
|
||||
`;
|
||||
} else {
|
||||
query = `
|
||||
} else {
|
||||
query = `
|
||||
SELECT
|
||||
${config.xColumn},
|
||||
${config.yColumns.join(', ')}
|
||||
FROM "${config.table}"
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.startDate && config.endDate) {
|
||||
query += ` WHERE ${config.dateColumn} BETWEEN ? AND ?`;
|
||||
params.push(config.startDate, config.endDate);
|
||||
}
|
||||
if (config.startDate && config.endDate) {
|
||||
query += ` WHERE ${config.dateColumn} BETWEEN ? AND ?`;
|
||||
params.push(config.startDate, config.endDate);
|
||||
}
|
||||
|
||||
query += ` ORDER BY ${config.xColumn}`;
|
||||
if (config.categoryColumn) {
|
||||
query += `, ${config.categoryColumn}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
query += ` ORDER BY ${config.xColumn}`;
|
||||
if (config.categoryColumn) {
|
||||
query += `, ${config.categoryColumn}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return { query, params };
|
||||
}
|
||||
return { query, params };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,31 +9,47 @@ function isPieConfig(config: ChartConfig): config is PieChartConfig {
|
|||
return config.chartType === 'pie';
|
||||
}
|
||||
|
||||
export function processChartData(rowObj: any, config: ChartConfig) {
|
||||
if (isPieConfig(config)) {
|
||||
const labels = rowObj.values.map((row: any[]) => row[0]);
|
||||
const datasets = [{
|
||||
data: rowObj.values.map((row: any[]) => row[1]),
|
||||
label: config.valueColumn
|
||||
}];
|
||||
return { labels, datasets };
|
||||
}
|
||||
function formatSeconds(totalSeconds: number): string {
|
||||
const hrs = Math.floor(totalSeconds / 3600);
|
||||
const mins = Math.floor((totalSeconds % 3600) / 60);
|
||||
const secs = Math.floor(totalSeconds % 60);
|
||||
|
||||
let labels: any[];
|
||||
let datasets: any[];
|
||||
|
||||
if (config.categoryColumn) {
|
||||
const { labels: groupLabels, datasets: groupDatasets } = processGroupedData(rowObj, config);
|
||||
labels = groupLabels;
|
||||
datasets = groupDatasets;
|
||||
} else {
|
||||
labels = rowObj.values.map((row: any[]) => row[0]);
|
||||
datasets = processUngroupedData(rowObj, config);
|
||||
}
|
||||
|
||||
return { labels, datasets };
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${pad(hrs)}:${pad(mins)}:${pad(secs)}`;
|
||||
}
|
||||
|
||||
export function processChartData(rowObj: any, config: ChartConfig) {
|
||||
if (isPieConfig(config)) {
|
||||
const isDuration = config.table === 'Time' && config.valueColumn === 'duration';
|
||||
|
||||
const labels = rowObj.values.map((row: any[]) => {
|
||||
const label = row[0];
|
||||
const value = row[1];
|
||||
return isDuration ? `${label} | ${formatSeconds(value)}` : label;
|
||||
});
|
||||
|
||||
const datasets = [{
|
||||
data: rowObj.values.map((row: any[]) => row[1]),
|
||||
label: config.valueColumn
|
||||
}];
|
||||
|
||||
return { labels, datasets };
|
||||
}
|
||||
|
||||
let labels: any[];
|
||||
let datasets: any[];
|
||||
|
||||
if (config.categoryColumn) {
|
||||
const { labels: groupLabels, datasets: groupDatasets } = processGroupedData(rowObj, config);
|
||||
labels = groupLabels;
|
||||
datasets = groupDatasets;
|
||||
} else {
|
||||
labels = rowObj.values.map((row: any[]) => row[0]);
|
||||
datasets = processUngroupedData(rowObj, config);
|
||||
}
|
||||
|
||||
return { labels, datasets };
|
||||
}
|
||||
|
||||
function processGroupedData(rowObj: any, config: TimeSeriesChartConfig) {
|
||||
const groupedData = new Map();
|
||||
|
|
|
|||
|
|
@ -1,33 +1,50 @@
|
|||
import { ChartConfig } from "./parseChartParams";
|
||||
import { DBService } from "../../../DBService";
|
||||
|
||||
export async function validateColumns(db: any, config: ChartConfig) {
|
||||
const tableInfo = db.exec(`PRAGMA table_info("${config.table}");`);
|
||||
const availableColumns = tableInfo[0].values.map((row: any[]) => row[1]);
|
||||
export async function validateColumns(dbService: DBService, config: ChartConfig) {
|
||||
const tableName = config.table;
|
||||
|
||||
let columns: string[] = [];
|
||||
|
||||
let requestedColumns = [];
|
||||
|
||||
if (config.chartType === 'pie') {
|
||||
requestedColumns = [config.categoryColumn, config.valueColumn];
|
||||
if (config.dateColumn && (config.startDate || config.endDate)) {
|
||||
requestedColumns.push(config.dateColumn);
|
||||
}
|
||||
} else {
|
||||
requestedColumns = [config.xColumn, ...config.yColumns];
|
||||
if (config.categoryColumn) {
|
||||
requestedColumns.push(config.categoryColumn);
|
||||
}
|
||||
if (config.dateColumn && (config.startDate || config.endDate)) {
|
||||
requestedColumns.push(config.dateColumn);
|
||||
}
|
||||
}
|
||||
|
||||
const invalidColumns = requestedColumns.filter(col => !availableColumns.includes(col));
|
||||
if (invalidColumns.length > 0) {
|
||||
return {
|
||||
message: `The following columns do not exist: ${invalidColumns.join(", ")}`,
|
||||
availableColumns
|
||||
};
|
||||
}
|
||||
try {
|
||||
const result = await dbService.getQuery<{ name: string }>(
|
||||
`PRAGMA table_info("${tableName}")`
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
columns = result.map((row: { name: string }) => row.name);
|
||||
} catch (error) {
|
||||
console.error(`[validateColumns] Failed to fetch columns for table "${tableName}"`, error);
|
||||
return {
|
||||
message: `Unable to fetch column info for table "${tableName}".`,
|
||||
availableColumns: []
|
||||
};
|
||||
}
|
||||
|
||||
let requestedColumns: string[] = [];
|
||||
|
||||
if (config.chartType === "pie") {
|
||||
requestedColumns = [config.categoryColumn, config.valueColumn];
|
||||
if (config.dateColumn && (config.startDate || config.endDate)) {
|
||||
requestedColumns.push(config.dateColumn);
|
||||
}
|
||||
} else {
|
||||
requestedColumns = [config.xColumn, ...config.yColumns];
|
||||
if (config.categoryColumn) {
|
||||
requestedColumns.push(config.categoryColumn);
|
||||
}
|
||||
if (config.dateColumn && (config.startDate || config.endDate)) {
|
||||
requestedColumns.push(config.dateColumn);
|
||||
}
|
||||
}
|
||||
|
||||
const invalidColumns = requestedColumns.filter(col => !columns.includes(col));
|
||||
|
||||
if (invalidColumns.length > 0) {
|
||||
return {
|
||||
message: `The following columns do not exist: ${invalidColumns.join(", ")}`,
|
||||
availableColumns: columns
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,98 @@
|
|||
import { Notice } from "obsidian";
|
||||
import { DBService } from "../../DBService";
|
||||
import { parseChartParams, validateColumns, buildSqlQuery, processChartData } from "./helpers";
|
||||
import { ChartType, createChart } from "./charts";
|
||||
import { pluginState } from "../../pluginState";
|
||||
import { pluginState, DATE_CHANGED_EVENT_NAME } from "../../pluginState";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
export async function processSqlChartBlock(dbService: DBService, source: string, el: HTMLElement) {
|
||||
const db = dbService.getDB();
|
||||
if (!db) {
|
||||
new Notice("No DB loaded. Please open the DB first.");
|
||||
el.createEl("p", { text: "Database not loaded." });
|
||||
return;
|
||||
}
|
||||
await renderChartBlock();
|
||||
|
||||
// replace @date with the selected date
|
||||
const injectedSource = source.replace(/@date/g, pluginState.selectedDate);
|
||||
const config = parseChartParams(injectedSource);
|
||||
const onDateChange = async (e: CustomEvent) => {
|
||||
el.empty(); // Clear existing chart or messages
|
||||
await renderChartBlock();
|
||||
};
|
||||
|
||||
if (!config) {
|
||||
// @ts-ignore
|
||||
const message = config?.chartType === 'pie'
|
||||
? "Required parameters for pie chart are: table, categoryColumn, valueColumn"
|
||||
: "Required parameters are: table, xColumn, yColumns";
|
||||
el.createEl("p", { text: message });
|
||||
return;
|
||||
}
|
||||
document.addEventListener(DATE_CHANGED_EVENT_NAME, onDateChange as EventListener);
|
||||
|
||||
try {
|
||||
const validationError = await validateColumns(db, config);
|
||||
if (validationError) {
|
||||
el.createEl("p", { text: validationError.message });
|
||||
if (validationError.availableColumns) {
|
||||
el.createEl("p", { text: `Available columns are: ${validationError.availableColumns.join(", ")}` });
|
||||
async function renderChartBlock() {
|
||||
// replace @date with the selected date
|
||||
const injectedSource = source.replace(/@date/g, pluginState.selectedDate);
|
||||
const config = parseChartParams(injectedSource);
|
||||
|
||||
if (!config) {
|
||||
// @ts-ignore
|
||||
const message = config?.chartType === 'pie'
|
||||
? "Required parameters for pie chart are: table, categoryColumn, valueColumn"
|
||||
: "Required parameters are: table, xColumn, yColumns";
|
||||
el.createEl("p", { text: message });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const validationError = await validateColumns(dbService, config);
|
||||
if (validationError) {
|
||||
el.createEl("p", { text: validationError.message });
|
||||
if (validationError.availableColumns) {
|
||||
el.createEl("p", { text: `Available columns are: ${validationError.availableColumns.join(", ")}` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
const { query, params } = buildSqlQuery(config);
|
||||
|
||||
let rows: any[];
|
||||
let labels, datasets;
|
||||
|
||||
if (dbService.mode === "remote") {
|
||||
// Use remote query
|
||||
rows = await dbService.getQuery(query, params);
|
||||
if (!rows || rows.length === 0) {
|
||||
el.createEl("p", { text: "No data found for the specified parameters." });
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = Object.keys(rows[0]);
|
||||
const values = rows.map(obj => columns.map(col => obj[col]));
|
||||
const execStyleResult = { columns, values };
|
||||
|
||||
({ labels, datasets } = processChartData(execStyleResult, config));
|
||||
} else {
|
||||
// Local mode: use exec
|
||||
const db = dbService.getDB();
|
||||
if (!db) {
|
||||
new Notice("No DB loaded. Please open the DB first.");
|
||||
el.createEl("p", { text: "Database not loaded." });
|
||||
return;
|
||||
}
|
||||
const result = db.exec(query, params);
|
||||
if (!result || result.length === 0 || result[0].values.length === 0) {
|
||||
el.createEl("p", { text: "No data found for the specified parameters." });
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = result[0].columns;
|
||||
const values = result[0].values;
|
||||
rows = values.map(rowArr =>
|
||||
Object.fromEntries(rowArr.map((val, idx) => [columns[idx], val]))
|
||||
);
|
||||
|
||||
({ labels, datasets } = processChartData(rows, config));
|
||||
}
|
||||
|
||||
const chartData = createChart(
|
||||
config.chartType as ChartType,
|
||||
labels,
|
||||
datasets,
|
||||
config.chartOptions
|
||||
);
|
||||
|
||||
const wrapper = el.createDiv({ cls: 'obsidian-sql-chart' });
|
||||
// @ts-ignore
|
||||
window.renderChart(chartData, wrapper);
|
||||
} catch (error) {
|
||||
el.createEl("p", { text: "An error occurred while processing the chart." });
|
||||
el.createEl("p", { text: String(error) });
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const { query, params } = buildSqlQuery(config);
|
||||
const result = db.exec(query, params);
|
||||
|
||||
if (!result || result.length === 0 || result[0].values.length === 0) {
|
||||
el.createEl("p", { text: "No data found for the specified parameters." });
|
||||
return;
|
||||
}
|
||||
|
||||
const { labels, datasets } = processChartData(result[0], config);
|
||||
|
||||
const chartData = createChart(
|
||||
config.chartType as ChartType,
|
||||
labels,
|
||||
datasets,
|
||||
config.chartOptions
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
window.renderChart(chartData, el);
|
||||
|
||||
} catch (error) {
|
||||
el.createEl("p", { text: "An error occurred while processing the chart." });
|
||||
el.createEl("p", { text: String(error) });
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue