Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions client/src/components/LibraryNavigator/LibraryModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { ProgressLocation, l10n, window } from "vscode";

import { SortModelItem } from "ag-grid-community";
import { Writable } from "stream";

import PaginatedResultSet from "./PaginatedResultSet";
Expand All @@ -27,12 +28,17 @@ class LibraryModel {
item: LibraryItem,
): PaginatedResultSet<{ data: TableData; error?: Error }> {
return new PaginatedResultSet<{ data: TableData; error?: Error }>(
async (start: number, end: number) => {
async (start: number, end: number, sortModel: SortModelItem[]) => {
await this.libraryAdapter.setup();
const limit = end - start + 1;
try {
return {
data: await this.libraryAdapter.getRows(item, start, limit),
data: await this.libraryAdapter.getRows(
item,
start,
limit,
sortModel,
),
};
} catch (e) {
return { error: e, data: { rows: [], count: 0 } };
Expand Down
17 changes: 10 additions & 7 deletions client/src/components/LibraryNavigator/PaginatedResultSet.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { SortModelItem } from "ag-grid-community";

class PaginatedResultSet<T> {
private queryForData: (start: number, end: number) => Promise<T>;
constructor(
protected readonly queryForData: PaginatedResultSet<T>["getData"],
) {}

constructor(queryForData: (start: number, end: number) => Promise<T>) {
this.queryForData = queryForData;
}

public async getData(start: number, end: number): Promise<T> {
return await this.queryForData(start, end);
public async getData(
start: number,
end: number,
sortModel: SortModelItem[],
): Promise<T> {
return await this.queryForData(start, end, sortModel);
}
}

Expand Down
9 changes: 8 additions & 1 deletion client/src/components/LibraryNavigator/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { SortModelItem } from "ag-grid-community";

import { ColumnCollection } from "../../connection/rest/api/compute";

export const LibraryType = "library";
Expand Down Expand Up @@ -39,7 +41,12 @@ export interface LibraryAdapter {
items: LibraryItem[];
count: number;
}>;
getRows(item: LibraryItem, start: number, limit: number): Promise<TableData>;
getRows(
item: LibraryItem,
start: number,
limit: number,
sortModel: SortModelItem[],
): Promise<TableData>;
getRowsAsCSV(
item: LibraryItem,
start: number,
Expand Down
2 changes: 2 additions & 0 deletions client/src/connection/itc/ItcLibraryAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { l10n } from "vscode";

import { SortModelItem } from "ag-grid-community";
import { ChildProcessWithoutNullStreams } from "child_process";

import { onRunError } from "../../commands/run";
Expand Down Expand Up @@ -91,6 +92,7 @@ class ItcLibraryAdapter implements LibraryAdapter {
item: LibraryItem,
start: number,
limit: number,
sortModel: SortModelItem[],
): Promise<TableData> {
const { rows: rawRowValues, count } = await this.getDatasetInformation(
item,
Expand Down
54 changes: 50 additions & 4 deletions client/src/connection/rest/RestLibraryAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright © 2024, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { SortModelItem } from "ag-grid-community";
import { AxiosResponse } from "axios";

import { getSession } from "..";
Expand Down Expand Up @@ -41,10 +42,15 @@ class RestLibraryAdapter implements LibraryAdapter {
}

public async getRows(
item: LibraryItem,
item: Pick<LibraryItem, "name" | "library">,
start: number,
limit: number,
sortModel: SortModelItem[],
): Promise<TableData> {
if (sortModel.length > 0) {
return await this.getSortedRows(item, start, limit, sortModel);
}

const { data } = await this.retryOnFail<RowCollection>(
async () =>
await this.dataAccessApi.getRows(
Expand All @@ -66,6 +72,43 @@ class RestLibraryAdapter implements LibraryAdapter {
};
}

private async getSortedRows(
item: Pick<LibraryItem, "name" | "library">,
start: number,
limit: number,
sortModel: SortModelItem[],
): Promise<TableData> {
const { data: viewData } = await this.retryOnFail(
async () =>
await this.dataAccessApi.createView(
{
sessionId: this.sessionId,
libref: item.library || "",
tableName: item.name,
viewRequest: {
sortBy: sortModel.map((sortModelItem) => ({
key: sortModelItem.colId,
direction:
sortModelItem.sort === "asc" ? "ascending" : "descending",
})),
},
},
requestOptions,
),
);

const results = await this.getRows(
{ library: viewData.libref, name: viewData.name },
start,
limit,
[],
);

await this.deleteTable({ library: viewData.libref, name: viewData.name });

return results;
}

public async getRowsAsCSV(
item: LibraryItem,
start: number,
Expand Down Expand Up @@ -155,15 +198,18 @@ class RestLibraryAdapter implements LibraryAdapter {
}
}

public async deleteTable(item: LibraryItem): Promise<void> {
public async deleteTable({
library,
name,
}: Pick<LibraryItem, "library" | "name">): Promise<void> {
await this.setup();
try {
await this.retryOnFail(
async () =>
await this.dataAccessApi.deleteTable({
sessionId: this.sessionId,
libref: item.library,
tableName: item.name,
libref: library,
tableName: name,
}),
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
5 changes: 4 additions & 1 deletion client/src/panels/DataViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { Uri, window } from "vscode";

import { SortModelItem } from "ag-grid-community";

import PaginatedResultSet from "../components/LibraryNavigator/PaginatedResultSet";
import { TableData } from "../components/LibraryNavigator/types";
import { Column } from "../connection/rest/api/compute";
Expand Down Expand Up @@ -66,14 +68,15 @@ class DataViewer extends WebView {
event: Event & {
key: string;
command: string;
data?: { start?: number; end?: number };
data?: { start?: number; end?: number; sortModel?: SortModelItem[] };
},
): Promise<void> {
switch (event.command) {
case "request:loadData": {
const { data, error } = await this._paginator.getData(
event.data!.start!,
event.data!.end!,
event.data!.sortModel!,
);
if (error) {
await window.showErrorMessage(error.message);
Expand Down
99 changes: 99 additions & 0 deletions client/src/webview/ColumnHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useRef } from "react";

import { AgColumn, GridApi } from "ag-grid-community";

import { ColumnHeaderProps } from "./ColumnHeaderMenu";

const getIconForColumnType = (type: string) => {
switch (type.toLocaleLowerCase()) {
case "float":
case "num":
return "float";
case "date":
return "date";
case "time":
return "time";
case "datetime":
return "date-time";
case "currency":
return "currency";
case "char":
return "char";
default:
return "";
}
};

const ColumnHeader = ({
api,
column,
currentColumn: getCurrentColumn,
columnType,
setColumnMenu,
theme,
}: {
api: GridApi;
column: AgColumn;
currentColumn: () => AgColumn | undefined;
columnType: string;
setColumnMenu: (props: ColumnHeaderProps) => void;
theme: string;
}) => {
const ref = useRef<HTMLButtonElement>(undefined!);
const currentColumn = getCurrentColumn();

return (
<div className={`ag-cell-label-container ${theme}`} role="presentation">
<div
data-ref="eLabel"
className="ag-header-cell-label"
role="presentation"
>
<span
className={`header-icon ${getIconForColumnType(columnType)}`}
></span>
<span className="ag-header-cell-text">{column.colId}</span>
{column.sort === "asc" && (
<span className="sort-icon-wrapper">
<span className="sort-icon ascending"></span>
</span>
)}
{column.sort === "desc" && (
<span className="sort-icon-wrapper">
<span className="sort-icon descending"></span>
</span>
)}
<div className="dropdown">
<button
ref={ref}
type="button"
className={currentColumn?.colId === column.colId ? "active" : ""}
onClick={() => {
if (currentColumn) {
return setColumnMenu(undefined);
}
const { height, top, left } = ref.current.getBoundingClientRect();
setColumnMenu({
left,
top: top + height,
column,
theme,
sortColumn: (direction: "asc" | "desc" | null) => {
api.applyColumnState({
state: [{ colId: column.colId, sort: direction }],
defaultState: { sort: null },
});
},
dismissMenu: () => setColumnMenu(undefined),
});
}}
>
<span></span>
</button>
</div>
</div>
</div>
);
};

export default ColumnHeader;
Loading
Loading