diff --git a/packages/apps/storybook/package.json b/packages/apps/storybook/package.json index 3fc5da3a..b4876630 100644 --- a/packages/apps/storybook/package.json +++ b/packages/apps/storybook/package.json @@ -14,7 +14,7 @@ "@itwin/delete-itwin-react": "^1.0.0", "@itwin/imodel-browser-react": "~1.1.1", "@itwin/itwinui-react": "^3.0.4", - "@itwin/manage-versions-react": "~1.1.0", + "@itwin/manage-versions-react": "~1.2.0", "@itwin/storybook-auth-addon": "^0.1.0", "@storybook/addon-actions": "^6.2.5", "@storybook/addon-essentials": "^6.2.5", diff --git a/packages/modules/manage-versions/CHANGELOG.json b/packages/modules/manage-versions/CHANGELOG.json index 74f162e4..984cc91a 100644 --- a/packages/modules/manage-versions/CHANGELOG.json +++ b/packages/modules/manage-versions/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@itwin/manage-versions-react", "entries": [ + { + "version": "1.2.0", + "tag": "@itwin/manage-versions-react_v1.2.0", + "date": "Thu, 30 Nov 2023 12:17:00 GMT", + "comments": { + "minor": [ + { + "comment": "Show included changesets in Named versions table on expand rows" + } + ] + } + }, { "version": "1.1.0", "tag": "@itwin/manage-versions-react_v1.1.0", diff --git a/packages/modules/manage-versions/CHANGELOG.md b/packages/modules/manage-versions/CHANGELOG.md index 1a1dd56a..4939e895 100644 --- a/packages/modules/manage-versions/CHANGELOG.md +++ b/packages/modules/manage-versions/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @itwin/manage-versions-react -This log was last generated on Thu, 16 Nov 2023 11:52:43 GMT and should not be manually modified. +This log was last generated on Thu, 30 Nov 2023 12:17:00 GMT and should not be manually modified. + +## 1.2.0 +Thu, 30 Nov 2023 12:17:00 GMT + +### Minor changes + +- Show included changesets in Named versions table on expand rows ## 1.1.0 Thu, 16 Nov 2023 11:52:43 GMT diff --git a/packages/modules/manage-versions/package.json b/packages/modules/manage-versions/package.json index 951f562a..03d8fae9 100644 --- a/packages/modules/manage-versions/package.json +++ b/packages/modules/manage-versions/package.json @@ -2,7 +2,7 @@ "name": "@itwin/manage-versions-react", "description": "Components that allow a user to manage Named Versions and Changesets.", "repository": "https://github.com/iTwin/admin-components-react/tree/main/packages/modules/manage-versions", - "version": "1.1.0", + "version": "1.2.0", "main": "cjs/index.js", "module": "esm/index.js", "types": "cjs/index.d.ts", diff --git a/packages/modules/manage-versions/src/clients/urlBuilder.ts b/packages/modules/manage-versions/src/clients/urlBuilder.ts index f7a7890c..fe5e29d5 100644 --- a/packages/modules/manage-versions/src/clients/urlBuilder.ts +++ b/packages/modules/manage-versions/src/clients/urlBuilder.ts @@ -7,10 +7,13 @@ export class UrlBuilder { skip?: number; top?: number; orderBy?: string; + lastIndex?: number; }) => { const query = Object.entries(params) .filter(([key, value]) => !!value) - .map(([key, value]) => `$${key}=${value}`) + .map(([key, value]) => + key === "lastIndex" ? `${key}=${value}` : `$${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 2b421cfd..587cc73c 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 @@ -42,7 +42,9 @@ describe("ChangesTab", () => { rows.forEach((row, index) => { const cells = row.querySelectorAll("._iui3-table-cell"); expect(cells.length).toBe(6); - expect(cells[0].textContent).toContain(MockedChangeset(index).index); + expect(cells[0].textContent).toContain( + MockedChangeset(index).index.toString() + ); expect(cells[1].textContent).toContain( MockedChangeset(index).description ); 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 eda5d8f0..e694e957 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.test.tsx @@ -73,6 +73,7 @@ describe("ManageVersions", () => { expect(cells.length).toBe(5); expect(cells[0].textContent).toContain(MockedVersion(index).name); expect(cells[1].textContent).toContain(MockedVersion(index).description); + expect(cells[2].textContent).toContain(MockedVersion(index).createdBy); expect(cells[3].textContent).toContain( new Date(MockedVersion(index).createdDateTime).toLocaleString() @@ -105,7 +106,9 @@ describe("ManageVersions", () => { changesetRows.forEach((row, index) => { const cells = row.querySelectorAll("._iui3-table-cell"); expect(cells.length).toBe(6); - expect(cells[0].textContent).toContain(MockedChangeset(index).index); + expect(cells[0].textContent).toContain( + MockedChangeset(index).index.toString() + ); expect(cells[1].textContent).toContain( MockedChangeset(index).description ); @@ -322,7 +325,9 @@ it("should render with changesets tab opened", async () => { changesetRows.forEach((row, index) => { const cells = row.querySelectorAll("._iui3-table-cell"); expect(cells.length).toBe(6); - expect(cells[0].textContent).toContain(MockedChangeset(index).index); + expect(cells[0].textContent).toContain( + MockedChangeset(index).index.toString() + ); expect(cells[1].textContent).toContain(MockedChangeset(index).description); expect(cells[2].textContent).toContain(MockedChangeset(index).createdBy); expect(cells[3].textContent).toContain( diff --git a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx index ee469063..aa33c698 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/ManageVersions.tsx @@ -12,6 +12,7 @@ import { Changeset, informationPanelDefaultStrings, NamedVersion, + VersionTableData, } from "../../models"; import ChangesTab from "./ChangesTab/ChangesTab"; import { @@ -87,6 +88,62 @@ export enum ManageVersionsTabs { const NAMED_VERSION_TOP = 100; const CHANGESET_TOP = 100; +const initialChangeset: Changeset = { + id: "", + index: 0, + displayName: "", + description: "", + pushDateTime: "", + synchronizationInfo: { changedFiles: [] }, + _links: {}, + creatorId: "", + createdBy: "", + application: { id: "", name: "" }, +}; + +const initializeVersionTableData = ( + versions: NamedVersion[], + versionTableData?: VersionTableData[] +): VersionTableData[] => { + return (versions ?? []).map((version, index) => { + const existingData = versionTableData?.[index]; + const defaultSubRows = existingData?.subRows ?? [initialChangeset]; + const subRowsLoaded = existingData?.subRowsLoaded ?? false; + return { version, subRows: defaultSubRows, subRowsLoaded }; + }); +}; + +const updateNamedVersionsProperties = ( + versionsToUpdate: NamedVersion[], + users: { [key: string]: string } | undefined +) => { + if (!versionsToUpdate.length) { + return; + } + return versionsToUpdate.map((newVersion) => { + const creatorId = newVersion._links.creator.href.substring( + newVersion._links.creator.href.lastIndexOf("/") + 1 + ); + return { + ...newVersion, + createdBy: users?.[creatorId] ?? "", + }; + }); +}; + +const updateChangesetsProperties = ( + changesetsToUpdate: Changeset[], + users: { [key: string]: string } | undefined +) => { + if (!changesetsToUpdate.length) { + return; + } + return changesetsToUpdate.map((changeSet) => ({ + ...changeSet, + createdBy: users?.[changeSet.creatorId] ?? "", + })); +}; + export const ManageVersions = (props: ManageVersionsProps) => { const { accessToken, @@ -134,13 +191,14 @@ export const ManageVersions = (props: ManageVersionsProps) => { return acc; }, {} as { [key: string]: string }); usersRef.current = userMapData; - }, []); + }, [changesetClient, imodelId]); React.useEffect(() => { setCurrentTab(currentTab); }, [currentTab]); - const [versions, setVersions] = React.useState(); + const [versionsTableData, setVersionsTableData] = + React.useState(); const [versionStatus, setVersionStatus] = React.useState( RequestStatus.NotStarted ); @@ -167,12 +225,14 @@ export const ManageVersions = (props: ManageVersionsProps) => { skip, }) .then((newVersions) => { - setVersionStatus(RequestStatus.Finished); - setVersions((oldVersions) => [ - ...(oldVersions ?? []), - ...newVersions, + const updateVersions = updateNamedVersionsProperties( + newVersions, + usersRef.current + ); + setVersionsTableData((oldVersions) => [ + ...initializeVersionTableData(updateVersions ?? [], oldVersions), ]); - updateNamedVersionsProperties(newVersions); + setVersionStatus(RequestStatus.Finished); }) .catch(() => setVersionStatus(RequestStatus.Failed)); }, @@ -180,50 +240,41 @@ export const ManageVersions = (props: ManageVersionsProps) => { ); const getMoreVersions = React.useCallback(() => { - if (versions && versions.length % NAMED_VERSION_TOP !== 0) { + if ( + versionsTableData && + versionsTableData.length % NAMED_VERSION_TOP !== 0 + ) { return; } - getVersions(versions?.length); - }, [getVersions, versions]); - - const refreshVersions = React.useCallback(() => { - setVersions(undefined); - getVersions(); - }, [getVersions]); + getVersions(versionsTableData?.length); + }, [getVersions, versionsTableData]); - const updateNamedVersionsProperties = (versionsToUpdate: NamedVersion[]) => { - if (!versionsToUpdate.length) { + const getChangesets = React.useCallback(() => { + if (changesets && changesets.length % CHANGESET_TOP !== 0) { return; } - const updatedNamedVersion = versionsToUpdate.map((newVersion) => { - const creatorId = newVersion._links.creator.href.substring( - newVersion._links.creator.href.lastIndexOf("/") + 1 - ); - return { - ...newVersion, - createdBy: usersRef.current?.[creatorId] ?? "", - }; - }); - setVersions(updatedNamedVersion); - }; - const updateChangesetsProperties = (changesetsToUpdate: Changeset[]) => { - if (!changesetsToUpdate.length) { - return; - } - const updatedChangeset = changesetsToUpdate.map((changeSet) => ({ - ...changeSet, - createdBy: usersRef.current?.[changeSet.creatorId] ?? "", - })); - setChangesets(updatedChangeset); - }; + setChangesetStatus(RequestStatus.InProgress); + changesetClient + .get(imodelId, { + top: CHANGESET_TOP, + skip: changesets?.length, + }) + .then((newChangesets) => { + setChangesets([ + ...(changesets ?? []), + ...(updateChangesetsProperties(newChangesets, usersRef.current) ?? + []), + ]); + setChangesetStatus(RequestStatus.Finished); + }) + .catch(() => setChangesetStatus(RequestStatus.Failed)); + }, [changesets, changesetClient, imodelId]); - React.useEffect(() => { - if (versionStatus === RequestStatus.NotStarted) { - getVersions(); - } - }, [getVersions, versionStatus]); + const refreshVersions = React.useCallback(() => { + getVersions(); + }, [getVersions]); React.useEffect(() => { const loadUsers = async () => { @@ -232,33 +283,37 @@ export const ManageVersions = (props: ManageVersionsProps) => { if (!usersRef.current) { loadUsers() .then(() => { - updateNamedVersionsProperties(versions ?? []); - updateChangesetsProperties(changesets ?? []); + const updatedVersionsTableData = versionsTableData?.map((td) => { + const updatedVersion = updateNamedVersionsProperties( + [td.version] ?? [], + usersRef.current + ); + return { + ...td, + version: updatedVersion ? updatedVersion[0] : td.version, + }; + }); + setVersionsTableData(updatedVersionsTableData); + setChangesets((prevChangesets) => [ + ...(updateChangesetsProperties( + changesets ?? [], + usersRef.current + ) ?? + prevChangesets ?? + []), + ]); }) .catch(() => { - console.error("unable to fetch users data"); + console.error("Unable to fetch users data"); }); } - }, [changesets, getUsers, versions]); + }, [changesets, getUsers, versionsTableData]); - const getChangesets = React.useCallback(() => { - if (changesets && changesets.length % CHANGESET_TOP !== 0) { - return; + React.useEffect(() => { + if (versionStatus === RequestStatus.NotStarted) { + getVersions(); } - - setChangesetStatus(RequestStatus.InProgress); - changesetClient - .get(imodelId, { - top: CHANGESET_TOP, - skip: changesets?.length, - }) - .then((newChangesets) => { - setChangesetStatus(RequestStatus.Finished); - setChangesets([...(changesets ?? []), ...newChangesets]); - updateChangesetsProperties(newChangesets); - }) - .catch(() => setChangesetStatus(RequestStatus.Failed)); - }, [changesetClient, changesets, imodelId]); + }, [getVersions, versionStatus]); React.useEffect(() => { if ( @@ -278,15 +333,28 @@ export const ManageVersions = (props: ManageVersionsProps) => { const latestVersion = React.useMemo( () => - [...(versions ?? [])].sort((v1, v2) => - new Date(v1.createdDateTime).valueOf() < - new Date(v2.createdDateTime).valueOf() + [...(versionsTableData ?? [])].sort((v1, v2) => + new Date(v1.version.createdDateTime).valueOf() < + new Date(v2.version.createdDateTime).valueOf() ? 1 : -1 )[0], - [versions] + [versionsTableData] ); + 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; + }); + }; + return ( { @@ -329,7 +399,7 @@ export const ManageVersions = (props: ManageVersionsProps) => { status={changesetStatus} loadMoreChanges={getChangesets} onVersionCreated={onVersionCreated} - latestVersion={latestVersion} + latestVersion={latestVersion?.version} /> diff --git a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.scss b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.scss index a9877676..8f0eab1d 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.scss +++ b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.scss @@ -7,4 +7,4 @@ .iui-table-body .iui-table-cell { word-break: break-word; } -} \ No newline at end of file +} 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 a707fa50..71ab2bee 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 @@ -5,11 +5,13 @@ import { fireEvent, render, screen, within } from "@testing-library/react"; import React from "react"; +import { ChangesetClient } from "../../../clients/changesetClient"; import { ConfigProvider } from "../../../common/configContext"; import { MOCKED_CONFIG_PROPS, + MockedChangeset, MockedVersion, - MockedVersionList, + MockedVersionTableData, } from "../../../mocks"; import { defaultStrings } from "../ManageVersions"; import { RequestStatus } from "../types"; @@ -17,11 +19,13 @@ import VersionsTab, { VersionsTabProps } from "./VersionsTab"; const renderComponent = (initialProps?: Partial) => { const props: VersionsTabProps = { - versions: MockedVersionList(), status: RequestStatus.Finished, onVersionUpdated: jest.fn(), loadMoreVersions: jest.fn(), onViewClick: jest.fn(), + tableData: MockedVersionTableData(), + changesetClient: new ChangesetClient("token"), + setRelatedChangesets: jest.fn(), ...initialProps, }; return render( @@ -36,56 +40,120 @@ describe("VersionsTab", () => { const onViewClick = jest.fn(); const { container } = renderComponent({ onViewClick }); const rows = container.querySelectorAll( - "._iui3-table-body ._iui3-table-row" + "div[role='rowgroup'] > div[role='row']" ); - expect(rows.length).toBe(3); + expect(rows.length).toBe(1); rows.forEach((row, index) => { - const cells = row.querySelectorAll("._iui3-table-cell"); + const cells = row.querySelectorAll("div[role='cell']"); expect(cells.length).toBe(6); - expect(cells[0].textContent).toContain(MockedVersion(index).name); - expect(cells[1].textContent).toContain(MockedVersion(index).description); - expect(cells[2].textContent).toContain(MockedVersion(index).createdBy); + expect(cells[0].textContent).toContain(MockedVersion().name); + expect(cells[1].textContent).toContain(MockedVersion().description); + expect(cells[2].textContent).toContain(MockedVersion().createdBy); expect(cells[3].textContent).toContain( - new Date(MockedVersion(index).createdDateTime).toLocaleString() + new Date(MockedVersion().createdDateTime).toLocaleString() ); expect(cells[4].textContent).toContain(defaultStrings.view); - fireEvent.click(cells[4].querySelector(".iui-anchor") as HTMLElement); + const viewSpan = screen.getByText("View"); + fireEvent.click(viewSpan); within(cells[5] as HTMLElement).getByTitle( defaultStrings.updateNamedVersion ); }); - - expect(onViewClick).toHaveBeenCalledTimes(3); + expect(onViewClick).toHaveBeenCalledTimes(1); }); it("should not show view column and name should not be clickable when onViewClick is not provided", () => { const { container } = renderComponent({ onViewClick: undefined }); const rows = container.querySelectorAll( - "._iui3-table-body ._iui3-table-row" + "[role='rowgroup'] > div[role='row']" ); - expect(rows.length).toBe(3); + expect(rows.length).toBe(1); expect(screen.queryAllByText(defaultStrings.view).length).toBe(0); }); it("should show empty data message", () => { - renderComponent({ versions: [] }); + renderComponent({ tableData: [] }); screen.getByText(defaultStrings.messageNoNamedVersions); }); it("should show error message that failed to fetch named versions", () => { - renderComponent({ versions: [], status: RequestStatus.Failed }); + renderComponent({ tableData: [], status: RequestStatus.Failed }); screen.getByText(defaultStrings.messageFailedGetNamedVersions); }); it("should show spinner when data is loading", () => { const { container } = renderComponent({ - versions: [], + tableData: [], status: RequestStatus.InProgress, }); expect( container.querySelector("._iui3-progress-indicator-radial") ).toBeTruthy(); }); + + it("should show included changesets on expand", () => { + const { container } = renderComponent({ + tableData: [ + { + version: MockedVersion(), + subRows: [MockedChangeset(1)], + subRowsLoaded: true, + }, + ], + }); + // check on expand changeset data must be there + const rowgroup = container.querySelector('[role="rowgroup"]') as Element; + const rowElements = rowgroup.querySelectorAll('[role="row"]'); + const cell = container.querySelector('[role="cell"]') as Element; + expect(rowElements.length).toBe(1); + fireEvent.click( + cell.querySelector( + "div[role='row'] > div[role='cell'] > button[type='button']:first-child" + ) as HTMLElement + ); + const rowsOnExpand = rowgroup.querySelectorAll('[role="row"]'); + expect(rowsOnExpand.length).toBe(2); + + rowsOnExpand.forEach((row, index) => { + const cells = row.querySelectorAll('[role="cell"]'); + expect(cells.length).toBe(6); + if (index === 0) { + expect(cells[0].textContent).toContain(MockedVersion().name); + expect(cells[1].textContent).toContain(MockedVersion().description); + expect(cells[2].textContent).toContain(MockedVersion().createdBy); + expect(cells[3].textContent).toContain( + new Date(MockedVersion().createdDateTime).toLocaleString() + ); + expect(cells[4].textContent).toContain(defaultStrings.view); + const viewSpan = screen.getByText("View"); + fireEvent.click(viewSpan); + const updateNamedVersionButton = within( + cells[5] as HTMLElement + ).queryByTitle(defaultStrings.updateNamedVersion); + + expect(updateNamedVersionButton).toBeTruthy(); + } else { + expect(cells[0].textContent).toContain( + MockedChangeset(index).displayName + ); + expect(cells[1].textContent).toContain( + MockedChangeset(index).description + ); + expect(cells[2].textContent).toContain( + MockedChangeset(index).createdBy + ); + expect(cells[3].textContent).toContain( + new Date(MockedChangeset(index).pushDateTime).toLocaleString() + ); + expect(cells[4].textContent).not.toContain(defaultStrings.view); + const updateNamedVersionButton = within( + cells[5] as HTMLElement + ).queryByTitle(defaultStrings.updateNamedVersion); + + expect(updateNamedVersionButton).toBeFalsy(); + } + }); + }); }); 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 ecaf6815..e3ab7cd5 100644 --- a/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx +++ b/packages/modules/manage-versions/src/components/ManageVersions/VersionsTab/VersionsTab.tsx @@ -9,23 +9,40 @@ import { IconButton, Table, Text } from "@itwin/itwinui-react"; import React from "react"; import { CellProps } from "react-table"; +import { ChangesetClient } from "../../../clients/changesetClient"; import { useConfig } from "../../../common/configContext"; -import { NamedVersion } from "../../../models"; +import { Changeset, NamedVersion, VersionTableData } from "../../../models"; import { UpdateVersionModal } from "../../CreateUpdateVersion/UpdateVersionModal/UpdateVersionModal"; import { RequestStatus } from "../types"; export type VersionsTabProps = { - versions: NamedVersion[]; status: RequestStatus; onVersionUpdated: () => void; loadMoreVersions: () => void; onViewClick?: (version: NamedVersion) => void; + tableData: VersionTableData[]; + changesetClient: ChangesetClient; + setRelatedChangesets: (versionId: string, changesets: Changeset[]) => void; +}; + +const isNamedVersion = ( + obj: VersionTableData | Changeset +): obj is VersionTableData => { + return "version" in obj; }; const VersionsTab = (props: VersionsTabProps) => { - const { versions, status, onVersionUpdated, loadMoreVersions, onViewClick } = - props; - const { stringsOverrides } = useConfig(); + const { + status, + onVersionUpdated, + loadMoreVersions, + onViewClick, + tableData, + changesetClient, + setRelatedChangesets, + } = props; + + const { stringsOverrides, imodelId } = useConfig(); const [currentVersion, setCurrentVersion] = React.useState< NamedVersion | undefined @@ -33,6 +50,63 @@ const VersionsTab = (props: VersionsTabProps) => { const [isUpdateVersionModalOpen, setIsUpdateVersionModalOpen] = React.useState(false); + async function fetchIncludedChangesets(lastIndex: number) { + try { + return await changesetClient.get(imodelId, { + top: 10, + lastIndex: lastIndex, + }); + } catch (error) { + throw error; + } + } + + const renderDateColumn = React.useMemo( + () => (row: VersionTableData | Changeset) => { + if (isNamedVersion(row)) { + return ( + + {new Date(row.version["createdDateTime"]).toLocaleString()} + + ); + } else { + const content = row["pushDateTime"]; + return content !== "" ? ( + {new Date(row["pushDateTime"]).toLocaleString()} + ) : ( + Loading Date + ); + } + }, + [] + ); + + const generateCellContent = React.useMemo( + () => + ( + row: VersionTableData | Changeset, + columnAccessor: keyof NamedVersion | keyof Changeset + ) => { + if (["createdDateTime", "pushDateTime"].includes(columnAccessor)) { + return renderDateColumn(row); + } + + if (isNamedVersion(row)) { + return ( + {row.version[columnAccessor as keyof NamedVersion]} + ); + } else { + const cellContent = row[columnAccessor as keyof Changeset]; + return cellContent !== "" ? ( + {cellContent} + ) : ( + Loading + ); + } + }, + [renderDateColumn] + ); + const columns = React.useMemo(() => { const tableColumns = [ { @@ -42,20 +116,32 @@ const VersionsTab = (props: VersionsTabProps) => { id: "NAME", Header: stringsOverrides.name, accessor: "name", + Cell: (props: CellProps) => { + const columnAccessor = isNamedVersion(props.row.original) + ? "name" + : "displayName"; + return generateCellContent(props.row.original, columnAccessor); + }, }, { id: "DESCRIPTION", Header: stringsOverrides.description, accessor: "description", + Cell: (props: CellProps) => { + return generateCellContent(props.row.original, "description"); + }, }, { id: "CREATOR", Header: stringsOverrides.user ?? "User", accessor: "createdBy", maxWidth: 220, - Cell: (props: CellProps) => { - return props.row.original.createdBy !== "" ? ( - {props.row.original.createdBy} + Cell: (props: CellProps) => { + const createdBy = isNamedVersion(props.row.original) + ? props.row.original.version.createdBy + : props.row.original.createdBy; + return createdBy !== "" ? ( + {createdBy} ) : ( Loading user info ); @@ -66,32 +152,33 @@ const VersionsTab = (props: VersionsTabProps) => { Header: stringsOverrides.time, accessor: "createdDateTime", maxWidth: 220, - Cell: (props: CellProps) => { - return ( - - {new Date( - props.row.original.createdDateTime - ).toLocaleString()} - - ); + Cell: (props: CellProps) => { + const columnAccessor = isNamedVersion(props.row.original) + ? "createdDateTime" + : "pushDateTime"; + return generateCellContent(props.row.original, columnAccessor); }, }, { id: "versions-table-actions", width: 62, - Cell: (props: CellProps) => { + Cell: (props: CellProps) => { return ( <> - { - setCurrentVersion(props.row.original); - setIsUpdateVersionModalOpen(true); - }} - title={stringsOverrides.updateNamedVersion} - styleType="borderless" - > - - + {isNamedVersion(props.row.original) ? ( + { + setCurrentVersion(props.row.original.version); + setIsUpdateVersionModalOpen(true); + }} + title={stringsOverrides.updateNamedVersion} + styleType="borderless" + > + + + ) : ( + <> + )} ); }, @@ -103,14 +190,16 @@ const VersionsTab = (props: VersionsTabProps) => { tableColumns[0].columns.splice(4, 0, { id: "versions-table-view", width: 100, - Cell: (props: CellProps) => { - return ( + Cell: (props: CellProps) => { + return isNamedVersion(props.row.original) ? ( onViewClick(props.row.original)} + onClick={() => onViewClick(props.row.original.version)} > {stringsOverrides.view} + ) : ( + <> ); }, }); @@ -124,6 +213,7 @@ const VersionsTab = (props: VersionsTabProps) => { stringsOverrides.updateNamedVersion, stringsOverrides.view, onViewClick, + generateCellContent, ]); const emptyTableContent = React.useMemo(() => { @@ -136,11 +226,43 @@ const VersionsTab = (props: VersionsTabProps) => { stringsOverrides.messageNoNamedVersions, ]); + const onExpandRow = (row?: VersionTableData[]) => { + row?.forEach((r) => { + if (!r.subRowsLoaded) { + fetchIncludedChangesets(r.version.changesetIndex) + .then((changesets) => { + const relatedChangesets: Changeset[] = []; + if (changesets !== undefined) { + for (const change of changesets) { + if ( + (change.index === r.version.changesetIndex && + change._links.namedVersion !== null) || + (change.index < r.version.changesetIndex && + change._links.namedVersion === null) + ) { + relatedChangesets.push(change); + } else if ( + change.index < r.version.changesetIndex && + change._links.namedVersion !== null + ) { + break; + } + } + } + setRelatedChangesets(r.version.id ?? "", relatedChangesets); + }) + .catch(() => { + console.error("Failed to get Changesets"); + }); + } + }); + }; + return ( <> - + columns={columns} - data={versions} + data={tableData} isLoading={ status === RequestStatus.InProgress || status === RequestStatus.NotStarted @@ -148,6 +270,8 @@ const VersionsTab = (props: VersionsTabProps) => { emptyTableContent={emptyTableContent} onBottomReached={loadMoreVersions} className="iac-versions-table" + onExpand={onExpandRow} + autoResetExpanded={false} /> {isUpdateVersionModalOpen && ( { return { id: `ch${index}`, - index: `${index}`, + index: index, displayName: `${index}`, description: `ch_description${index}`, pushDateTime: MOCKED_DATE, @@ -65,6 +66,16 @@ export const MockedChangesetList = (count = 3) => { return [...new Array(count)].map((_, index) => MockedChangeset(index)); }; +export const MockedVersionTableData = () => { + return [ + { + version: MockedVersion(), + subRows: [MockedChangeset(1)], + subRowsLoaded: false, + }, + ]; +}; + export const MockedUsers = () => { return [ { diff --git a/packages/modules/manage-versions/src/models/changeset.ts b/packages/modules/manage-versions/src/models/changeset.ts index 16286134..08ec86a6 100644 --- a/packages/modules/manage-versions/src/models/changeset.ts +++ b/packages/modules/manage-versions/src/models/changeset.ts @@ -6,7 +6,7 @@ export type Changeset = { id: string; displayName: string; description: string; - index: string; + index: number; pushDateTime: string; synchronizationInfo: { changedFiles: string[]; diff --git a/packages/modules/manage-versions/src/models/namedVersion.ts b/packages/modules/manage-versions/src/models/namedVersion.ts index ce92a024..544179a4 100644 --- a/packages/modules/manage-versions/src/models/namedVersion.ts +++ b/packages/modules/manage-versions/src/models/namedVersion.ts @@ -2,7 +2,9 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -export type NamedVersion = { +import { Changeset } from "./changeset"; + +export type NamedVersionBackend = { id: string; displayName: string; name: string; @@ -16,5 +18,15 @@ export type NamedVersion = { href: string; }; }; + changesetIndex: number; +}; + +export type NamedVersion = NamedVersionBackend & { createdBy: string; }; + +export type VersionTableData = { + version: NamedVersion; + subRows: Changeset[]; + subRowsLoaded: boolean; +}; diff --git a/packages/modules/manage-versions/src/models/requestOptions.ts b/packages/modules/manage-versions/src/models/requestOptions.ts index 7e1f8677..bc1dec06 100644 --- a/packages/modules/manage-versions/src/models/requestOptions.ts +++ b/packages/modules/manage-versions/src/models/requestOptions.ts @@ -5,4 +5,5 @@ export type RequestOptions = { skip?: number; top?: number; + lastIndex?: number; }; diff --git a/packages/modules/manage-versions/src/models/utils.ts b/packages/modules/manage-versions/src/models/utils.ts index cbc45f84..f8da42fb 100644 --- a/packages/modules/manage-versions/src/models/utils.ts +++ b/packages/modules/manage-versions/src/models/utils.ts @@ -1,9 +1,9 @@ -import { InformationPanelStringOverrides } from "../components/ManageVersions/types"; - /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +import { InformationPanelStringOverrides } from "../components/ManageVersions/types"; + export const dateTimeFormatOptions: Intl.DateTimeFormatOptions = { year: "numeric", month: "short",