Skip to content

Commit fa2610d

Browse files
authored
Add Server-Side Filtering for Versions and Changesets (#190)
1 parent 1ee0752 commit fa2610d

15 files changed

Lines changed: 316 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@itwin/manage-versions-react",
5+
"comment": "Added server-side filtering for Named Versions (name, description) and Changesets (index range) tables with improved performance and preserved expandable row state.",
6+
"type": "patch"
7+
}
8+
],
9+
"packageName": "@itwin/manage-versions-react"
10+
}

packages/modules/manage-versions/src/clients/changesetClient.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe("ChangesetClient", () => {
1919

2020
await changesetClient.get(MOCKED_IMODEL_ID);
2121
expect(mockHttpGet).toHaveBeenCalledWith(
22-
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index+desc`,
22+
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index%20desc`,
2323
{
2424
headers: {
2525
Prefer: "return=representation",
@@ -34,7 +34,7 @@ describe("ChangesetClient", () => {
3434

3535
await changesetClient.get(MOCKED_IMODEL_ID, { top: 10, skip: 20 });
3636
expect(mockHttpGet).toHaveBeenCalledWith(
37-
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index+desc&$top=10&$skip=20`,
37+
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index%20desc&$top=10&$skip=20`,
3838
{
3939
headers: {
4040
Prefer: "return=representation",

packages/modules/manage-versions/src/clients/changesetClient.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ export class ChangesetClient {
2121
imodelId: string,
2222
requestOptions: RequestOptions = {}
2323
): Promise<Changeset[]> {
24+
const options = {
25+
orderBy: "index+desc",
26+
...requestOptions,
27+
};
28+
2429
return this._http
2530
.get(
2631
`${UrlBuilder.buildChangesetUrl(
2732
imodelId,
2833
this._serverEnvironmentPrefix
29-
)}${UrlBuilder.getQuery({ orderBy: "index+desc", ...requestOptions })}`,
34+
)}${UrlBuilder.getQuery(options)}`,
3035
{
3136
headers: {
3237
[HttpHeaderNames.Prefer]: "return=representation",

packages/modules/manage-versions/src/clients/namedVersionClient.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe("NamedVersionClient", () => {
2121

2222
await namedVersionClient.get(MOCKED_IMODEL_ID);
2323
expect(mockHttpGet).toHaveBeenCalledWith(
24-
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex+desc`,
24+
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex%20desc`,
2525
{
2626
headers: {
2727
Prefer: "return=representation",
@@ -36,7 +36,7 @@ describe("NamedVersionClient", () => {
3636

3737
await namedVersionClient.get(MOCKED_IMODEL_ID, { top: 10, skip: 20 });
3838
expect(mockHttpGet).toHaveBeenCalledWith(
39-
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex+desc&$top=10&$skip=20`,
39+
`https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex%20desc&$top=10&$skip=20`,
4040
{
4141
headers: {
4242
Prefer: "return=representation",

packages/modules/manage-versions/src/clients/namedVersionClient.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@ export class NamedVersionClient {
2121
imodelId: string,
2222
requestOptions: RequestOptions = {}
2323
): Promise<NamedVersion[]> {
24+
const options = {
25+
orderBy: "changesetIndex+desc",
26+
...requestOptions,
27+
};
28+
2429
return this._http
2530
.get(
2631
`${UrlBuilder.buildVersionsUrl(
2732
imodelId,
2833
undefined,
2934
this._serverEnvironmentPrefix
30-
)}${UrlBuilder.getQuery({
31-
orderBy: "changesetIndex+desc",
32-
...requestOptions,
33-
})}`,
35+
)}${UrlBuilder.getQuery(options)}`,
3436
{
3537
headers: {
3638
[HttpHeaderNames.Prefer]: "return=representation",

packages/modules/manage-versions/src/clients/urlBuilder.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ describe("UrlBuilder", () => {
3232
it("should return query with given params", () => {
3333
expect(
3434
UrlBuilder.getQuery({ skip: 20, top: 10, orderBy: "index+desc" })
35-
).toEqual("?$skip=20&$top=10&$orderBy=index+desc");
35+
).toEqual("?$skip=20&$top=10&$orderBy=index%20desc");
3636
});
3737

3838
it("should return query with given params (one with falsy value)", () => {
39-
expect(UrlBuilder.getQuery({ skip: 0, top: 10 })).toEqual("?$top=10");
39+
expect(UrlBuilder.getQuery({ skip: 0, top: 10 })).toEqual(
40+
"?$skip=0&$top=10"
41+
);
4042
expect(UrlBuilder.getQuery({ skip: undefined, top: 10 })).toEqual(
4143
"?$top=10"
4244
);
@@ -45,7 +47,7 @@ describe("UrlBuilder", () => {
4547

4648
it("should return empty string when query params are empty", () => {
4749
expect(UrlBuilder.getQuery({})).toEqual("");
48-
expect(UrlBuilder.getQuery({ skip: 0, top: 0 })).toEqual("");
50+
expect(UrlBuilder.getQuery({ skip: 0, top: 0 })).toEqual("?$skip=0&$top=0");
4951
expect(UrlBuilder.getQuery({ skip: undefined, top: undefined })).toEqual(
5052
""
5153
);

packages/modules/manage-versions/src/clients/urlBuilder.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,31 @@ export class UrlBuilder {
88
top?: number;
99
orderBy?: string;
1010
lastIndex?: number;
11+
afterIndex?: number;
12+
$search?: string;
13+
name?: string;
1114
}) => {
1215
const query = Object.entries(params)
13-
.filter(([key, value]) => !!value)
14-
.map(([key, value]) =>
15-
key === "lastIndex" ? `${key}=${value}` : `$${key}=${value}`
16+
.filter(
17+
([key, value]) => value !== undefined && value !== null && value !== ""
1618
)
19+
.map(([key, value]) => {
20+
if (key === "lastIndex" || key === "afterIndex") {
21+
return `${key}=${value}`;
22+
} else if (key === "orderBy") {
23+
// Replace + with space before encoding
24+
const orderByValue = (value as string).replace(/\+/g, " ");
25+
return `$orderBy=${encodeURIComponent(orderByValue)}`;
26+
} else if (key === "skip") {
27+
return `$skip=${value}`;
28+
} else if (key === "top") {
29+
return `$top=${value}`;
30+
} else if (key === "name") {
31+
return `name=${encodeURIComponent(value as string)}`;
32+
} else {
33+
return `${key}=${value}`;
34+
}
35+
})
1736
.join("&");
1837
return query ? `?${query}` : "";
1938
};

packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const renderComponent = (initialProps?: Partial<ChangesTabProps>) => {
3131
loadMoreChanges: jest.fn(),
3232
latestVersion: undefined,
3333
onVersionCreated: jest.fn(),
34+
onFilterChange: jest.fn(),
3435
...initialProps,
3536
};
3637
return render(

packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
SvgInfoCircular,
99
SvgNamedVersionAdd,
1010
} from "@itwin/itwinui-icons-react";
11-
import { IconButton, Table, Text } from "@itwin/itwinui-react";
11+
import { IconButton, Table, tableFilters, Text } from "@itwin/itwinui-react";
1212
import { CellProps } from "@itwin/itwinui-react/react-table";
1313
import React from "react";
1414

@@ -28,6 +28,7 @@ export type ChangesTabProps = {
2828
loadMoreChanges: () => void;
2929
onVersionCreated: () => void;
3030
latestVersion: NamedVersion | undefined;
31+
onFilterChange: (filters: { id: string; value: any }[]) => void;
3132
};
3233

3334
const ChangesTab = (props: ChangesTabProps) => {
@@ -37,6 +38,7 @@ const ChangesTab = (props: ChangesTabProps) => {
3738
loadMoreChanges,
3839
onVersionCreated,
3940
latestVersion,
41+
onFilterChange,
4042
} = props;
4143

4244
const { stringsOverrides } = useConfig();
@@ -65,10 +67,12 @@ const ChangesTab = (props: ChangesTabProps) => {
6567
Header: "Name",
6668
columns: [
6769
{
68-
id: "INDEX",
70+
id: "index",
6971
Header: "#",
7072
accessor: "index",
7173
width: 90,
74+
Filter: tableFilters.NumberRangeFilter(),
75+
filter: "between",
7276
},
7377
{
7478
id: "DESCRIPTION",
@@ -177,6 +181,8 @@ const ChangesTab = (props: ChangesTabProps) => {
177181
<Table<Changeset>
178182
columns={columns}
179183
data={changesets}
184+
manualFilters={true}
185+
onFilter={onFilterChange}
180186
bodyProps={{
181187
className: "iac-changes-table-body",
182188
}}
@@ -185,6 +191,7 @@ const ChangesTab = (props: ChangesTabProps) => {
185191
status === RequestStatus.NotStarted
186192
}
187193
emptyTableContent={emptyTableContent}
194+
emptyFilteredTableContent={stringsOverrides.messageNoFilterResults}
188195
onBottomReached={loadMoreChanges}
189196
className="iac-changes-table"
190197
/>

packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ describe("ManageVersions", () => {
138138
expect(mockGetChangesets).toHaveBeenCalledWith(MOCKED_IMODEL_ID, {
139139
top: 100,
140140
skip: 0,
141+
orderBy: "index+desc",
141142
});
142143
});
143144

0 commit comments

Comments
 (0)