diff --git a/common/changes/@itwin/manage-versions-react/sourabh-adding-server-side-filters-for-versions-and-changesets_2025-12-03-16-45.json b/common/changes/@itwin/manage-versions-react/sourabh-adding-server-side-filters-for-versions-and-changesets_2025-12-03-16-45.json new file mode 100644 index 00000000..05e8335e --- /dev/null +++ b/common/changes/@itwin/manage-versions-react/sourabh-adding-server-side-filters-for-versions-and-changesets_2025-12-03-16-45.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/manage-versions-react", + "comment": "Added server-side filtering for Named Versions (name, description) and Changesets (index range) tables with improved performance and preserved expandable row state.", + "type": "patch" + } + ], + "packageName": "@itwin/manage-versions-react" +} \ No newline at end of file diff --git a/packages/modules/manage-versions/src/clients/changesetClient.test.ts b/packages/modules/manage-versions/src/clients/changesetClient.test.ts index 79559a79..c6f3f18e 100644 --- a/packages/modules/manage-versions/src/clients/changesetClient.test.ts +++ b/packages/modules/manage-versions/src/clients/changesetClient.test.ts @@ -19,7 +19,7 @@ describe("ChangesetClient", () => { await changesetClient.get(MOCKED_IMODEL_ID); expect(mockHttpGet).toHaveBeenCalledWith( - `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index+desc`, + `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index%20desc`, { headers: { Prefer: "return=representation", @@ -34,7 +34,7 @@ describe("ChangesetClient", () => { await changesetClient.get(MOCKED_IMODEL_ID, { top: 10, skip: 20 }); expect(mockHttpGet).toHaveBeenCalledWith( - `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index+desc&$top=10&$skip=20`, + `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/changesets?$orderBy=index%20desc&$top=10&$skip=20`, { headers: { Prefer: "return=representation", diff --git a/packages/modules/manage-versions/src/clients/changesetClient.ts b/packages/modules/manage-versions/src/clients/changesetClient.ts index 436f681d..78cb1a31 100644 --- a/packages/modules/manage-versions/src/clients/changesetClient.ts +++ b/packages/modules/manage-versions/src/clients/changesetClient.ts @@ -21,12 +21,17 @@ export class ChangesetClient { imodelId: string, requestOptions: RequestOptions = {} ): Promise { + const options = { + orderBy: "index+desc", + ...requestOptions, + }; + return this._http .get( `${UrlBuilder.buildChangesetUrl( imodelId, this._serverEnvironmentPrefix - )}${UrlBuilder.getQuery({ orderBy: "index+desc", ...requestOptions })}`, + )}${UrlBuilder.getQuery(options)}`, { headers: { [HttpHeaderNames.Prefer]: "return=representation", diff --git a/packages/modules/manage-versions/src/clients/namedVersionClient.test.ts b/packages/modules/manage-versions/src/clients/namedVersionClient.test.ts index 0b0746d5..6a74ad98 100644 --- a/packages/modules/manage-versions/src/clients/namedVersionClient.test.ts +++ b/packages/modules/manage-versions/src/clients/namedVersionClient.test.ts @@ -21,7 +21,7 @@ describe("NamedVersionClient", () => { await namedVersionClient.get(MOCKED_IMODEL_ID); expect(mockHttpGet).toHaveBeenCalledWith( - `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex+desc`, + `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex%20desc`, { headers: { Prefer: "return=representation", @@ -36,7 +36,7 @@ describe("NamedVersionClient", () => { await namedVersionClient.get(MOCKED_IMODEL_ID, { top: 10, skip: 20 }); expect(mockHttpGet).toHaveBeenCalledWith( - `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex+desc&$top=10&$skip=20`, + `https://api.bentley.com/imodels/${MOCKED_IMODEL_ID}/namedversions?$orderBy=changesetIndex%20desc&$top=10&$skip=20`, { headers: { Prefer: "return=representation", diff --git a/packages/modules/manage-versions/src/clients/namedVersionClient.ts b/packages/modules/manage-versions/src/clients/namedVersionClient.ts index b6687274..cd913f9a 100644 --- a/packages/modules/manage-versions/src/clients/namedVersionClient.ts +++ b/packages/modules/manage-versions/src/clients/namedVersionClient.ts @@ -21,16 +21,18 @@ export class NamedVersionClient { imodelId: string, requestOptions: RequestOptions = {} ): Promise { + const options = { + orderBy: "changesetIndex+desc", + ...requestOptions, + }; + return this._http .get( `${UrlBuilder.buildVersionsUrl( imodelId, undefined, this._serverEnvironmentPrefix - )}${UrlBuilder.getQuery({ - orderBy: "changesetIndex+desc", - ...requestOptions, - })}`, + )}${UrlBuilder.getQuery(options)}`, { headers: { [HttpHeaderNames.Prefer]: "return=representation", diff --git a/packages/modules/manage-versions/src/clients/urlBuilder.test.ts b/packages/modules/manage-versions/src/clients/urlBuilder.test.ts index 61f25701..7bdfdf5e 100644 --- a/packages/modules/manage-versions/src/clients/urlBuilder.test.ts +++ b/packages/modules/manage-versions/src/clients/urlBuilder.test.ts @@ -32,11 +32,13 @@ describe("UrlBuilder", () => { it("should return query with given params", () => { expect( UrlBuilder.getQuery({ skip: 20, top: 10, orderBy: "index+desc" }) - ).toEqual("?$skip=20&$top=10&$orderBy=index+desc"); + ).toEqual("?$skip=20&$top=10&$orderBy=index%20desc"); }); it("should return query with given params (one with falsy value)", () => { - expect(UrlBuilder.getQuery({ skip: 0, top: 10 })).toEqual("?$top=10"); + expect(UrlBuilder.getQuery({ skip: 0, top: 10 })).toEqual( + "?$skip=0&$top=10" + ); expect(UrlBuilder.getQuery({ skip: undefined, top: 10 })).toEqual( "?$top=10" ); @@ -45,7 +47,7 @@ describe("UrlBuilder", () => { it("should return empty string when query params are empty", () => { expect(UrlBuilder.getQuery({})).toEqual(""); - expect(UrlBuilder.getQuery({ skip: 0, top: 0 })).toEqual(""); + expect(UrlBuilder.getQuery({ skip: 0, top: 0 })).toEqual("?$skip=0&$top=0"); expect(UrlBuilder.getQuery({ skip: undefined, top: undefined })).toEqual( "" ); diff --git a/packages/modules/manage-versions/src/clients/urlBuilder.ts b/packages/modules/manage-versions/src/clients/urlBuilder.ts index fe5e29d5..ebd88872 100644 --- a/packages/modules/manage-versions/src/clients/urlBuilder.ts +++ b/packages/modules/manage-versions/src/clients/urlBuilder.ts @@ -8,12 +8,31 @@ export class UrlBuilder { top?: number; orderBy?: string; lastIndex?: number; + afterIndex?: number; + $search?: string; + name?: string; }) => { const query = Object.entries(params) - .filter(([key, value]) => !!value) - .map(([key, value]) => - key === "lastIndex" ? `${key}=${value}` : `$${key}=${value}` + .filter( + ([key, value]) => value !== undefined && value !== null && value !== "" ) + .map(([key, value]) => { + if (key === "lastIndex" || key === "afterIndex") { + return `${key}=${value}`; + } else if (key === "orderBy") { + // Replace + with space before encoding + const orderByValue = (value as string).replace(/\+/g, " "); + return `$orderBy=${encodeURIComponent(orderByValue)}`; + } else if (key === "skip") { + return `$skip=${value}`; + } else if (key === "top") { + return `$top=${value}`; + } else if (key === "name") { + return `name=${encodeURIComponent(value as string)}`; + } else { + return `${key}=${value}`; + } + }) .join("&"); return query ? `?${query}` : ""; }; diff --git a/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.test.tsx b/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.test.tsx index 7b7052ba..15fc9583 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.test.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.test.tsx @@ -31,6 +31,7 @@ const renderComponent = (initialProps?: Partial) => { loadMoreChanges: jest.fn(), latestVersion: undefined, onVersionCreated: jest.fn(), + onFilterChange: jest.fn(), ...initialProps, }; return render( diff --git a/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.tsx b/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.tsx index 0b9a84ba..7defc4fe 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ChangesTab/ChangesTab.tsx @@ -8,7 +8,7 @@ import { SvgInfoCircular, SvgNamedVersionAdd, } from "@itwin/itwinui-icons-react"; -import { IconButton, Table, Text } from "@itwin/itwinui-react"; +import { IconButton, Table, tableFilters, Text } from "@itwin/itwinui-react"; import { CellProps } from "@itwin/itwinui-react/react-table"; import React from "react"; @@ -28,6 +28,7 @@ export type ChangesTabProps = { loadMoreChanges: () => void; onVersionCreated: () => void; latestVersion: NamedVersion | undefined; + onFilterChange: (filters: { id: string; value: any }[]) => void; }; const ChangesTab = (props: ChangesTabProps) => { @@ -37,6 +38,7 @@ const ChangesTab = (props: ChangesTabProps) => { loadMoreChanges, onVersionCreated, latestVersion, + onFilterChange, } = props; const { stringsOverrides } = useConfig(); @@ -65,10 +67,12 @@ const ChangesTab = (props: ChangesTabProps) => { Header: "Name", columns: [ { - id: "INDEX", + id: "index", Header: "#", accessor: "index", width: 90, + Filter: tableFilters.NumberRangeFilter(), + filter: "between", }, { id: "DESCRIPTION", @@ -177,6 +181,8 @@ const ChangesTab = (props: ChangesTabProps) => { columns={columns} data={changesets} + manualFilters={true} + onFilter={onFilterChange} bodyProps={{ className: "iac-changes-table-body", }} @@ -185,6 +191,7 @@ const ChangesTab = (props: ChangesTabProps) => { status === RequestStatus.NotStarted } emptyTableContent={emptyTableContent} + emptyFilteredTableContent={stringsOverrides.messageNoFilterResults} onBottomReached={loadMoreChanges} className="iac-changes-table" /> diff --git a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx index 2ba98f99..cb78807b 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx @@ -138,6 +138,7 @@ describe("ManageVersions", () => { expect(mockGetChangesets).toHaveBeenCalledWith(MOCKED_IMODEL_ID, { top: 100, skip: 0, + orderBy: "index+desc", }); }); diff --git a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx index 5f0ca8f2..0d084cbd 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx @@ -54,6 +54,7 @@ export const defaultStrings: ManageVersionsStringOverrides = { "There are no Named Versions created. To create first go to Changes.", messageFailedGetChanges: "Could not get changes. Please try again later.", messageNoChanges: "There are no changes synchronized.", + messageNoFilterResults: "No results found. Clear or try another filter.", messageVersionCreated: 'Named Version "{{name}}" was successfully created.', messageVersionNameExists: "Named Version with the same name already exists.", messageInsufficientPermissionsToCreateVersion: @@ -252,11 +253,20 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { const [versionStatus, setVersionStatus] = React.useState( RequestStatus.NotStarted ); + const [versionNameFilter, setVersionNameFilter] = React.useState(""); + const [versionDescriptionFilter, setVersionDescriptionFilter] = + React.useState(""); const [changesets, setChangesets] = React.useState(); const [changesetStatus, setChangesetStatus] = React.useState( RequestStatus.NotStarted ); + const [changesetAfterIndex, setChangesetAfterIndex] = React.useState< + number | undefined + >(); + const [changesetLastIndex, setChangesetLastIndex] = React.useState< + number | undefined + >(); const changeTab = React.useCallback( (tab: ManageVersionsTabs) => { @@ -267,13 +277,28 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { ); const getVersions = React.useCallback( - (skip?: number, reloadSubrows?: boolean) => { + ( + skip?: number, + reloadSubrows?: boolean, + nameFilter?: string, + descriptionFilter?: string + ) => { setVersionStatus(RequestStatus.InProgress); + const requestOptions: any = { + top: NAMED_VERSION_TOP, + skip, + }; + + if (nameFilter) { + requestOptions.name = nameFilter; + } + + if (descriptionFilter) { + requestOptions.$search = descriptionFilter; + } + versionClient - .get(imodelId, { - top: NAMED_VERSION_TOP, - skip, - }) + .get(imodelId, requestOptions) .then((newVersions) => { newVersions.sort((v1, v2) => v2.changesetIndex - v1.changesetIndex); const updateVersions = updateNamedVersionsProperties( @@ -303,38 +328,150 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { return; } - getVersions(versionsTableData?.length); - }, [getVersions, versionsTableData]); - - const getChangesets = React.useCallback(() => { - if (changesets && changesets.length % CHANGESET_TOP !== 0) { - return; - } + getVersions( + versionsTableData?.length, + false, + versionNameFilter, + versionDescriptionFilter + ); + }, [ + getVersions, + versionsTableData, + versionNameFilter, + versionDescriptionFilter, + ]); + + const getChangesets = React.useCallback( + (afterIndex?: number, lastIndex?: number) => { + if (changesets && changesets.length % CHANGESET_TOP !== 0) { + return; + } - setChangesetStatus(RequestStatus.InProgress); - changesetClient - .get(imodelId, { + setChangesetStatus(RequestStatus.InProgress); + const requestOptions: any = { top: CHANGESET_TOP, skip: changesets?.length, - }) - .then((newChangesets) => { - setChangesets([ - ...(changesets ?? []), - ...(updateChangesetsProperties(newChangesets, usersRef.current) ?? - []), - ]); - setChangesetStatus(RequestStatus.Finished); - }) - .catch(() => setChangesetStatus(RequestStatus.Failed)); - }, [changesets, changesetClient, imodelId]); + orderBy: "index+desc", + }; + + if (afterIndex !== undefined) { + requestOptions.afterIndex = afterIndex; + } + if (lastIndex !== undefined) { + requestOptions.lastIndex = lastIndex; + } + + changesetClient + .get(imodelId, requestOptions) + .then((newChangesets) => { + setChangesets([ + ...(changesets ?? []), + ...(updateChangesetsProperties(newChangesets, usersRef.current) ?? + []), + ]); + setChangesetStatus(RequestStatus.Finished); + }) + .catch(() => setChangesetStatus(RequestStatus.Failed)); + }, + [changesets, changesetClient, imodelId] + ); const refreshVersions = React.useCallback( (reloadSubrows?: boolean) => { - getVersions(undefined, reloadSubrows); + setVersionsTableData([]); + getVersions( + undefined, + reloadSubrows, + versionNameFilter, + versionDescriptionFilter + ); + }, + [getVersions, versionNameFilter, versionDescriptionFilter] + ); + + const handleVersionFilterChange = React.useCallback( + (filters: { id: string; value: any }[]) => { + const nameFilter = filters.find((f) => f.id === "name"); + const descriptionFilter = filters.find((f) => f.id === "description"); + + const nameText = nameFilter?.value || ""; + const descriptionText = descriptionFilter?.value || ""; + + setVersionNameFilter(nameText); + setVersionDescriptionFilter(descriptionText); + + getVersions(undefined, false, nameText, descriptionText); }, [getVersions] ); + const handleChangesetFilterChange = React.useCallback( + (filters: { id: string; value: any }[]) => { + // Extract index filter + const indexFilter = filters.find((f) => f.id === "index"); + + let afterIndex = indexFilter?.value?.[0]; + let lastIndex = indexFilter?.value?.[1]; + + // Swap if user entered From > To (swap BEFORE subtraction) + if ( + afterIndex !== undefined && + afterIndex !== null && + lastIndex !== undefined && + lastIndex !== null && + afterIndex > lastIndex + ) { + const temp = afterIndex; + afterIndex = lastIndex; + lastIndex = temp; + } + + // Subtract 1 from afterIndex to make it inclusive (UI shows "From" which should include that index) + if (afterIndex !== undefined && afterIndex !== null) { + afterIndex = afterIndex - 1; + } + + // Special case: if both are same after subtraction, adjust lastIndex + if ( + afterIndex !== undefined && + afterIndex !== null && + lastIndex !== undefined && + lastIndex !== null && + afterIndex === lastIndex + ) { + lastIndex = lastIndex + 1; // Ensure we get the single row + } + + setChangesetAfterIndex(afterIndex); + setChangesetLastIndex(lastIndex); + setChangesets([]); // Reset changesets + setChangesetStatus(RequestStatus.InProgress); + + const requestOptions: any = { + top: CHANGESET_TOP, + orderBy: "index desc", // Descending order + }; + + if (afterIndex !== undefined && afterIndex !== null && afterIndex >= 0) { + requestOptions.afterIndex = afterIndex; + } + if (lastIndex !== undefined && lastIndex !== null) { + requestOptions.lastIndex = lastIndex; + } + + changesetClient + .get(imodelId, requestOptions) + .then((newChangesets) => { + const updatedChangesets = + updateChangesetsProperties(newChangesets, usersRef.current) ?? []; + setChangesets(updatedChangesets); + setChangesetStatus(RequestStatus.Finished); + }) + .catch(() => setChangesetStatus(RequestStatus.Failed)); + }, + [changesetClient, imodelId] + ); + React.useEffect(() => { const loadUsers = async () => { await getUsers(); @@ -384,9 +521,15 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { _currentTab === ManageVersionsTabs.Changes && changesetStatus === RequestStatus.NotStarted ) { - getChangesets(); + getChangesets(changesetAfterIndex, changesetLastIndex); } - }, [changesetStatus, _currentTab, getChangesets]); + }, [ + changesetStatus, + _currentTab, + getChangesets, + changesetAfterIndex, + changesetLastIndex, + ]); const onVersionCreated = React.useCallback(() => { changeTab(ManageVersionsTabs.Versions); @@ -409,14 +552,22 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { const setRelatedChangesets = (versionId: string, changesets: Changeset[]) => { const updateChangesets = updateChangesetsProperties(changesets, usersRef.current) ?? []; - setVersionsTableData((prevVersionsTableData) => { - const updatedVersions = prevVersionsTableData?.map((version) => - version.version.id === versionId - ? { ...version, subRows: updateChangesets, subRowsLoaded: true } - : version - ); - return updatedVersions ?? prevVersionsTableData; - }); + const changesetsWithSubRows = updateChangesets.map((changeset) => ({ + ...changeset, + subRows: [], + })); + setVersionsTableData( + (prevVersionsTableData) => + prevVersionsTableData?.map((version) => + version.version.id === versionId + ? { + ...version, + subRows: changesetsWithSubRows, + subRowsLoaded: true, + } + : version + ) ?? prevVersionsTableData + ); }; const [showHiddenVersions, setShowHiddenVersions] = @@ -519,15 +670,19 @@ const ManageVersionsComponent = (props: ManageVersionsProps) => { setRelatedChangesets={setRelatedChangesets} handleHideVersion={handleToggleVersionState} showHiddenVersions={showHiddenVersions} + onFilterChange={handleVersionFilterChange} /> )} {_currentTab === ManageVersionsTabs.Changes && ( + getChangesets(changesetAfterIndex, changesetLastIndex) + } onVersionCreated={onVersionCreated} latestVersion={latestVersion?.version} + onFilterChange={handleChangesetFilterChange} /> )} diff --git a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.test.tsx b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.test.tsx index a57e87bf..aec657ea 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.test.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.test.tsx @@ -46,6 +46,7 @@ const renderComponent = (initialProps?: Partial) => { setRelatedChangesets: jest.fn(), handleHideVersion: jest.fn(), showHiddenVersions: false, + onFilterChange: jest.fn(), ...initialProps, }; return render( diff --git a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx index ece65d70..b1b39364 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx @@ -10,7 +10,15 @@ import { SvgVisibilityHide, SvgVisibilityShow, } from "@itwin/itwinui-icons-react"; -import { Flex, Table, Text, Tooltip, useToaster } from "@itwin/itwinui-react"; +import { + Anchor, + Flex, + Table, + tableFilters, + Text, + Tooltip, + useToaster, +} from "@itwin/itwinui-react"; import { ActionType, CellProps, @@ -41,6 +49,7 @@ export type VersionsTabProps = { setRelatedChangesets: (versionId: string, changesets: Changeset[]) => void; handleHideVersion: (version: NamedVersion) => void; showHiddenVersions: boolean; + onFilterChange: (filters: { id: string; value: any }[]) => void; }; const isNamedVersion = ( @@ -60,6 +69,7 @@ const VersionsTab = (props: VersionsTabProps) => { setRelatedChangesets, handleHideVersion, showHiddenVersions, + onFilterChange, } = props; const toaster = useToaster(); const { stringsOverrides, imodelId, enableHideVersions } = useConfig(); @@ -251,9 +261,22 @@ const VersionsTab = (props: VersionsTabProps) => { }, }, { - id: "NAME", + id: "name", Header: stringsOverrides.name, - accessor: "name", + accessor: (row: VersionTableData | Changeset) => { + return isNamedVersion(row) ? row.version.name : row.displayName; + }, + Filter: tableFilters.TextFilter(), + filter: (rows: any[], _id: string, filterValue: string) => { + // Only filter parent rows (VersionTableData), not subRows (Changesets) + return rows.filter((row) => { + if (!isNamedVersion(row.original)) { + return true; + } + const name = row.original.version.name.toLowerCase(); + return name.includes(filterValue.toLowerCase()); + }); + }, Cell: (props: CellProps) => { const columnAccessor = isNamedVersion(props.row.original) ? "name" @@ -262,9 +285,26 @@ const VersionsTab = (props: VersionsTabProps) => { }, }, { - id: "DESCRIPTION", + id: "description", Header: stringsOverrides.description, - accessor: "description", + accessor: (row: VersionTableData | Changeset) => { + return isNamedVersion(row) + ? row.version.description + : row.description; + }, + Filter: tableFilters.TextFilter(), + filter: (rows: any[], _id: string, filterValue: string) => { + // Only filter parent rows (VersionTableData), not subRows (Changesets) + return rows.filter((row) => { + if (!isNamedVersion(row.original)) { + return true; + } + const description = ( + row.original.version.description || "" + ).toLowerCase(); + return description.includes(filterValue.toLowerCase()); + }); + }, Cell: (props: CellProps) => { return generateCellContent(props.row.original, "description"); }, @@ -321,12 +361,9 @@ const VersionsTab = (props: VersionsTabProps) => { width: 100, Cell: (props: CellProps) => { return isNamedVersion(props.row.original) ? ( - onViewClick(props.row.original.version)} - > + onViewClick(props.row.original.version)}> {stringsOverrides.view} - + ) : ( <> ); @@ -398,6 +435,8 @@ const VersionsTab = (props: VersionsTabProps) => { columns={columns} data={tableData} + manualFilters={true} + onFilter={onFilterChange} isLoading={ status === RequestStatus.InProgress || status === RequestStatus.NotStarted @@ -406,9 +445,12 @@ const VersionsTab = (props: VersionsTabProps) => { className: "iac-versions-table-body", }} emptyTableContent={emptyTableContent} + emptyFilteredTableContent={stringsOverrides.messageNoFilterResults} onBottomReached={loadMoreVersions} className="iac-versions-table" onExpand={onExpandRow} + initialState={{ hiddenColumns }} + autoResetFilters={false} stateReducer={useCallback( ( newState: TableState, diff --git a/packages/modules/manage-versions/src/components/ManageVersions/types.ts b/packages/modules/manage-versions/src/components/ManageVersions/types.ts index c2ddca62..ae214f70 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/types.ts +++ b/packages/modules/manage-versions/src/components/ManageVersions/types.ts @@ -50,6 +50,8 @@ export type ManageVersionsStringOverrides = { messageFailedGetChanges: string; /** Message when there are no changes. Default `There are no changes synchronized.`. */ messageNoChanges: string; + /** Message when no results found after filtering. Default `No results found. Clear or try another filter.`. */ + messageNoFilterResults: string; /** Default `Named Version "{{name}}" was successfully created.` - `{{name}}` - Named Version name. */ messageVersionCreated: string; /** Default `Named Version with the same name already exists.` */ diff --git a/packages/modules/manage-versions/src/models/requestOptions.ts b/packages/modules/manage-versions/src/models/requestOptions.ts index bc1dec06..ebee51a9 100644 --- a/packages/modules/manage-versions/src/models/requestOptions.ts +++ b/packages/modules/manage-versions/src/models/requestOptions.ts @@ -6,4 +6,8 @@ export type RequestOptions = { skip?: number; top?: number; lastIndex?: number; + afterIndex?: number; + orderBy?: string; + $search?: string; + name?: string; };