Skip to content

Commit

Permalink
Improve query result tab (#202)
Browse files Browse the repository at this point in the history
* feat: always show query result footer

* feat:
- show rows and columns count
- show table name as tab result name if the query is from one table

* refactor: change rows and columns count

* feat: export query result as xlsx format

* refactor: put getSingleTableName in try

* lazy loading the xlsx library

---------

Co-authored-by: Visal .In <[email protected]>
  • Loading branch information
keppere and invisal authored Dec 14, 2024
1 parent 226fb20 commit c8ebe3b
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 3 deletions.
95 changes: 95 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"sql-formatter": "^15.3.2",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"xlsx": "^0.18.5",
"zod": "^3.22.4"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions src/components/gui/export/export-result-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export default function ExportResultButton({
content = handler();
}

if (!content) return;

const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
Expand Down Expand Up @@ -63,6 +65,7 @@ export default function ExportResultButton({
<SelectItem value="csv">CSV</SelectItem>
<SelectItem value="json">JSON</SelectItem>
<SelectItem value="sql">SQL</SelectItem>
<SelectItem value="xlsx">EXCEL</SelectItem>
</SelectContent>
</Select>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/gui/sortable-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const WindowTabItemButton = forwardRef<
return (
<button
className={cn(
"relative h-[40px] border-x text-neutral-500 flex items-center text-left text-xs px-2 w-[170px] hover:dark:text-white hover:text-black",
"relative h-[40px] border-x text-neutral-500 flex items-center text-left text-xs px-2 min-w-[170px] max-w-[300px] hover:dark:text-white hover:text-black",
isDragging && "z-20",
selected
? "bg-neutral-50 dark:bg-neutral-950 text-primary"
Expand Down
38 changes: 37 additions & 1 deletion src/components/gui/tabs/query-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ export default function QueryWindow({
),
key: "query_" + queryResult.order,
identifier: "query_" + queryResult.order,
title: "Query " + (queryIdx + 1),
title:
`${getSingleTableName(queryResult.sql) ?? "Query " + (queryIdx + 1)}` +
` (${queryResult.result.rows.length}x${queryResult.result.headers.length})`,
icon: LucideGrid,
})),
...(progress
Expand Down Expand Up @@ -357,3 +359,37 @@ export default function QueryWindow({
</ResizablePanelGroup>
);
}

function getSingleTableName(query: string): string | null {
try {
// Normalize query by removing extra spaces and converting to lowercase
const normalizedQuery = query.replace(/\s+/g, " ").trim().toLowerCase();

// Match the table names after "from" keyword
const fromMatch = normalizedQuery.match(/from\s+([^\s,;]+)/i);
const joinMatches = normalizedQuery.match(/join\s+([^\s,;]+)/gi);

// If there are JOINs, more than one table is referenced
if (joinMatches && joinMatches.length > 0) {
return null;
}

// Check if a single table is present
if (fromMatch) {
const tableName = fromMatch[1];

// Ensure no additional tables are mentioned
const additionalTablesMatch = normalizedQuery.match(/,\s*[^\s,;]+/);
if (additionalTablesMatch) {
return null;
}

return tableName;
}

// No table found
return null;
} catch (e) {
return null;
}
}
29 changes: 28 additions & 1 deletion src/components/lib/export-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export function exportRowsToSqlInsert(
function cellToExcelValue(value: unknown) {
if (value === undefined) return "";
if (value === null) return "NULL";
return value.toString();
const parsed = Number(value);
return isNaN(parsed) ? value : parsed;
}

export function exportRowsToExcel(records: unknown[][]) {
Expand All @@ -49,6 +50,31 @@ export function exportRowsToExcel(records: unknown[][]) {
return result.join("\r\n");
}

export function exportToExcel(
records: unknown[][],
headers: string[],
tablename: string
) {
const processedData = records.map((row) =>
row.map((cell) => {
return cellToExcelValue(cell);
})
);

const data = [headers, ...processedData];
console.log(data);

import("xlsx").then((module) => {
const XLSX = module;
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(workbook, worksheet, "sheet1");
XLSX.writeFile(workbook, `${tablename}.xlsx`);
});

return "";
}

export function exportRowsToJson(
headers: string[],
records: unknown[][]
Expand Down Expand Up @@ -102,5 +128,6 @@ export function getFormatHandlers(
csv: () => exportRowsToCsv(headers, records),
json: () => exportRowsToJson(headers, records),
sql: () => exportRowsToSqlInsert(tableName, headers, records),
xlsx: () => exportToExcel(records, headers, tableName),
};
}

0 comments on commit c8ebe3b

Please sign in to comment.