Skip to content

Commit

Permalink
Merge branch 'main' of github.com:halcyon-tech/vscode-db2i
Browse files Browse the repository at this point in the history
  • Loading branch information
worksofliam committed Mar 26, 2024
2 parents 661cdeb + 6678ba2 commit 2262873
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 45 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
},
{
"id": "queryHistory",
"name": "Query History",
"name": "Statement History",
"visibility": "visible",
"when": "code-for-ibmi:connected == true"
},
Expand All @@ -240,7 +240,7 @@
"viewsWelcome": [
{
"view": "queryHistory",
"contents": "Query history will appear here."
"contents": "Statement history will appear here."
},
{
"view": "jobManager",
Expand Down Expand Up @@ -413,13 +413,13 @@
},
{
"command": "vscode-db2i.queryHistory.remove",
"title": "Remove query from history",
"title": "Remove statement from history",
"category": "Db2 for i",
"icon": "$(trash)"
},
{
"command": "vscode-db2i.queryHistory.clear",
"title": "Clear query history",
"title": "Clear statement history",
"category": "Db2 for i",
"icon": "$(trash)"
},
Expand Down
25 changes: 24 additions & 1 deletion src/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import vscode from 'vscode';
const QUERIES_KEY = `queries`;
const SERVERCOMPONENT_KEY = `serverVersion`

export type QueryList = string[];
export interface QueryHistoryItem {
query: string;
unix: number;
}

export type QueryList = QueryHistoryItem[];

abstract class Storage {
protected readonly globalState;
Expand Down Expand Up @@ -54,6 +59,24 @@ export class ConnectionStorage extends Storage {
return this.set(SERVERCOMPONENT_KEY, name);
}

/**
* Eventually we will want to remove this function, but for now we need to fix the past queries
*/
fixPastQueries() {
const currentList = this.getPastQueries() as (string|QueryHistoryItem)[];
const hasOldFormat = currentList.some(item => typeof item === `string`);
if (hasOldFormat) {
const newList = currentList.map(item => {
if (typeof item === `string`) {
return { query: item, unix: Math.floor(Date.now() / 1000) - 86400 };
} else {
return item;
}
});
return this.setPastQueries(newList);
}
}

getPastQueries() {
return this.get<QueryList>(QUERIES_KEY) || [];
}
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export async function onConnectOrServerInstall(): Promise<boolean> {
Config.setConnectionName(instance.getConnection().currentConnectionName);
determineFeatures();

await Config.fixPastQueries();

await ServerComponent.initialise().then(installed => {
if (installed) {
JobManagerView.setVisible(true);
Expand Down
7 changes: 7 additions & 0 deletions src/views/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export function getHeader(options: {withCollapsed?: boolean} = {}): string {
top: 0; /* Don't forget this, required for the stickiness */
}
tfoot tr {
background-color: var(--vscode-multiDiffEditor-headerBackground);
text-align: left;
position: sticky; /* Lock the footer row to the bottom so it's always visible as rows are scrolled */
bottom: 0; /* Don't forget this, required for the stickiness */
}
#resultset th,
#resultset td {
padding: 5px 15px;
Expand Down
83 changes: 76 additions & 7 deletions src/views/queryHistoryView.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import vscode, { MarkdownString, ThemeIcon, TreeItem, window, workspace } from "vscode";
import { TreeDataProvider } from "vscode";
import { Config } from "../config";
import { QueryHistoryItem } from "../Storage";

const openSqlDocumentCommand = `vscode-db2i.openSqlDocument`;

Expand All @@ -22,7 +23,7 @@ export class queryHistory implements TreeDataProvider<any> {
vscode.commands.registerCommand(`vscode-db2i.queryHistory.prepend`, async (newQuery?: string) => {
if (newQuery && Config.ready) {
let currentList = Config.getPastQueries();
const existingQuery = currentList.findIndex(query => query.trim() === newQuery.trim());
const existingQuery = currentList.findIndex(queryItem => queryItem.query.trim() === newQuery.trim());

// If it exists, remove it
if (existingQuery > 0) {
Expand All @@ -31,7 +32,10 @@ export class queryHistory implements TreeDataProvider<any> {

// If it's at the top, don't add it, it's already at the top
if (existingQuery !== 0) {
currentList.splice(0, 0, newQuery);
currentList.splice(0, 0, {
query: newQuery,
unix: Math.floor(Date.now() / 1000)
});
}

await Config.setPastQueries(currentList);
Expand All @@ -40,11 +44,13 @@ export class queryHistory implements TreeDataProvider<any> {
}
}),

vscode.commands.registerCommand(`vscode-db2i.queryHistory.remove`, async (node: PastQuery) => {
vscode.commands.registerCommand(`vscode-db2i.queryHistory.remove`, async (node: PastQueryNode) => {
if (node && Config.ready) {
let currentList = Config.getPastQueries();
const chosenQuery = node.query;
const existingQuery = currentList.findIndex(query => query.trim() === chosenQuery.trim());
const existingQuery = currentList.findIndex(queryItem =>
queryItem.query.trim() === chosenQuery.trim()
);

// If it exists, remove it
if (existingQuery >= 0) {
Expand Down Expand Up @@ -72,17 +78,80 @@ export class queryHistory implements TreeDataProvider<any> {
return element;
}

async getChildren(): Promise<vscode.TreeItem[]> {
async getChildren(timePeriod?: TimePeriodNode): Promise<vscode.TreeItem[]> {
if (Config.ready) {
return Config.getPastQueries().map(query => new PastQuery(query));
if (timePeriod) {
return timePeriod.getChildren();

} else {
const currentList = Config.getPastQueries();

const day = 60 * 60 * 24;
const week = day * 7;
const month = day * 30;

const now = Math.floor(Date.now() / 1000);
const dayAgo = now - day;
const weekAgo = now - week;
const monthAgo = now - month;

let pastDayQueries: PastQueryNode[] = [];
let pastWeekQueries: PastQueryNode[] = [];
let pastMonthQueries: PastQueryNode[] = [];
let olderQueries: PastQueryNode[] = [];

currentList.forEach(queryItem => {
// The smaller the unix value, the older it is
if (queryItem.unix < monthAgo) {
olderQueries.push(new PastQueryNode(queryItem.query));
} else if (queryItem.unix < weekAgo) {
pastMonthQueries.push(new PastQueryNode(queryItem.query));
} else if (queryItem.unix < dayAgo) {
pastWeekQueries.push(new PastQueryNode(queryItem.query));
} else {
pastDayQueries.push(new PastQueryNode(queryItem.query));
}
});

let nodes: TimePeriodNode[] = [];

if (pastDayQueries.length > 0) {
nodes.push(new TimePeriodNode(`Past day`, pastDayQueries, true));
}
if (pastWeekQueries.length > 0) {
nodes.push(new TimePeriodNode(`Past week`, pastWeekQueries));
}
if (pastMonthQueries.length > 0) {
nodes.push(new TimePeriodNode(`Past month`, pastMonthQueries));
}
if (olderQueries.length > 0) {
nodes.push(new TimePeriodNode(`Older`, olderQueries));
}

return nodes;
}

} else {
return [new TreeItem(`A connection is required for query history`)];
}
}
}

class PastQuery extends vscode.TreeItem {
class TimePeriodNode extends vscode.TreeItem {
constructor(public period: string, private nodes: PastQueryNode[], expanded = false) {
super(period, expanded ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed);

this.contextValue = `timePeriod`;

this.iconPath = new ThemeIcon(`calendar`);
}

getChildren() {
return this.nodes;
}
}

class PastQueryNode extends vscode.TreeItem {
constructor(public query: string) {
super(query.length > 63 ? query.substring(0, 60) + `...` : query);

Expand Down
67 changes: 34 additions & 33 deletions src/views/results/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,27 @@ export function generateScroller(basicSelect: string, isCL: boolean): string {
*/
const vscode = acquireVsCodeApi();
const basicSelect = ${JSON.stringify(basicSelect)};
const htmlTableId = 'resultset';
const statusId = 'status';
const messageSpanId = 'messageSpan';
let myQueryId = '';
let mustLoadHeaders = true;
let needToInitializeTable = true;
let totalRows = 0;
let noMoreRows = false;
let isFetching = false;
window.addEventListener("load", main);
function main() {
let Observer = new IntersectionObserver(function(entries) {
new IntersectionObserver(function(entries) {
// isIntersecting is true when element and viewport are overlapping
// isIntersecting is false when element and viewport don't overlap
if(entries[0].isIntersecting === true) {
if (isFetching === false && noMoreRows === false) {
fetchNextPage();
}
}
}, { threshold: [0] });
Observer.observe(document.getElementById("nextButton"));
}, { threshold: [0] }).observe(document.getElementById(messageSpanId));
window.addEventListener('message', event => {
const data = event.data;
Expand All @@ -85,21 +86,21 @@ export function generateScroller(basicSelect: string, isCL: boolean): string {
isFetching = false;
noMoreRows = data.isDone;
if (mustLoadHeaders && event.data.columnList) {
setHeaders('resultset', event.data.columnList);
mustLoadHeaders = false;
if (needToInitializeTable && event.data.columnList) {
initializeTable(event.data.columnList);
needToInitializeTable = false;
}
if (data.rows && data.rows.length > 0) {
totalRows += data.rows.length;
appendRows('resultset', data.rows);
appendRows(data.rows);
}
const nextButton = document.getElementById("nextButton");
if (data.rows === undefined && totalRows === 0) {
nextButton.innerText = 'Query executed with no result set returned. Rows affected: ' + data.update_count;
document.getElementById(messageSpanId).innerText = 'Statement executed with no result set returned. Rows affected: ' + data.update_count;
} else {
nextButton.innerText = noMoreRows ? ('Loaded ' + totalRows + '. End of data') : ('Loaded ' + totalRows + '. Fetching more...');
document.getElementById(statusId).innerText = noMoreRows ? ('Loaded ' + totalRows + '. End of data') : ('Loaded ' + totalRows + '. More available.');
document.getElementById(messageSpanId).style.visibility = "hidden";
}
break;
Expand All @@ -124,27 +125,26 @@ export function generateScroller(basicSelect: string, isCL: boolean): string {
document.getElementById("spinnerContent").style.display = 'none';
}
function setHeaders(tableId, columns) {
var tHeadRef = document.getElementById(tableId).getElementsByTagName('thead')[0];
tHeadRef.innerHTML = '';
// Insert a row at the end of table
var newRow = tHeadRef.insertRow();
columns.forEach(colName => {
// Insert a cell at the end of the row
var newCell = newRow.insertCell();
// Append a text node to the cell
var newText = document.createTextNode(colName);
newCell.appendChild(newText);
});
function initializeTable(columns) {
// Initialize the header
var header = document.getElementById(htmlTableId).getElementsByTagName('thead')[0];
header.innerHTML = '';
var headerRow = header.insertRow();
columns.forEach(colName => headerRow.insertCell().appendChild(document.createTextNode(colName)));
// Initialize the footer
var footer = document.getElementById(htmlTableId).getElementsByTagName('tfoot')[0];
footer.innerHTML = '';
var newCell = footer.insertRow().insertCell();
newCell.colSpan = columns.length;
newCell.id = statusId;
newCell.appendChild(document.createTextNode(' '));
}
function appendRows(tableId, arrayOfObjects) {
var tBodyRef = document.getElementById(tableId).getElementsByTagName('tbody')[0];
function appendRows(rows) {
var tBodyRef = document.getElementById(htmlTableId).getElementsByTagName('tbody')[0];
for (const row of arrayOfObjects) {
for (const row of rows) {
// Insert a row at the end of table
var newRow = tBodyRef.insertRow()
Expand All @@ -170,8 +170,9 @@ export function generateScroller(basicSelect: string, isCL: boolean): string {
<table id="resultset">
<thead></thead>
<tbody></tbody>
</table>
<p id="nextButton"></p>
<tfoot></tfoot>
</table>
<p id="messageSpan"></p>
<div id="spinnerContent" class="center-screen">
<p id="loadingText">Running statement</p>
<span class="loader"></span>
Expand Down

0 comments on commit 2262873

Please sign in to comment.