Container Copy Job implementation for SQL accounts (#2241)

* Initial dev for container copy

* remove padding from label

* Added Copy Job prerequisites screen

* Added hooks to evaluate reader role access

* added copyjob pre-requsite screen along with it's validations

* Added monitor copy job list screen

* added copy job list refresh and reset functionality

* remove arm token dependency

* fetch account details from account id instead of context

* Fix lint & typescript checks

* show copyjob screen from portal navigation

* adding copy job details screen

* remove duplicate code & show sql accounts only

* ui fixes for list job page

* pending icon

* copy job details screen ui

* reset .vscode/settings.json

* Fixed existing UTs

* disabling action buttons until it's in progress

* fixed formatting

* Adding loader on submit button and show job creation errors in the panel itself

* updating disabling action menu item logic

* added custom pager

* fix lint and ts errors

* updating file names and removing comments

* remove comments

* modularize the arom common code

* Adding content and removing tooltip

* updating job details screen

* updating online copy enabled screen

* Adding below changes
- Don't show permission screen for same account in offline mode
- Don't show identity permissions for same account in online mode
- Show error message if selected containers are identical
- Update abort signal messages

* added feedback code from explorer

* Add tooltips and long polling
- Added tooltips to permission sections
- Implemented long polling for PITR and online copy enabled sections
- Long polling automatically stops after 15 minutes
- After polling ends, a refresh button will be displayed

---------

Co-authored-by: nishthaAhujaa <nishtha17354@iiittd.ac.in>
This commit is contained in:
BChoudhury-ms
2025-11-05 22:54:00 +05:30
committed by GitHub
parent 3718f5a16a
commit 2417da152d
78 changed files with 4152 additions and 36 deletions

View File

@@ -0,0 +1,116 @@
import { DatabaseAccount } from "Contracts/DataModels";
import { CopyJobErrorType } from "./Types/CopyJobTypes";
const azurePortalMpacEndpoint = "https://ms.portal.azure.com/";
export const buildResourceLink = (resource: DatabaseAccount): string => {
const resourceId = resource.id;
let parentOrigin = window.location.ancestorOrigins?.[0] ?? window.location.origin;
if (/\/\/localhost:/.test(parentOrigin)) {
parentOrigin = azurePortalMpacEndpoint;
} else if (/\/\/cosmos\.azure/.test(parentOrigin)) {
parentOrigin = parentOrigin.replace("cosmos.azure", "portal.azure");
}
parentOrigin = parentOrigin.replace(/\/$/, "");
return `${parentOrigin}/#resource${resourceId}`;
};
export const COSMOS_SQL_COMPONENT = "CosmosDBSql";
export const COPY_JOB_API_VERSION = "2025-05-01-preview";
export function buildDataTransferJobPath({
subscriptionId,
resourceGroup,
accountName,
jobName,
action,
}: {
subscriptionId: string;
resourceGroup: string;
accountName: string;
jobName?: string;
action?: string;
}) {
let path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/dataTransferJobs`;
if (jobName) {
path += `/${jobName}`;
}
if (action) {
path += `/${action}`;
}
return path;
}
export function convertTime(timeStr: string): string | null {
const timeParts = timeStr.split(":").map(Number);
if (timeParts.length !== 3 || timeParts.some(isNaN)) {
return null;
}
const formatPart = (value: number, unit: string) => {
if (unit === "seconds") {
value = Math.round(value);
}
return value > 0 ? `${value.toString().padStart(2, "0")} ${unit}` : "";
};
const [hours, minutes, seconds] = timeParts;
const formattedTimeParts = [
formatPart(hours, "hours"),
formatPart(minutes, "minutes"),
formatPart(seconds, "seconds"),
]
.filter(Boolean)
.join(", ");
return formattedTimeParts || "0 seconds";
}
export function formatUTCDateTime(utcStr: string): { formattedDateTime: string; timestamp: number } | null {
const date = new Date(utcStr);
if (isNaN(date.getTime())) {
return null;
}
return {
formattedDateTime: new Intl.DateTimeFormat("en-US", {
dateStyle: "short",
timeStyle: "medium",
timeZone: "UTC",
}).format(date),
timestamp: date.getTime(),
};
}
export function convertToCamelCase(str: string): string {
const formattedStr = str
.split(/\s+/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join("");
return formattedStr;
}
export function extractErrorMessage(error: CopyJobErrorType): CopyJobErrorType {
return {
...error,
message: error.message.split("\r\n\r\n")[0],
};
}
export function getAccountDetailsFromResourceId(accountId: string | undefined) {
if (!accountId) {
return null;
}
const pattern = new RegExp(
"/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB/databaseAccounts/([^/]+)",
"i",
);
const matches = accountId.match(pattern);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, subscriptionId, resourceGroup, accountName] = matches || [];
return { subscriptionId, resourceGroup, accountName };
}