import { DefaultButton, DirectionalHint, FontWeights, IContextualMenuProps, IMessageBarStyles, IconButton, Link, MessageBar, MessageBarType, PrimaryButton, ProgressIndicator, Stack, Text, } from "@fluentui/react"; import * as React from "react"; import * as ViewModels from "../../../../Contracts/ViewModels"; import { handleError } from "Common/ErrorHandlingUtils"; import { cancelDataTransferJob, completeDataTransferJob, pauseDataTransferJob, pollDataTransferJob, resumeDataTransferJob, } from "Common/dataAccess/dataTransfers"; import { Platform, configContext } from "ConfigContext"; import { CopyJobActions, CopyJobMigrationType } from "Explorer/ContainerCopy/Enums/CopyJobEnums"; import { useDialog } from "Explorer/Controls/Dialog"; import Explorer from "Explorer/Explorer"; import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane"; import { Keys, t } from "Localization"; import { CosmosSqlDataTransferDataSourceSink, DataTransferJobGetResults, } from "Utils/arm/generatedClients/dataTransferService/types"; import { refreshDataTransferJobs, useDataTransferJobs } from "hooks/useDataTransferJobs"; import { useSidePanel } from "hooks/useSidePanel"; import { userContext } from "../../../../UserContext"; export interface PartitionKeyComponentProps { database: ViewModels.Database; collection: ViewModels.Collection; explorer: Explorer; isReadOnly?: boolean; // true: cannot change partition key } const darkThemeMessageBarStyles: Partial = { root: { selectors: { "&.ms-MessageBar--warning": { backgroundColor: "var(--colorStatusWarningBackground1)", border: "1px solid var(--colorStatusWarningBorder1)", }, ".ms-MessageBar-icon": { color: "var(--colorNeutralForeground1)", }, ".ms-MessageBar-text": { color: "var(--colorNeutralForeground1)", }, }, }, }; export const PartitionKeyComponent: React.FC = ({ database, collection, explorer, isReadOnly, }) => { const { dataTransferJobs } = useDataTransferJobs(); const [portalDataTransferJob, setPortalDataTransferJob] = React.useState(null); React.useEffect(() => { if (isReadOnly) { return; } const loadDataTransferJobs = refreshDataTransferOperations; loadDataTransferJobs(); }, [isReadOnly]); React.useEffect(() => { const currentJob = findPortalDataTransferJob(); setPortalDataTransferJob(currentJob); startPollingforUpdate(currentJob); }, [dataTransferJobs]); const isHierarchicalPartitionedContainer = (): boolean => collection.partitionKey?.kind === "MultiHash"; const getPartitionKeyValue = (): string => { return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", "); }; const partitionKeyName = t(Keys.controls.settings.partitionKey.partitionKey); const partitionKeyValue = getPartitionKeyValue(); const textHeadingStyle = { root: { fontWeight: FontWeights.semibold, fontSize: 16, color: "var(--colorNeutralForeground1)" }, }; const textSubHeadingStyle = { root: { fontWeight: FontWeights.semibold, color: "var(--colorNeutralForeground1)" }, }; const textSubHeadingStyle1 = { root: { color: "var(--colorNeutralForeground1)" }, }; const startPollingforUpdate = async (currentJob: DataTransferJobGetResults) => { if (isCurrentJobInProgress(currentJob)) { const jobName = currentJob?.properties?.jobName; try { await pollDataTransferJob( jobName, userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, ); } catch (error) { handleError(error, "ChangePartitionKey", `Failed to complete data transfer job ${jobName}`); } } }; const cancelRunningDataTransferJob = async (currentJob: DataTransferJobGetResults) => { await cancelDataTransferJob( userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, currentJob?.properties?.jobName, ); }; const pauseRunningDataTransferJob = async (currentJob: DataTransferJobGetResults) => { await pauseDataTransferJob( userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, currentJob?.properties?.jobName, ); }; const resumePausedDataTransferJob = async (currentJob: DataTransferJobGetResults) => { await resumeDataTransferJob( userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, currentJob?.properties?.jobName, ); startPollingforUpdate(currentJob); }; const completeOnlineDataTransferJob = async (currentJob: DataTransferJobGetResults) => { await completeDataTransferJob( userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, currentJob?.properties?.jobName, ); }; const isOnlineJob = (currentJob: DataTransferJobGetResults): boolean => { const mode = (currentJob?.properties?.mode ?? "").toLowerCase(); return mode === CopyJobMigrationType.Online; }; const showActionConfirmationDialog = ( currentJob: DataTransferJobGetResults, action: CopyJobActions, onConfirm: () => void, ): void => { const jobName = currentJob?.properties?.jobName; const dialogBody = action === CopyJobActions.cancel ? ( {t(Keys.controls.settings.partitionKeyEditor.confirmCancel1)}
{jobName}
{t(Keys.controls.settings.partitionKeyEditor.confirmCancel2)}
) : action === CopyJobActions.complete ? ( {t(Keys.controls.settings.partitionKeyEditor.confirmComplete1)}
{jobName}
{t(Keys.controls.settings.partitionKeyEditor.confrimComplete2)}
) : null; useDialog .getState() .showOkCancelModalDialog("", null, t(Keys.common.confirm), onConfirm, t(Keys.common.cancel), null, dialogBody); }; const getOnlineJobMenuProps = (currentJob: DataTransferJobGetResults): IContextualMenuProps => { const jobStatus = currentJob?.properties?.status; const isPaused = jobStatus === "Paused"; const items: IContextualMenuProps["items"] = []; if (!isPaused) { items.push({ key: CopyJobActions.pause, text: t(Keys.containerCopy.monitorJobs.actions.pause), iconProps: { iconName: "Pause" }, onClick: () => { pauseRunningDataTransferJob(currentJob); }, }); } if (isPaused) { items.push({ key: CopyJobActions.resume, text: t(Keys.containerCopy.monitorJobs.actions.resume), iconProps: { iconName: "Play" }, onClick: () => { resumePausedDataTransferJob(currentJob); }, }); } items.push({ key: CopyJobActions.cancel, text: t(Keys.common.cancel), iconProps: { iconName: "Cancel" }, onClick: () => showActionConfirmationDialog(currentJob, CopyJobActions.cancel, () => cancelRunningDataTransferJob(currentJob)), }); items.push({ key: CopyJobActions.complete, text: t(Keys.containerCopy.monitorJobs.actions.complete), iconProps: { iconName: "CheckMark" }, onClick: () => showActionConfirmationDialog(currentJob, CopyJobActions.complete, () => completeOnlineDataTransferJob(currentJob), ), }); return { items, directionalHint: DirectionalHint.leftTopEdge, directionalHintFixed: false, }; }; const isCurrentJobInProgress = (currentJob: DataTransferJobGetResults) => { const jobStatus = currentJob?.properties?.status; return ( jobStatus && jobStatus !== "Completed" && jobStatus !== "Cancelled" && jobStatus !== "Failed" && jobStatus !== "Faulted" ); }; const refreshDataTransferOperations = async () => { await refreshDataTransferJobs( userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, ); }; const findPortalDataTransferJob = (): DataTransferJobGetResults => { return dataTransferJobs.find((feed: DataTransferJobGetResults) => { const sourceSink: CosmosSqlDataTransferDataSourceSink = feed?.properties ?.source as CosmosSqlDataTransferDataSourceSink; return sourceSink.databaseName === collection.databaseId && sourceSink.containerName === collection.id(); }); }; const getProgressDescription = (): string => { const processedCount = portalDataTransferJob?.properties?.processedCount; const totalCount = portalDataTransferJob?.properties?.totalCount; const processedCountString = totalCount > 0 ? t(Keys.controls.settings.partitionKeyEditor.documentsProcessed, { processedCount: String(processedCount), totalCount: String(totalCount), }) : ""; return `${portalDataTransferJob?.properties?.status} ${processedCountString}`; }; const startPartitionkeyChangeWorkflow = () => { useSidePanel.getState().openSidePanel( t(Keys.controls.settings.partitionKeyEditor.changePartitionKey, { partitionKeyName: t(Keys.controls.settings.partitionKey.partitionKey).toLowerCase(), }), , ); }; const getPercentageComplete = () => { const jobStatus = portalDataTransferJob?.properties?.status; const isCompleted = jobStatus === "Completed"; if (isCompleted) { return 1; } const processedCount = portalDataTransferJob?.properties?.processedCount; const totalCount = portalDataTransferJob?.properties?.totalCount; const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob); return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0; }; return ( {!isReadOnly && ( {t(Keys.controls.settings.partitionKeyEditor.changePartitionKey, { partitionKeyName: partitionKeyName.toLowerCase(), })} )} {t(Keys.controls.settings.partitionKeyEditor.currentPartitionKey, { partitionKeyName: partitionKeyName.toLowerCase(), })} {t(Keys.controls.settings.partitionKeyEditor.partitioning)} {partitionKeyValue} {isHierarchicalPartitionedContainer() ? t(Keys.controls.settings.partitionKeyEditor.hierarchical) : t(Keys.controls.settings.partitionKeyEditor.nonHierarchical)} {!isReadOnly && ( <> {t(Keys.controls.settings.partitionKeyEditor.safeguardWarning)} {t(Keys.common.learnMore)} {t(Keys.controls.settings.partitionKeyEditor.changeDescription)} {configContext.platform !== Platform.Emulator && ( )} {portalDataTransferJob && ( {t(Keys.controls.settings.partitionKeyEditor.changeJob, { partitionKeyName })} {isCurrentJobInProgress(portalDataTransferJob) && (isOnlineJob(portalDataTransferJob) ? ( ) : ( cancelRunningDataTransferJob(portalDataTransferJob)} /> ))} )} )} ); };