Skip to content
Open
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
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@

"typescript.format.semicolons": "insert",
"typescript.preferences.importModuleSpecifierEnding": "js",
"typescript.preferences.preferTypeOnlyAutoImports": true,
"typescript.preferences.organizeImports": {
"caseFirst": "lower",
"caseSensitivity": "caseSensitive",
"numericCollation": true,
"typeOrder": "last",
},
"typescript.reportStyleChecksAsWarnings": true,

"[javascript][typescript][typescriptreact]": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,11 @@ export class VersionCompareManager {
IModelVersion.asOfChangeSet(changesetId),
);

// Keep metadata around for UI uses and other queries
this.currentVersion = currentVersion;
this.targetVersion = targetVersion;
// Keep metadata around for UI uses and other queries. We may receive an
// immutable React state, thus a copy is needed in case user ever atttempts
// to mutate the objects.
this.currentVersion = structuredClone(currentVersion);
this.targetVersion = structuredClone(targetVersion);

this.loadingProgressEvent.raiseEvent(
IModelApp.localization.getLocalizedString("VersionCompare:versionCompare.msg_getChangedElements"),
Expand Down Expand Up @@ -414,9 +416,11 @@ export class VersionCompareManager {
IModelVersion.asOfChangeSet(changesetId),
);

// Keep metadata around for UI uses and other queries
this.currentVersion = currentVersion;
this.targetVersion = targetVersion;
// Keep metadata around for UI uses and other queries. We may receive an
// immutable React state, thus a copy is needed in case user ever atttempts
// to mutate the objects.
this.currentVersion = structuredClone(currentVersion);
this.targetVersion = structuredClone(targetVersion);

this.loadingProgressEvent.raiseEvent(
IModelApp.localization.getLocalizedString("VersionCompare:versionCompare.msg_getChangedElements"),
Expand Down
24 changes: 16 additions & 8 deletions packages/changed-elements-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
export { type FilterData, type FilterOptions, type SavedFiltersManager } from "./SavedFiltersManager.js";
export { VersionCompareContext, type VersionCompareContextValue } from "./VersionCompareContext.js";
export { type ChangedElementEntry } from "./api/ChangedElementEntryCache.js";
export * from "./api/ChangedElementsApiClient.js";
export * from "./api/ChangedElementsClientBase.js";
Expand All @@ -16,17 +14,27 @@ export { VersionCompareManager } from "./api/VersionCompareManager.js";
export * from "./api/VersionCompareTiles.js";
export * from "./api/VersionCompareVisualization.js";
export type { MainVisualizationOptions, VisualizationHandler } from "./api/VisualizationHandler.js";
export {
ComparisonJobClient, type ComparisonJobClientParams,
} from "./clients/ComparisonJobClient.js";
export type {
ComparisonJob, ComparisonJobCompleted, ComparisonJobFailed, ComparisonJobQueued,
ComparisonJobStarted,
} from "./clients/IComparisonJobClient.js";
export type {
Changeset, GetChangesetsParams, GetNamedVersionsParams, IModelsClient, NamedVersion,
} from "./clients/iModelsClient.js";
export { ITwinIModelsClient, type ITwinIModelsClientParams } from "./clients/iTwinIModelsClient.js";
export { ComparisonJobClient, type ComparisonJobClientParams } from "./clients/ComparisonJobClient.js";
export * from "./contentviews/PropertyComparisonTable.js";
export type { FilterData, FilterOptions, SavedFiltersManager } from "./SavedFiltersManager.js";
export { VersionCompareContext, type VersionCompareContextValue } from "./VersionCompareContext.js";
export * from "./widgets/ChangedElementsWidget.js";
export * from "./widgets/comparisonJobWidget/VersionCompareDialogProvider.js";
export {
VersionCompareSelectDialogV2,
} from "./widgets/comparisonJobWidget/VersionCompareSelectDialogV2.js";
export type { JobAndNamedVersions } from "./widgets/comparisonJobWidget/NamedVersions.js";
export { pollForInProgressJobs } from "./widgets/comparisonJobWidget/useNamedVersionLoader.js";
export * from "./widgets/comparisonJobWidget/versionCompareToasts.js";
export { ChangedElementsListComponent } from "./widgets/EnhancedElementsInspector.js";
export * from "./widgets/VersionCompareSelectWidget.js";
export * from "./widgets/comparisonJobWidget/components/VersionCompareSelectModal.js"
export * from "./widgets/comparisonJobWidget/components/VersionCompareDialogProvider.js"
export * from "./widgets/comparisonJobWidget/common/versionCompareToasts.js"
export type {JobAndNamedVersions} from "./widgets/comparisonJobWidget/models/ComparisonJobModels.js"
export type {ComparisonJob, ComparisonJobCompleted, ComparisonJobFailed, ComparisonJobQueued, ComparisonJobStarted} from "./clients/IComparisonJobClient.js";
12 changes: 0 additions & 12 deletions packages/changed-elements-react/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,3 @@ export async function tryXTimes<T>(func: () => Promise<T>, attempts: number, del
}
throw error;
}

/**
Creates a map from an array of values.
* Expects createKey to supply a unique key per entry; otherwise will cause other entries with same key to be overwritten.
*/
export const arrayToMap = <T, U>(array: T[], createKey: (entry: T) => U) => {
const newMap = new Map<U, T>();
array.forEach((entry) => {
newMap.set(createKey(entry), entry);
});
return newMap;
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,37 @@
*--------------------------------------------------------------------------------------------*/
import { BeEvent, Logger, type Id64String } from "@itwin/core-bentley";
import {
IModelApp, IModelConnection, NotifyMessageDetails, OutputMessagePriority, ScreenViewport
IModelApp, NotifyMessageDetails, OutputMessagePriority, type IModelConnection,
type ScreenViewport
} from "@itwin/core-frontend";
import { SvgAdd, SvgCompare, SvgExport, SvgStop } from "@itwin/itwinui-icons-react";
import { IconButton, ProgressRadial } from "@itwin/itwinui-react";
import { Component, ReactElement, ReactNode } from "react";
import { FilterOptions } from "../SavedFiltersManager.js";
import { type ChangedElementEntry } from "../api/ChangedElementEntryCache.js";
import { ReportProperty } from "../api/ReportGenerator.js";
import { Component, type ReactElement, type ReactNode } from "react";

import type { FilterOptions } from "../SavedFiltersManager.js";
import type { ChangedElementEntry } from "../api/ChangedElementEntryCache.js";
import type { ReportProperty } from "../api/ReportGenerator.js";
import { VersionCompareUtils, VersionCompareVerboseMessages } from "../api/VerboseMessages.js";
import { VersionCompare } from "../api/VersionCompare.js";
import { VersionCompareManager } from "../api/VersionCompareManager.js";
import type { VersionCompareManager } from "../api/VersionCompareManager.js";
import { CenteredDiv } from "../common/CenteredDiv.js";
import { EmptyStateComponent } from "../common/EmptyStateComponent.js";
import { Widget as WidgetComponent } from "../common/Widget/Widget.js";
import { PropertyLabelCache } from "../dialogs/PropertyLabelCache.js";
import { ReportGeneratorDialog } from "../dialogs/ReportGeneratorDialog.js";
import { ChangedElementsInspector } from "./EnhancedElementsInspector.js";
import "./ChangedElementsWidget.scss";
import InfoButton from "./InformationButton.js";
import { VersionCompareSelectDialogV2 } from "./comparisonJobWidget/components/VersionCompareSelectModal.js";
import { FeedbackButton } from "./FeedbackButton.js";
import InfoButton from "./InformationButton.js";
import { VersionCompareSelectDialog } from "./VersionCompareSelectWidget.js";
import { ComparisonJobUpdateType, VersionCompareSelectProviderV2 } from "./comparisonJobWidget/components/VersionCompareDialogProvider.js";
import { JobAndNamedVersions } from "./comparisonJobWidget/models/ComparisonJobModels.js";
import type { JobAndNamedVersions } from "./comparisonJobWidget/NamedVersions.js";
import {
VersionCompareSelectProviderV2, type ComparisonJobUpdateType
} from "./comparisonJobWidget/VersionCompareDialogProvider.js";
import {
VersionCompareSelectDialogV2
} from "./comparisonJobWidget/VersionCompareSelectDialogV2.js";

import "./ChangedElementsWidget.scss";

export const changedElementsWidgetAttachToViewportEvent = new BeEvent<(vp: ScreenViewport) => void>();

Expand Down Expand Up @@ -409,22 +416,33 @@ export class ChangedElementsWidget extends Component<ChangedElementsWidgetProps,
initialProperties={this.state.reportProperties}
/>
}
{this.props.useV2Widget ?
<VersionCompareSelectProviderV2 onJobUpdate={this.props.onJobUpdate} enableComparisonJobUpdateToasts={this.props.enableComparisonJobUpdateToasts}>
{this.state.versionSelectDialogVisible &&
<VersionCompareSelectDialogV2
data-testid="⁠comparison-widget-v2-modal"
iModelConnection={this.props.iModelConnection}
onClose={this._handleVersionSelectDialogClose}
manageNamedVersionsSlot={this.props.manageNamedVersionsSlot}
/>}
</VersionCompareSelectProviderV2> :
this.state.versionSelectDialogVisible &&
<VersionCompareSelectDialog
isOpen
iModelConnection={this.props.iModelConnection}
onClose={this._handleVersionSelectDialogClose}
/>}
<VersionCompareSelectProviderV2
onJobUpdate={this.props.onJobUpdate}
enableComparisonJobUpdateToasts={
!this.state.versionSelectDialogVisible && this.props.enableComparisonJobUpdateToasts
}
>
{
this.state.versionSelectDialogVisible &&
(
this.props.useV2Widget
? (
<VersionCompareSelectDialogV2
data-testid="comparison-widget-v2-modal"
iModelConnection={this.props.iModelConnection}
onClose={this._handleVersionSelectDialogClose}
manageNamedVersionsSlot={this.props.manageNamedVersionsSlot}
/>
) : (
<VersionCompareSelectDialog
isOpen
iModelConnection={this.props.iModelConnection}
onClose={this._handleVersionSelectDialogClose}
/>
)
)
}
</VersionCompareSelectProviderV2>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,33 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { ComparisonJob } from "../../../clients/IComparisonJobClient";
import { NamedVersion } from "../../../clients/iModelsClient";
import type { ComparisonJob } from "../../clients/IComparisonJobClient.js";
import type { NamedVersion } from "../../clients/iModelsClient.js";

/**
* Holds the version state of named versions and the current version.
*/
export interface CurrentNamedVersionAndNamedVersions {
entries: NamedVersion[];
versionState: VersionState[];
currentVersion: NamedVersion | undefined;
}

export type VersionState = {
jobId: string;
state: VersionProcessedState;
// nullable because we don't run jobs in V1. For v2 use only.
jobStatus?: JobStatus;
// nullable because we don't run jobs in V1. For v2 use only.
jobProgress?: JobProgress;
};

export enum VersionProcessedState {
Verifying,
Processed,
Processing,
Unavailable,
}

/**
* Job status used for identification of job progress
Expand All @@ -17,26 +42,16 @@ import { NamedVersion } from "../../../clients/iModelsClient";
*/
export type JobStatus = "Unknown" | "Available" | "Not Processed" | "Processing" | "Error" | "Queued";

/**
* Used to display progress of a job.
* current progress / maximum progress.
*/
export type JobProgress = {
currentProgress: number;
maxProgress: number;
};

/**
* Holds both the job progress and job status.
*/
export type JobStatusAndJobProgress = {
jobStatus: JobStatus;
jobProgress: JobProgress;
};

/**
* Holds comparison job and its named versions.
*/
export type JobAndNamedVersions = {
comparisonJob?: ComparisonJob;
targetNamedVersion: NamedVersion;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { createContext, type ReactElement, type ReactNode, useRef } from "react";

import type { JobAndNamedVersions } from "./NamedVersions.js";

/**
* Comparison Job Update Type
* - "JobComplete" = job is completed
* - "JobError" = job error
* - "JobProcessing" = job is started
* - "ComparisonVisualizationStarting" = version compare visualization is starting
*/
export type ComparisonJobUpdateType = "JobComplete" | "JobError" | "JobProcessing"
| "ComparisonVisualizationStarting";

export interface V2Context {
addRunningJob: (jobId: string, comparisonJob: JobAndNamedVersions) => void;
removeRunningJob: (jobId: string) => void;
getRunningJobs: () => JobAndNamedVersions[];
getPendingJobs: () => JobAndNamedVersions[];
addPendingJob: (jobId: string, comparisonJob: JobAndNamedVersions) => void;
removePendingJob: (jobId: string) => void;
getToastsEnabled: () => boolean;
runOnJobUpdate: (
comparisonJobUpdateType: ComparisonJobUpdateType,
jobAndNamedVersions?: JobAndNamedVersions,
) => Promise<void>;
}

export const V2DialogContext = createContext<V2Context>({} as V2Context);

export type V2DialogProviderProps = {
children: ReactNode;

/**
* Optional. When enabled will toast messages regarding job status. If not defined,
* will default to false and will not show toasts.
*/
enableComparisonJobUpdateToasts?: boolean;

/**
* Optional. A call back function for handling job updates.
* @param comparisonJobUpdateType param for the type of update:
* - "JobComplete" = invoked when job is completed
* - "JobError" = invoked on job error
* - "JobProcessing" = invoked on job is started
* - "ComparisonVisualizationStarting" = invoked on when version compare visualization is starting
* @param jobAndNamedVersion param contain job and named version info to be passed to call back
*/
onJobUpdate?: (
comparisonJobUpdateType: ComparisonJobUpdateType,
jobAndNamedVersions?: JobAndNamedVersions,
) => Promise<void>;
};

/**
* V2DialogProvider use comparison jobs for processing. Used for tracking if the
* dialog is open or closed. This is useful for managing toast messages associated
* with dialog. Also caches comparison jobs that are pending creation or are currently
* running. To help populate new modal ref.
*
* @exmaple
* <V2DialogProvider>
* {
* isOpenCondition &&
* <VersionCompareSelectDialogV2
* iModelConnection={this.props.iModelConnection}
* onClose={this._handleVersionSelectDialogClose}
* />
* }
* </V2DialogProvider>
*/
export function VersionCompareSelectProviderV2(
{ children, enableComparisonJobUpdateToasts, onJobUpdate }: V2DialogProviderProps,
): ReactElement {
const dialogRunningJobs = useRef(new Map<string, JobAndNamedVersions>());
const dialogPendingJobs = useRef(new Map<string, JobAndNamedVersions>());
const addRunningJob = (jobId: string, jobAndNamedVersions: JobAndNamedVersions) => {
dialogRunningJobs.current.set(jobId, jobAndNamedVersions);
};
const removeRunningJob = (jobId: string) => {
dialogRunningJobs.current.delete(jobId);
};
const getRunningJobs = () => Array.from(dialogRunningJobs.current.values());
const addPendingJob = (jobId: string, jobAndNamedVersions: JobAndNamedVersions) => {
dialogPendingJobs.current.set(jobId, jobAndNamedVersions);
};
const removePendingJob = (jobId: string) => {
dialogPendingJobs.current.delete(jobId);
};
const getPendingJobs = () => Array.from(dialogPendingJobs.current.values());

// This is a hack to get the most recent value of enableComparisonJobUpdateToasts
// in useNamedVersionLoader because the effects there don't list all their dependencies
const enableComparisonJobUpdateToastsRef = useRef(enableComparisonJobUpdateToasts);
enableComparisonJobUpdateToastsRef.current = enableComparisonJobUpdateToasts;

const getToastsEnabled = () => enableComparisonJobUpdateToastsRef.current ?? false;
const runOnJobUpdate = async (
comparisonEventType: ComparisonJobUpdateType,
jobAndNamedVersions?: JobAndNamedVersions,
) => {
await onJobUpdate?.(comparisonEventType, jobAndNamedVersions);
};
return (
<V2DialogContext.Provider value={{
addRunningJob, removeRunningJob, getRunningJobs, getPendingJobs, addPendingJob,
removePendingJob, getToastsEnabled, runOnJobUpdate,
}}>
{children}
</V2DialogContext.Provider>
);
}
Loading
Loading