Skip to content

Commit c5e6517

Browse files
Scott DoverScott Dover
authored andcommitted
chore: implement sort for rest
Signed-off-by: Scott Dover <[email protected]>
1 parent 84d9dd2 commit c5e6517

File tree

13 files changed

+369
-63
lines changed

13 files changed

+369
-63
lines changed

client/src/connection/rest/RestLibraryAdapter.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,15 @@ class RestLibraryAdapter implements LibraryAdapter {
4242
}
4343

4444
public async getRows(
45-
item: LibraryItem,
45+
item: Pick<LibraryItem, "name" | "library">,
4646
start: number,
4747
limit: number,
4848
sortModel: SortModelItem[],
4949
): Promise<TableData> {
50+
if (sortModel.length > 0) {
51+
return await this.getSortedRows(item, start, limit, sortModel);
52+
}
53+
5054
const { data } = await this.retryOnFail<RowCollection>(
5155
async () =>
5256
await this.dataAccessApi.getRows(
@@ -68,6 +72,43 @@ class RestLibraryAdapter implements LibraryAdapter {
6872
};
6973
}
7074

75+
private async getSortedRows(
76+
item: Pick<LibraryItem, "name" | "library">,
77+
start: number,
78+
limit: number,
79+
sortModel: SortModelItem[],
80+
): Promise<TableData> {
81+
const { data: viewData } = await this.retryOnFail(
82+
async () =>
83+
await this.dataAccessApi.createView(
84+
{
85+
sessionId: this.sessionId,
86+
libref: item.library || "",
87+
tableName: item.name,
88+
viewRequest: {
89+
sortBy: sortModel.map((sortModelItem) => ({
90+
key: sortModelItem.colId,
91+
direction:
92+
sortModelItem.sort === "asc" ? "ascending" : "descending",
93+
})),
94+
},
95+
},
96+
requestOptions,
97+
),
98+
);
99+
100+
const results = await this.getRows(
101+
{ library: viewData.libref, name: viewData.name },
102+
start,
103+
limit,
104+
[],
105+
);
106+
107+
await this.deleteTable({ library: viewData.libref, name: viewData.name });
108+
109+
return results;
110+
}
111+
71112
public async getRowsAsCSV(
72113
item: LibraryItem,
73114
start: number,
@@ -157,15 +198,18 @@ class RestLibraryAdapter implements LibraryAdapter {
157198
}
158199
}
159200

160-
public async deleteTable(item: LibraryItem): Promise<void> {
201+
public async deleteTable({
202+
library,
203+
name,
204+
}: Pick<LibraryItem, "library" | "name">): Promise<void> {
161205
await this.setup();
162206
try {
163207
await this.retryOnFail(
164208
async () =>
165209
await this.dataAccessApi.deleteTable({
166210
sessionId: this.sessionId,
167-
libref: item.library,
168-
tableName: item.name,
211+
libref: library,
212+
tableName: name,
169213
}),
170214
);
171215
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useRef } from "react";
2+
3+
import { AgColumn, GridApi } from "ag-grid-community";
4+
5+
import { ColumnHeaderProps } from "./ColumnHeaderMenu";
6+
7+
const getIconForColumnType = (type: string) => {
8+
switch (type.toLocaleLowerCase()) {
9+
case "float":
10+
case "num":
11+
return "float";
12+
case "date":
13+
return "date";
14+
case "time":
15+
return "time";
16+
case "datetime":
17+
return "date-time";
18+
case "currency":
19+
return "currency";
20+
case "char":
21+
return "char";
22+
default:
23+
return "";
24+
}
25+
};
26+
27+
const ColumnHeader = ({
28+
api,
29+
column,
30+
currentColumn: getCurrentColumn,
31+
columnType,
32+
setColumnMenu,
33+
}: {
34+
api: GridApi;
35+
column: AgColumn;
36+
currentColumn: () => AgColumn | undefined;
37+
columnType: string;
38+
setColumnMenu: (props: ColumnHeaderProps) => void;
39+
}) => {
40+
const ref = useRef<HTMLButtonElement>(undefined!);
41+
const currentColumn = getCurrentColumn();
42+
43+
console.log({ currentColumn, column });
44+
return (
45+
<div className="ag-cell-label-container" role="presentation">
46+
<div
47+
data-ref="eLabel"
48+
className="ag-header-cell-label"
49+
role="presentation"
50+
>
51+
<span
52+
className={`header-icon ${getIconForColumnType(columnType)}`}
53+
></span>
54+
<span className="ag-header-cell-text">{column.colId}</span>
55+
<span
56+
data-ref="eSortOrder"
57+
className="ag-header-icon ag-header-label-icon ag-sort-order"
58+
aria-hidden="true"
59+
></span>
60+
{column.sort === "asc" && (
61+
<span className="ag-header-icon ag-header-label-icon ag-sort-ascending-icon"></span>
62+
)}
63+
{column.sort === "desc" && (
64+
<span className="ag-header-icon ag-header-label-icon ag-sort-descending-icon"></span>
65+
)}
66+
<div className="dropdown">
67+
<button
68+
ref={ref}
69+
type="button"
70+
className={currentColumn?.colId === column.colId ? "active" : ""}
71+
onClick={() => {
72+
if (currentColumn) {
73+
return setColumnMenu(undefined);
74+
}
75+
const { height, top, right } =
76+
ref.current.getBoundingClientRect();
77+
setColumnMenu({
78+
left: right,
79+
top: top + height,
80+
column,
81+
sortColumn: (direction: "asc" | "desc" | null) => {
82+
api.applyColumnState({
83+
state: [{ colId: column.colId, sort: direction }],
84+
defaultState: { sort: null },
85+
});
86+
// console.log(props.api.getColumnState());
87+
// props.api.applyColumnState({
88+
// state: [{}]
89+
// })
90+
// const currentSort = props.api.sort
91+
// console.log(direction);
92+
// console.log(props);
93+
},
94+
dismissMenu: () => setColumnMenu(undefined),
95+
});
96+
}}
97+
>
98+
<span></span>
99+
</button>
100+
</div>
101+
</div>
102+
</div>
103+
);
104+
};
105+
106+
export default ColumnHeader;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { AgColumn } from "ag-grid-community";
2+
3+
export interface ColumnHeaderProps {
4+
left: number;
5+
top: number;
6+
column: AgColumn;
7+
sortColumn: (direction: "asc" | "desc") => void;
8+
dismissMenu: () => void;
9+
}
10+
11+
const ColumnHeaderMenu = ({
12+
left,
13+
top,
14+
column,
15+
sortColumn,
16+
dismissMenu,
17+
}: ColumnHeaderProps) => {
18+
return (
19+
<div className="header-menu" style={{ left, top }}>
20+
<ul>
21+
<li>
22+
<span>Sort</span>
23+
<ul>
24+
<li>
25+
{column.sort === "asc" && <span></span>}
26+
<button
27+
type="button"
28+
onClick={() => {
29+
sortColumn(column.sort === "asc" ? null : "asc");
30+
dismissMenu();
31+
}}
32+
>
33+
Ascending
34+
</button>
35+
</li>
36+
<li>
37+
{column.sort === "desc" && <span></span>}
38+
<button
39+
type="button"
40+
onClick={() => {
41+
sortColumn(column.sort === "desc" ? null : "desc");
42+
dismissMenu();
43+
}}
44+
>
45+
Descending
46+
</button>
47+
</li>
48+
</ul>
49+
</li>
50+
</ul>
51+
</div>
52+
);
53+
};
54+
55+
export default ColumnHeaderMenu;

client/src/webview/DataViewer.css

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,108 @@ body,
7474
background: url(../../../icons/dark/tableHeaderCharacterTypeDark.svg) center
7575
no-repeat;
7676
}
77+
78+
.header-menu {
79+
position: absolute;
80+
z-index: 999;
81+
}
82+
.header-menu > ul {
83+
position: relative;
84+
bottom: 0;
85+
right: 100%;
86+
}
87+
.header-menu ul {
88+
background: green;
89+
background: var(--vscode-editorHoverWidget-background);
90+
border: 1px solid var(--vscode-editorHoverWidget-border);
91+
padding: 0;
92+
margin: 0;
93+
list-style-type: none;
94+
min-width: 100px;
95+
color: var(--vscode-editorWidget-foreground);
96+
box-shadow: var(--vscode-widget-shadow);
97+
}
98+
99+
.ag-header-cell-label button,
100+
.header-menu button {
101+
background: none;
102+
border: none;
103+
margin: 0;
104+
padding: 0;
105+
}
106+
107+
.header-menu button {
108+
color: var(--vscode-editorWidget-foreground);
109+
}
110+
111+
.header-menu li {
112+
padding: 0.5rem;
113+
white-space: nowrap;
114+
}
115+
116+
.header-menu li:hover {
117+
background: var(--vscode-toolbar-hoverBackground);
118+
}
119+
120+
.header-menu > ul > li {
121+
position: relative;
122+
}
123+
124+
.header-menu > ul > li > ul {
125+
display: none;
126+
}
127+
128+
.header-menu > ul > li:hover > ul {
129+
display: block;
130+
position: absolute;
131+
left: 100%;
132+
top: 0;
133+
}
134+
135+
.header-menu li > span:has(+ ul) {
136+
display: block;
137+
background: url(../../../icons/light/chevron-right.svg) right center no-repeat;
138+
}
139+
140+
.vscode-dark .header-menu li > span:has(+ ul) {
141+
background: url(../../../icons/dark/chevron-right.svg) right center no-repeat;
142+
}
143+
144+
.ag-header-cell-label {
145+
position: relative;
146+
}
147+
148+
.ag-header-cell-label .dropdown {
149+
position: absolute;
150+
right: 0;
151+
}
152+
153+
button {
154+
cursor: pointer;
155+
}
156+
157+
.ag-header-cell-label button {
158+
display: none;
159+
width: 16px;
160+
height: 16px;
161+
}
162+
163+
.ag-header-cell-label button span {
164+
background: url(../../../icons/light/more.svg);
165+
background-size: 16px 16px;
166+
}
167+
168+
.ag-header-cell-label button:hover,
169+
.ag-header-cell-label button.active {
170+
background: var(--vscode-toolbar-hoverBackground);
171+
}
172+
173+
.vscode-dark .ag-header-cell-label button {
174+
background: url(../../../icons/dark/more.svg);
175+
background-size: 16px 16px;
176+
}
177+
178+
.ag-header-cell-label:hover button,
179+
.ag-header-cell-label button.active {
180+
display: block;
181+
}

0 commit comments

Comments
 (0)