Merge branch 'master' into testreport_view

This commit is contained in:
sunghyunkang1111
2026-05-20 11:18:49 -05:00
committed by GitHub
110 changed files with 5815 additions and 1624 deletions
+42 -43
View File
@@ -875,9 +875,10 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -1752,15 +1753,16 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
"version": "7.29.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz",
"integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.25.0",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/traverse": "^7.25.0"
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
@@ -10991,13 +10993,13 @@
}
},
"node_modules/axios": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
"integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}
@@ -15339,9 +15341,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"funding": [
{
"type": "github",
@@ -15351,7 +15353,8 @@
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
]
],
"license": "BSD-3-Clause"
},
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
@@ -15560,9 +15563,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"dev": true,
"funding": [
{
@@ -15570,6 +15573,7 @@
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
@@ -22925,7 +22929,9 @@
"license": "ISC"
},
"node_modules/minimatch": {
"version": "3.1.2",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -25352,25 +25358,16 @@
}
},
"node_modules/recursive-readdir": {
"version": "2.2.2",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
"integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimatch": "3.0.4"
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/recursive-readdir/node_modules/minimatch": {
"version": "3.0.4",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
"node": ">=6.0.0"
}
},
"node_modules/redent": {
@@ -27498,21 +27495,23 @@
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
"integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/typedoc/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
+19 -3
View File
@@ -19,9 +19,16 @@ export interface MinimalQueryIterator {
// Pick<QueryIterator<any>, "fetchNext">;
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
TelemetryProcessor.traceStart(Action.ExecuteQuery);
return documentsIterator.fetchNext().then((response) => {
TelemetryProcessor.traceSuccess(Action.ExecuteQuery, { dataExplorerArea: Constants.Areas.Tab });
const startKey = TelemetryProcessor.traceStart(Action.ExecuteQuery);
return documentsIterator
.fetchNext()
.then((response) => {
const durationMs = Date.now() - startKey;
TelemetryProcessor.traceSuccess(
Action.ExecuteQuery,
{ dataExplorerArea: Constants.Areas.Tab, durationMs },
startKey,
);
const documents = response.resources;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any
@@ -36,5 +43,14 @@ export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex
activityId: response.activityId,
requestCharge: response.requestCharge,
};
})
.catch((error) => {
const durationMs = Date.now() - startKey;
TelemetryProcessor.traceFailure(
Action.ExecuteQuery,
{ dataExplorerArea: Constants.Areas.Tab, durationMs, error: error.message },
startKey,
);
throw error;
});
}
+5 -4
View File
@@ -34,6 +34,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
databaseId: params.databaseId,
databaseLevelThroughput: params.databaseLevelThroughput,
offerThroughput: params.offerThroughput,
targetAccountOverride: params.targetAccountOverride,
};
await createDatabase(createDatabaseParams);
}
@@ -63,7 +64,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
};
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
if (!params.createNewDatabase) {
if (!params.createNewDatabase && !params.targetAccountOverride) {
const isValid = await useDatabases.getState().validateCollectionId(params.databaseId, params.collectionId);
if (!isValid) {
const collectionName = getCollectionName().toLocaleLowerCase();
@@ -122,9 +123,9 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
};
const createResponse = await createUpdateSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.targetAccountOverride?.subscriptionId ?? userContext.subscriptionId,
params.targetAccountOverride?.resourceGroup ?? userContext.resourceGroup,
params.targetAccountOverride?.accountName ?? userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload,
@@ -0,0 +1,137 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
jest.mock("../../Utils/arm/generatedClients/cosmos/sqlResources");
import ko from "knockout";
import { AuthType } from "../../AuthType";
import { CreateDatabaseParams, DatabaseAccount } from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { useDatabases } from "../../Explorer/useDatabases";
import { updateUserContext } from "../../UserContext";
import { createUpdateSqlDatabase } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { SqlDatabaseGetResults } from "../../Utils/arm/generatedClients/cosmos/types";
import { createDatabase } from "./createDatabase";
const mockCreateUpdateSqlDatabase = createUpdateSqlDatabase as jest.MockedFunction<typeof createUpdateSqlDatabase>;
describe("createDatabase", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: { name: "default-account" } as DatabaseAccount,
subscriptionId: "default-subscription",
resourceGroup: "default-rg",
apiType: "SQL",
authType: AuthType.AAD,
});
});
beforeEach(() => {
jest.clearAllMocks();
mockCreateUpdateSqlDatabase.mockResolvedValue({
properties: { resource: { id: "db", _rid: "", _self: "", _ts: 0, _etag: "" } },
} as SqlDatabaseGetResults);
useDatabases.setState({
databases: [],
validateDatabaseId: () => true,
} as unknown as ReturnType<typeof useDatabases.getState>);
});
it("should call ARM createUpdateSqlDatabase when logged in with AAD", async () => {
await createDatabase({ databaseId: "testDb" });
expect(mockCreateUpdateSqlDatabase).toHaveBeenCalled();
});
describe("targetAccountOverride behavior", () => {
it("should use targetAccountOverride subscriptionId, resourceGroup, and accountName for SQL DB creation", async () => {
const params: CreateDatabaseParams = {
databaseId: "testDb",
targetAccountOverride: {
subscriptionId: "override-sub",
resourceGroup: "override-rg",
accountName: "override-account",
capabilities: [],
},
};
await createDatabase(params);
expect(mockCreateUpdateSqlDatabase).toHaveBeenCalledWith(
"override-sub",
"override-rg",
"override-account",
"testDb",
expect.any(Object),
);
});
it("should use userContext values when targetAccountOverride is not provided", async () => {
await createDatabase({ databaseId: "testDb" });
expect(mockCreateUpdateSqlDatabase).toHaveBeenCalledWith(
"default-subscription",
"default-rg",
"default-account",
"testDb",
expect.any(Object),
);
});
it("should skip validateDatabaseId check when targetAccountOverride is provided", async () => {
// Simulate database already existing — validateDatabaseId returns false
useDatabases.setState({
databases: [{ id: ko.observable("testDb") } as unknown as ViewModels.Database],
validateDatabaseId: () => false,
} as unknown as ReturnType<typeof useDatabases.getState>);
const params: CreateDatabaseParams = {
databaseId: "testDb",
targetAccountOverride: {
subscriptionId: "override-sub",
resourceGroup: "override-rg",
accountName: "override-account",
capabilities: [],
},
};
// Should NOT throw even though the normal duplicate check would fail
await expect(createDatabase(params)).resolves.not.toThrow();
expect(mockCreateUpdateSqlDatabase).toHaveBeenCalled();
});
it("should throw if validateDatabaseId returns false and no targetAccountOverride is set", async () => {
useDatabases.setState({
databases: [{ id: ko.observable("existingDb") } as unknown as ViewModels.Database],
validateDatabaseId: () => false,
} as unknown as ReturnType<typeof useDatabases.getState>);
await expect(createDatabase({ databaseId: "existingDb" })).rejects.toThrow();
expect(mockCreateUpdateSqlDatabase).not.toHaveBeenCalled();
});
it("should pass databaseId in request payload regardless of targetAccountOverride", async () => {
const params: CreateDatabaseParams = {
databaseId: "my-database",
targetAccountOverride: {
subscriptionId: "any-sub",
resourceGroup: "any-rg",
accountName: "any-account",
capabilities: [],
},
};
await createDatabase(params);
expect(mockCreateUpdateSqlDatabase).toHaveBeenCalledWith(
expect.any(String),
expect.any(String),
expect.any(String),
"my-database",
expect.objectContaining({
properties: expect.objectContaining({
resource: expect.objectContaining({ id: "my-database" }),
}),
}),
);
});
});
});
+5 -8
View File
@@ -41,7 +41,7 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
}
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
if (!useDatabases.getState().validateDatabaseId(params.databaseId)) {
if (!params.targetAccountOverride && !useDatabases.getState().validateDatabaseId(params.databaseId)) {
const databaseName = getDatabaseName().toLocaleLowerCase();
throw new Error(`Create ${databaseName} failed: ${databaseName} with id ${params.databaseId} already exists`);
}
@@ -72,13 +72,10 @@ async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promi
options,
},
};
const createResponse = await createUpdateSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload,
);
const sub = params.targetAccountOverride?.subscriptionId ?? userContext.subscriptionId;
const rg = params.targetAccountOverride?.resourceGroup ?? userContext.resourceGroup;
const acct = params.targetAccountOverride?.accountName ?? userContext.databaseAccount.name;
const createResponse = await createUpdateSqlDatabase(sub, rg, acct, params.databaseId, rpPayload);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
+72 -6
View File
@@ -1,11 +1,15 @@
import { configContext } from "ConfigContext";
import { Keys, t } from "Localization";
import { ApiType, userContext } from "UserContext";
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import {
cancel,
complete,
create,
get,
listByDatabaseAccount,
pause,
resume,
} from "Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
import {
CosmosCassandraDataTransferDataSourceSink,
@@ -31,6 +35,7 @@ export interface DataTransferParams {
sourceCollectionName: string;
targetDatabaseName: string;
targetCollectionName: string;
mode?: "Offline" | "Online";
}
export const getDataTransferJobs = async (
@@ -80,6 +85,7 @@ export const initiateDataTransfer = async (params: DataTransferParams): Promise<
sourceCollectionName,
targetDatabaseName,
targetCollectionName,
mode,
} = params;
const sourcePayload = createPayload(apiType, sourceDatabaseName, sourceCollectionName);
const targetPayload = createPayload(apiType, targetDatabaseName, targetCollectionName);
@@ -87,6 +93,7 @@ export const initiateDataTransfer = async (params: DataTransferParams): Promise<
properties: {
source: sourcePayload,
destination: targetPayload,
...(mode ? { mode } : {}),
},
};
return create(subscriptionId, resourceGroupName, accountName, jobName, body);
@@ -137,30 +144,52 @@ const pollDataTransferJobOperation = async (
if (status === "Cancelled") {
removeFromPolling(jobName);
clearMessage && clearMessage();
const cancelMessage = `Data transfer job ${jobName} cancelled`;
const cancelMessage = t(Keys.containerCopy.dataTransfers.polling.cancelConsoleMessage, { jobName: jobName });
NotificationConsoleUtils.logConsoleError(cancelMessage);
throw new AbortError(cancelMessage);
}
if (status === "Paused") {
removeFromPolling(jobName);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleInfo(
t(Keys.containerCopy.dataTransfers.polling.pauseConsoleMessage, { jobName: jobName }),
);
return body;
}
if (status === "Failed" || status === "Faulted") {
removeFromPolling(jobName);
const errorMessage = body?.properties?.error
? JSON.stringify(body?.properties?.error)
: "Operation could not be completed";
: t(Keys.containerCopy.dataTransfers.polling.defaultErrorMessage);
const error = new Error(errorMessage);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`);
NotificationConsoleUtils.logConsoleError(
t(Keys.containerCopy.dataTransfers.polling.errorConsoleMessage, {
errorMessage: errorMessage,
jobName: jobName,
}),
);
throw new AbortError(error);
}
if (status === "Completed") {
removeFromPolling(jobName);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleInfo(`Data transfer job ${jobName} completed`);
NotificationConsoleUtils.logConsoleInfo(
t(Keys.containerCopy.dataTransfers.polling.completedConsoleMessage, {
jobName: jobName,
}),
);
return body;
}
const processedCount = body.properties.processedCount;
const totalCount = body.properties.totalCount;
const retryMessage = `Data transfer job ${jobName} in progress, total count: ${totalCount}, processed count: ${processedCount}`;
throw new Error(retryMessage);
throw new Error(
t(Keys.containerCopy.dataTransfers.polling.retryConsoleMessage, {
jobName: jobName,
processedCount: processedCount,
totalCount: totalCount,
}),
);
};
export const cancelDataTransferJob = async (
@@ -174,6 +203,43 @@ export const cancelDataTransferJob = async (
removeFromPolling(cancelResult?.properties?.jobName);
};
export const pauseDataTransferJob = async (
subscriptionId: string,
resourceGroupName: string,
accountName: string,
jobName: string,
): Promise<void> => {
const pauseResult: DataTransferJobGetResults = await pause(subscriptionId, resourceGroupName, accountName, jobName);
updateDataTransferJob(pauseResult);
removeFromPolling(pauseResult?.properties?.jobName);
};
export const resumeDataTransferJob = async (
subscriptionId: string,
resourceGroupName: string,
accountName: string,
jobName: string,
): Promise<void> => {
const resumeResult: DataTransferJobGetResults = await resume(subscriptionId, resourceGroupName, accountName, jobName);
updateDataTransferJob(resumeResult);
};
export const completeDataTransferJob = async (
subscriptionId: string,
resourceGroupName: string,
accountName: string,
jobName: string,
): Promise<void> => {
const completeResult: DataTransferJobGetResults = await complete(
subscriptionId,
resourceGroupName,
accountName,
jobName,
);
updateDataTransferJob(completeResult);
removeFromPolling(completeResult?.properties?.jobName);
};
const createPayload = (
apiType: ApiType,
databaseName: string,
+148 -1
View File
@@ -1,11 +1,12 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { updateUserContext } from "../../UserContext";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { readDatabases } from "./readDatabases";
import { readDatabases, readDatabasesWithARM } from "./readDatabases";
describe("readDatabases", () => {
beforeAll(() => {
@@ -42,3 +43,149 @@ describe("readDatabases", () => {
expect(client).toHaveBeenCalled();
});
});
describe("readDatabasesWithARM (with accountOverride)", () => {
const mockDatabase = { id: "testDb", _rid: "", _self: "", _etag: "", _ts: 0 };
const mockArmResponse = { value: [{ properties: { resource: mockDatabase } }] };
beforeAll(() => {
updateUserContext({
databaseAccount: { name: "context-account" } as DatabaseAccount,
subscriptionId: "context-sub",
resourceGroup: "context-rg",
apiType: "SQL",
});
});
beforeEach(() => {
jest.clearAllMocks();
});
it("should call ARM with a path that includes the provided subscriptionId, resourceGroup, and accountName", async () => {
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({ subscriptionId: "test-sub", resourceGroup: "test-rg", accountName: "test-account" });
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({
path: expect.stringContaining("/subscriptions/test-sub/resourceGroups/test-rg/"),
}),
);
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({
path: expect.stringContaining("/databaseAccounts/test-account/sqlDatabases"),
}),
);
});
it("should use apiType from accountOverride when provided (SQL)", async () => {
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account", apiType: "SQL" });
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({ path: expect.stringContaining("/sqlDatabases") }),
);
});
it("should use apiType from accountOverride when provided (Mongo)", async () => {
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({
subscriptionId: "sub",
resourceGroup: "rg",
accountName: "account",
apiType: "Mongo",
});
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({ path: expect.stringContaining("/mongodbDatabases") }),
);
});
it("should use apiType from accountOverride when provided (Cassandra)", async () => {
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({
subscriptionId: "sub",
resourceGroup: "rg",
accountName: "account",
apiType: "Cassandra",
});
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({ path: expect.stringContaining("/cassandraKeyspaces") }),
);
});
it("should use apiType from accountOverride when provided (Gremlin)", async () => {
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({
subscriptionId: "sub",
resourceGroup: "rg",
accountName: "account",
apiType: "Gremlin",
});
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({ path: expect.stringContaining("/gremlinDatabases") }),
);
});
it("should fall back to userContext.apiType when apiType is not in accountOverride", async () => {
updateUserContext({ apiType: "Mongo" });
(armRequest as jest.Mock).mockResolvedValue(mockArmResponse);
await readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account" });
expect(armRequest).toHaveBeenCalledWith(
expect.objectContaining({ path: expect.stringContaining("/mongodbDatabases") }),
);
updateUserContext({ apiType: "SQL" }); // restore
});
it("should throw for unsupported apiType", async () => {
await expect(
readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account", apiType: "Tables" }),
).rejects.toThrow("Unsupported default experience type: Tables");
});
it("should return mapped database resources from the response", async () => {
const db1 = { id: "db1", _rid: "r1", _self: "/dbs/db1", _etag: "", _ts: 1 };
const db2 = { id: "db2", _rid: "r2", _self: "/dbs/db2", _etag: "", _ts: 2 };
(armRequest as jest.Mock).mockResolvedValue({
value: [{ properties: { resource: db1 } }, { properties: { resource: db2 } }],
});
const result = await readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account" });
expect(result).toEqual([db1, db2]);
});
it("should return an empty array when the response is null", async () => {
(armRequest as jest.Mock).mockResolvedValue(null);
const result = await readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account" });
expect(result).toEqual([]);
});
it("should return an empty array when value is an empty list", async () => {
(armRequest as jest.Mock).mockResolvedValue({ value: [] });
const result = await readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account" });
expect(result).toEqual([]);
});
it("should throw and propagate errors from the ARM call", async () => {
(armRequest as jest.Mock).mockRejectedValue(new Error("ARM request failed"));
await expect(
readDatabasesWithARM({ subscriptionId: "sub", resourceGroup: "rg", accountName: "account" }),
).rejects.toThrow("ARM request failed");
});
});
+12 -5
View File
@@ -4,7 +4,7 @@ import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../Shared/Telemetry/TelemetryProcessor";
import { FabricArtifactInfo, userContext } from "../../UserContext";
import { ApiType, FabricArtifactInfo, userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
@@ -96,10 +96,17 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
return databases;
}
async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
export async function readDatabasesWithARM(accountOverride?: {
subscriptionId: string;
resourceGroup: string;
accountName: string;
apiType?: ApiType;
}): Promise<DataModels.Database[]> {
let rpResponse;
const { subscriptionId, resourceGroup, apiType, databaseAccount } = userContext;
const accountName = databaseAccount.name;
const subscriptionId = accountOverride?.subscriptionId ?? userContext.subscriptionId ?? "";
const resourceGroup = accountOverride?.resourceGroup ?? userContext.resourceGroup ?? "";
const accountName = accountOverride?.accountName ?? userContext?.databaseAccount?.name ?? "";
const apiType = accountOverride?.apiType ?? userContext.apiType;
switch (apiType) {
case "SQL":
@@ -118,5 +125,5 @@ async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
throw new Error(`Unsupported default experience type: ${apiType}`);
}
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database);
return rpResponse?.value?.map((database) => database.properties?.resource as DataModels.Database) ?? [];
}
+12
View File
@@ -406,11 +406,22 @@ export interface AutoPilotOfferSettings {
targetMaxThroughput?: number;
}
export interface AccountOverride {
subscriptionId: string;
resourceGroup: string;
accountName: string;
capabilities: Capability[];
capacityMode?: CapacityMode;
enableFreeTier?: boolean;
enableAnalyticalStorage?: boolean;
}
export interface CreateDatabaseParams {
autoPilotMaxThroughput?: number;
databaseId: string;
databaseLevelThroughput?: boolean;
offerThroughput?: number;
targetAccountOverride?: AccountOverride;
}
export interface CreateCollectionParamsBase {
@@ -430,6 +441,7 @@ export interface CreateCollectionParamsBase {
export interface CreateCollectionParams extends CreateCollectionParamsBase {
createNewDatabase: boolean;
collectionId: string;
targetAccountOverride?: AccountOverride;
}
export interface CreateMaterializedViewsParams extends CreateCollectionParamsBase {
@@ -457,13 +457,13 @@ describe("CopyJobActions", () => {
jobName: "test-job",
migrationType: "online" as any,
source: {
subscription: {} as any,
subscriptionId: "sub-123",
account: { id: "account-1", name: "source-account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "sub-123",
subscription: {} as any,
account: { id: "account-1", name: "target-account" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -498,7 +498,7 @@ describe("CopyJobActions", () => {
);
const callArgs = (dataTransferService.create as jest.Mock).mock.calls[0][4];
expect(callArgs.properties.source.remoteAccountName).toBeUndefined();
expect(callArgs.properties.destination.remoteAccountName).toBeUndefined();
expect(mockRefreshJobList).toHaveBeenCalled();
expect(mockOnSuccess).toHaveBeenCalled();
@@ -509,13 +509,13 @@ describe("CopyJobActions", () => {
jobName: "cross-account-job",
migrationType: "offline" as any,
source: {
subscription: {} as any,
subscriptionId: "sub-123",
account: { id: "account-1", name: "source-account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "sub-456",
subscription: {} as any,
account: { id: "account-2", name: "target-account" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -528,7 +528,7 @@ describe("CopyJobActions", () => {
await submitCreateCopyJob(mockState, mockOnSuccess);
const callArgs = (dataTransferService.create as jest.Mock).mock.calls[0][4];
expect(callArgs.properties.source.remoteAccountName).toBe("source-account");
expect(callArgs.properties.destination.remoteAccountName).toBe("target-account");
expect(mockOnSuccess).toHaveBeenCalled();
});
@@ -537,13 +537,13 @@ describe("CopyJobActions", () => {
jobName: "failing-job",
migrationType: "online" as any,
source: {
subscription: {} as any,
subscriptionId: "sub-123",
account: { id: "account-1", name: "source-account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "sub-123",
subscription: {} as any,
account: { id: "account-1", name: "target-account" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -566,13 +566,13 @@ describe("CopyJobActions", () => {
jobName: "test-job",
migrationType: "online" as any,
source: {
subscription: {} as any,
subscriptionId: "sub-123",
account: { id: "account-1", name: "source-account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "sub-123",
subscription: {} as any,
account: { id: "account-1", name: "target-account" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -1,4 +1,5 @@
import Explorer from "Explorer/Explorer";
import { Keys, t } from "Localization";
import React from "react";
import { userContext } from "UserContext";
import { getDataTransferJobs } from "../../../Common/dataAccess/dataTransfers";
@@ -15,7 +16,6 @@ import {
CreateJobRequest,
DataTransferJobGetResults,
} from "../../../Utils/arm/generatedClients/dataTransferService/types";
import ContainerCopyMessages from "../ContainerCopyMessages";
import {
convertTime,
convertToCamelCase,
@@ -35,7 +35,7 @@ export const openCreateCopyJobPanel = (explorer: Explorer) => {
const sidePanelState = useSidePanel.getState();
sidePanelState.setPanelHasConsole(false);
sidePanelState.openSidePanel(
ContainerCopyMessages.createCopyJobPanelTitle,
t(Keys.containerCopy.createCopyJob.panelTitle),
<CreateCopyJobScreensProvider explorer={explorer} />,
"650px",
);
@@ -45,7 +45,7 @@ export const openCopyJobDetailsPanel = (job: CopyJobType) => {
const sidePanelState = useSidePanel.getState();
sidePanelState.setPanelHasConsole(false);
sidePanelState.openSidePanel(
ContainerCopyMessages.copyJobDetailsPanelTitle(job.Name),
job.Name || t(Keys.containerCopy.jobDetails.panelTitleDefault),
<CopyJobDetails job={job} />,
"650px",
);
@@ -137,12 +137,12 @@ export const submitCreateCopyJob = async (state: CopyJobContextState, onSuccess:
properties: {
source: {
component: "CosmosDBSql",
...(isSameAccount ? {} : { remoteAccountName: source?.account?.name }),
databaseName: source?.databaseId,
containerName: source?.containerId,
},
destination: {
component: "CosmosDBSql",
...(isSameAccount ? {} : { remoteAccountName: target?.account?.name }),
databaseName: target?.databaseId,
containerName: target?.containerId,
},
@@ -193,7 +193,7 @@ export const updateCopyJobStatus = async (job: CopyJobType, action: string): Pro
const pattern = new RegExp(`'(${statusList.join("|")})'`, "g");
const normalizedErrorMessage = errorMessage.replace(
pattern,
`'${ContainerCopyMessages.MonitorJobs.Status.InProgress}'`,
`'${t(Keys.containerCopy.monitorJobs.status.inProgress)}'`,
);
logError(`Error updating copy job status: ${normalizedErrorMessage}`, "CopyJob/CopyJobActions.updateCopyJobStatus");
throw error;
@@ -5,10 +5,10 @@ import RefreshIcon from "../../../../images/refresh-cosmos.svg";
import SunIcon from "../../../../images/SunIcon.svg";
import { configContext, Platform } from "../../../ConfigContext";
import { useThemeStore } from "../../../hooks/useTheme";
import { Keys, t } from "Localization";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer";
import * as Actions from "../Actions/CopyJobActions";
import ContainerCopyMessages from "../ContainerCopyMessages";
import { MonitorCopyJobsRefState } from "../MonitorCopyJobs/MonitorCopyJobRefState";
import { CopyJobCommandBarBtnType } from "../Types/CopyJobTypes";
@@ -19,15 +19,15 @@ function getCopyJobBtns(explorer: Explorer, isDarkMode: boolean): CopyJobCommand
{
key: "createCopyJob",
iconSrc: AddIcon,
label: ContainerCopyMessages.createCopyJobButtonLabel,
ariaLabel: ContainerCopyMessages.createCopyJobButtonAriaLabel,
label: t(Keys.containerCopy.commandBar.createCopyJobButtonLabel),
ariaLabel: t(Keys.containerCopy.commandBar.createCopyJobButtonAriaLabel),
onClick: () => Actions.openCreateCopyJobPanel(explorer),
},
{
key: "refresh",
iconSrc: RefreshIcon,
label: ContainerCopyMessages.refreshButtonLabel,
ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel,
label: t(Keys.common.refresh),
ariaLabel: t(Keys.containerCopy.commandBar.refreshButtonAriaLabel),
onClick: () => monitorCopyJobsRef?.refreshJobList(),
},
{
@@ -48,8 +48,8 @@ function getCopyJobBtns(explorer: Explorer, isDarkMode: boolean): CopyJobCommand
buttons.push({
key: "feedback",
iconSrc: FeedbackIcon,
label: ContainerCopyMessages.feedbackButtonLabel,
ariaLabel: ContainerCopyMessages.feedbackButtonAriaLabel,
label: t(Keys.containerCopy.commandBar.feedbackButtonLabel),
ariaLabel: t(Keys.containerCopy.commandBar.feedbackButtonAriaLabel),
onClick: () => {
explorer.openContainerCopyFeedbackBlade();
},
@@ -1,193 +0,0 @@
export default {
// Copy Job Command Bar
feedbackButtonLabel: "Feedback",
feedbackButtonAriaLabel: "Provide feedback on copy jobs",
refreshButtonLabel: "Refresh",
refreshButtonAriaLabel: "Refresh copy jobs",
createCopyJobButtonLabel: "Create Copy Job",
createCopyJobButtonAriaLabel: "Create a new container copy job",
// No Copy Jobs Found
noCopyJobsTitle: "No copy jobs to show",
createCopyJobButtonText: "Create a container copy job",
// Copy Job Details
copyJobDetailsPanelTitle: (jobName: string) => jobName || "Job Details",
errorTitle: "Error Details",
selectedContainers: "Selected Containers",
// Create Copy Job Panel
createCopyJobPanelTitle: "Create copy job",
// Select Account Screen
selectAccountDescription: "Please select a source account from which to copy.",
subscriptionDropdownLabel: "Subscription",
subscriptionDropdownPlaceholder: "Select a subscription",
sourceAccountDropdownLabel: "Account",
sourceAccountDropdownPlaceholder: "Select an account",
migrationTypeOptions: {
offline: {
title: "Offline mode",
description:
"Offline container copy jobs let you copy data from a source container to a destination Cosmos DB container for supported APIs. To ensure data integrity between the source and destination, we recommend stopping updates on the source container before creating the copy job. Learn more about [offline copy jobs](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql).",
},
online: {
title: "Online mode",
description:
"Online container copy jobs let you copy data from a source container to a destination Cosmos DB NoSQL API container using the [All Versions and Delete](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) change feed. This allows updates to continue on the source while data is copied. A brief downtime is required at the end to safely switch over client applications to the destination container. Learn more about [online copy jobs](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started).",
},
},
// Select Source and Target Containers Screen
selectSourceAndTargetContainersDescription:
"Please select a source container and a destination container to copy to.",
sourceContainerSubHeading: "Source container",
targetContainerSubHeading: "Destination container",
databaseDropdownLabel: "Database",
databaseDropdownPlaceholder: "Select a database",
containerDropdownLabel: "Container",
containerDropdownPlaceholder: "Select a container",
createNewContainerSubHeading: "Select the properties for your container.",
createContainerButtonLabel: "Create a new container",
createContainerHeading: "Create new container",
// Preview and Create Screen
jobNameLabel: "Job name",
sourceSubscriptionLabel: "Source subscription",
sourceAccountLabel: "Source account",
sourceDatabaseLabel: "Source database",
sourceContainerLabel: "Source container",
targetDatabaseLabel: "Destination database",
targetContainerLabel: "Destination container",
// Assign Permissions Screen
assignPermissions: {
crossAccountDescription:
"To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.",
intraAccountOnlineDescription: (accountName: string) =>
`Follow the steps below to enable online copy on your "${accountName}" account.`,
crossAccountConfiguration: {
title: "Cross-account container copy",
description: (sourceAccount: string, destinationAccount: string) =>
`Please follow the instruction below to grant requisite permissions to copy data from "${sourceAccount}" to "${destinationAccount}".`,
},
onlineConfiguration: {
title: "Online container copy",
description: (accountName: string) =>
`Please follow the instructions below to enable online copy on your "${accountName}" account.`,
},
},
toggleBtn: {
onText: "On",
offText: "Off",
},
popoverOverlaySpinnerLabel: "Please wait while we process your request...",
addManagedIdentity: {
title: "System-assigned managed identity enabled.",
description:
"A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.",
descriptionHrefText: "Learn more about Managed identities.",
descriptionHref: "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
toggleLabel: "System assigned managed identity",
tooltip: {
content: "Learn more about",
hrefText: "Managed Identities.",
href: "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
},
userAssignedIdentityTooltip: "You can select an existing user assigned identity or create a new one.",
userAssignedIdentityLabel: "You may also select a user assigned managed identity.",
createUserAssignedIdentityLink: "Create User Assigned Managed Identity",
enablementTitle: "Enable system assigned managed identity",
enablementDescription: (accountName: string) =>
accountName
? `Enable system-assigned managed identity on the ${accountName}. To confirm, click the "Yes" button.`
: "",
},
defaultManagedIdentity: {
title: "System-assigned managed identity set as default.",
description: (accountName: string) =>
`Set the system-assigned managed identity as default for "${accountName}" by switching it on.`,
tooltip: {
content: "Learn more about",
hrefText: "Default Managed Identities.",
href: "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
},
popoverTitle: "System assigned managed identity set as default",
popoverDescription: (accountName: string) =>
`Assign the system-assigned managed identity as the default for "${accountName}". To confirm, click the "Yes" button. `,
},
readPermissionAssigned: {
title: "Read permissions assigned to the default identity.",
description:
"To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.",
tooltip: {
content: "Learn more about",
hrefText: "Read permissions.",
href: "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
},
popoverTitle: "Read permissions assigned to default identity.",
popoverDescription:
"Assign read permissions of the source account to the default identity of the destination account. To confirm click the “Yes” button.",
},
pointInTimeRestore: {
title: "Point In Time Restore enabled",
description: (accessName: string) =>
`To facilitate online container copy jobs, please update your "${accessName}" backup policy from periodic to continuous backup. Enabling continuous backup is required for this functionality.`,
tooltip: {
content: "Learn more about",
hrefText: "Continuous Backup",
href: "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
},
buttonText: "Enable Point In Time Restore",
},
onlineCopyEnabled: {
title: "Online copy enabled",
description: (accountName: string) =>
`Enable online container copy by clicking the button below on your "${accountName}" account.`,
hrefText: "Learn more about online copy jobs",
href: "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
buttonText: "Enable Online Copy",
validateAllVersionsAndDeletesChangeFeedSpinnerLabel:
"Validating All versions and deletes change feed mode (preview)...",
enablingAllVersionsAndDeletesChangeFeedSpinnerLabel:
"Enabling All versions and deletes change feed mode (preview)...",
enablingOnlineCopySpinnerLabel: (accountName: string) =>
`Enabling online copy on your "${accountName}" account ...`,
},
MonitorJobs: {
Columns: {
lastUpdatedTime: "Date & time",
name: "Job name",
status: "Status",
completionPercentage: "Completion %",
duration: "Duration",
error: "Error message",
mode: "Mode",
actions: "Actions",
},
Actions: {
pause: "Pause",
resume: "Resume",
cancel: "Cancel",
complete: "Complete",
viewDetails: "View Details",
},
Status: {
Pending: "Queued",
InProgress: "Running",
Running: "Running",
Partitioning: "Running",
Paused: "Paused",
Completed: "Completed",
Failed: "Failed",
Faulted: "Failed",
Skipped: "Cancelled",
Cancelled: "Cancelled",
},
dialog: {
heading: "",
confirmButtonText: "Confirm",
cancelButtonText: "Cancel",
},
},
};
@@ -59,12 +59,6 @@ describe("CopyJobContext", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null,
account: null,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "test-subscription-id",
account: {
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
@@ -75,7 +69,13 @@ describe("CopyJobContext", () => {
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
target: {
subscription: null,
account: null,
databaseId: "",
containerId: "",
},
sourceReadWriteAccessFromTarget: false,
});
expect(contextValue.flow).toBeNull();
expect(contextValue.contextError).toBeNull();
@@ -598,8 +598,8 @@ describe("CopyJobContext", () => {
</CopyJobContextProvider>,
);
expect(contextValue.copyJobState.source?.subscription?.subscriptionId).toBeUndefined();
expect(contextValue.copyJobState.source?.account?.name).toBeUndefined();
expect(contextValue.copyJobState.source?.subscriptionId).toBe("test-subscription-id");
expect(contextValue.copyJobState.source?.account?.name).toBe("test-account");
});
it("should initialize target with userContext values", () => {
@@ -616,11 +616,11 @@ describe("CopyJobContext", () => {
</CopyJobContextProvider>,
);
expect(contextValue.copyJobState.target.subscriptionId).toBe("test-subscription-id");
expect(contextValue.copyJobState.target.account.name).toBe("test-account");
expect(contextValue.copyJobState.target.subscription).toBeNull();
expect(contextValue.copyJobState.target.account).toBeNull();
});
it("should initialize sourceReadAccessFromTarget as false", () => {
it("should initialize sourceReadWriteAccessFromTarget as false", () => {
let contextValue: any;
render(
@@ -634,7 +634,7 @@ describe("CopyJobContext", () => {
</CopyJobContextProvider>,
);
expect(contextValue.copyJobState.sourceReadAccessFromTarget).toBe(false);
expect(contextValue.copyJobState.sourceReadWriteAccessFromTarget).toBe(false);
});
it("should initialize with empty database and container ids", () => {
@@ -23,18 +23,18 @@ const getInitialCopyJobState = (): CopyJobContextState => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null,
account: null,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: userContext.subscriptionId || "",
account: userContext.databaseAccount || null,
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
target: {
subscription: null,
account: null,
databaseId: "",
containerId: "",
},
sourceReadWriteAccessFromTarget: false,
};
};
@@ -2,9 +2,9 @@ import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { DatabaseAccount } from "Contracts/DataModels";
import { CopyJobContextProviderType } from "Explorer/ContainerCopy/Types/CopyJobTypes";
import { Keys, t } from "Localization";
import React from "react";
import { updateSystemIdentity } from "../../../../../Utils/arm/identityUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../Context/CopyJobContext";
import AddManagedIdentity from "./AddManagedIdentity";
@@ -67,7 +67,7 @@ describe("AddManagedIdentity", () => {
databaseId: "target-db",
containerId: "target-container",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
};
const mockContextValue = {
@@ -133,16 +133,16 @@ describe("AddManagedIdentity", () => {
it("renders all required elements", () => {
renderWithContext();
expect(screen.getByText(ContainerCopyMessages.addManagedIdentity.description)).toBeInTheDocument();
expect(screen.getByText(ContainerCopyMessages.addManagedIdentity.descriptionHrefText)).toBeInTheDocument();
expect(screen.getByText(t(Keys.containerCopy.addManagedIdentity.description))).toBeInTheDocument();
expect(screen.getByText(t(Keys.containerCopy.addManagedIdentity.descriptionHrefText))).toBeInTheDocument();
expect(screen.getByRole("switch")).toBeInTheDocument();
});
it("renders description link with correct href", () => {
renderWithContext();
const link = screen.getByText(ContainerCopyMessages.addManagedIdentity.descriptionHrefText);
expect(link.closest("a")).toHaveAttribute("href", ContainerCopyMessages.addManagedIdentity.descriptionHref);
const link = screen.getByText(t(Keys.containerCopy.addManagedIdentity.descriptionHrefText));
expect(link.closest("a")).toHaveAttribute("href", t(Keys.containerCopy.addManagedIdentity.descriptionHref));
expect(link.closest("a")).toHaveAttribute("target", "_blank");
expect(link.closest("a")).toHaveAttribute("rel", "noopener noreferrer");
});
@@ -175,7 +175,7 @@ describe("AddManagedIdentity", () => {
const toggle = screen.getByRole("switch");
fireEvent.click(toggle);
expect(screen.getByText(ContainerCopyMessages.addManagedIdentity.enablementTitle)).toBeInTheDocument();
expect(screen.getByText(t(Keys.containerCopy.addManagedIdentity.enablementTitle))).toBeInTheDocument();
});
it("hides popover when toggle is off", () => {
@@ -185,7 +185,7 @@ describe("AddManagedIdentity", () => {
fireEvent.click(toggle);
fireEvent.click(toggle);
expect(screen.queryByText(ContainerCopyMessages.addManagedIdentity.enablementTitle)).not.toBeInTheDocument();
expect(screen.queryByText(t(Keys.containerCopy.addManagedIdentity.enablementTitle))).not.toBeInTheDocument();
});
});
@@ -197,9 +197,9 @@ describe("AddManagedIdentity", () => {
});
it("displays correct enablement description with account name", () => {
const expectedDescription = ContainerCopyMessages.addManagedIdentity.enablementDescription(
mockCopyJobState.target.account.name,
);
const expectedDescription = t(Keys.containerCopy.addManagedIdentity.enablementDescription, {
accountName: mockCopyJobState.source.account.name,
});
expect(screen.getByText(expectedDescription)).toBeInTheDocument();
});
@@ -220,7 +220,7 @@ describe("AddManagedIdentity", () => {
const cancelButton = screen.getByText("Cancel");
fireEvent.click(cancelButton);
expect(screen.queryByText(ContainerCopyMessages.addManagedIdentity.enablementTitle)).not.toBeInTheDocument();
expect(screen.queryByText(t(Keys.containerCopy.addManagedIdentity.enablementTitle))).not.toBeInTheDocument();
const toggle = screen.getByRole("switch");
expect(toggle).not.toBeChecked();
@@ -1,7 +1,7 @@
import { Link, Stack, Text, Toggle } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import { updateSystemIdentity } from "../../../../../Utils/arm/identityUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import InfoTooltip from "../Components/InfoTooltip";
import PopoverMessage from "../Components/PopoverContainer";
@@ -11,14 +11,14 @@ import useToggle from "./hooks/useToggle";
const managedIdentityTooltip = (
<Text>
{ContainerCopyMessages.addManagedIdentity.tooltip.content} &nbsp;
{t(Keys.containerCopy.addManagedIdentity.tooltipContent)} &nbsp;
<Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.addManagedIdentity.tooltip.href}
href={t(Keys.containerCopy.addManagedIdentity.tooltipHref)}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.addManagedIdentity.tooltip.hrefText}
{t(Keys.containerCopy.addManagedIdentity.tooltipHrefText)}
</Link>
</Text>
);
@@ -32,9 +32,9 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
return (
<Stack className="addManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<Text className="themeText">
{ContainerCopyMessages.addManagedIdentity.description}&ensp;
<Link href={ContainerCopyMessages.addManagedIdentity.descriptionHref} target="_blank" rel="noopener noreferrer">
{ContainerCopyMessages.addManagedIdentity.descriptionHrefText}
{t(Keys.containerCopy.addManagedIdentity.description)}&ensp;
<Link href={t(Keys.containerCopy.addManagedIdentity.descriptionHref)} target="_blank" rel="noopener noreferrer">
{t(Keys.containerCopy.addManagedIdentity.descriptionHrefText)}
</Link>{" "}
&nbsp;
<InfoTooltip content={managedIdentityTooltip} />
@@ -42,18 +42,20 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
<Toggle
data-test="btn-toggle"
checked={systemAssigned}
onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText}
onText={t(Keys.common.on)}
offText={t(Keys.common.off)}
onChange={onToggle}
/>
<PopoverMessage
isLoading={loading}
visible={systemAssigned}
title={ContainerCopyMessages.addManagedIdentity.enablementTitle}
title={t(Keys.containerCopy.addManagedIdentity.enablementTitle)}
onCancel={() => onToggle(null, false)}
onPrimary={handleAddSystemIdentity}
>
{ContainerCopyMessages.addManagedIdentity.enablementDescription(copyJobState.target?.account?.name)}
{t(Keys.containerCopy.addManagedIdentity.enablementDescription, {
accountName: copyJobState.source?.account?.name,
})}
</PopoverMessage>
</Stack>
);
@@ -1,10 +1,10 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../Context/CopyJobContext";
import { CopyJobContextProviderType } from "../../../Types/CopyJobTypes";
import AddReadPermissionToDefaultIdentity from "./AddReadPermissionToDefaultIdentity";
import AddReadWritePermissionToDefaultIdentity from "./AddReadWritePermissionToDefaultIdentity";
jest.mock("../../../../../Common/Logger", () => ({
logError: jest.fn(),
@@ -73,7 +73,7 @@ import { assignRole, RoleAssignmentType } from "../../../../../Utils/arm/RbacUti
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import useToggle from "./hooks/useToggle";
describe("AddReadPermissionToDefaultIdentity Component", () => {
describe("AddReadWritePermissionToDefaultIdentity Component", () => {
const mockUseToggle = useToggle as jest.MockedFunction<typeof useToggle>;
const mockAssignRole = assignRole as jest.MockedFunction<typeof assignRole>;
const mockGetAccountDetailsFromResourceId = getAccountDetailsFromResourceId as jest.MockedFunction<
@@ -86,7 +86,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "source-sub-id" } as Subscription,
subscriptionId: "source-sub-id",
account: {
id: "/subscriptions/source-sub-id/resourceGroups/source-rg/providers/Microsoft.DocumentDB/databaseAccounts/source-account",
name: "source-account",
@@ -96,12 +96,16 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
properties: {
documentEndpoint: "https://source-account.documents.azure.com:443/",
},
identity: {
principalId: "source-principal-id",
type: "SystemAssigned",
},
},
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "target-sub-id",
subscription: { subscriptionId: "target-sub-id" } as Subscription,
account: {
id: "/subscriptions/target-sub-id/resourceGroups/target-rg/providers/Microsoft.DocumentDB/databaseAccounts/target-account",
name: "target-account",
@@ -119,7 +123,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
databaseId: "target-db",
containerId: "target-container",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
},
setCopyJobState: jest.fn(),
setContextError: jest.fn(),
@@ -133,7 +137,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
const renderComponent = (contextValue = mockContextValue) => {
return render(
<CopyJobContext.Provider value={contextValue}>
<AddReadPermissionToDefaultIdentity />
<AddReadWritePermissionToDefaultIdentity />
</CopyJobContext.Provider>,
);
};
@@ -164,12 +168,12 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
expect(container).toMatchSnapshot();
});
it("should render correctly when sourceReadAccessFromTarget is true", () => {
it("should render correctly when sourceReadWriteAccessFromTarget is true", () => {
const contextWithAccess = {
...mockContextValue,
copyJobState: {
...mockContextValue.copyJobState,
sourceReadAccessFromTarget: true,
sourceReadWriteAccessFromTarget: true,
},
};
const { container } = renderComponent(contextWithAccess);
@@ -180,7 +184,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
describe("Component Structure", () => {
it("should display the description text", () => {
renderComponent();
expect(screen.getByText(ContainerCopyMessages.readPermissionAssigned.description)).toBeInTheDocument();
expect(screen.getByText(t(Keys.containerCopy.readWritePermissionAssigned.description))).toBeInTheDocument();
});
it("should display the info tooltip", () => {
@@ -212,10 +216,10 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
expect(screen.getByTestId("popover-message")).toBeInTheDocument();
expect(screen.getByTestId("popover-title")).toHaveTextContent(
ContainerCopyMessages.readPermissionAssigned.popoverTitle,
t(Keys.containerCopy.readWritePermissionAssigned.popoverTitle),
);
expect(screen.getByTestId("popover-content")).toHaveTextContent(
ContainerCopyMessages.readPermissionAssigned.popoverDescription,
t(Keys.containerCopy.readWritePermissionAssigned.popoverDescription),
);
});
@@ -243,11 +247,11 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
expect(mockOnToggle).toHaveBeenCalledWith(null, false);
});
it("should call handleAddReadPermission when primary button is clicked", async () => {
it("should call handleAddReadWritePermission when primary button is clicked", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockResolvedValue({ id: "role-assignment-id" } as RoleAssignmentType);
@@ -258,22 +262,22 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
await waitFor(() => {
expect(mockGetAccountDetailsFromResourceId).toHaveBeenCalledWith(
"/subscriptions/source-sub-id/resourceGroups/source-rg/providers/Microsoft.DocumentDB/databaseAccounts/source-account",
"/subscriptions/target-sub-id/resourceGroups/target-rg/providers/Microsoft.DocumentDB/databaseAccounts/target-account",
);
});
});
});
describe("handleAddReadPermission Function", () => {
describe("handleAddReadWritePermission Function", () => {
beforeEach(() => {
mockUseToggle.mockReturnValue([true, jest.fn()]);
});
it("should successfully assign role and update context", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockResolvedValue({ id: "role-assignment-id" } as RoleAssignmentType);
@@ -284,10 +288,10 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
await waitFor(() => {
expect(mockAssignRole).toHaveBeenCalledWith(
"source-sub-id",
"source-rg",
"source-account",
"target-principal-id",
"target-sub-id",
"target-rg",
"target-account",
"source-principal-id",
);
});
@@ -298,9 +302,9 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
it("should handle error when assignRole fails", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockRejectedValue(new Error("Permission denied"));
@@ -312,7 +316,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
await waitFor(() => {
expect(mockLogError).toHaveBeenCalledWith(
"Permission denied",
"CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission",
"CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission",
);
});
@@ -323,9 +327,9 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
it("should handle error without message", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockRejectedValue({});
@@ -336,23 +340,23 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
await waitFor(() => {
expect(mockLogError).toHaveBeenCalledWith(
"Error assigning read permission to default identity. Please try again later.",
"CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission",
"Error assigning read-write permission to default identity. Please try again later.",
"CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission",
);
});
await waitFor(() => {
expect(mockContextValue.setContextError).toHaveBeenCalledWith(
"Error assigning read permission to default identity. Please try again later.",
"Error assigning read-write permission to default identity. Please try again later.",
);
});
});
it("should show loading state during role assignment", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockImplementation(
@@ -371,9 +375,9 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
it.skip("should not assign role when assignRole returns falsy", async () => {
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockResolvedValue(null);
@@ -431,10 +435,10 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
...mockContextValue,
copyJobState: {
...mockContextValue.copyJobState,
target: {
...mockContextValue.copyJobState.target,
source: {
...mockContextValue.copyJobState.source,
account: {
...mockContextValue.copyJobState.target.account!,
...mockContextValue.copyJobState.source.account!,
identity: {
principalId: "",
type: "SystemAssigned",
@@ -446,9 +450,9 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
mockUseToggle.mockReturnValue([true, jest.fn()]);
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockResolvedValue({ id: "role-assignment-id" } as RoleAssignmentType);
@@ -458,7 +462,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
fireEvent.click(primaryButton);
await waitFor(() => {
expect(mockAssignRole).toHaveBeenCalledWith("source-sub-id", "source-rg", "source-account", "");
expect(mockAssignRole).toHaveBeenCalledWith("target-sub-id", "target-rg", "target-account", "");
});
});
});
@@ -476,9 +480,9 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
mockUseToggle.mockReturnValue([true, jest.fn()]);
mockGetAccountDetailsFromResourceId.mockReturnValue({
subscriptionId: "source-sub-id",
resourceGroup: "source-rg",
accountName: "source-account",
subscriptionId: "target-sub-id",
resourceGroup: "target-rg",
accountName: "target-account",
});
mockAssignRole.mockResolvedValue({ id: "role-assignment-id" } as RoleAssignmentType);
@@ -496,7 +500,7 @@ describe("AddReadPermissionToDefaultIdentity Component", () => {
expect(updatedState).toEqual({
...mockContextValue.copyJobState,
sourceReadAccessFromTarget: true,
sourceReadWriteAccessFromTarget: true,
});
});
});
@@ -1,8 +1,8 @@
import { Link, Stack, Text, Toggle } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import { logError } from "../../../../../Common/Logger";
import { assignRole } from "../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import InfoTooltip from "../Components/InfoTooltip";
@@ -12,51 +12,54 @@ import useToggle from "./hooks/useToggle";
const TooltipContent = (
<Text>
{ContainerCopyMessages.readPermissionAssigned.tooltip.content} &nbsp;
{t(Keys.containerCopy.readWritePermissionAssigned.tooltipContent)} &nbsp;
<Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.readPermissionAssigned.tooltip.href}
href={t(Keys.containerCopy.readWritePermissionAssigned.tooltipHref)}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText}
{t(Keys.containerCopy.readWritePermissionAssigned.tooltipHrefText)}
</Link>
</Text>
);
type AddReadPermissionToDefaultIdentityProps = Partial<PermissionSectionConfig>;
const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIdentityProps> = () => {
type AddReadWritePermissionToDefaultIdentityProps = Partial<PermissionSectionConfig>;
const AddReadWritePermissionToDefaultIdentity: React.FC<AddReadWritePermissionToDefaultIdentityProps> = () => {
const [loading, setLoading] = React.useState(false);
const { copyJobState, setCopyJobState, setContextError } = useCopyJobContext();
const [readPermissionAssigned, onToggle] = useToggle(false);
const [readWritePermissionAssigned, onToggle] = useToggle(copyJobState.sourceReadWriteAccessFromTarget ?? false);
const handleAddReadPermission = async () => {
const handleAddReadWritePermission = async () => {
const { source, target } = copyJobState;
const selectedSourceAccount = source?.account;
const selectedTargetAccount = target?.account;
try {
const {
subscriptionId: sourceSubscriptionId,
resourceGroup: sourceResourceGroup,
accountName: sourceAccountName,
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
subscriptionId: targetSubscriptionId,
resourceGroup: targetResourceGroup,
accountName: targetAccountName,
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
setLoading(true);
const assignedRole = await assignRole(
sourceSubscriptionId,
sourceResourceGroup,
sourceAccountName,
target?.account?.identity?.principalId ?? "",
targetSubscriptionId,
targetResourceGroup,
targetAccountName,
source?.account?.identity?.principalId ?? "",
);
if (assignedRole) {
setCopyJobState((prevState) => ({
...prevState,
sourceReadAccessFromTarget: true,
sourceReadWriteAccessFromTarget: true,
}));
}
} catch (error) {
const errorMessage =
error.message || "Error assigning read permission to default identity. Please try again later.";
logError(errorMessage, "CopyJob/AddReadPermissionToDefaultIdentity.handleAddReadPermission");
error.message || "Error assigning read-write permission to default identity. Please try again later.";
logError(errorMessage, "CopyJob/AddReadWritePermissionToDefaultIdentity.handleAddReadWritePermission");
setContextError(errorMessage);
} finally {
setLoading(false);
@@ -66,14 +69,14 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
return (
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<Text className="toggle-label">
{ContainerCopyMessages.readPermissionAssigned.description}&ensp;
{t(Keys.containerCopy.readWritePermissionAssigned.description)}&ensp;
<InfoTooltip content={TooltipContent} />
</Text>
<Toggle
data-test="btn-toggle"
checked={readPermissionAssigned}
onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText}
checked={readWritePermissionAssigned}
onText={t(Keys.common.on)}
offText={t(Keys.common.off)}
onChange={onToggle}
inlineLabel
styles={{
@@ -83,15 +86,15 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
/>
<PopoverMessage
isLoading={loading}
visible={readPermissionAssigned}
title={ContainerCopyMessages.readPermissionAssigned.popoverTitle}
visible={readWritePermissionAssigned}
title={t(Keys.containerCopy.readWritePermissionAssigned.popoverTitle)}
onCancel={() => onToggle(null, false)}
onPrimary={handleAddReadPermission}
onPrimary={handleAddReadWritePermission}
>
{ContainerCopyMessages.readPermissionAssigned.popoverDescription}
{t(Keys.containerCopy.readWritePermissionAssigned.popoverDescription)}
</PopoverMessage>
</Stack>
);
};
export default AddReadPermissionToDefaultIdentity;
export default AddReadWritePermissionToDefaultIdentity;
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import { render, RenderResult } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
import { CopyJobContextProviderType, CopyJobContextState } from "../../../Types/CopyJobTypes";
@@ -43,12 +43,12 @@ jest.mock("./AddManagedIdentity", () => {
return MockAddManagedIdentity;
});
jest.mock("./AddReadPermissionToDefaultIdentity", () => {
const MockAddReadPermissionToDefaultIdentity = () => {
return <div data-testid="add-read-permission">Add Read Permission Component</div>;
jest.mock("./AddReadWritePermissionToDefaultIdentity", () => {
const MockAddReadWritePermissionToDefaultIdentity = () => {
return <div data-testid="add-read-write-permission">Add Read-Write Permission Component</div>;
};
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
return MockAddReadPermissionToDefaultIdentity;
MockAddReadWritePermissionToDefaultIdentity.displayName = "MockAddReadWritePermissionToDefaultIdentity";
return MockAddReadWritePermissionToDefaultIdentity;
});
jest.mock("./DefaultManagedIdentity", () => {
@@ -85,18 +85,18 @@ describe("AssignPermissions Component", () => {
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "source-sub" } as any,
subscriptionId: "source-sub",
account: { id: "source-account", name: "Source Account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "target-sub",
subscription: { subscriptionId: "target-sub" } as any,
account: { id: "target-account", name: "Target Account" } as any,
databaseId: "target-db",
containerId: "target-container",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
...overrides,
});
@@ -154,7 +154,7 @@ describe("AssignPermissions Component", () => {
const copyJobState = createMockCopyJobState();
const { getByText } = renderWithContext(copyJobState);
expect(getByText(ContainerCopyMessages.assignPermissions.crossAccountDescription)).toBeInTheDocument();
expect(getByText(t(Keys.containerCopy.assignPermissions.crossAccountDescription))).toBeInTheDocument();
});
it("should display intra account description for same accounts with online migration", async () => {
@@ -164,13 +164,13 @@ describe("AssignPermissions Component", () => {
const copyJobState = createMockCopyJobState({
migrationType: CopyJobMigrationType.Online,
source: {
subscription: { subscriptionId: "same-sub" } as any,
subscriptionId: "same-sub",
account: { id: "same-account", name: "Same Account" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "same-sub",
subscription: { subscriptionId: "same-sub" } as any,
account: { id: "same-account", name: "Same Account" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -179,7 +179,9 @@ describe("AssignPermissions Component", () => {
const { getByText } = renderWithContext(copyJobState);
expect(
getByText(ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription("Same Account")),
getByText(
t(Keys.containerCopy.assignPermissions.intraAccountOnlineDescription, { accountName: "Same Account" }),
),
).toBeInTheDocument();
});
});
@@ -201,7 +203,7 @@ describe("AssignPermissions Component", () => {
completed: true,
},
{
id: "readPermissionAssigned",
id: "readWritePermissionAssigned",
title: "Read Permission Assigned",
Component: () => <div data-testid="add-read-permission">Add Read Permission Component</div>,
disabled: false,
@@ -347,7 +349,7 @@ describe("AssignPermissions Component", () => {
it("should handle missing account names", () => {
const copyJobState = createMockCopyJobState({
source: {
subscription: { subscriptionId: "source-sub" } as any,
subscriptionId: "source-sub",
account: { id: "source-account" } as any,
databaseId: "source-db",
containerId: "source-container",
@@ -1,10 +1,10 @@
import { Image, Stack, Text } from "@fluentui/react";
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components";
import { Keys, t } from "Localization";
import React, { useEffect } from "react";
import CheckmarkIcon from "../../../../../../images/successfulPopup.svg";
import WarningIcon from "../../../../../../images/warning.svg";
import ShimmerTree, { IndentLevel } from "../../../../../Common/ShimmerTree/ShimmerTree";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { isIntraAccountCopy } from "../../../CopyJobUtils";
import { CopyJobMigrationType } from "../../../Enums/CopyJobEnums";
@@ -106,11 +106,11 @@ const AssignPermissions = () => {
tokens={{ childrenGap: 20 }}
>
<Text variant="medium" style={{ color: "var(--colorNeutralForeground1)" }}>
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
copyJobState?.source?.account?.name || "",
)
: ContainerCopyMessages.assignPermissions.crossAccountDescription}
{isSameAccount && copyJobState?.migrationType === CopyJobMigrationType.Online
? t(Keys.containerCopy.assignPermissions.intraAccountOnlineDescription, {
accountName: copyJobState?.source?.account?.name || "",
})
: t(Keys.containerCopy.assignPermissions.crossAccountDescription)}
</Text>
{totalSectionsCount === 0 ? (
@@ -1,8 +1,8 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import { updateDefaultIdentity } from "../../../../../Utils/arm/identityUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../Context/CopyJobContext";
import DefaultManagedIdentity from "./DefaultManagedIdentity";
@@ -69,6 +69,12 @@ const mockUseToggle = useToggle as jest.MockedFunction<typeof useToggle>;
describe("DefaultManagedIdentity", () => {
const mockCopyJobContextValue = {
copyJobState: {
source: {
account: {
name: "test-cosmos-account",
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-cosmos-account",
},
},
target: {
account: {
name: "test-cosmos-account",
@@ -166,7 +172,7 @@ describe("DefaultManagedIdentity", () => {
expect(popover).toBeInTheDocument();
const title = screen.getByTestId("popover-title");
expect(title).toHaveTextContent(ContainerCopyMessages.defaultManagedIdentity.popoverTitle);
expect(title).toHaveTextContent(t(Keys.containerCopy.defaultManagedIdentity.popoverTitle));
const content = screen.getByTestId("popover-content");
expect(content).toHaveTextContent(
@@ -260,6 +266,12 @@ describe("DefaultManagedIdentity", () => {
const contextValueWithoutAccount = {
...mockCopyJobContextValue,
copyJobState: {
source: {
account: {
name: "",
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/",
},
},
target: {
account: {
name: "",
@@ -277,6 +289,9 @@ describe("DefaultManagedIdentity", () => {
const contextValueWithNullAccount = {
...mockCopyJobContextValue,
copyJobState: {
source: {
account: null as DatabaseAccount | null,
},
target: {
account: null as DatabaseAccount | null,
},
@@ -339,8 +354,8 @@ describe("DefaultManagedIdentity", () => {
it("should display correct toggle button text", () => {
renderComponent();
const onText = screen.queryByText(ContainerCopyMessages.toggleBtn.onText);
const offText = screen.queryByText(ContainerCopyMessages.toggleBtn.offText);
const onText = screen.queryByText(t(Keys.common.on));
const offText = screen.queryByText(t(Keys.common.off));
expect(onText || offText).toBeTruthy();
});
@@ -348,7 +363,7 @@ describe("DefaultManagedIdentity", () => {
it("should display correct link text in tooltip", () => {
renderComponent();
const linkText = screen.getByText(ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText);
const linkText = screen.getByText(t(Keys.containerCopy.defaultManagedIdentity.tooltipHrefText));
expect(linkText).toBeInTheDocument();
});
});
@@ -1,7 +1,7 @@
import { Link, Stack, Text, Toggle } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import { updateDefaultIdentity } from "../../../../../Utils/arm/identityUtils";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import InfoTooltip from "../Components/InfoTooltip";
import PopoverMessage from "../Components/PopoverContainer";
@@ -11,14 +11,14 @@ import useToggle from "./hooks/useToggle";
const managedIdentityTooltip = (
<Text>
{ContainerCopyMessages.defaultManagedIdentity.tooltip.content} &nbsp;
{t(Keys.containerCopy.defaultManagedIdentity.tooltipContent)} &nbsp;
<Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.defaultManagedIdentity.tooltip.href}
href={t(Keys.containerCopy.defaultManagedIdentity.tooltipHref)}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText}
{t(Keys.containerCopy.defaultManagedIdentity.tooltipHrefText)}
</Link>
</Text>
);
@@ -32,14 +32,17 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
return (
<Stack className="defaultManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<div className="toggle-label">
{ContainerCopyMessages.defaultManagedIdentity.description(copyJobState?.target?.account?.name)} &nbsp;
{t(Keys.containerCopy.defaultManagedIdentity.description, {
accountName: copyJobState?.source?.account?.name,
})}{" "}
&nbsp;
<InfoTooltip content={managedIdentityTooltip} />
</div>
<Toggle
data-test="btn-toggle"
checked={defaultSystemAssigned}
onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText}
onText={t(Keys.common.on)}
offText={t(Keys.common.off)}
onChange={onToggle}
inlineLabel
styles={{
@@ -50,11 +53,13 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
<PopoverMessage
isLoading={loading}
visible={defaultSystemAssigned}
title={ContainerCopyMessages.defaultManagedIdentity.popoverTitle}
title={t(Keys.containerCopy.defaultManagedIdentity.popoverTitle)}
onCancel={() => onToggle(null, false)}
onPrimary={handleAddSystemIdentity}
>
{ContainerCopyMessages.defaultManagedIdentity.popoverDescription(copyJobState?.target?.account?.name)}
{t(Keys.containerCopy.defaultManagedIdentity.popoverDescription, {
accountName: copyJobState?.source?.account?.name,
})}
</PopoverMessage>
</Stack>
);
@@ -2,12 +2,12 @@ import "@testing-library/jest-dom";
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { DatabaseAccount } from "Contracts/DataModels";
import { CopyJobContextProviderType } from "Explorer/ContainerCopy/Types/CopyJobTypes";
import { Keys, t } from "Localization";
import React from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { CapabilityNames } from "../../../../../Common/Constants";
import { logError } from "../../../../../Common/Logger";
import { update as updateDatabaseAccount } from "../../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../Context/CopyJobContext";
import OnlineCopyEnabled from "./OnlineCopyEnabled";
@@ -97,7 +97,9 @@ describe("OnlineCopyEnabled", () => {
it("should render the description with account name", () => {
renderComponent();
const description = screen.getByText(ContainerCopyMessages.onlineCopyEnabled.description("test-account"));
const description = screen.getByText(
t(Keys.containerCopy.onlineCopyEnabled.description, { accountName: "test-account" }),
);
expect(description).toBeInTheDocument();
});
@@ -105,10 +107,10 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const link = screen.getByRole("link", {
name: ContainerCopyMessages.onlineCopyEnabled.hrefText,
name: t(Keys.containerCopy.onlineCopyEnabled.hrefText),
});
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute("href", ContainerCopyMessages.onlineCopyEnabled.href);
expect(link).toHaveAttribute("href", t(Keys.containerCopy.onlineCopyEnabled.href));
expect(link).toHaveAttribute("target", "_blank");
expect(link).toHaveAttribute("rel", "noopener noreferrer");
});
@@ -117,7 +119,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const button = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
expect(button).toBeInTheDocument();
expect(button).not.toBeDisabled();
@@ -134,7 +136,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const refreshButton = screen.queryByRole("button", {
name: ContainerCopyMessages.refreshButtonLabel,
name: t(Keys.common.refresh),
});
expect(refreshButton).not.toBeInTheDocument();
});
@@ -167,7 +169,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -222,7 +224,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -246,7 +248,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -259,7 +261,9 @@ describe("OnlineCopyEnabled", () => {
await waitFor(() => {
expect(
screen.getByText(ContainerCopyMessages.onlineCopyEnabled.enablingOnlineCopySpinnerLabel("test-account")),
screen.getByText(
t(Keys.containerCopy.onlineCopyEnabled.enablingOnlineCopySpinnerLabel, { accountName: "test-account" }),
),
).toBeInTheDocument();
});
});
@@ -272,7 +276,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -306,7 +310,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -318,7 +322,7 @@ describe("OnlineCopyEnabled", () => {
});
const refreshButton = screen.getByRole("button", {
name: ContainerCopyMessages.refreshButtonLabel,
name: t(Keys.common.refresh),
});
await act(async () => {
@@ -349,7 +353,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -379,7 +383,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -401,7 +405,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -418,7 +422,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -436,7 +440,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
await act(async () => {
@@ -450,7 +454,7 @@ describe("OnlineCopyEnabled", () => {
mockFetchDatabaseAccount.mockImplementation(() => new Promise(() => {}));
const refreshButton = screen.getByRole("button", {
name: ContainerCopyMessages.refreshButtonLabel,
name: t(Keys.common.refresh),
});
await act(async () => {
@@ -536,7 +540,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent(contextWithNoCapabilities);
const enableButton = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
expect(enableButton).toBeInTheDocument();
});
@@ -547,7 +551,7 @@ describe("OnlineCopyEnabled", () => {
renderComponent();
const button = screen.getByRole("button", {
name: ContainerCopyMessages.onlineCopyEnabled.buttonText,
name: t(Keys.containerCopy.onlineCopyEnabled.buttonText),
});
expect(button).toBeInTheDocument();
});
@@ -1,12 +1,12 @@
import { Link, PrimaryButton, Stack } from "@fluentui/react";
import { DatabaseAccount } from "Contracts/DataModels";
import { Keys, t } from "Localization";
import React from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { CapabilityNames } from "../../../../../Common/Constants";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import { logError } from "../../../../../Common/Logger";
import { update as updateDatabaseAccount } from "../../../../../Utils/arm/generatedClients/cosmos/databaseAccounts";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import { AccountValidatorFn } from "../../../Types/CopyJobTypes";
@@ -76,21 +76,25 @@ const OnlineCopyEnabled: React.FC = () => {
setShowRefreshButton(false);
try {
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.validateAllVersionsAndDeletesChangeFeedSpinnerLabel);
setLoaderMessage(t(Keys.containerCopy.onlineCopyEnabled.validateAllVersionsAndDeletesChangeFeedSpinnerLabel));
const sourAccountBeforeUpdate = await fetchDatabaseAccount(
sourceSubscriptionId,
sourceResourceGroup,
sourceAccountName,
);
if (!sourAccountBeforeUpdate?.properties.enableAllVersionsAndDeletesChangeFeed) {
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.enablingAllVersionsAndDeletesChangeFeedSpinnerLabel);
setLoaderMessage(t(Keys.containerCopy.onlineCopyEnabled.enablingAllVersionsAndDeletesChangeFeedSpinnerLabel));
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
properties: {
enableAllVersionsAndDeletesChangeFeed: true,
},
});
}
setLoaderMessage(ContainerCopyMessages.onlineCopyEnabled.enablingOnlineCopySpinnerLabel(sourceAccountName));
setLoaderMessage(
t(Keys.containerCopy.onlineCopyEnabled.enablingOnlineCopySpinnerLabel, {
accountName: sourceAccountName,
}),
);
await updateDatabaseAccount(sourceSubscriptionId, sourceResourceGroup, sourceAccountName, {
properties: {
capabilities: [...sourceAccountCapabilities, { name: CapabilityNames.EnableOnlineCopyFeature }],
@@ -132,16 +136,16 @@ const OnlineCopyEnabled: React.FC = () => {
<Stack className="onlineCopyContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<LoadingOverlay isLoading={loading} label={loaderMessage} />
<Stack.Item className="info-message">
{ContainerCopyMessages.onlineCopyEnabled.description(source?.account?.name || "")}&ensp;
<Link href={ContainerCopyMessages.onlineCopyEnabled.href} target="_blank" rel="noopener noreferrer">
{ContainerCopyMessages.onlineCopyEnabled.hrefText}
{t(Keys.containerCopy.onlineCopyEnabled.description, { accountName: source?.account?.name || "" })}&ensp;
<Link href={t(Keys.containerCopy.onlineCopyEnabled.href)} target="_blank" rel="noopener noreferrer">
{t(Keys.containerCopy.onlineCopyEnabled.hrefText)}
</Link>
</Stack.Item>
<Stack.Item>
{showRefreshButton ? (
<PrimaryButton
className="fullWidth"
text={ContainerCopyMessages.refreshButtonLabel}
text={t(Keys.common.refresh)}
iconProps={{ iconName: "Refresh" }}
onClick={handleRefresh}
disabled={loading}
@@ -149,7 +153,7 @@ const OnlineCopyEnabled: React.FC = () => {
) : (
<PrimaryButton
className="fullWidth"
text={loading ? "" : ContainerCopyMessages.onlineCopyEnabled.buttonText}
text={loading ? "" : t(Keys.containerCopy.onlineCopyEnabled.buttonText)}
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
disabled={loading}
onClick={handleOnlineCopyEnable}
@@ -50,18 +50,18 @@ describe("PointInTimeRestore", () => {
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "test-sub", displayName: "Test Subscription" },
subscriptionId: "test-sub",
account: mockSourceAccount,
databaseId: "test-db",
containerId: "test-container",
},
target: {
subscriptionId: "test-sub",
subscription: { subscriptionId: "test-sub", displayName: "Test Subscription" },
account: mockSourceAccount,
databaseId: "target-db",
containerId: "target-container",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
} as CopyJobContextState;
const mockSetCopyJobState = jest.fn();
@@ -1,10 +1,10 @@
import { Link, PrimaryButton, Stack, Text } from "@fluentui/react";
import { DatabaseAccount } from "Contracts/DataModels";
import { Keys, t } from "Localization";
import React, { useEffect, useRef, useState } from "react";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import { logError } from "../../../../../Common/Logger";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { buildResourceLink, getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import { AccountValidatorFn } from "../../../Types/CopyJobTypes";
@@ -12,14 +12,14 @@ import InfoTooltip from "../Components/InfoTooltip";
const tooltipContent = (
<Text>
{ContainerCopyMessages.pointInTimeRestore.tooltip.content} &nbsp;
{t(Keys.containerCopy.pointInTimeRestore.tooltipContent)} &nbsp;
<Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.pointInTimeRestore.tooltip.href}
href={t(Keys.containerCopy.pointInTimeRestore.tooltipHref)}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.pointInTimeRestore.tooltip.hrefText}
{t(Keys.containerCopy.pointInTimeRestore.tooltipHrefText)}
</Link>
</Text>
);
@@ -119,9 +119,9 @@ const PointInTimeRestore: React.FC = () => {
return (
<Stack className="pointInTimeRestoreContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<LoadingOverlay isLoading={loading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<LoadingOverlay isLoading={loading} label={t(Keys.containerCopy.popoverOverlaySpinnerLabel)} />
<Stack.Item className="toggle-label">
{ContainerCopyMessages.pointInTimeRestore.description(source.account?.name ?? "")}
{t(Keys.containerCopy.pointInTimeRestore.description, { accessName: source.account?.name ?? "" })}
{tooltipContent && (
<>
{" "}
@@ -134,7 +134,7 @@ const PointInTimeRestore: React.FC = () => {
<PrimaryButton
data-test="pointInTimeRestore:RefreshBtn"
className="fullWidth"
text={ContainerCopyMessages.refreshButtonLabel}
text={t(Keys.common.refresh)}
iconProps={{ iconName: "Refresh" }}
onClick={handleRefresh}
/>
@@ -142,7 +142,7 @@ const PointInTimeRestore: React.FC = () => {
<PrimaryButton
data-test="pointInTimeRestore:PrimaryBtn"
className="fullWidth"
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
text={loading ? "" : t(Keys.containerCopy.pointInTimeRestore.buttonText)}
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}
disabled={loading}
onClick={openWindowAndMonitor}
@@ -7,7 +7,7 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
<span
class="themeText css-110"
>
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.
<a
class="ms-Link root-111"
@@ -95,7 +95,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
<span
class="themeText css-110"
>
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.
<a
class="ms-Link root-111"
@@ -204,7 +204,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
<span
class="themeText css-110"
>
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
Enable system-assigned managed identity on the source-account-name. To confirm, click the "Yes" button.
</span>
<div
class="ms-Stack css-125"
@@ -267,7 +267,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
<span
class="themeText css-110"
>
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.
<a
class="ms-Link root-111"
@@ -359,7 +359,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
<span
class="themeText css-110"
>
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
Enable system-assigned managed identity on the source-account-name. To confirm, click the "Yes" button.
</span>
<div
class="ms-Stack css-125"
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle missing source account 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Edge Cases should handle missing source account 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -8,7 +8,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -24,7 +24,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -63,7 +63,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
</div>
`;
exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle missing target account identity 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Edge Cases should handle missing target account identity 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -71,7 +71,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -87,7 +87,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -126,7 +126,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
</div>
`;
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly when sourceReadAccessFromTarget is true 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly when sourceReadWriteAccessFromTarget is true 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -134,7 +134,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -150,7 +150,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -189,7 +189,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
</div>
`;
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly when toggle is on 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly when toggle is on 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -197,7 +197,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -213,7 +213,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -255,12 +255,12 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
<div
data-testid="popover-title"
>
Read permissions assigned to default identity.
Assign read-write permissions to default identity.
</div>
<div
data-testid="popover-content"
>
Assign read permissions of the source account to the default identity of the destination account. To confirm click the Yes button.
Assign read-write permissions on the destination account to the default identity of the source account. To confirm, click the "Yes" button.
</div>
<button
data-testid="popover-cancel"
@@ -277,7 +277,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
</div>
`;
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly with default state 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly with default state 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -285,7 +285,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -301,7 +301,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -340,7 +340,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
</div>
`;
exports[`AddReadPermissionToDefaultIdentity Component Rendering should render correctly with different context states 1`] = `
exports[`AddReadWritePermissionToDefaultIdentity Component Rendering should render correctly with different context states 1`] = `
<div>
<div
class="ms-Stack defaultManagedIdentityContainer css-109"
@@ -348,7 +348,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
<span
class="toggle-label css-110"
>
To allow data copy from source to the destination container, provide read access of the source account to the default identity of the destination account.
To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.
<div
data-testid="info-tooltip"
@@ -364,7 +364,7 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
rel="noopener noreferrer"
target="_blank"
>
Read permissions.
Read-write permissions.
</a>
</span>
</div>
@@ -9,7 +9,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
class="ms-Stack css-111"
@@ -212,7 +212,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
class="ms-Stack css-111"
@@ -618,7 +618,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
class="ms-Stack css-111"
@@ -1153,7 +1153,7 @@ exports[`AssignPermissions Component Permission Groups should render permission
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
class="ms-Stack css-111"
@@ -1307,7 +1307,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
data-testid="shimmer-tree"
@@ -1329,7 +1329,7 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
<span
class="css-110"
>
To copy data from the source to the destination container, ensure that the managed identity of the destination account has read access to the source account by completing the following steps.
To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.
</span>
<div
data-testid="shimmer-tree"
@@ -9,6 +9,7 @@ exports[`DefaultManagedIdentity Edge Cases should handle missing account name gr
class="toggle-label"
>
Set the system-assigned managed identity as default for "" by switching it on.
 
<div
data-testid="info-tooltip"
@@ -71,7 +72,8 @@ exports[`DefaultManagedIdentity Edge Cases should handle null account 1`] = `
<div
class="toggle-label"
>
Set the system-assigned managed identity as default for "undefined" by switching it on.
Set the system-assigned managed identity as default for "" by switching it on.
 
<div
data-testid="info-tooltip"
@@ -135,6 +137,7 @@ exports[`DefaultManagedIdentity Loading States should render loading state snaps
class="toggle-label"
>
Set the system-assigned managed identity as default for "test-cosmos-account" by switching it on.
 
<div
data-testid="info-tooltip"
@@ -227,6 +230,7 @@ exports[`DefaultManagedIdentity Rendering should render correctly with default s
class="toggle-label"
>
Set the system-assigned managed identity as default for "test-cosmos-account" by switching it on.
 
<div
data-testid="info-tooltip"
@@ -290,6 +294,7 @@ exports[`DefaultManagedIdentity Toggle Interactions should render toggle with ch
class="toggle-label"
>
Set the system-assigned managed identity as default for "test-cosmos-account" by switching it on.
 
<div
data-testid="info-tooltip"
@@ -26,18 +26,18 @@ const useManagedIdentity = (
const handleAddSystemIdentity = useCallback(async (): Promise<void> => {
try {
setLoading(true);
const selectedTargetAccount = copyJobState?.target?.account;
const selectedSourceAccount = copyJobState?.source?.account;
const {
subscriptionId: targetSubscriptionId,
resourceGroup: targetResourceGroup,
accountName: targetAccountName,
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id) || {};
subscriptionId: sourceSubscriptionId,
resourceGroup: sourceResourceGroup,
accountName: sourceAccountName,
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id) || {};
const updatedAccount = await updateIdentityFn(targetSubscriptionId, targetResourceGroup, targetAccountName);
const updatedAccount = await updateIdentityFn(sourceSubscriptionId, sourceResourceGroup, sourceAccountName);
if (updatedAccount) {
setCopyJobState((prevState) => ({
...prevState,
target: { ...prevState.target, account: updatedAccount },
source: { ...prevState.source, account: updatedAccount },
}));
}
} catch (error) {
@@ -46,7 +46,7 @@ const useManagedIdentity = (
setContextError(errorMessage);
setLoading(false);
}
}, [copyJobState?.target?.account?.id, updateIdentityFn, setCopyJobState]);
}, [copyJobState?.source?.account?.id, updateIdentityFn, setCopyJobState]);
return { loading, handleAddSystemIdentity };
};
@@ -13,7 +13,7 @@ import {
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
import * as CopyJobPrerequisitesCacheModule from "../../../Utils/useCopyJobPrerequisitesCache";
import usePermissionSections, {
checkTargetHasReaderRoleOnSource,
checkTargetHasReadWriteRoleOnSource,
PermissionGroupConfig,
SECTION_IDS,
} from "./usePermissionsSection";
@@ -40,12 +40,12 @@ jest.mock("../AddManagedIdentity", () => {
return MockAddManagedIdentity;
});
jest.mock("../AddReadPermissionToDefaultIdentity", () => {
const MockAddReadPermissionToDefaultIdentity = () => {
return <div data-testid="add-read-permission">AddReadPermissionToDefaultIdentity</div>;
jest.mock("../AddReadWritePermissionToDefaultIdentity", () => {
const MockAddReadWritePermissionToDefaultIdentity = () => {
return <div data-testid="add-read-write-permission">AddReadWritePermissionToDefaultIdentity</div>;
};
MockAddReadPermissionToDefaultIdentity.displayName = "MockAddReadPermissionToDefaultIdentity";
return MockAddReadPermissionToDefaultIdentity;
MockAddReadWritePermissionToDefaultIdentity.displayName = "MockAddReadWritePermissionToDefaultIdentity";
return MockAddReadWritePermissionToDefaultIdentity;
});
jest.mock("../DefaultManagedIdentity", () => {
@@ -133,7 +133,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscription: undefined,
subscriptionId: "",
databaseId: "",
containerId: "",
},
@@ -152,7 +152,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscriptionId: "",
subscription: undefined,
databaseId: "",
containerId: "",
},
@@ -193,7 +193,7 @@ describe("usePermissionsSection", () => {
expect(capturedResult[0].sections.map((s) => s.id)).toEqual([
SECTION_IDS.addManagedIdentity,
SECTION_IDS.defaultManagedIdentity,
SECTION_IDS.readPermissionAssigned,
SECTION_IDS.readWritePermissionAssigned,
]);
});
@@ -208,7 +208,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscription: undefined,
subscriptionId: "",
databaseId: "",
containerId: "",
},
@@ -222,7 +222,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscriptionId: "",
subscription: undefined,
databaseId: "",
containerId: "",
},
@@ -284,16 +284,19 @@ describe("usePermissionsSection", () => {
describe("Section validation", () => {
it("should validate addManagedIdentity section correctly", async () => {
const stateWithSystemAssigned = createMockState({
target: {
source: {
account: {
id: "target-account-id",
name: "target-account",
id: "source-account-id",
name: "source-account",
identity: {
type: IdentityType.SystemAssigned,
principalId: "principal-123",
},
properties: {
defaultIdentity: DefaultIdentityType.FirstPartyIdentity,
backupPolicy: {
type: BackupPolicyType.Periodic,
},
capabilities: [],
},
location: "",
type: "",
@@ -322,16 +325,20 @@ describe("usePermissionsSection", () => {
it("should validate defaultManagedIdentity section correctly", async () => {
const stateWithSystemAssignedIdentity = createMockState({
target: {
source: {
account: {
id: "target-account-id",
name: "target-account",
id: "source-account-id",
name: "source-account",
identity: {
type: IdentityType.SystemAssigned,
principalId: "principal-123",
},
properties: {
defaultIdentity: DefaultIdentityType.SystemAssignedIdentity,
backupPolicy: {
type: BackupPolicyType.Periodic,
},
capabilities: [],
},
location: "",
type: "",
@@ -358,16 +365,17 @@ describe("usePermissionsSection", () => {
expect(defaultManagedIdentitySection?.completed).toBe(true);
});
it("should validate readPermissionAssigned section with reader role", async () => {
it("should validate readWritePermissionAssigned section with contributor role", async () => {
const mockRoleDefinitions: RbacUtils.RoleDefinitionType[] = [
{
id: "role-1",
name: "Custom Role",
name: "00000000-0000-0000-0000-000000000002",
permissions: [
{
dataActions: [
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write",
],
},
],
@@ -383,16 +391,20 @@ describe("usePermissionsSection", () => {
mockedRbacUtils.fetchRoleDefinitions.mockResolvedValue(mockRoleDefinitions);
const state = createMockState({
target: {
source: {
account: {
id: "target-account-id",
name: "target-account",
id: "source-account-id",
name: "source-account",
identity: {
type: IdentityType.SystemAssigned,
principalId: "principal-123",
},
properties: {
defaultIdentity: DefaultIdentityType.SystemAssignedIdentity,
backupPolicy: {
type: BackupPolicyType.Periodic,
},
capabilities: [],
},
location: "",
type: "",
@@ -407,7 +419,9 @@ describe("usePermissionsSection", () => {
render(<TestWrapper state={state} onResult={noop} />);
await waitFor(() => {
expect(screen.getByTestId(`section-${SECTION_IDS.readPermissionAssigned}-completed`)).toHaveTextContent("true");
expect(screen.getByTestId(`section-${SECTION_IDS.readWritePermissionAssigned}-completed`)).toHaveTextContent(
"true",
);
});
expect(mockedRbacUtils.fetchRoleAssignments).toHaveBeenCalledWith(
@@ -435,7 +449,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscription: undefined,
subscriptionId: "",
databaseId: "",
containerId: "",
},
@@ -476,7 +490,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscription: undefined,
subscriptionId: "",
databaseId: "",
containerId: "",
},
@@ -546,7 +560,7 @@ describe("usePermissionsSection", () => {
type: "",
kind: "",
},
subscriptionId: "",
subscription: undefined,
databaseId: "",
containerId: "",
},
@@ -568,12 +582,12 @@ describe("usePermissionsSection", () => {
});
});
describe("checkTargetHasReaderRoleOnSource", () => {
it("should return true for built-in Reader role", () => {
describe("checkTargetHasReadWriteRoleOnSource", () => {
it("should return true for built-in Contributor role", () => {
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
{
id: "role-1",
name: "00000000-0000-0000-0000-000000000001",
name: "00000000-0000-0000-0000-000000000002",
permissions: [],
assignableScopes: [],
resourceGroup: "",
@@ -583,20 +597,21 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
];
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
expect(result).toBe(true);
});
it("should return true for custom role with required data actions", () => {
it("should return true for custom role with read-write data actions", () => {
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
{
id: "role-1",
name: "Custom Reader Role",
name: "Custom Contributor Role",
permissions: [
{
dataActions: [
"Microsoft.DocumentDB/databaseAccounts/readMetadata",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read",
"Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write",
],
},
],
@@ -608,7 +623,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
];
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
expect(result).toBe(true);
});
@@ -630,12 +645,12 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
];
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
expect(result).toBe(false);
});
it("should return false for empty role definitions", () => {
const result = checkTargetHasReaderRoleOnSource([]);
const result = checkTargetHasReadWriteRoleOnSource([]);
expect(result).toBe(false);
});
@@ -653,11 +668,11 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
];
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
expect(result).toBe(false);
});
it("should handle multiple roles and return true if any has sufficient permissions", () => {
it("should handle multiple roles and return true if any has sufficient read-write permissions", () => {
const roleDefinitions: RbacUtils.RoleDefinitionType[] = [
{
id: "role-1",
@@ -675,7 +690,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
{
id: "role-2",
name: "00000000-0000-0000-0000-000000000001",
name: "00000000-0000-0000-0000-000000000002",
permissions: [],
assignableScopes: [],
resourceGroup: "",
@@ -685,7 +700,7 @@ describe("checkTargetHasReaderRoleOnSource", () => {
},
];
const result = checkTargetHasReaderRoleOnSource(roleDefinitions);
const result = checkTargetHasReadWriteRoleOnSource(roleDefinitions);
expect(result).toBe(true);
});
});
@@ -1,7 +1,7 @@
import { Keys, t } from "Localization";
import { useEffect, useMemo, useRef, useState } from "react";
import { CapabilityNames } from "../../../../../../Common/Constants";
import { fetchRoleAssignments, fetchRoleDefinitions, RoleDefinitionType } from "../../../../../../Utils/arm/RbacUtils";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { getAccountDetailsFromResourceId, getContainerIdentifiers, isIntraAccountCopy } from "../../../../CopyJobUtils";
import {
BackupPolicyType,
@@ -12,7 +12,7 @@ import {
import { CopyJobContextState } from "../../../../Types/CopyJobTypes";
import { useCopyJobPrerequisitesCache } from "../../../Utils/useCopyJobPrerequisitesCache";
import AddManagedIdentity from "../AddManagedIdentity";
import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
import AddReadWritePermissionToDefaultIdentity from "../AddReadWritePermissionToDefaultIdentity";
import DefaultManagedIdentity from "../DefaultManagedIdentity";
import OnlineCopyEnabled from "../OnlineCopyEnabled";
import PointInTimeRestore from "../PointInTimeRestore";
@@ -36,58 +36,60 @@ export interface PermissionGroupConfig {
export const SECTION_IDS = {
addManagedIdentity: "addManagedIdentity",
defaultManagedIdentity: "defaultManagedIdentity",
readPermissionAssigned: "readPermissionAssigned",
readWritePermissionAssigned: "readWritePermissionAssigned",
pointInTimeRestore: "pointInTimeRestore",
onlineCopyEnabled: "onlineCopyEnabled",
} as const;
const COSMOS_DB_BUILT_IN_DATA_CONTRIBUTOR_ROLE_ID = "00000000-0000-0000-0000-000000000002";
const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
{
id: SECTION_IDS.addManagedIdentity,
title: ContainerCopyMessages.addManagedIdentity.title,
title: t(Keys.containerCopy.addManagedIdentity.title),
Component: AddManagedIdentity,
disabled: true,
validate: (state: CopyJobContextState) => {
const targetAccountIdentityType = (state?.target?.account?.identity?.type ?? "").toLowerCase();
const sourceAccountIdentityType = (state?.source?.account?.identity?.type ?? "").toLowerCase();
return (
targetAccountIdentityType === IdentityType.SystemAssigned ||
targetAccountIdentityType === IdentityType.UserAssigned
sourceAccountIdentityType === IdentityType.SystemAssigned ||
sourceAccountIdentityType === IdentityType.UserAssigned
);
},
},
{
id: SECTION_IDS.defaultManagedIdentity,
title: ContainerCopyMessages.defaultManagedIdentity.title,
title: t(Keys.containerCopy.defaultManagedIdentity.title),
Component: DefaultManagedIdentity,
disabled: true,
validate: (state: CopyJobContextState) => {
const targetAccountDefaultIdentity = (state?.target?.account?.properties?.defaultIdentity ?? "").toLowerCase();
return targetAccountDefaultIdentity === DefaultIdentityType.SystemAssignedIdentity;
const sourceAccountDefaultIdentity = (state?.source?.account?.properties?.defaultIdentity ?? "").toLowerCase();
return sourceAccountDefaultIdentity === DefaultIdentityType.SystemAssignedIdentity;
},
},
{
id: SECTION_IDS.readPermissionAssigned,
title: ContainerCopyMessages.readPermissionAssigned.title,
Component: AddReadPermissionToDefaultIdentity,
id: SECTION_IDS.readWritePermissionAssigned,
title: t(Keys.containerCopy.readWritePermissionAssigned.title),
Component: AddReadWritePermissionToDefaultIdentity,
disabled: true,
validate: async (state: CopyJobContextState) => {
const principalId = state?.target?.account?.identity?.principalId;
const selectedSourceAccount = state?.source?.account;
const principalId = state?.source?.account?.identity?.principalId;
const selectedTargetAccount = state?.target?.account;
const {
subscriptionId: sourceSubscriptionId,
resourceGroup: sourceResourceGroup,
accountName: sourceAccountName,
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
subscriptionId: targetSubscriptionId,
resourceGroup: targetResourceGroup,
accountName: targetAccountName,
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
const rolesAssigned = await fetchRoleAssignments(
sourceSubscriptionId,
sourceResourceGroup,
sourceAccountName,
targetSubscriptionId,
targetResourceGroup,
targetAccountName,
principalId,
);
const roleDefinitions = await fetchRoleDefinitions(rolesAssigned ?? []);
return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []);
return checkTargetHasReadWriteRoleOnSource(roleDefinitions ?? []);
},
},
];
@@ -95,7 +97,7 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
{
id: SECTION_IDS.pointInTimeRestore,
title: ContainerCopyMessages.pointInTimeRestore.title,
title: t(Keys.containerCopy.pointInTimeRestore.title),
Component: PointInTimeRestore,
disabled: true,
validate: (state: CopyJobContextState) => {
@@ -105,7 +107,7 @@ const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
},
{
id: SECTION_IDS.onlineCopyEnabled,
title: ContainerCopyMessages.onlineCopyEnabled.title,
title: t(Keys.containerCopy.onlineCopyEnabled.title),
Component: OnlineCopyEnabled,
disabled: true,
validate: (state: CopyJobContextState) => {
@@ -119,18 +121,34 @@ const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
];
/**
* Checks if the user has the Reader role based on role definitions.
* Checks if the user has contributor-style read-write access on the source account.
*/
export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinitionType[]): boolean {
return roleDefinitions?.some(
(role) =>
role.name === "00000000-0000-0000-0000-000000000001" ||
role.permissions.some(
(permission) =>
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata") &&
permission.dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read"),
),
);
export function checkTargetHasReadWriteRoleOnSource(roleDefinitions: RoleDefinitionType[]): boolean {
return roleDefinitions?.some((role) => {
if (role.name === COSMOS_DB_BUILT_IN_DATA_CONTRIBUTOR_ROLE_ID) {
return true;
}
const dataActions = role.permissions?.flatMap((permission) => permission.dataActions ?? []) ?? [];
const hasAccountWildcard = dataActions.includes("Microsoft.DocumentDB/databaseAccounts/*");
const hasContainerWildcard =
hasAccountWildcard || dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*");
const hasItemsWildcard =
hasContainerWildcard ||
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*");
const hasAccountReadMetadata =
hasAccountWildcard || dataActions.includes("Microsoft.DocumentDB/databaseAccounts/readMetadata");
const hasItemRead =
hasItemsWildcard ||
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/read");
const hasItemWrite =
hasItemsWildcard ||
dataActions.includes("Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/write");
return hasAccountReadMetadata && hasItemRead && hasItemWrite;
});
}
/**
@@ -194,11 +212,11 @@ const usePermissionSections = (state: CopyJobContextState): PermissionGroupConfi
if (crossAccountSections.length > 0) {
groups.push({
id: "crossAccountConfigs",
title: ContainerCopyMessages.assignPermissions.crossAccountConfiguration.title,
description: ContainerCopyMessages.assignPermissions.crossAccountConfiguration.description(
sourceAccountName,
targetAccountName,
),
title: t(Keys.containerCopy.assignPermissions.crossAccountConfiguration.title),
description: t(Keys.containerCopy.assignPermissions.crossAccountConfiguration.description, {
sourceAccount: sourceAccountName,
destinationAccount: targetAccountName,
}),
sections: crossAccountSections,
});
}
@@ -206,8 +224,10 @@ const usePermissionSections = (state: CopyJobContextState): PermissionGroupConfi
if (state.migrationType === CopyJobMigrationType.Online) {
groups.push({
id: "onlineConfigs",
title: ContainerCopyMessages.assignPermissions.onlineConfiguration.title,
description: ContainerCopyMessages.assignPermissions.onlineConfiguration.description(sourceAccountName),
title: t(Keys.containerCopy.assignPermissions.onlineConfiguration.title),
description: t(Keys.containerCopy.assignPermissions.onlineConfiguration.description, {
accountName: sourceAccountName,
}),
sections: [...PERMISSION_SECTIONS_FOR_ONLINE_JOBS],
});
}
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import PopoverMessage from "./PopoverContainer";
jest.mock("../../../../../Common/LoadingOverlay", () => {
@@ -181,7 +181,7 @@ describe("PopoverMessage Component", () => {
it("should use correct loading overlay label", () => {
render(<PopoverMessage {...defaultProps} isLoading={true} />);
const loadingOverlay = screen.getByTestId("loading-overlay");
expect(loadingOverlay).toHaveAttribute("aria-label", ContainerCopyMessages.popoverOverlaySpinnerLabel);
expect(loadingOverlay).toHaveAttribute("aria-label", t(Keys.containerCopy.popoverOverlaySpinnerLabel));
});
});
@@ -1,9 +1,9 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/display-name */
import { DefaultButton, PrimaryButton, Stack, Text } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import LoadingOverlay from "../../../../../Common/LoadingOverlay";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
interface PopoverContainerProps {
isLoading?: boolean;
@@ -22,7 +22,7 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
tokens={{ childrenGap: 20 }}
style={{ maxWidth: 450 }}
>
<LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<LoadingOverlay isLoading={isLoading} label={t(Keys.containerCopy.popoverOverlaySpinnerLabel)} />
<Text variant="mediumPlus" className="themeText" style={{ fontWeight: 600 }}>
{title}
</Text>
@@ -4,8 +4,8 @@ import { CopyJobMigrationType } from "Explorer/ContainerCopy/Enums/CopyJobEnums"
import { CopyJobContextProviderType } from "Explorer/ContainerCopy/Types/CopyJobTypes";
import Explorer from "Explorer/Explorer";
import { useSidePanel } from "hooks/useSidePanel";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import AddCollectionPanelWrapper from "./AddCollectionPanelWrapper";
@@ -81,7 +81,7 @@ describe("AddCollectionPanelWrapper", () => {
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
},
setCopyJobState: mockSetCopyJobState,
flow: null,
@@ -109,7 +109,9 @@ describe("AddCollectionPanelWrapper", () => {
expect(container.querySelector(".addCollectionPanelWrapper")).toBeInTheDocument();
expect(container.querySelector(".addCollectionPanelHeader")).toBeInTheDocument();
expect(container.querySelector(".addCollectionPanelBody")).toBeInTheDocument();
expect(screen.getByText(ContainerCopyMessages.createNewContainerSubHeading)).toBeInTheDocument();
expect(
screen.getByText(t(Keys.containerCopy.selectContainers.createNewContainerSubHeadingDefault)),
).toBeInTheDocument();
expect(screen.getByTestId("add-collection-panel")).toBeInTheDocument();
});
@@ -138,7 +140,7 @@ describe("AddCollectionPanelWrapper", () => {
it("should set header text to create container heading on mount", () => {
render(<AddCollectionPanelWrapper />);
expect(mockSetHeaderText).toHaveBeenCalledWith(ContainerCopyMessages.createContainerHeading);
expect(mockSetHeaderText).toHaveBeenCalledWith(t(Keys.containerCopy.selectContainers.createContainerHeading));
});
it("should reset header text to create copy job panel title on unmount", () => {
@@ -146,13 +148,13 @@ describe("AddCollectionPanelWrapper", () => {
unmount();
expect(mockSetHeaderText).toHaveBeenCalledWith(ContainerCopyMessages.createCopyJobPanelTitle);
expect(mockSetHeaderText).toHaveBeenCalledWith(t(Keys.containerCopy.createCopyJob.panelTitle));
});
it("should not change header text if already set correctly", () => {
const modifiedSidePanelState = {
...mockSidePanelState,
headerText: ContainerCopyMessages.createContainerHeading,
headerText: t(Keys.containerCopy.selectContainers.createContainerHeading),
};
mockUseSidePanel.getState = jest.fn().mockReturnValue(modifiedSidePanelState);
@@ -245,10 +247,10 @@ describe("AddCollectionPanelWrapper", () => {
describe("Component Lifecycle", () => {
it("should properly cleanup on unmount", () => {
const { unmount } = render(<AddCollectionPanelWrapper />);
expect(mockSetHeaderText).toHaveBeenCalledWith(ContainerCopyMessages.createContainerHeading);
expect(mockSetHeaderText).toHaveBeenCalledWith(t(Keys.containerCopy.selectContainers.createContainerHeading));
mockSetHeaderText.mockClear();
unmount();
expect(mockSetHeaderText).toHaveBeenCalledWith(ContainerCopyMessages.createCopyJobPanelTitle);
expect(mockSetHeaderText).toHaveBeenCalledWith(t(Keys.containerCopy.createCopyJob.panelTitle));
});
it("should re-render correctly when props change", () => {
@@ -1,11 +1,14 @@
import { Stack, Text } from "@fluentui/react";
import { IDropdownOption, MessageBar, MessageBarType, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
import { readDatabasesWithARM } from "Common/dataAccess/readDatabases";
import { AccountOverride } from "Contracts/DataModels";
import Explorer from "Explorer/Explorer";
import { useSidePanel } from "hooks/useSidePanel";
import { produce } from "immer";
import React, { useCallback, useEffect } from "react";
import { Keys, t } from "Localization";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { AddCollectionPanel } from "../../../../Panes/AddCollectionPanel/AddCollectionPanel";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
type AddCollectionPanelWrapperProps = {
explorer?: Explorer;
@@ -13,18 +16,88 @@ type AddCollectionPanelWrapperProps = {
};
const AddCollectionPanelWrapper: React.FunctionComponent<AddCollectionPanelWrapperProps> = ({ explorer, goBack }) => {
const { setCopyJobState } = useCopyJobContext();
const { setCopyJobState, copyJobState } = useCopyJobContext();
const [destinationDatabases, setDestinationDatabases] = useState<IDropdownOption[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [permissionError, setPermissionError] = useState<string | null>(null);
const targetAccountOverride: AccountOverride | undefined = useMemo(() => {
const accountId = copyJobState?.target?.account?.id;
if (!accountId) {
return undefined;
}
const details = getAccountDetailsFromResourceId(accountId);
if (!details?.subscriptionId || !details?.resourceGroup || !details?.accountName) {
return undefined;
}
return {
subscriptionId: details.subscriptionId,
resourceGroup: details.resourceGroup,
accountName: details.accountName,
capabilities: copyJobState?.target?.account?.properties?.capabilities ?? [],
capacityMode: copyJobState?.target?.account?.properties?.capacityMode,
enableFreeTier: copyJobState?.target?.account?.properties?.enableFreeTier,
enableAnalyticalStorage: copyJobState?.target?.account?.properties?.enableAnalyticalStorage,
};
}, [copyJobState?.target?.account?.id]);
useEffect(() => {
const sidePanelStore = useSidePanel.getState();
if (sidePanelStore.headerText !== ContainerCopyMessages.createContainerHeading) {
sidePanelStore.setHeaderText(ContainerCopyMessages.createContainerHeading);
if (sidePanelStore.headerText !== t(Keys.containerCopy.selectContainers.createContainerHeading)) {
sidePanelStore.setHeaderText(t(Keys.containerCopy.selectContainers.createContainerHeading));
}
return () => {
sidePanelStore.setHeaderText(ContainerCopyMessages.createCopyJobPanelTitle);
sidePanelStore.setHeaderText(t(Keys.containerCopy.createCopyJob.panelTitle));
};
}, []);
useEffect(() => {
if (!targetAccountOverride) {
setIsLoading(false);
return undefined;
}
let cancelled = false;
const fetchDatabases = async () => {
setIsLoading(true);
setPermissionError(null);
try {
const databases = await readDatabasesWithARM({
subscriptionId: targetAccountOverride.subscriptionId,
resourceGroup: targetAccountOverride.resourceGroup,
accountName: targetAccountOverride.accountName,
apiType: "SQL",
});
if (!cancelled) {
setDestinationDatabases(databases.map((db) => ({ key: db.id, text: db.id })));
}
} catch (error) {
if (!cancelled) {
const message = error?.message || String(error);
if (message.includes("AuthorizationFailed") || message.includes("403")) {
setPermissionError(
`You do not have sufficient permissions to access the destination account "${targetAccountOverride.accountName}". ` +
"Please ensure you have at least Contributor or Owner access to create databases and containers.",
);
} else {
setPermissionError(
`Failed to load databases from the destination account "${targetAccountOverride.accountName}": ${message}`,
);
}
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
}
};
fetchDatabases();
return () => {
cancelled = true;
};
}, [targetAccountOverride]);
const handleAddCollectionSuccess = useCallback(
(collectionData: { databaseId: string; collectionId: string }) => {
setCopyJobState(
@@ -38,13 +111,41 @@ const AddCollectionPanelWrapper: React.FunctionComponent<AddCollectionPanelWrapp
[goBack],
);
if (isLoading) {
return (
<Stack horizontalAlign="center" verticalAlign="center" styles={{ root: { padding: 20 } }}>
<Spinner size={SpinnerSize.large} label="Loading destination account databases..." />
</Stack>
);
}
if (permissionError) {
return (
<Stack styles={{ root: { padding: 20 } }}>
<MessageBar messageBarType={MessageBarType.error}>{permissionError}</MessageBar>
</Stack>
);
}
return (
<Stack className="addCollectionPanelWrapper">
<Stack.Item className="addCollectionPanelHeader">
<Text className="themeText">{ContainerCopyMessages.createNewContainerSubHeading}</Text>
<Text className="themeText">
{targetAccountOverride?.accountName
? t(Keys.containerCopy.selectContainers.createNewContainerSubHeading, {
accountName: targetAccountOverride.accountName,
})
: t(Keys.containerCopy.selectContainers.createNewContainerSubHeadingDefault)}
</Text>
</Stack.Item>
<Stack.Item className="addCollectionPanelBody">
<AddCollectionPanel explorer={explorer} isCopyJobFlow={true} onSubmitSuccess={handleAddCollectionSuccess} />
<AddCollectionPanel
explorer={explorer}
isCopyJobFlow={true}
onSubmitSuccess={handleAddCollectionSuccess}
targetAccountOverride={targetAccountOverride}
externalDatabaseOptions={destinationDatabases}
/>
</Stack.Item>
</Stack>
);
@@ -3,19 +3,19 @@
exports[`AddCollectionPanelWrapper Component Rendering should match snapshot 1`] = `
<div>
<div
class="ms-Stack addCollectionPanelWrapper css-109"
class="ms-Stack addCollectionPanelWrapper css-115"
>
<div
class="ms-StackItem addCollectionPanelHeader css-110"
class="ms-StackItem addCollectionPanelHeader css-116"
>
<span
class="themeText css-111"
class="themeText css-117"
>
Select the properties for your container.
Configure the properties for the new container.
</span>
</div>
<div
class="ms-StackItem addCollectionPanelBody css-110"
class="ms-StackItem addCollectionPanelBody css-116"
>
<div
data-testid="add-collection-panel"
@@ -44,19 +44,19 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot 1`]
exports[`AddCollectionPanelWrapper Component Rendering should match snapshot with both props 1`] = `
<div>
<div
class="ms-Stack addCollectionPanelWrapper css-109"
class="ms-Stack addCollectionPanelWrapper css-115"
>
<div
class="ms-StackItem addCollectionPanelHeader css-110"
class="ms-StackItem addCollectionPanelHeader css-116"
>
<span
class="themeText css-111"
class="themeText css-117"
>
Select the properties for your container.
Configure the properties for the new container.
</span>
</div>
<div
class="ms-StackItem addCollectionPanelBody css-110"
class="ms-StackItem addCollectionPanelBody css-116"
>
<div
data-testid="add-collection-panel"
@@ -85,19 +85,19 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
exports[`AddCollectionPanelWrapper Component Rendering should match snapshot with explorer prop 1`] = `
<div>
<div
class="ms-Stack addCollectionPanelWrapper css-109"
class="ms-Stack addCollectionPanelWrapper css-115"
>
<div
class="ms-StackItem addCollectionPanelHeader css-110"
class="ms-StackItem addCollectionPanelHeader css-116"
>
<span
class="themeText css-111"
class="themeText css-117"
>
Select the properties for your container.
Configure the properties for the new container.
</span>
</div>
<div
class="ms-StackItem addCollectionPanelBody css-110"
class="ms-StackItem addCollectionPanelBody css-116"
>
<div
data-testid="add-collection-panel"
@@ -126,19 +126,19 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
exports[`AddCollectionPanelWrapper Component Rendering should match snapshot with goBack prop 1`] = `
<div>
<div
class="ms-Stack addCollectionPanelWrapper css-109"
class="ms-Stack addCollectionPanelWrapper css-115"
>
<div
class="ms-StackItem addCollectionPanelHeader css-110"
class="ms-StackItem addCollectionPanelHeader css-116"
>
<span
class="themeText css-111"
class="themeText css-117"
>
Select the properties for your container.
Configure the properties for the new container.
</span>
</div>
<div
class="ms-StackItem addCollectionPanelBody css-110"
class="ms-StackItem addCollectionPanelBody css-116"
>
<div
data-testid="add-collection-panel"
@@ -87,18 +87,18 @@ describe("PreviewCopyJob", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: mockSubscription,
subscriptionId: "test-subscription-id",
account: mockDatabaseAccount,
databaseId: "source-database",
containerId: "source-container",
},
target: {
subscriptionId: "test-subscription-id",
subscription: mockSubscription,
account: mockDatabaseAccount,
databaseId: "target-database",
containerId: "target-container",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
...overrides,
};
@@ -146,7 +146,7 @@ describe("PreviewCopyJob", () => {
it("should render with missing source subscription information", () => {
const mockContext = createMockContext({
source: {
subscription: undefined,
subscriptionId: "",
account: mockDatabaseAccount,
databaseId: "source-database",
containerId: "source-container",
@@ -165,7 +165,7 @@ describe("PreviewCopyJob", () => {
it("should render with missing source account information", () => {
const mockContext = createMockContext({
source: {
subscription: mockSubscription,
subscriptionId: "test-subscription-id",
account: null,
databaseId: "source-database",
containerId: "source-container",
@@ -184,13 +184,13 @@ describe("PreviewCopyJob", () => {
it("should render with undefined database and container names", () => {
const mockContext = createMockContext({
source: {
subscription: mockSubscription,
subscriptionId: "test-subscription-id",
account: mockDatabaseAccount,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "test-subscription-id",
subscription: mockSubscription,
account: mockDatabaseAccount,
databaseId: "",
containerId: "",
@@ -219,7 +219,7 @@ describe("PreviewCopyJob", () => {
const mockContext = createMockContext({
source: {
subscription: longNameSubscription,
subscriptionId: longNameSubscription.subscriptionId,
account: longNameAccount,
databaseId: "long-database-name-for-testing-purposes",
containerId: "long-container-name-for-testing-purposes",
@@ -253,13 +253,13 @@ describe("PreviewCopyJob", () => {
it("should handle special characters in database and container names", () => {
const mockContext = createMockContext({
source: {
subscription: mockSubscription,
subscriptionId: "test-subscription-id",
account: mockDatabaseAccount,
databaseId: "test-db_with@special#chars",
containerId: "test-container_with@special#chars",
},
target: {
subscriptionId: "test-subscription-id",
subscription: mockSubscription,
account: mockDatabaseAccount,
databaseId: "target-db_with@special#chars",
containerId: "target-container_with@special#chars",
@@ -285,12 +285,12 @@ describe("PreviewCopyJob", () => {
const mockContext = createMockContext({
target: {
subscriptionId: "target-subscription-id",
subscription: mockSubscription,
account: targetAccount,
databaseId: "target-database",
containerId: "target-container",
},
sourceReadAccessFromTarget: true,
sourceReadWriteAccessFromTarget: true,
});
const { container } = render(
@@ -350,7 +350,7 @@ describe("PreviewCopyJob", () => {
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
});
it("should display proper field labels from ContainerCopyMessages", () => {
it("should display proper field labels", () => {
const mockContext = createMockContext();
const { getByText } = render(
@@ -360,7 +360,7 @@ describe("PreviewCopyJob", () => {
);
expect(getByText(/Job name/i)).toBeInTheDocument();
expect(getByText(/Source subscription/i)).toBeInTheDocument();
expect(getByText(/Source account/i)).toBeInTheDocument();
expect(getByText(/Destination subscription/i)).toBeInTheDocument();
expect(getByText(/Destination account/i)).toBeInTheDocument();
});
});
@@ -1,6 +1,6 @@
import { DetailsList, DetailsListLayoutMode, Stack, Text, TextField } from "@fluentui/react";
import { Keys, t } from "Localization";
import React, { useEffect } from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { getDefaultJobName } from "../../../CopyJobUtils";
import FieldRow from "../Components/FieldRow";
@@ -32,19 +32,19 @@ const PreviewCopyJob: React.FC = () => {
};
return (
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer" data-test="Panel:PreviewCopyJob">
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
<FieldRow label={t(Keys.containerCopy.preview.jobNameLabel)}>
<TextField data-test="job-name-textfield" value={jobName} onChange={onJobNameChange} />
</FieldRow>
<Stack>
<Text className="bold themeText">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
<Text data-test="source-subscription-name" className="themeText">
{copyJobState.source?.subscription?.displayName}
<Text className="bold themeText">{t(Keys.containerCopy.preview.subscriptionLabel)}</Text>
<Text data-test="destination-subscription-name" className="themeText">
{copyJobState.target?.subscription?.displayName}
</Text>
</Stack>
<Stack>
<Text className="bold themeText">{ContainerCopyMessages.sourceAccountLabel}</Text>
<Text data-test="source-account-name" className="themeText">
{copyJobState.source?.account?.name}
<Text className="bold themeText">{t(Keys.containerCopy.preview.accountLabel)}</Text>
<Text data-test="destination-account-name" className="themeText">
{copyJobState.target?.account?.name}
</Text>
</Stack>
<Stack>
@@ -1,5 +1,5 @@
import { IColumn } from "@fluentui/react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { Keys, t } from "Localization";
const commonProps = {
minWidth: 130,
@@ -17,25 +17,25 @@ export const getPreviewCopyJobDetailsListColumns = (): IColumn[] => {
return [
{
key: "sourcedbname",
name: ContainerCopyMessages.sourceDatabaseLabel,
name: t(Keys.containerCopy.preview.sourceDatabaseLabel),
fieldName: "sourceDatabaseName",
...commonProps,
},
{
key: "sourcecolname",
name: ContainerCopyMessages.sourceContainerLabel,
name: t(Keys.containerCopy.preview.sourceContainerLabel),
fieldName: "sourceContainerName",
...commonProps,
},
{
key: "targetdbname",
name: ContainerCopyMessages.targetDatabaseLabel,
name: t(Keys.containerCopy.preview.targetDatabaseLabel),
fieldName: "targetDatabaseName",
...commonProps,
},
{
key: "targetcolname",
name: ContainerCopyMessages.targetContainerLabel,
name: t(Keys.containerCopy.preview.targetContainerLabel),
fieldName: "targetContainerName",
...commonProps,
},
@@ -49,11 +49,11 @@ exports[`PreviewCopyJob should handle special characters in database and contain
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -64,11 +64,11 @@ exports[`PreviewCopyJob should handle special characters in database and contain
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -371,11 +371,11 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -386,13 +386,13 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
target-account
</span>
</div>
<div
@@ -693,11 +693,11 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -708,11 +708,11 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -1015,13 +1015,13 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
This is a very long subscription name that might cause display issues if not handled properly
Test Subscription
</span>
</div>
<div
@@ -1030,13 +1030,13 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
this-is-a-very-long-database-account-name-that-might-cause-display-issues
test-account
</span>
</div>
<div
@@ -1337,11 +1337,11 @@ exports[`PreviewCopyJob should render with missing source account information 1`
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -1352,7 +1352,13 @@ exports[`PreviewCopyJob should render with missing source account information 1`
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="destination-account-name"
>
test-account
</span>
</div>
<div
@@ -1653,7 +1659,13 @@ exports[`PreviewCopyJob should render with missing source subscription informati
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="destination-subscription-name"
>
Test Subscription
</span>
</div>
<div
@@ -1662,11 +1674,11 @@ exports[`PreviewCopyJob should render with missing source subscription informati
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -1969,11 +1981,11 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -1984,11 +1996,11 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -2291,11 +2303,11 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -2306,11 +2318,11 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -2613,11 +2625,11 @@ exports[`PreviewCopyJob should render with undefined database and container name
<span
class="bold themeText css-125"
>
Source subscription
Destination subscription
</span>
<span
class="themeText css-125"
data-test="source-subscription-name"
data-test="destination-subscription-name"
>
Test Subscription
</span>
@@ -2628,11 +2640,11 @@ exports[`PreviewCopyJob should render with undefined database and container name
<span
class="bold themeText css-125"
>
Source account
Destination account
</span>
<span
class="themeText css-125"
data-test="source-account-name"
data-test="destination-account-name"
>
test-account
</span>
@@ -1,11 +1,11 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import { configContext, Platform } from "../../../../../../ConfigContext";
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
import * as useDatabaseAccountsHook from "../../../../../../hooks/useDatabaseAccounts";
import { apiType, userContext } from "../../../../../../UserContext";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes";
@@ -38,6 +38,12 @@ describe("AccountDropdown", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscriptionId: "",
account: null,
databaseId: "",
containerId: "",
},
target: {
subscription: {
subscriptionId: "test-subscription-id",
displayName: "Test Subscription",
@@ -46,13 +52,7 @@ describe("AccountDropdown", () => {
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
account: null,
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
} as CopyJobContextState;
const mockCopyJobContextValue = {
@@ -129,11 +129,11 @@ describe("AccountDropdown", () => {
renderWithContext();
expect(
screen.getByText(`${ContainerCopyMessages.sourceAccountDropdownLabel}:`, { exact: true }),
screen.getByText(`${t(Keys.containerCopy.selectAccount.accountDropdownLabel)}:`, { exact: true }),
).toBeInTheDocument();
expect(screen.getByRole("combobox")).toHaveAttribute(
"aria-label",
ContainerCopyMessages.sourceAccountDropdownLabel,
t(Keys.containerCopy.selectAccount.accountDropdownLabel),
);
});
@@ -202,7 +202,7 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toEqual({
expect(newState.target.account).toEqual({
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
});
@@ -226,20 +226,21 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toEqual({
expect(newState.target.account).toEqual({
...mockDatabaseAccount2,
id: normalizeAccountId(mockDatabaseAccount2.id),
});
});
it("should keep current account if it exists in the filtered list", async () => {
const normalizedAccount1 = { ...mockDatabaseAccount1, id: normalizeAccountId(mockDatabaseAccount1.id) };
const contextWithSelectedAccount = {
...mockCopyJobContextValue,
copyJobState: {
...mockCopyJobState,
source: {
...mockCopyJobState.source,
account: mockDatabaseAccount1,
target: {
...mockCopyJobState.target,
account: normalizedAccount1,
},
},
};
@@ -256,12 +257,9 @@ describe("AccountDropdown", () => {
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
expect(newState).toEqual({
...contextWithSelectedAccount.copyJobState,
source: {
...contextWithSelectedAccount.copyJobState.source,
account: {
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
},
target: {
...contextWithSelectedAccount.copyJobState.target,
account: normalizedAccount1,
},
});
});
@@ -297,8 +295,8 @@ describe("AccountDropdown", () => {
...mockCopyJobContextValue,
copyJobState: {
...mockCopyJobState,
source: {
...mockCopyJobState.source,
target: {
...mockCopyJobState.target,
account: portalAccount,
},
},
@@ -323,8 +321,8 @@ describe("AccountDropdown", () => {
...mockCopyJobContextValue,
copyJobState: {
...mockCopyJobState,
source: {
...mockCopyJobState.source,
target: {
...mockCopyJobState.target,
account: hostedAccount,
},
},
@@ -361,8 +359,8 @@ describe("AccountDropdown", () => {
...mockCopyJobContextValue,
copyJobState: {
...mockCopyJobState,
source: {
...mockCopyJobState.source,
target: {
...mockCopyJobState.target,
subscription: null,
},
} as CopyJobContextState,
@@ -376,13 +374,13 @@ describe("AccountDropdown", () => {
});
it("should not update state if account is already selected and the same", async () => {
const selectedAccount = mockDatabaseAccount1;
const selectedAccount = { ...mockDatabaseAccount1, id: normalizeAccountId(mockDatabaseAccount1.id) };
const contextWithSelectedAccount = {
...mockCopyJobContextValue,
copyJobState: {
...mockCopyJobState,
source: {
...mockCopyJobState.source,
target: {
...mockCopyJobState.target,
account: selectedAccount,
},
},
@@ -409,7 +407,7 @@ describe("AccountDropdown", () => {
renderWithContext();
const dropdown = screen.getByRole("combobox");
expect(dropdown).toHaveAttribute("aria-label", ContainerCopyMessages.sourceAccountDropdownLabel);
expect(dropdown).toHaveAttribute("aria-label", t(Keys.containerCopy.selectAccount.accountDropdownLabel));
});
it("should have required attribute", () => {
@@ -2,11 +2,11 @@
/* eslint-disable react/display-name */
import { Dropdown } from "@fluentui/react";
import { configContext, Platform } from "ConfigContext";
import { Keys, t } from "Localization";
import React, { useEffect } from "react";
import { DatabaseAccount } from "../../../../../../Contracts/DataModels";
import { useDatabaseAccounts } from "../../../../../../hooks/useDatabaseAccounts";
import { apiType, userContext } from "../../../../../../UserContext";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
import FieldRow from "../../Components/FieldRow";
@@ -25,7 +25,7 @@ export const normalizeAccountId = (id: string = "") => {
export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const { copyJobState, setCopyJobState } = useCopyJobContext();
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const selectedSubscriptionId = copyJobState?.target?.subscription?.subscriptionId;
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
const sqlApiOnlyAccounts = (allAccounts || [])
.filter((account) => apiType(account) === "SQL")
@@ -36,11 +36,11 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const updateCopyJobState = (newAccount: DatabaseAccount) => {
setCopyJobState((prevState) => {
if (prevState.source?.account?.id !== newAccount.id) {
if (prevState.target?.account?.id !== newAccount.id) {
return {
...prevState,
source: {
...prevState.source,
target: {
...prevState.target,
account: newAccount,
},
};
@@ -51,13 +51,13 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
useEffect(() => {
if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) {
const currentAccountId = copyJobState?.source?.account?.id;
const currentAccountId = copyJobState?.target?.account?.id;
const predefinedAccountId = normalizeAccountId(userContext.databaseAccount?.id);
const selectedAccountId = currentAccountId || predefinedAccountId;
const targetAccount: DatabaseAccount | null =
const matchedAccount: DatabaseAccount | null =
sqlApiOnlyAccounts.find((account) => account.id === selectedAccountId) || null;
updateCopyJobState(targetAccount || sqlApiOnlyAccounts[0]);
updateCopyJobState(matchedAccount || sqlApiOnlyAccounts[0]);
}
}, [sqlApiOnlyAccounts?.length, selectedSubscriptionId]);
@@ -77,13 +77,13 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
};
const isAccountDropdownDisabled = !selectedSubscriptionId || accountOptions.length === 0;
const selectedAccountId = normalizeAccountId(copyJobState?.source?.account?.id ?? "");
const selectedAccountId = normalizeAccountId(copyJobState?.target?.account?.id ?? "");
return (
<FieldRow label={ContainerCopyMessages.sourceAccountDropdownLabel}>
<FieldRow label={t(Keys.containerCopy.selectAccount.accountDropdownLabel)}>
<Dropdown
placeholder={ContainerCopyMessages.sourceAccountDropdownPlaceholder}
ariaLabel={ContainerCopyMessages.sourceAccountDropdownLabel}
placeholder={t(Keys.containerCopy.selectAccount.accountDropdownPlaceholder)}
ariaLabel={t(Keys.containerCopy.selectAccount.accountDropdownLabel)}
options={accountOptions}
disabled={isAccountDropdownDisabled}
required
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
import { MigrationType } from "./MigrationType";
@@ -29,7 +29,7 @@ describe("MigrationType", () => {
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
},
setCopyJobState: mockSetCopyJobState,
flow: { currentScreen: "selectAccount" },
@@ -53,9 +53,9 @@ describe("MigrationType", () => {
expect(screen.getByRole("radiogroup")).toBeInTheDocument();
const offlineRadio = screen.getByRole("radio", {
name: ContainerCopyMessages.migrationTypeOptions.offline.title,
name: t(Keys.containerCopy.migrationType.offline.title),
});
const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title });
const onlineRadio = screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) });
expect(offlineRadio).toBeInTheDocument();
expect(onlineRadio).toBeInTheDocument();
@@ -65,9 +65,9 @@ describe("MigrationType", () => {
it("should render with online mode selected by default", () => {
render(<MigrationType />);
const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title });
const onlineRadio = screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) });
const offlineRadio = screen.getByRole("radio", {
name: ContainerCopyMessages.migrationTypeOptions.offline.title,
name: t(Keys.containerCopy.migrationType.offline.title),
});
expect(onlineRadio).toBeChecked();
@@ -86,9 +86,9 @@ describe("MigrationType", () => {
render(<MigrationType />);
const offlineRadio = screen.getByRole("radio", {
name: ContainerCopyMessages.migrationTypeOptions.offline.title,
name: t(Keys.containerCopy.migrationType.offline.title),
});
const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title });
const onlineRadio = screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) });
expect(offlineRadio).toBeChecked();
expect(onlineRadio).not.toBeChecked();
@@ -141,7 +141,7 @@ describe("MigrationType", () => {
render(<MigrationType />);
const offlineRadio = screen.getByRole("radio", {
name: ContainerCopyMessages.migrationTypeOptions.offline.title,
name: t(Keys.containerCopy.migrationType.offline.title),
});
fireEvent.click(offlineRadio);
@@ -167,7 +167,7 @@ describe("MigrationType", () => {
render(<MigrationType />);
const onlineRadio = screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title });
const onlineRadio = screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) });
fireEvent.click(onlineRadio);
expect(mockSetCopyJobState).toHaveBeenCalledWith(expect.any(Function));
@@ -198,11 +198,9 @@ describe("MigrationType", () => {
render(<MigrationType />);
expect(
screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.offline.title }),
).toBeInTheDocument();
expect(
screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }),
screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.offline.title) }),
).toBeInTheDocument();
expect(screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) })).toBeInTheDocument();
});
});
@@ -220,11 +218,9 @@ describe("MigrationType", () => {
expect(container.querySelector("[data-test='migration-type']")).toBeInTheDocument();
expect(
screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.offline.title }),
).toBeInTheDocument();
expect(
screen.getByRole("radio", { name: ContainerCopyMessages.migrationTypeOptions.online.title }),
screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.offline.title) }),
).toBeInTheDocument();
expect(screen.getByRole("radio", { name: t(Keys.containerCopy.migrationType.online.title) })).toBeInTheDocument();
});
it("should handle null copyJobState gracefully", () => {
@@ -3,20 +3,20 @@
import { ChoiceGroup, IChoiceGroupOption, Stack, Text } from "@fluentui/react";
import MarkdownRender from "@nteract/markdown";
import { useCopyJobContext } from "Explorer/ContainerCopy/Context/CopyJobContext";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
interface MigrationTypeProps {}
const options: IChoiceGroupOption[] = [
{
key: CopyJobMigrationType.Offline,
text: ContainerCopyMessages.migrationTypeOptions.offline.title,
text: t(Keys.containerCopy.migrationType.offline.title),
styles: { root: { width: "33%" } },
},
{
key: CopyJobMigrationType.Online,
text: ContainerCopyMessages.migrationTypeOptions.online.title,
text: t(Keys.containerCopy.migrationType.online.title),
styles: { root: { width: "33%" } },
},
];
@@ -47,8 +47,13 @@ export const MigrationType: React.FC<MigrationTypeProps> = React.memo(() => {
};
const selectedKey = copyJobState?.migrationType ?? "";
const selectedKeyLowercase = selectedKey.toLowerCase() as keyof typeof ContainerCopyMessages.migrationTypeOptions;
const selectedKeyContent = ContainerCopyMessages.migrationTypeOptions[selectedKeyLowercase];
const selectedKeyLowercase = selectedKey.toLowerCase() as "offline" | "online";
const migrationTypeDescriptionKey =
selectedKeyLowercase === "offline"
? Keys.containerCopy.migrationType.offline.description
: selectedKeyLowercase === "online"
? Keys.containerCopy.migrationType.online.description
: null;
return (
<Stack data-test="migration-type" className="migrationTypeContainer">
@@ -61,14 +66,14 @@ export const MigrationType: React.FC<MigrationTypeProps> = React.memo(() => {
styles={choiceGroupStyles}
/>
</Stack.Item>
{selectedKeyContent && (
{migrationTypeDescriptionKey && (
<Stack.Item styles={{ root: { marginTop: 10 } }}>
<Text
variant="small"
className="migrationTypeDescription"
data-test={`migration-type-description-${selectedKeyLowercase}`}
>
<MarkdownRender source={selectedKeyContent.description} linkTarget="_blank" />
<MarkdownRender source={t(migrationTypeDescriptionKey)} linkTarget="_blank" />
</Text>
</Stack.Item>
)}
@@ -8,14 +8,9 @@ import { SubscriptionDropdown } from "./SubscriptionDropdown";
jest.mock("../../../../../../hooks/useSubscriptions");
jest.mock("../../../../../../UserContext");
jest.mock("../../../../ContainerCopyMessages");
const mockUseSubscriptions = jest.requireMock("../../../../../../hooks/useSubscriptions").useSubscriptions;
const mockUserContext = jest.requireMock("../../../../../../UserContext").userContext;
const mockContainerCopyMessages = jest.requireMock("../../../../ContainerCopyMessages").default;
mockContainerCopyMessages.subscriptionDropdownLabel = "Subscription";
mockContainerCopyMessages.subscriptionDropdownPlaceholder = "Select a subscription";
describe("SubscriptionDropdown", () => {
let mockExplorer: Explorer;
@@ -1,11 +1,11 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/display-name */
import { Dropdown } from "@fluentui/react";
import { Keys, t } from "Localization";
import React, { useEffect } from "react";
import { Subscription } from "../../../../../../Contracts/DataModels";
import { useSubscriptions } from "../../../../../../hooks/useSubscriptions";
import { userContext } from "../../../../../../UserContext";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../../Context/CopyJobContext";
import FieldRow from "../../Components/FieldRow";
@@ -17,11 +17,11 @@ export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.m
const updateCopyJobState = (newSubscription: Subscription) => {
setCopyJobState((prevState) => {
if (prevState.source?.subscription?.subscriptionId !== newSubscription.subscriptionId) {
if (prevState.target?.subscription?.subscriptionId !== newSubscription.subscriptionId) {
return {
...prevState,
source: {
...prevState.source,
target: {
...prevState.target,
subscription: newSubscription,
account: null,
},
@@ -33,7 +33,7 @@ export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.m
useEffect(() => {
if (subscriptions && subscriptions.length > 0) {
const currentSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const currentSubscriptionId = copyJobState?.target?.subscription?.subscriptionId;
const predefinedSubscriptionId = userContext.subscriptionId;
const selectedSubscriptionId = currentSubscriptionId || predefinedSubscriptionId;
@@ -61,13 +61,13 @@ export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.m
}
};
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const selectedSubscriptionId = copyJobState?.target?.subscription?.subscriptionId;
return (
<FieldRow label={ContainerCopyMessages.subscriptionDropdownLabel}>
<FieldRow label={t(Keys.containerCopy.selectAccount.subscriptionDropdownLabel)}>
<Dropdown
placeholder={ContainerCopyMessages.subscriptionDropdownPlaceholder}
ariaLabel={ContainerCopyMessages.subscriptionDropdownLabel}
placeholder={t(Keys.containerCopy.selectAccount.subscriptionDropdownPlaceholder)}
ariaLabel={t(Keys.containerCopy.selectAccount.subscriptionDropdownLabel)}
data-test="subscription-dropdown"
options={subscriptionOptions}
required
@@ -30,18 +30,18 @@ describe("SelectAccount", () => {
jobName: "",
migrationType: CopyJobMigrationType.Online,
source: {
subscription: null as any,
account: null as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "",
},
sourceReadAccessFromTarget: false,
target: {
subscription: null as any,
account: null as any,
databaseId: "",
containerId: "",
},
sourceReadWriteAccessFromTarget: false,
},
setCopyJobState: mockSetCopyJobState,
flow: { currentScreen: "selectAccount" },
@@ -68,7 +68,7 @@ describe("SelectAccount", () => {
expect(container.firstChild).toHaveAttribute("data-test", "Panel:SelectAccountContainer");
expect(container.firstChild).toHaveClass("selectAccountContainer");
expect(screen.getByText(/Please select a source account from which to copy/i)).toBeInTheDocument();
expect(screen.getByText(/Please select a destination account to copy to/i)).toBeInTheDocument();
expect(screen.getByTestId("subscription-dropdown")).toBeInTheDocument();
expect(screen.getByTestId("account-dropdown")).toBeInTheDocument();
@@ -1,6 +1,6 @@
import { Stack, Text } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { AccountDropdown } from "./Components/AccountDropdown";
import { MigrationType } from "./Components/MigrationType";
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
@@ -8,7 +8,7 @@ import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
const SelectAccount = React.memo(() => {
return (
<Stack data-test="Panel:SelectAccountContainer" className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
<Text className="themeText">{ContainerCopyMessages.selectAccountDescription}</Text>
<Text className="themeText">{t(Keys.containerCopy.selectAccount.description)}</Text>
<SubscriptionDropdown />
@@ -8,7 +8,7 @@ exports[`SelectAccount Component Rendering should render correctly with snapshot
<span
class="themeText css-110"
>
Please select a source account from which to copy.
Please select a destination account to copy to.
</span>
<div
data-testid="subscription-dropdown"
@@ -7,19 +7,9 @@ import { dropDownChangeHandler } from "./DropDownChangeHandler";
const createMockInitialState = (): CopyJobContextState => ({
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
source: {
subscription: {
subscriptionId: "source-sub-id",
displayName: "Source Subscription",
state: "Enabled",
subscriptionPolicies: {
locationPlacementId: "test",
quotaId: "test",
spendingLimit: "Off",
},
authorizationSource: "test",
},
account: {
id: "source-account-id",
name: "source-account",
@@ -50,7 +40,17 @@ const createMockInitialState = (): CopyJobContextState => ({
containerId: "source-container",
},
target: {
subscription: {
subscriptionId: "target-sub-id",
displayName: "Target Subscription",
state: "Enabled",
subscriptionPolicies: {
locationPlacementId: "test",
quotaId: "test",
spendingLimit: "Off",
},
authorizationSource: "test",
},
account: {
id: "target-account-id",
name: "target-account",
@@ -169,7 +169,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.source.databaseId).toBe("new-source-db");
expect(capturedState.source.containerId).toBeUndefined();
expect(capturedState.source.subscription).toEqual(initialState.source.subscription);
expect(capturedState.source.subscriptionId).toEqual(initialState.source.subscriptionId);
expect(capturedState.source.account).toEqual(initialState.source.account);
expect(capturedState.target).toEqual(initialState.target);
});
@@ -181,7 +181,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.jobName).toBe(initialState.jobName);
expect(capturedState.migrationType).toBe(initialState.migrationType);
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
expect(capturedState.sourceReadWriteAccessFromTarget).toBe(initialState.sourceReadWriteAccessFromTarget);
});
});
@@ -193,7 +193,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.source.containerId).toBe("new-source-container");
expect(capturedState.source.databaseId).toBe(initialState.source.databaseId);
expect(capturedState.source.subscription).toEqual(initialState.source.subscription);
expect(capturedState.source.subscriptionId).toEqual(initialState.source.subscriptionId);
expect(capturedState.source.account).toEqual(initialState.source.account);
expect(capturedState.target).toEqual(initialState.target);
});
@@ -215,7 +215,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.target.databaseId).toBe("new-target-db");
expect(capturedState.target.containerId).toBeUndefined();
expect(capturedState.target.subscriptionId).toBe(initialState.target.subscriptionId);
expect(capturedState.target.subscription).toEqual(initialState.target.subscription);
expect(capturedState.target.account).toEqual(initialState.target.account);
expect(capturedState.source).toEqual(initialState.source);
});
@@ -227,7 +227,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.jobName).toBe(initialState.jobName);
expect(capturedState.migrationType).toBe(initialState.migrationType);
expect(capturedState.sourceReadAccessFromTarget).toBe(initialState.sourceReadAccessFromTarget);
expect(capturedState.sourceReadWriteAccessFromTarget).toBe(initialState.sourceReadWriteAccessFromTarget);
});
});
@@ -239,7 +239,7 @@ describe("dropDownChangeHandler", () => {
expect(capturedState.target.containerId).toBe("new-target-container");
expect(capturedState.target.databaseId).toBe(initialState.target.databaseId);
expect(capturedState.target.subscriptionId).toBe(initialState.target.subscriptionId);
expect(capturedState.target.subscription).toEqual(initialState.target.subscription);
expect(capturedState.target.account).toEqual(initialState.target.account);
expect(capturedState.source).toEqual(initialState.source);
});
@@ -15,15 +15,6 @@ jest.mock("../../../../../hooks/useDataContainers", () => ({
useDataContainers: jest.fn(),
}));
jest.mock("../../../ContainerCopyMessages", () => ({
__esModule: true,
default: {
selectSourceAndTargetContainersDescription: "Select source and target containers for migration",
sourceContainerSubHeading: "Source Container",
targetContainerSubHeading: "Target Container",
},
}));
jest.mock("./Events/DropDownChangeHandler", () => ({
dropDownChangeHandler: jest.fn(() => () => jest.fn()),
}));
@@ -73,7 +64,7 @@ describe("SelectSourceAndTargetContainers", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "test-subscription-id" },
subscriptionId: "test-subscription-id",
account: {
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
name: "test-account",
@@ -82,7 +73,7 @@ describe("SelectSourceAndTargetContainers", () => {
containerId: "container1",
},
target: {
subscriptionId: "test-subscription-id",
subscription: { subscriptionId: "test-subscription-id" },
account: {
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/test-account",
name: "test-account",
@@ -90,7 +81,7 @@ describe("SelectSourceAndTargetContainers", () => {
databaseId: "db2",
containerId: "container2",
},
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
};
const mockMemoizedData = {
@@ -124,22 +115,26 @@ describe("SelectSourceAndTargetContainers", () => {
describe("Component Rendering", () => {
it("should render without crashing", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
expect(
screen.getByText("Please select a source container and a destination container to copy to."),
).toBeInTheDocument();
});
it("should render description text", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
expect(
screen.getByText("Please select a source container and a destination container to copy to."),
).toBeInTheDocument();
});
it("should render source container section", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Source Container")).toBeInTheDocument();
expect(screen.getByText("Source container")).toBeInTheDocument();
});
it("should render target container section", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Target Container")).toBeInTheDocument();
expect(screen.getByText("Destination container")).toBeInTheDocument();
});
it("should return null when source is not available", () => {
@@ -238,14 +233,14 @@ describe("SelectSourceAndTargetContainers", () => {
describe("Component Props", () => {
it("should pass showAddCollectionPanel to DatabaseContainerSection", () => {
renderWithContext(<SelectSourceAndTargetContainers showAddCollectionPanel={mockShowAddCollectionPanel} />);
expect(screen.getByText("Target Container")).toBeInTheDocument();
expect(screen.getByText("Destination container")).toBeInTheDocument();
});
it("should render without showAddCollectionPanel prop", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Source Container")).toBeInTheDocument();
expect(screen.getByText("Target Container")).toBeInTheDocument();
expect(screen.getByText("Source container")).toBeInTheDocument();
expect(screen.getByText("Destination container")).toBeInTheDocument();
});
});
@@ -310,13 +305,13 @@ describe("SelectSourceAndTargetContainers", () => {
it("should pass correct props to source DatabaseContainerSection", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Source Container")).toBeInTheDocument();
expect(screen.getByText("Source container")).toBeInTheDocument();
});
it("should pass correct props to target DatabaseContainerSection", () => {
renderWithContext(<SelectSourceAndTargetContainers showAddCollectionPanel={mockShowAddCollectionPanel} />);
expect(screen.getByText("Target Container")).toBeInTheDocument();
expect(screen.getByText("Destination container")).toBeInTheDocument();
});
it("should disable source container dropdown when no database is selected", () => {
@@ -329,7 +324,7 @@ describe("SelectSourceAndTargetContainers", () => {
} as ReturnType<typeof useSourceAndTargetData>);
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Source Container")).toBeInTheDocument();
expect(screen.getByText("Source container")).toBeInTheDocument();
});
it("should disable target container dropdown when no database is selected", () => {
@@ -342,7 +337,7 @@ describe("SelectSourceAndTargetContainers", () => {
} as ReturnType<typeof useSourceAndTargetData>);
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Target Container")).toBeInTheDocument();
expect(screen.getByText("Destination container")).toBeInTheDocument();
});
});
@@ -353,7 +348,9 @@ describe("SelectSourceAndTargetContainers", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
expect(
screen.getByText("Please select a source container and a destination container to copy to."),
).toBeInTheDocument();
});
it("should handle hooks throwing errors gracefully", () => {
@@ -421,7 +418,9 @@ describe("SelectSourceAndTargetContainers", () => {
it("should apply correct spacing tokens", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
expect(screen.getByText("Select source and target containers for migration")).toBeInTheDocument();
expect(
screen.getByText("Please select a source container and a destination container to copy to."),
).toBeInTheDocument();
});
});
@@ -429,9 +428,9 @@ describe("SelectSourceAndTargetContainers", () => {
it("should render description, source section, and target section in correct order", () => {
renderWithContext(<SelectSourceAndTargetContainers />);
const description = screen.getByText("Select source and target containers for migration");
const sourceSection = screen.getByText("Source Container");
const targetSection = screen.getByText("Target Container");
const description = screen.getByText("Please select a source container and a destination container to copy to.");
const sourceSection = screen.getByText("Source container");
const targetSection = screen.getByText("Destination container");
expect(description).toBeInTheDocument();
expect(sourceSection).toBeInTheDocument();
@@ -1,9 +1,9 @@
import { Stack } from "@fluentui/react";
import { DatabaseModel } from "Contracts/DataModels";
import { Keys, t } from "Localization";
import React from "react";
import { useDatabases } from "../../../../../hooks/useDatabases";
import { useDataContainers } from "../../../../../hooks/useDataContainers";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
import { DatabaseContainerSection } from "./components/DatabaseContainerSection";
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
@@ -52,9 +52,9 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
className="selectSourceAndTargetContainers"
tokens={{ childrenGap: 25 }}
>
<span className="themeText">{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
<span className="themeText">{t(Keys.containerCopy.selectContainers.description)}</span>
<DatabaseContainerSection
heading={ContainerCopyMessages.sourceContainerSubHeading}
heading={t(Keys.containerCopy.selectContainers.sourceContainerSubHeading)}
databaseOptions={sourceDatabaseOptions}
selectedDatabase={source?.databaseId}
databaseDisabled={false}
@@ -66,7 +66,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
sectionType="source"
/>
<DatabaseContainerSection
heading={ContainerCopyMessages.targetContainerSubHeading}
heading={t(Keys.containerCopy.selectContainers.targetContainerSubHeading)}
databaseOptions={targetDatabaseOptions}
selectedDatabase={target?.databaseId}
databaseDisabled={false}
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { DatabaseContainerSectionProps, DropdownOptionType } from "../../../../Types/CopyJobTypes";
import { DatabaseContainerSection } from "./DatabaseContainerSection";
@@ -60,11 +60,14 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
expect(databaseDropdown).toBeInTheDocument();
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
expect(databaseDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
);
expect(databaseDropdown).not.toBeDisabled();
});
@@ -72,30 +75,35 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(containerDropdown).toBeInTheDocument();
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
expect(containerDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.containerDropdownLabel),
);
expect(containerDropdown).not.toBeDisabled();
});
it("renders database label correctly", () => {
render(<DatabaseContainerSection {...defaultProps} />);
expect(screen.getByText(`${ContainerCopyMessages.databaseDropdownLabel}:`)).toBeInTheDocument();
expect(screen.getByText(`${t(Keys.containerCopy.selectContainers.databaseDropdownLabel)}:`)).toBeInTheDocument();
});
it("renders container label correctly", () => {
render(<DatabaseContainerSection {...defaultProps} />);
expect(screen.getByText(`${ContainerCopyMessages.containerDropdownLabel}:`)).toBeInTheDocument();
expect(screen.getByText(`${t(Keys.containerCopy.selectContainers.containerDropdownLabel)}:`)).toBeInTheDocument();
});
it("does not render create container button when handleOnDemandCreateContainer is not provided", () => {
render(<DatabaseContainerSection {...defaultProps} />);
expect(screen.queryByText(ContainerCopyMessages.createContainerButtonLabel)).not.toBeInTheDocument();
expect(
screen.queryByText(t(Keys.containerCopy.selectContainers.createContainerButtonLabel)),
).not.toBeInTheDocument();
});
it("renders create container button when handleOnDemandCreateContainer is provided", () => {
@@ -107,7 +115,7 @@ describe("DatabaseContainerSection", () => {
const createButton = container.querySelector(".create-container-link-btn");
expect(createButton).toBeInTheDocument();
expect(createButton).toHaveTextContent(ContainerCopyMessages.createContainerButtonLabel);
expect(createButton).toHaveTextContent(t(Keys.containerCopy.selectContainers.createContainerButtonLabel));
});
});
@@ -121,7 +129,7 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithDisabledDatabase} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
expect(databaseDropdown).toHaveAttribute("aria-disabled", "true");
@@ -136,7 +144,7 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithDisabledContainer} />);
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(containerDropdown).toHaveAttribute("aria-disabled", "true");
@@ -152,10 +160,10 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithFalsyDisabled} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(databaseDropdown).not.toHaveAttribute("aria-disabled", "true");
@@ -167,21 +175,27 @@ describe("DatabaseContainerSection", () => {
it("calls databaseOnChange when database dropdown selection changes", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
fireEvent.click(databaseDropdown);
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
expect(databaseDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
);
});
it("calls containerOnChange when container dropdown selection changes", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
fireEvent.click(containerDropdown);
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
expect(containerDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.containerDropdownLabel),
);
});
it("calls handleOnDemandCreateContainer when create container button is clicked", () => {
@@ -192,7 +206,7 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithCreateHandler} />);
const createButton = screen.getByText(ContainerCopyMessages.createContainerButtonLabel);
const createButton = screen.getByText(t(Keys.containerCopy.selectContainers.createContainerButtonLabel));
fireEvent.click(createButton);
expect(mockHandleOnDemandCreateContainer).toHaveBeenCalledTimes(1);
@@ -235,10 +249,10 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithEmptyOptions} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(databaseDropdown).toBeInTheDocument();
@@ -251,24 +265,30 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(databaseDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.databaseDropdownLabel);
expect(containerDropdown).toHaveAttribute("aria-label", ContainerCopyMessages.containerDropdownLabel);
expect(databaseDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
);
expect(containerDropdown).toHaveAttribute(
"aria-label",
t(Keys.containerCopy.selectContainers.containerDropdownLabel),
);
});
it("has proper required attributes for dropdowns", () => {
render(<DatabaseContainerSection {...defaultProps} />);
const databaseDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.databaseDropdownLabel,
name: t(Keys.containerCopy.selectContainers.databaseDropdownLabel),
});
const containerDropdown = screen.getByRole("combobox", {
name: ContainerCopyMessages.containerDropdownLabel,
name: t(Keys.containerCopy.selectContainers.containerDropdownLabel),
});
expect(databaseDropdown).toHaveAttribute("aria-required", "true");
@@ -278,8 +298,8 @@ describe("DatabaseContainerSection", () => {
it("maintains proper label associations", () => {
render(<DatabaseContainerSection {...defaultProps} />);
expect(screen.getByText(`${ContainerCopyMessages.databaseDropdownLabel}:`)).toBeInTheDocument();
expect(screen.getByText(`${ContainerCopyMessages.containerDropdownLabel}:`)).toBeInTheDocument();
expect(screen.getByText(`${t(Keys.containerCopy.selectContainers.databaseDropdownLabel)}:`)).toBeInTheDocument();
expect(screen.getByText(`${t(Keys.containerCopy.selectContainers.containerDropdownLabel)}:`)).toBeInTheDocument();
});
});
@@ -299,7 +319,9 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...minimalProps} />);
expect(screen.getByText("Test Heading")).toBeInTheDocument();
expect(screen.queryByText(ContainerCopyMessages.createContainerButtonLabel)).not.toBeInTheDocument();
expect(
screen.queryByText(t(Keys.containerCopy.selectContainers.createContainerButtonLabel)),
).not.toBeInTheDocument();
});
it("handles empty string selections", () => {
@@ -366,7 +388,7 @@ describe("DatabaseContainerSection", () => {
const { container } = render(<DatabaseContainerSection {...propsWithCreateHandler} />);
const createButton = screen.getByText(ContainerCopyMessages.createContainerButtonLabel);
const createButton = screen.getByText(t(Keys.containerCopy.selectContainers.createContainerButtonLabel));
expect(createButton).toBeInTheDocument();
const containerSection = container.querySelector(".databaseContainerSection");
@@ -381,7 +403,7 @@ describe("DatabaseContainerSection", () => {
render(<DatabaseContainerSection {...propsWithCreateHandler} />);
expect(screen.getByText(ContainerCopyMessages.createContainerButtonLabel)).toBeInTheDocument();
expect(screen.getByText(t(Keys.containerCopy.selectContainers.createContainerButtonLabel))).toBeInTheDocument();
});
});
@@ -1,6 +1,6 @@
import { ActionButton, Dropdown, Stack } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { DatabaseContainerSectionProps } from "../../../../Types/CopyJobTypes";
import FieldRow from "../../Components/FieldRow";
@@ -19,10 +19,10 @@ export const DatabaseContainerSection = ({
}: DatabaseContainerSectionProps) => (
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
<label className="subHeading">{heading}</label>
<FieldRow label={ContainerCopyMessages.databaseDropdownLabel}>
<FieldRow label={t(Keys.containerCopy.selectContainers.databaseDropdownLabel)}>
<Dropdown
placeholder={ContainerCopyMessages.databaseDropdownPlaceholder}
ariaLabel={ContainerCopyMessages.databaseDropdownLabel}
placeholder={t(Keys.containerCopy.selectContainers.databaseDropdownPlaceholder)}
ariaLabel={t(Keys.containerCopy.selectContainers.databaseDropdownLabel)}
options={databaseOptions}
required
disabled={!!databaseDisabled}
@@ -31,11 +31,11 @@ export const DatabaseContainerSection = ({
data-test={`${sectionType}-databaseDropdown`}
/>
</FieldRow>
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
<FieldRow label={t(Keys.containerCopy.selectContainers.containerDropdownLabel)}>
<Stack>
<Dropdown
placeholder={ContainerCopyMessages.containerDropdownPlaceholder}
ariaLabel={ContainerCopyMessages.containerDropdownLabel}
placeholder={t(Keys.containerCopy.selectContainers.containerDropdownPlaceholder)}
ariaLabel={t(Keys.containerCopy.selectContainers.containerDropdownLabel)}
options={containerOptions}
required
disabled={!!containerDisabled}
@@ -49,7 +49,7 @@ export const DatabaseContainerSection = ({
style={{ color: "var(--colorBrandForeground1)" }}
onClick={() => handleOnDemandCreateContainer()}
>
{ContainerCopyMessages.createContainerButtonLabel}
{t(Keys.containerCopy.selectContainers.createContainerButtonLabel)}
</ActionButton>
)}
</Stack>
@@ -69,15 +69,15 @@ describe("useSourceAndTargetData", () => {
const mockCopyJobState: CopyJobContextState = {
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
sourceReadAccessFromTarget: false,
sourceReadWriteAccessFromTarget: false,
source: {
subscription: mockSubscription,
subscriptionId: "source-subscription-id",
account: mockSourceAccount,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "target-subscription-id",
subscription: mockSubscription,
account: mockTargetAccount,
databaseId: "target-db",
containerId: "target-container",
@@ -86,13 +86,13 @@ describe("useCopyJobNavigation", () => {
jobName: "test-job",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "source-sub-id" } as any,
subscriptionId: "source-sub-id",
account: { id: "source-account-id", name: "Account-1" } as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "target-sub-id",
subscription: { subscriptionId: "target-sub-id" } as any,
account: { id: "target-account-id", name: "Account-2" } as any,
databaseId: "target-db",
containerId: "target-container",
@@ -142,14 +142,14 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: { subscriptionId: "test-sub" } as any,
subscriptionId: "test-sub",
account: { name: "test-account" } as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
account: null as any,
subscription: { subscriptionId: "test-sub" } as any,
account: { name: "test-account" } as any,
databaseId: "",
containerId: "",
},
@@ -171,14 +171,14 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
account: { name: "test-account" } as any,
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
account: null as any,
subscription: null as any,
account: { name: "test-account" } as any,
databaseId: "",
containerId: "",
},
@@ -210,13 +210,13 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
subscriptionId: "",
account: null as any,
databaseId: "source-db",
containerId: "source-container",
},
target: {
subscriptionId: "",
subscription: null as any,
account: null as any,
databaseId: "target-db",
containerId: "target-container",
@@ -240,13 +240,13 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "source-container",
},
target: {
subscriptionId: "",
subscription: null as any,
account: null as any,
databaseId: "target-db",
containerId: "target-container",
@@ -288,13 +288,13 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "valid-job-name_123",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
subscription: null as any,
account: null as any,
databaseId: "",
containerId: "",
@@ -318,13 +318,13 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "invalid job name with spaces!",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
subscription: null as any,
account: null as any,
databaseId: "",
containerId: "",
@@ -348,13 +348,13 @@ describe("useCreateCopyJobScreensList", () => {
jobName: "",
migrationType: CopyJobMigrationType.Offline,
source: {
subscription: null as any,
subscriptionId: "",
account: null as any,
databaseId: "",
containerId: "",
},
target: {
subscriptionId: "",
subscription: null as any,
account: null as any,
databaseId: "",
containerId: "",
@@ -36,7 +36,7 @@ function useCreateCopyJobScreensList(goBack: () => void): Screen[] {
component: <SelectAccount />,
validations: [
{
validate: (state: CopyJobContextState) => !!state?.source?.subscription && !!state?.source?.account,
validate: (state: CopyJobContextState) => !!state?.target?.subscription && !!state?.target?.account,
message: "Please select a subscription and account to proceed",
},
],
@@ -20,28 +20,6 @@ jest.mock("../../../Controls/Dialog", () => ({
},
}));
jest.mock("../../ContainerCopyMessages", () => ({
__esModule: true,
default: {
MonitorJobs: {
Columns: {
actions: "Actions",
},
Actions: {
pause: "Pause",
resume: "Resume",
cancel: "Cancel",
complete: "Complete",
},
dialog: {
heading: "Confirm Action",
confirmButtonText: "Confirm",
cancelButtonText: "Cancel",
},
},
},
}));
describe("CopyJobActionMenu", () => {
const createMockJob = (overrides: Partial<CopyJobType> = {}): CopyJobType =>
({
@@ -301,8 +279,8 @@ describe("CopyJobActionMenu", () => {
fireEvent.click(cancelButton);
expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith(
"Confirm Action",
null,
"",
"",
"Confirm",
expect.any(Function),
"Cancel",
@@ -358,8 +336,8 @@ describe("CopyJobActionMenu", () => {
fireEvent.click(completeButton);
expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith(
"Confirm Action",
null,
"",
"",
"Confirm",
expect.any(Function),
"Cancel",
@@ -402,8 +380,8 @@ describe("CopyJobActionMenu", () => {
fireEvent.click(cancelButton);
expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith(
"Confirm Action",
null,
"",
"",
"Confirm",
expect.any(Function),
"Cancel",
@@ -433,8 +411,8 @@ describe("CopyJobActionMenu", () => {
fireEvent.click(completeButton);
expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith(
"Confirm Action",
null,
"",
"",
"Confirm",
expect.any(Function),
"Cancel",
@@ -849,8 +827,8 @@ describe("CopyJobActionMenu", () => {
fireEvent.click(cancelButton);
expect(mockShowOkCancelModalDialog).toHaveBeenCalledWith(
"Confirm Action", // title
null, // subText
"", // title
"", // subText
"Confirm", // confirmLabel
expect.any(Function), // onOk
"Cancel", // cancelLabel
@@ -1,7 +1,7 @@
import { DirectionalHint, IconButton, IContextualMenuProps, Stack } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import { useDialog } from "../../../Controls/Dialog";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobActions, CopyJobMigrationType, CopyJobStatusType } from "../../Enums/CopyJobEnums";
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
@@ -49,11 +49,11 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
useDialog
.getState()
.showOkCancelModalDialog(
ContainerCopyMessages.MonitorJobs.dialog.heading,
null,
ContainerCopyMessages.MonitorJobs.dialog.confirmButtonText,
"",
"",
t(Keys.common.confirm),
() => handleClick(job, action, setUpdatingJobAction),
ContainerCopyMessages.MonitorJobs.dialog.cancelButtonText,
t(Keys.common.cancel),
null,
action in dialogBody ? dialogBody[action as keyof typeof dialogBody](job.Name) : null,
);
@@ -65,21 +65,21 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
const baseItems = [
{
key: CopyJobActions.pause,
text: ContainerCopyMessages.MonitorJobs.Actions.pause,
text: t(Keys.containerCopy.monitorJobs.actions.pause),
iconProps: { iconName: "Pause" },
onClick: () => handleClick(job, CopyJobActions.pause, setUpdatingJobAction),
disabled: isThisJobUpdating,
},
{
key: CopyJobActions.cancel,
text: ContainerCopyMessages.MonitorJobs.Actions.cancel,
text: t(Keys.common.cancel),
iconProps: { iconName: "Cancel" },
onClick: () => showActionConfirmationDialog(job, CopyJobActions.cancel),
disabled: isThisJobUpdating,
},
{
key: CopyJobActions.resume,
text: ContainerCopyMessages.MonitorJobs.Actions.resume,
text: t(Keys.containerCopy.monitorJobs.actions.resume),
iconProps: { iconName: "Play" },
onClick: () => handleClick(job, CopyJobActions.resume, setUpdatingJobAction),
disabled: isThisJobUpdating,
@@ -101,7 +101,7 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
if ((job.Mode ?? "").toLowerCase() === CopyJobMigrationType.Online) {
filteredItems.push({
key: CopyJobActions.complete,
text: ContainerCopyMessages.MonitorJobs.Actions.complete,
text: t(Keys.containerCopy.monitorJobs.actions.complete),
iconProps: { iconName: "CheckMark" },
onClick: () => showActionConfirmationDialog(job, CopyJobActions.complete),
disabled: isThisJobUpdating,
@@ -124,8 +124,8 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }}
menuProps={{ items: getMenuItems(), directionalHint: DirectionalHint.leftTopEdge, directionalHintFixed: false }}
menuIconProps={{ iconName: "", className: "hidden" }}
ariaLabel={ContainerCopyMessages.MonitorJobs.Columns.actions}
title={ContainerCopyMessages.MonitorJobs.Columns.actions}
ariaLabel={t(Keys.containerCopy.monitorJobs.columns.actions)}
title={t(Keys.containerCopy.monitorJobs.columns.actions)}
/>
);
};
@@ -1,8 +1,8 @@
import { IColumn } from "@fluentui/react";
import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
import { getColumns } from "./CopyJobColumns";
@@ -79,14 +79,14 @@ describe("CopyJobColumns", () => {
expect(actualKeys).toEqual(expectedKeys);
});
it("should have correct column names from ContainerCopyMessages", () => {
it("should have correct column names", () => {
const columns = getColumns(mockHandleSort, mockHandleActionClick, undefined, false);
expect(columns[0].name).toBe(ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime);
expect(columns[1].name).toBe(ContainerCopyMessages.MonitorJobs.Columns.name);
expect(columns[2].name).toBe(ContainerCopyMessages.MonitorJobs.Columns.mode);
expect(columns[3].name).toBe(ContainerCopyMessages.MonitorJobs.Columns.completionPercentage);
expect(columns[4].name).toBe(ContainerCopyMessages.MonitorJobs.Columns.status);
expect(columns[0].name).toBe(t(Keys.containerCopy.monitorJobs.columns.lastUpdatedTime));
expect(columns[1].name).toBe(t(Keys.containerCopy.monitorJobs.columns.name));
expect(columns[2].name).toBe(t(Keys.containerCopy.monitorJobs.columns.mode));
expect(columns[3].name).toBe(t(Keys.containerCopy.monitorJobs.columns.completionPercentage));
expect(columns[4].name).toBe(t(Keys.containerCopy.monitorJobs.columns.status));
expect(columns[5].name).toBe("");
});
@@ -1,6 +1,6 @@
import { IColumn } from "@fluentui/react";
import { Keys, t } from "Localization";
import React from "react";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
import CopyJobActionMenu from "./CopyJobActionMenu";
import CopyJobStatusWithIcon from "./CopyJobStatusWithIcon";
@@ -13,7 +13,7 @@ export const getColumns = (
): IColumn[] => [
{
key: "LastUpdatedTime",
name: ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime,
name: t(Keys.containerCopy.monitorJobs.columns.lastUpdatedTime),
fieldName: "LastUpdatedTime",
minWidth: 140,
maxWidth: 300,
@@ -24,7 +24,7 @@ export const getColumns = (
},
{
key: "Name",
name: ContainerCopyMessages.MonitorJobs.Columns.name,
name: t(Keys.containerCopy.monitorJobs.columns.name),
fieldName: "Name",
minWidth: 140,
maxWidth: 300,
@@ -36,7 +36,7 @@ export const getColumns = (
},
{
key: "Mode",
name: ContainerCopyMessages.MonitorJobs.Columns.mode,
name: t(Keys.containerCopy.monitorJobs.columns.mode),
fieldName: "Mode",
minWidth: 90,
maxWidth: 200,
@@ -47,7 +47,7 @@ export const getColumns = (
},
{
key: "CompletionPercentage",
name: ContainerCopyMessages.MonitorJobs.Columns.completionPercentage,
name: t(Keys.containerCopy.monitorJobs.columns.completionPercentage),
fieldName: "CompletionPercentage",
minWidth: 110,
maxWidth: 200,
@@ -59,7 +59,7 @@ export const getColumns = (
},
{
key: "CopyJobStatus",
name: ContainerCopyMessages.MonitorJobs.Columns.status,
name: t(Keys.containerCopy.monitorJobs.columns.status),
fieldName: "Status",
minWidth: 130,
maxWidth: 200,
@@ -13,22 +13,6 @@ jest.mock("./CopyJobStatusWithIcon", () => {
return MockCopyJobStatusWithIcon;
});
jest.mock("../../ContainerCopyMessages", () => ({
errorTitle: "Error Details",
sourceDatabaseLabel: "Source Database",
sourceContainerLabel: "Source Container",
targetDatabaseLabel: "Destination Database",
targetContainerLabel: "Destination Container",
sourceAccountLabel: "Source Account",
MonitorJobs: {
Columns: {
lastUpdatedTime: "Date & time",
status: "Status",
mode: "Mode",
},
},
}));
describe("CopyJobDetails", () => {
const mockBasicJob: CopyJobType = {
ID: "test-job-1",
@@ -102,8 +86,8 @@ describe("CopyJobDetails", () => {
expect(screen.getByText("Date & time")).toBeInTheDocument();
expect(screen.getByText("2024-01-01T10:00:00Z")).toBeInTheDocument();
expect(screen.getByText("Source Account")).toBeInTheDocument();
expect(screen.getByText("sourceAccount")).toBeInTheDocument();
expect(screen.getByText("Destination account")).toBeInTheDocument();
expect(screen.getByText("targetAccount")).toBeInTheDocument();
expect(screen.getByText("Mode")).toBeInTheDocument();
expect(screen.getByText("Offline")).toBeInTheDocument();
@@ -263,7 +247,7 @@ describe("CopyJobDetails", () => {
expect(screen.getByText("complex_source_container_with_underscores")).toBeInTheDocument();
expect(screen.getByText("complex-target-db-with-hyphens")).toBeInTheDocument();
expect(screen.getByText("complex_target_container_with_underscores")).toBeInTheDocument();
expect(screen.getByText("complex.source.account")).toBeInTheDocument();
expect(screen.getByText("complex.target.account")).toBeInTheDocument();
});
});
@@ -322,11 +306,11 @@ describe("CopyJobDetails", () => {
render(<CopyJobDetails job={mockBasicJob} />);
const dateTimeHeading = screen.getByText("Date & time");
const sourceAccountHeading = screen.getByText("Source Account");
const destinationAccountHeading = screen.getByText("Destination account");
const modeHeading = screen.getByText("Mode");
expect(dateTimeHeading).toHaveClass("bold");
expect(sourceAccountHeading).toHaveClass("bold");
expect(destinationAccountHeading).toHaveClass("bold");
expect(modeHeading).toHaveClass("bold");
});
});
@@ -1,7 +1,7 @@
import { DetailsList, DetailsListLayoutMode, IColumn, Stack, Text } from "@fluentui/react";
import { Keys, t } from "Localization";
import React, { memo } from "react";
import { useThemeStore } from "../../../../hooks/useTheme";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
import { CopyJobType } from "../../Types/CopyJobTypes";
import CopyJobStatusWithIcon from "./CopyJobStatusWithIcon";
@@ -31,31 +31,31 @@ const getCopyJobDetailsListColumns = (): IColumn[] => {
return [
{
key: "sourcedbcol",
name: ContainerCopyMessages.sourceDatabaseLabel,
name: t(Keys.containerCopy.preview.sourceDatabaseLabel),
fieldName: "sourceDatabaseName",
...commonProps,
},
{
key: "sourcecol",
name: ContainerCopyMessages.sourceContainerLabel,
name: t(Keys.containerCopy.preview.sourceContainerLabel),
fieldName: "sourceContainerName",
...commonProps,
},
{
key: "targetdbcol",
name: ContainerCopyMessages.targetDatabaseLabel,
name: t(Keys.containerCopy.preview.targetDatabaseLabel),
fieldName: "targetDatabaseName",
...commonProps,
},
{
key: "targetcol",
name: ContainerCopyMessages.targetContainerLabel,
name: t(Keys.containerCopy.preview.targetContainerLabel),
fieldName: "targetContainerName",
...commonProps,
},
{
key: "statuscol",
name: ContainerCopyMessages.MonitorJobs.Columns.status,
name: t(Keys.containerCopy.monitorJobs.columns.status),
fieldName: "jobStatus",
onRender: ({ jobStatus }: { jobStatus: CopyJobStatusType }) => <CopyJobStatusWithIcon status={jobStatus} />,
...commonProps,
@@ -92,7 +92,7 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
{job.Error ? (
<Stack.Item data-testid="error-stack" style={sectionCss.verticalAlign}>
<Text className="bold themeText" style={sectionCss.headingText}>
{ContainerCopyMessages.errorTitle}
{t(Keys.containerCopy.jobDetails.errorTitle)}
</Text>
<Text as="pre" style={errorMessageStyle}>
{job.Error.message}
@@ -102,15 +102,15 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
<Stack.Item data-testid="selectedcollection-stack">
<Stack tokens={{ childrenGap: 15 }}>
<Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime}</Text>
<Text className="bold themeText">{t(Keys.containerCopy.monitorJobs.columns.lastUpdatedTime)}</Text>
<Text className="themeText">{job.LastUpdatedTime}</Text>
</Stack.Item>
<Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold themeText">{ContainerCopyMessages.sourceAccountLabel}</Text>
<Text className="themeText">{job.Source?.remoteAccountName}</Text>
<Text className="bold themeText">{t(Keys.containerCopy.preview.accountLabel)}</Text>
<Text className="themeText">{job.Destination?.remoteAccountName}</Text>
</Stack.Item>
<Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.mode}</Text>
<Text className="bold themeText">{t(Keys.containerCopy.monitorJobs.columns.mode)}</Text>
<Text className="themeText">{job.Mode}</Text>
</Stack.Item>
</Stack>
@@ -1,7 +1,7 @@
import { FontIcon, mergeStyles, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
import { Keys, t } from "Localization";
import PropTypes from "prop-types";
import React from "react";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
const iconClass = mergeStyles({
@@ -30,12 +30,25 @@ const statusIconColors: Partial<Record<CopyJobStatusType, string>> = {
[CopyJobStatusType.Paused]: "var(--colorBrandForeground1)",
};
const statusKeyMap: Record<CopyJobStatusType, string> = {
[CopyJobStatusType.Pending]: Keys.containerCopy.monitorJobs.status.pending,
[CopyJobStatusType.InProgress]: Keys.containerCopy.monitorJobs.status.inProgress,
[CopyJobStatusType.Running]: Keys.containerCopy.monitorJobs.status.running,
[CopyJobStatusType.Partitioning]: Keys.containerCopy.monitorJobs.status.partitioning,
[CopyJobStatusType.Paused]: Keys.containerCopy.monitorJobs.status.paused,
[CopyJobStatusType.Completed]: Keys.containerCopy.monitorJobs.status.completed,
[CopyJobStatusType.Failed]: Keys.containerCopy.monitorJobs.status.failed,
[CopyJobStatusType.Faulted]: Keys.containerCopy.monitorJobs.status.faulted,
[CopyJobStatusType.Skipped]: Keys.containerCopy.monitorJobs.status.skipped,
[CopyJobStatusType.Cancelled]: Keys.containerCopy.monitorJobs.status.cancelled,
};
export interface CopyJobStatusWithIconProps {
status: CopyJobStatusType;
}
const CopyJobStatusWithIcon: React.FC<CopyJobStatusWithIconProps> = React.memo(({ status }) => {
const statusText = ContainerCopyMessages.MonitorJobs.Status[status] || "Unknown";
const statusText = statusKeyMap[status] ? t(statusKeyMap[status] as Parameters<typeof t>[0]) : "Unknown";
const isSpinnerStatus = [
CopyJobStatusType.Running,
@@ -3,9 +3,9 @@ jest.mock("../../Actions/CopyJobActions");
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import Explorer from "Explorer/Explorer";
import { Keys, t } from "Localization";
import React from "react";
import * as Actions from "../../Actions/CopyJobActions";
import ContainerCopyMessages from "../../ContainerCopyMessages";
import CopyJobsNotFound from "./CopyJobs.NotFound";
describe("CopyJobsNotFound", () => {
@@ -22,10 +22,10 @@ describe("CopyJobsNotFound", () => {
const image = container.querySelector(".notFoundContainer .ms-Image");
expect(image).toBeInTheDocument();
expect(image).toHaveAttribute("style", "width: 100px; height: 100px;");
expect(getByText(ContainerCopyMessages.noCopyJobsTitle)).toBeInTheDocument();
expect(getByText(t(Keys.containerCopy.noCopyJobs.title))).toBeInTheDocument();
const button = screen.getByRole("button", {
name: ContainerCopyMessages.createCopyJobButtonText,
name: t(Keys.containerCopy.noCopyJobs.createCopyJobButtonText),
});
expect(button).toBeInTheDocument();
expect(button).toHaveClass("createCopyJobButton");
@@ -45,7 +45,7 @@ describe("CopyJobsNotFound", () => {
render(<CopyJobsNotFound explorer={mockExplorer} />);
const button = screen.getByRole("button", {
name: ContainerCopyMessages.createCopyJobButtonText,
name: t(Keys.containerCopy.noCopyJobs.createCopyJobButtonText),
});
fireEvent.click(button);
@@ -58,11 +58,11 @@ describe("CopyJobsNotFound", () => {
render(<CopyJobsNotFound explorer={mockExplorer} />);
const button = screen.getByRole("button", {
name: ContainerCopyMessages.createCopyJobButtonText,
name: t(Keys.containerCopy.noCopyJobs.createCopyJobButtonText),
});
expect(button).toBeInTheDocument();
expect(button.textContent).toBe(ContainerCopyMessages.createCopyJobButtonText);
expect(button.textContent).toBe(t(Keys.containerCopy.noCopyJobs.createCopyJobButtonText));
});
it("should use memo to prevent unnecessary re-renders", () => {
@@ -1,9 +1,9 @@
import { ActionButton, Image } from "@fluentui/react";
import Explorer from "Explorer/Explorer";
import { Keys, t } from "Localization";
import React from "react";
import CopyJobIcon from "../../../../../images/ContainerCopy/copy-jobs.svg";
import * as Actions from "../../Actions/CopyJobActions";
import ContainerCopyMessages from "../../ContainerCopyMessages";
interface CopyJobsNotFoundProps {
explorer: Explorer;
@@ -12,14 +12,14 @@ interface CopyJobsNotFoundProps {
const CopyJobsNotFound: React.FC<CopyJobsNotFoundProps> = ({ explorer }) => {
return (
<div className="notFoundContainer flexContainer centerContent">
<Image src={CopyJobIcon} alt={ContainerCopyMessages.noCopyJobsTitle} width={100} height={100} />
<h4 className="noCopyJobsMessage">{ContainerCopyMessages.noCopyJobsTitle}</h4>
<Image src={CopyJobIcon} alt={t(Keys.containerCopy.noCopyJobs.title)} width={100} height={100} />
<h4 className="noCopyJobsMessage">{t(Keys.containerCopy.noCopyJobs.title)}</h4>
<ActionButton
allowDisabledFocus
className="createCopyJobButton"
onClick={() => Actions.openCreateCopyJobPanel(explorer)}
>
{ContainerCopyMessages.createCopyJobButtonText}
{t(Keys.containerCopy.noCopyJobs.createCopyJobButtonText)}
</ActionButton>
</div>
);
@@ -55,15 +55,15 @@ export interface DatabaseContainerSectionProps {
export interface CopyJobContextState {
jobName: string;
migrationType: CopyJobMigrationType;
sourceReadAccessFromTarget?: boolean;
sourceReadWriteAccessFromTarget?: boolean;
source: {
subscription: Subscription | null;
subscriptionId: string;
account: DatabaseAccount | null;
databaseId: string;
containerId: string;
};
target: {
subscriptionId: string;
subscription: Subscription | null;
account: DatabaseAccount | null;
databaseId: string;
containerId: string;
@@ -10,7 +10,7 @@ import {
Stack,
TextField,
} from "@fluentui/react";
import { FullTextIndex, FullTextPath, FullTextPolicy } from "Contracts/DataModels";
import { AccountOverride, FullTextIndex, FullTextPath, FullTextPolicy } from "Contracts/DataModels";
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
import * as React from "react";
import { isFullTextSearchPreviewFeaturesEnabled } from "Utils/CapabilityUtils";
@@ -25,6 +25,7 @@ export interface FullTextPoliciesComponentProps {
discardChanges?: boolean;
onChangesDiscarded?: () => void;
englishOnly?: boolean;
targetAccountOverride?: AccountOverride;
}
export interface FullTextPolicyData {
@@ -206,6 +207,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
discardChanges,
onChangesDiscarded,
englishOnly,
targetAccountOverride,
}): JSX.Element => {
const getFullTextPathError = (path: string, index?: number): string => {
let error = "";
@@ -236,7 +238,9 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
const [fullTextPathData, setFullTextPathData] = React.useState<FullTextPolicyData[]>(initializeData(fullTextPolicy));
const [defaultLanguage, setDefaultLanguage] = React.useState<string>(
fullTextPolicy ? fullTextPolicy.defaultLanguage : (getFullTextLanguageOptions()[0].key as never),
fullTextPolicy
? fullTextPolicy.defaultLanguage
: (getFullTextLanguageOptions(englishOnly, targetAccountOverride)[0].key as never),
);
React.useEffect(() => {
@@ -307,7 +311,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
<Dropdown
required={true}
styles={dropdownStyles}
options={getFullTextLanguageOptions(englishOnly)}
options={getFullTextLanguageOptions(englishOnly, targetAccountOverride)}
selectedKey={defaultLanguage}
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
setDefaultLanguage(option.key as never)
@@ -352,7 +356,7 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
<Dropdown
required={true}
styles={dropdownStyles}
options={getFullTextLanguageOptions(englishOnly)}
options={getFullTextLanguageOptions(englishOnly, targetAccountOverride)}
selectedKey={fullTextPolicy.language}
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
onFullTextPathPolicyChange(index, option)
@@ -395,8 +399,12 @@ export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPolicies
);
};
export const getFullTextLanguageOptions = (englishOnly?: boolean): IDropdownOption[] => {
const multiLanguageSupportEnabled: boolean = isFullTextSearchPreviewFeaturesEnabled() && !englishOnly;
export const getFullTextLanguageOptions = (
englishOnly?: boolean,
targetAccountOverride?: AccountOverride,
): IDropdownOption[] => {
const multiLanguageSupportEnabled: boolean =
isFullTextSearchPreviewFeaturesEnabled(targetAccountOverride) && !englishOnly;
const fullTextLanguageOptions: IDropdownOption[] = [
{
key: "en-US",
@@ -1,23 +1,85 @@
import { shallow } from "enzyme";
import { render, screen, waitFor } from "@testing-library/react";
import { DatabaseAccount } from "Contracts/DataModels";
import {
PartitionKeyComponent,
PartitionKeyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import Explorer from "Explorer/Explorer";
import { useDataTransferJobs } from "hooks/useDataTransferJobs";
import React from "react";
import { updateUserContext } from "UserContext";
import { DataTransferJobGetResults } from "Utils/arm/generatedClients/dataTransferService/types";
import Explorer from "../../../Explorer";
jest.mock("Common/dataAccess/dataTransfers", () => ({
cancelDataTransferJob: jest.fn().mockResolvedValue(undefined),
pauseDataTransferJob: jest.fn().mockResolvedValue(undefined),
resumeDataTransferJob: jest.fn().mockResolvedValue(undefined),
completeDataTransferJob: jest.fn().mockResolvedValue(undefined),
pollDataTransferJob: jest.fn().mockResolvedValue(undefined),
}));
jest.mock("hooks/useDataTransferJobs", () => ({
useDataTransferJobs: jest.fn(() => ({ dataTransferJobs: [] })),
refreshDataTransferJobs: jest.fn().mockResolvedValue(undefined),
}));
jest.mock("hooks/useSidePanel", () => ({
useSidePanel: {
getState: () => ({
openSidePanel: jest.fn(),
}),
},
}));
jest.mock("ConfigContext", () => ({
configContext: { platform: "Portal" },
Platform: { Emulator: "Emulator", Portal: "Portal" },
}));
jest.mock("Explorer/Explorer", () => {
return jest.fn().mockImplementation(() => ({
refreshAllDatabases: jest.fn(),
refreshExplorer: jest.fn(),
}));
});
const mockOfflineJob = {
properties: {
jobName: "Portal_test_123",
source: { component: "CosmosDBSql" as const, databaseName: "testDb", containerName: "testCol" },
destination: { component: "CosmosDBSql" as const, databaseName: "testDb", containerName: "newCol" },
status: "InProgress",
processedCount: 50,
totalCount: 100,
mode: "Offline" as const,
},
} as DataTransferJobGetResults;
const mockOnlineJob = {
properties: {
jobName: "Portal_test_456",
source: { component: "CosmosDBSql" as const, databaseName: "testDb", containerName: "testCol" },
destination: { component: "CosmosDBSql" as const, databaseName: "testDb", containerName: "newCol" },
status: "InProgress",
processedCount: 50,
totalCount: 100,
mode: "Online" as const,
},
} as DataTransferJobGetResults;
describe("PartitionKeyComponent", () => {
// Create a test setup function to get fresh instances for each test
const setupTest = () => {
// Create an instance of the mocked Explorer
const explorer = new Explorer();
// Create minimal mock objects for database and collection
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockDatabase = {} as any as import("../../../../Contracts/ViewModels").Database;
const mockCollection = {
id: jest.fn().mockReturnValue("testCol"),
databaseId: "testDb",
partitionKey: { kind: "Hash", paths: ["/id"], version: 2 },
partitionKeyProperties: ["id"],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockCollection = {} as any as import("../../../../Contracts/ViewModels").Collection;
} as any as import("../../../../Contracts/ViewModels").Collection;
// Create props with the mocked Explorer instance
const props: PartitionKeyComponentProps = {
database: mockDatabase,
collection: mockCollection,
@@ -27,15 +89,53 @@ describe("PartitionKeyComponent", () => {
return { explorer, props };
};
it("renders default component and matches snapshot", () => {
const { props } = setupTest();
const wrapper = shallow(<PartitionKeyComponent {...props} />);
expect(wrapper).toMatchSnapshot();
beforeEach(() => {
jest.clearAllMocks();
updateUserContext({
databaseAccount: {
name: "testAccount",
id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/testAccount",
properties: {
documentEndpoint: "https://test.documents.azure.com",
},
} as unknown as DatabaseAccount,
subscriptionId: "sub1",
resourceGroup: "rg1",
});
});
it("renders read-only component and matches snapshot", () => {
it("renders partition key value", () => {
const { props } = setupTest();
const wrapper = shallow(<PartitionKeyComponent {...props} isReadOnly={true} />);
expect(wrapper).toMatchSnapshot();
render(<PartitionKeyComponent {...props} />);
expect(screen.getByText("/id")).toBeTruthy();
});
it("renders read-only component without change button", () => {
const { props } = setupTest();
const { container } = render(<PartitionKeyComponent {...props} isReadOnly={true} />);
expect(container.querySelector("[data-test='change-partition-key-button']")).toBeNull();
});
it("shows cancel button for offline job in progress", () => {
(useDataTransferJobs as unknown as jest.Mock).mockReturnValue({
dataTransferJobs: [mockOfflineJob],
});
const { props } = setupTest();
const { container } = render(<PartitionKeyComponent {...props} />);
// For offline jobs, the online action menu should not be present
expect(container.querySelector("[data-test='online-job-action-menu']")).toBeNull();
});
it("shows ellipsis action menu for online job in progress", async () => {
(useDataTransferJobs as unknown as jest.Mock).mockReturnValue({
dataTransferJobs: [mockOnlineJob],
});
const { props } = setupTest();
const { container } = render(<PartitionKeyComponent {...props} />);
await waitFor(() => {
expect(container.querySelector("[data-test='online-job-action-menu']")).toBeTruthy();
});
});
});
@@ -1,7 +1,10 @@
import {
DefaultButton,
DirectionalHint,
FontWeights,
IContextualMenuProps,
IMessageBarStyles,
IconButton,
Link,
MessageBar,
MessageBarType,
@@ -14,8 +17,16 @@ import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { handleError } from "Common/ErrorHandlingUtils";
import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/dataTransfers";
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";
@@ -94,11 +105,11 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
const textSubHeadingStyle1 = {
root: { color: "var(--colorNeutralForeground1)" },
};
const startPollingforUpdate = (currentJob: DataTransferJobGetResults) => {
const startPollingforUpdate = async (currentJob: DataTransferJobGetResults) => {
if (isCurrentJobInProgress(currentJob)) {
const jobName = currentJob?.properties?.jobName;
try {
pollDataTransferJob(
await pollDataTransferJob(
jobName,
userContext.subscriptionId,
userContext.resourceGroup,
@@ -119,6 +130,124 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
);
};
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 ? (
<Stack tokens={{ childrenGap: 10 }}>
<Stack.Item>
{t(Keys.controls.settings.partitionKeyEditor.confirmCancel1)}
<br />
<b>{jobName}</b>
</Stack.Item>
<Stack.Item>{t(Keys.controls.settings.partitionKeyEditor.confirmCancel2)}</Stack.Item>
</Stack>
) : action === CopyJobActions.complete ? (
<Stack tokens={{ childrenGap: 10 }}>
<Stack.Item>
{t(Keys.controls.settings.partitionKeyEditor.confirmComplete1)}
<br />
<b>{jobName}</b>
</Stack.Item>
<Stack.Item>{t(Keys.controls.settings.partitionKeyEditor.confrimComplete2)}</Stack.Item>
</Stack>
) : 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 (
@@ -269,12 +398,26 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
},
}}
></ProgressIndicator>
{isCurrentJobInProgress(portalDataTransferJob) && (
{isCurrentJobInProgress(portalDataTransferJob) &&
(isOnlineJob(portalDataTransferJob) ? (
<IconButton
data-test="online-job-action-menu"
role="button"
iconProps={{
iconName: "More",
styles: { root: { fontSize: "20px", fontWeight: "bold" } },
}}
menuProps={getOnlineJobMenuProps(portalDataTransferJob)}
menuIconProps={{ iconName: "", className: "hidden" }}
ariaLabel={t(Keys.containerCopy.monitorJobs.columns.actions)}
title={t(Keys.containerCopy.monitorJobs.columns.actions)}
/>
) : (
<DefaultButton
text={t(Keys.controls.settings.partitionKeyEditor.cancelButton)}
onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)}
/>
)}
))}
</Stack>
</Stack>
)}
@@ -1,271 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PartitionKeyComponent renders default component and matches snapshot 1`] = `
<Stack
styles={
{
"root": {
"maxWidth": 600,
},
}
}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
"fontSize": 16,
"fontWeight": 600,
},
}
}
>
Change partition key
</Text>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
"fontWeight": 600,
},
}
}
>
Current partition key
</Text>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
"fontWeight": 600,
},
}
}
>
Partitioning
</Text>
</Stack>
<Stack
data-test="partition-key-values"
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
},
}
}
/>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
},
}
}
>
Non-hierarchical
</Text>
</Stack>
</Stack>
</Stack>
<StyledMessageBar
data-test="partition-key-warning"
messageBarIconProps={
{
"className": "messageBarWarningIcon",
"iconName": "WarningSolid",
}
}
messageBarType={5}
styles={
{
"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)",
},
},
},
}
}
>
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the source container for the entire duration of the partition key change process.
<StyledLinkBase
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
style={
{
"color": "var(--colorBrandForeground1)",
}
}
target="_blank"
underline={true}
>
Learn more
</StyledLinkBase>
</StyledMessageBar>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
},
}
}
>
To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.
</Text>
<CustomizedPrimaryButton
data-test="change-partition-key-button"
onClick={[Function]}
styles={
{
"root": {
"width": "fit-content",
},
}
}
text="Change"
/>
</Stack>
`;
exports[`PartitionKeyComponent renders read-only component and matches snapshot 1`] = `
<Stack
styles={
{
"root": {
"maxWidth": 600,
},
}
}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
"fontWeight": 600,
},
}
}
>
Current partition key
</Text>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
"fontWeight": 600,
},
}
}
>
Partitioning
</Text>
</Stack>
<Stack
data-test="partition-key-values"
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
},
}
}
/>
<Text
styles={
{
"root": {
"color": "var(--colorNeutralForeground1)",
},
}
}
>
Non-hierarchical
</Text>
</Stack>
</Stack>
</Stack>
</Stack>
`;
@@ -291,7 +291,7 @@ describe("SettingsUtils", () => {
it("handles partition key tab title based on fabric native", () => {
// Assuming initially not fabric native
expect(getTabTitle(SettingsV2TabTypes.PartitionKeyTab)).toBe("Partition Keys (preview)");
expect(getTabTitle(SettingsV2TabTypes.PartitionKeyTab)).toBe("Partition Keys");
});
it("throws error for unknown tab type", () => {
@@ -2,7 +2,6 @@ import { Keys, t } from "Localization";
import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
import { userContext } from "../../../UserContext";
import { isCapabilityEnabled } from "../../../Utils/CapabilityUtils";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
@@ -185,9 +184,7 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
case SettingsV2TabTypes.IndexingPolicyTab:
return t(Keys.controls.settings.tabTitles.indexingPolicy);
case SettingsV2TabTypes.PartitionKeyTab:
return isFabricNative()
? t(Keys.controls.settings.tabTitles.partitionKeys)
: t(Keys.controls.settings.tabTitles.partitionKeysPreview);
return t(Keys.controls.settings.tabTitles.partitionKeys);
case SettingsV2TabTypes.ComputedPropertiesTab:
return t(Keys.controls.settings.tabTitles.computedProperties);
case SettingsV2TabTypes.ContainerVectorPolicyTab:
@@ -429,7 +429,7 @@ exports[`SettingsComponent renders 1`] = `
"data-test": "settings-tab-header/PartitionKeyTab",
}
}
headerText="Partition Keys (preview)"
headerText="Partition Keys"
itemKey="PartitionKeyTab"
key="PartitionKeyTab"
style={
@@ -670,7 +670,7 @@ exports[`SettingsComponent renders 1`] = `
"data-test": "settings-tab-header/GlobalSecondaryIndexTab",
}
}
headerText="Global Secondary Index (Preview)"
headerText="Global Secondary Index"
itemKey="GlobalSecondaryIndexTab"
key="GlobalSecondaryIndexTab"
style={
+169
View File
@@ -0,0 +1,169 @@
jest.mock("Utils/arm/generatedClients/cosmos/databaseAccounts");
jest.mock("Utils/NotificationConsoleUtils", () => ({
logConsoleProgress: jest.fn(() => jest.fn()), // returns a clearMessage fn
logConsoleInfo: jest.fn(),
logConsoleError: jest.fn(),
}));
jest.mock("Shared/Telemetry/TelemetryProcessor");
import { Capability, DatabaseAccount } from "../Contracts/DataModels";
import { updateUserContext, userContext } from "../UserContext";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import Explorer from "./Explorer";
const mockUpdate = update as jest.MockedFunction<typeof update>;
// Capture `useDialog.getState().openDialog` calls
const mockOpenDialog = jest.fn();
const mockCloseDialog = jest.fn();
jest.mock("./Controls/Dialog", () => ({
useDialog: {
getState: jest.fn(() => ({
openDialog: mockOpenDialog,
closeDialog: mockCloseDialog,
})),
},
}));
// Silence useNotebook subscription calls
jest.mock("./Notebook/useNotebook", () => ({
useNotebook: {
subscribe: jest.fn(),
getState: jest.fn().mockReturnValue(
new Proxy(
{},
{
get: () => jest.fn().mockResolvedValue(undefined),
},
),
),
},
}));
describe("Explorer.openEnableSynapseLinkDialog", () => {
let explorer: Explorer;
const baseAccount: DatabaseAccount = {
id: "/subscriptions/ctx-sub/resourceGroups/ctx-rg/providers/Microsoft.DocumentDB/databaseAccounts/ctx-account",
name: "ctx-account",
location: "East US",
type: "Microsoft.DocumentDB/databaseAccounts",
kind: "GlobalDocumentDB",
tags: {},
properties: {
documentEndpoint: "https://ctx-account.documents.azure.com:443/",
capabilities: [] as Capability[],
enableMultipleWriteLocations: false,
},
};
beforeAll(() => {
updateUserContext({
databaseAccount: baseAccount,
subscriptionId: "ctx-sub",
resourceGroup: "ctx-rg",
});
});
beforeEach(() => {
jest.clearAllMocks();
mockUpdate.mockResolvedValue(undefined);
explorer = new Explorer();
});
describe("without targetAccountOverride", () => {
it("should open a dialog when called without override", () => {
explorer.openEnableSynapseLinkDialog();
expect(mockOpenDialog).toHaveBeenCalledTimes(1);
});
it("should use userContext values in the update call on primary button click", async () => {
explorer.openEnableSynapseLinkDialog();
const dialogProps = mockOpenDialog.mock.calls[0][0];
await dialogProps.onPrimaryButtonClick();
expect(mockUpdate).toHaveBeenCalledWith(
"ctx-sub",
"ctx-rg",
"ctx-account",
expect.objectContaining({
properties: { enableAnalyticalStorage: true },
}),
);
});
it("should update userContext.databaseAccount.properties when no override is provided", async () => {
explorer.openEnableSynapseLinkDialog();
const dialogProps = mockOpenDialog.mock.calls[0][0];
await dialogProps.onPrimaryButtonClick();
expect(userContext.databaseAccount.properties.enableAnalyticalStorage).toBe(true);
});
});
describe("with targetAccountOverride", () => {
const override = {
subscriptionId: "override-sub",
resourceGroup: "override-rg",
accountName: "override-account",
capabilities: [] as Capability[],
};
it("should open a dialog when called with override", () => {
explorer.openEnableSynapseLinkDialog(override);
expect(mockOpenDialog).toHaveBeenCalledTimes(1);
});
it("should use override values in the update call on primary button click", async () => {
explorer.openEnableSynapseLinkDialog(override);
const dialogProps = mockOpenDialog.mock.calls[0][0];
await dialogProps.onPrimaryButtonClick();
expect(mockUpdate).toHaveBeenCalledWith(
"override-sub",
"override-rg",
"override-account",
expect.objectContaining({
properties: { enableAnalyticalStorage: true },
}),
);
});
it("should NOT update userContext.databaseAccount.properties when override is provided", async () => {
// Reset the property first
userContext.databaseAccount.properties.enableAnalyticalStorage = false;
explorer.openEnableSynapseLinkDialog(override);
const dialogProps = mockOpenDialog.mock.calls[0][0];
await dialogProps.onPrimaryButtonClick();
expect(userContext.databaseAccount.properties.enableAnalyticalStorage).toBe(false);
});
it("should use override values — NOT userContext — even when userContext has different values", async () => {
explorer.openEnableSynapseLinkDialog(override);
const dialogProps = mockOpenDialog.mock.calls[0][0];
await dialogProps.onPrimaryButtonClick();
// update should NOT be called with ctx-sub / ctx-rg / ctx-account
expect(mockUpdate).not.toHaveBeenCalledWith("ctx-sub", expect.anything(), expect.anything(), expect.anything());
});
});
describe("secondary button click", () => {
it("should close the dialog on secondary button click", () => {
explorer.openEnableSynapseLinkDialog();
const dialogProps = mockOpenDialog.mock.calls[0][0];
dialogProps.onSecondaryButtonClick();
expect(mockCloseDialog).toHaveBeenCalledTimes(1);
});
});
});
+8 -2
View File
@@ -223,7 +223,11 @@ export default class Explorer {
this.refreshNotebookList();
}
public openEnableSynapseLinkDialog(): void {
public openEnableSynapseLinkDialog(targetAccountOverride?: DataModels.AccountOverride): void {
const subscriptionId = targetAccountOverride?.subscriptionId ?? userContext.subscriptionId;
const resourceGroup = targetAccountOverride?.resourceGroup ?? userContext.resourceGroup;
const accountName = targetAccountOverride?.accountName ?? userContext.databaseAccount.name;
const addSynapseLinkDialogProps: DialogProps = {
linkProps: {
linkText: "Learn more",
@@ -245,7 +249,7 @@ export default class Explorer {
useDialog.getState().closeDialog();
try {
await update(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, {
await update(subscriptionId, resourceGroup, accountName, {
properties: {
enableAnalyticalStorage: true,
},
@@ -254,7 +258,9 @@ export default class Explorer {
clearInProgressMessage();
logConsoleInfo("Enabled Azure Synapse Link for this account");
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime);
if (!targetAccountOverride) {
userContext.databaseAccount.properties.enableAnalyticalStorage = true;
}
} catch (error) {
clearInProgressMessage();
logConsoleError(`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`);
@@ -1,3 +1,4 @@
import { Capability } from "Contracts/DataModels";
import { shallow } from "enzyme";
import React from "react";
import Explorer from "../../Explorer";
@@ -12,4 +13,58 @@ describe("AddCollectionPanel", () => {
const wrapper = shallow(<AddCollectionPanel {...props} />);
expect(wrapper).toMatchSnapshot();
});
describe("targetAccountOverride prop", () => {
it("should render with targetAccountOverride prop set", () => {
const override = {
subscriptionId: "override-sub",
resourceGroup: "override-rg",
accountName: "override-account",
capabilities: [] as Capability[],
};
const wrapper = shallow(<AddCollectionPanel {...props} targetAccountOverride={override} />);
expect(wrapper).toBeDefined();
});
it("should pass targetAccountOverride to openEnableSynapseLinkDialog button click", () => {
const mockOpenEnableSynapseLinkDialog = jest.fn();
const explorerWithMock = { ...props.explorer, openEnableSynapseLinkDialog: mockOpenEnableSynapseLinkDialog };
const override = {
subscriptionId: "override-sub",
resourceGroup: "override-rg",
accountName: "override-account",
capabilities: [] as Capability[],
};
const wrapper = shallow(
<AddCollectionPanel explorer={explorerWithMock as unknown as Explorer} targetAccountOverride={override} />,
);
// isSynapseLinkEnabled section requires specific conditions; verify the component exists
expect(wrapper).toBeDefined();
});
});
describe("externalDatabaseOptions prop", () => {
it("should accept externalDatabaseOptions without error", () => {
const externalOptions = [
{ key: "db1", text: "Database One" },
{ key: "db2", text: "Database Two" },
];
const wrapper = shallow(<AddCollectionPanel {...props} externalDatabaseOptions={externalOptions} />);
expect(wrapper).toBeDefined();
});
});
describe("isCopyJobFlow prop", () => {
it("should render with isCopyJobFlow=true", () => {
const wrapper = shallow(<AddCollectionPanel {...props} isCopyJobFlow={true} />);
expect(wrapper).toBeDefined();
});
it("should render with isCopyJobFlow=false (default behaviour)", () => {
const wrapper = shallow(<AddCollectionPanel {...props} isCopyJobFlow={false} />);
expect(wrapper).toBeDefined();
});
});
});
@@ -20,6 +20,7 @@ import { createCollection } from "Common/dataAccess/createCollection";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
import { AccountOverride } from "Contracts/DataModels";
import { FullTextPoliciesComponent } from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
import {
@@ -67,6 +68,8 @@ export interface AddCollectionPanelProps {
isQuickstart?: boolean;
isCopyJobFlow?: boolean;
onSubmitSuccess?: (collectionData: { databaseId: string; collectionId: string }) => void;
targetAccountOverride?: AccountOverride;
externalDatabaseOptions?: IDropdownOption[];
}
export const DefaultVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy = {
@@ -167,7 +170,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/>
)}
{!this.state.errorMessage && isFreeTierAccount() && (
{!this.state.errorMessage && isFreeTierAccount(this.props.targetAccountOverride) && (
<PanelInfoErrorComponent
message={getUpsellMessage(userContext.portalEnv, true, isFirstResourceCreated, true)}
messageType="info"
@@ -644,7 +647,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
{!isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
{!isServerlessAccount(this.props.targetAccountOverride) &&
!this.state.createNewDatabase &&
this.isSelectedDatabaseSharedThroughput() && (
<Stack horizontal verticalAlign="center">
<Checkbox
label={t(Keys.panes.addCollection.provisionDedicatedThroughput, {
@@ -687,10 +692,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.shouldShowCollectionThroughputInput() && !isFabricNative() && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
showFreeTierExceedThroughputTooltip={
isFreeTierAccount(this.props.targetAccountOverride) && !isFirstResourceCreated
}
isDatabase={false}
isSharded={this.state.isSharded}
isFreeTier={isFreeTierAccount()}
isFreeTier={isFreeTierAccount(this.props.targetAccountOverride)}
isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
@@ -767,7 +774,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<input
className="panelRadioBtn"
checked={this.state.enableAnalyticalStore}
disabled={!isSynapseLinkEnabled()}
disabled={!isSynapseLinkEnabled(this.props.targetAccountOverride)}
aria-label={t(Keys.panes.addCollection.enableAnalyticalStore)}
aria-checked={this.state.enableAnalyticalStore}
name="analyticalStore"
@@ -782,7 +789,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<input
className="panelRadioBtn"
checked={!this.state.enableAnalyticalStore}
disabled={!isSynapseLinkEnabled()}
disabled={!isSynapseLinkEnabled(this.props.targetAccountOverride)}
aria-label={t(Keys.panes.addCollection.disableAnalyticalStore)}
aria-checked={!this.state.enableAnalyticalStore}
name="analyticalStore"
@@ -796,7 +803,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</div>
</Stack>
{!isSynapseLinkEnabled() && (
{!isSynapseLinkEnabled(this.props.targetAccountOverride) && (
<Stack className="panelGroupSpacing">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{t(Keys.panes.addCollection.analyticalStoreSynapseLinkRequired, {
@@ -814,7 +821,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Text>
<DefaultButton
text={t(Keys.panes.addCollection.enable)}
onClick={() => this.props.explorer.openEnableSynapseLinkDialog()}
onClick={() => this.props.explorer.openEnableSynapseLinkDialog(this.props.targetAccountOverride)}
style={{ height: 27, width: 80 }}
styles={{ label: { fontSize: 12 } }}
/>
@@ -865,6 +872,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack id="collapsibleFullTextPolicySectionContent" styles={{ root: { position: "relative" } }}>
<Stack styles={{ root: { paddingLeft: 40 } }}>
<FullTextPoliciesComponent
targetAccountOverride={this.props.targetAccountOverride}
fullTextPolicy={this.state.fullTextPolicy}
onFullTextPathChange={(
fullTextPolicy: DataModels.FullTextPolicy,
@@ -1000,6 +1008,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private getDatabaseOptions(): IDropdownOption[] {
if (this.props.externalDatabaseOptions) {
return this.props.externalDatabaseOptions;
}
return useDatabases.getState().databases?.map((database) => ({
key: database.id(),
text: database.id(),
@@ -1087,6 +1098,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false;
}
if (this.props.targetAccountOverride) {
return false;
}
const selectedDatabase = useDatabases
.getState()
.databases?.find((database) => database.id() === this.state.selectedDatabaseId);
@@ -1124,7 +1139,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
// }
private shouldShowCollectionThroughputInput(): boolean {
if (isServerlessAccount()) {
if (isServerlessAccount(this.props.targetAccountOverride)) {
return false;
}
@@ -1140,7 +1155,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private shouldShowIndexingOptionsForFreeTierAccount(): boolean {
if (!isFreeTierAccount()) {
if (!isFreeTierAccount(this.props.targetAccountOverride)) {
return false;
}
@@ -1148,7 +1163,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private shouldShowVectorSearchParameters() {
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
const targetAccount = this.props.targetAccountOverride;
return (
isVectorSearchEnabled(targetAccount) &&
(isServerlessAccount(targetAccount) || this.shouldShowCollectionThroughputInput())
);
}
private shouldShowFullTextSearchParameters() {
@@ -1227,7 +1246,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private getAnalyticalStorageTtl(): number {
if (!isSynapseLinkEnabled()) {
if (!isSynapseLinkEnabled(this.props.targetAccountOverride)) {
return undefined;
}
@@ -1367,13 +1386,16 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
createMongoWildcardIndex: this.state.createMongoWildCardIndex,
vectorEmbeddingPolicy,
fullTextPolicy: this.state.fullTextPolicy,
targetAccountOverride: this.props.targetAccountOverride,
};
this.setState({ isExecuting: true });
try {
await createCollection(createCollectionParams);
if (!this.props.isCopyJobFlow) {
await this.props.explorer.refreshAllDatabases();
}
if (this.props.isQuickstart) {
const database = useDatabases.getState().findDatabaseWithId(databaseId);
if (database) {
@@ -1402,7 +1424,13 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
useSidePanel.getState().closeSidePanel();
}
} catch (error) {
const errorMessage: string = getErrorMessage(error);
const rawMessage: string = getErrorMessage(error);
const errorMessage =
this.props.isCopyJobFlow && (rawMessage.includes("AuthorizationFailed") || rawMessage.includes("403"))
? `You do not have permission to create databases or containers on the destination account (${
this.props.targetAccountOverride?.accountName ?? "unknown"
}). Please ensure you have Contributor or Owner access.`
: rawMessage;
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });
const failureTelemetryData = { ...telemetryData, error: errorMessage, errorStack: getErrorStack(error) };
TelemetryProcessor.traceFailure(Action.CreateCollection, failureTelemetryData, startKey);
@@ -2,6 +2,7 @@ import { DirectionalHint, Icon, Link, Stack, Text, TooltipHost } from "@fluentui
import * as Constants from "Common/Constants";
import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
import { AccountOverride } from "Contracts/DataModels";
import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
import { Keys, t } from "Localization";
import { isFabricNative } from "Platform/Fabric/FabricUtil";
@@ -68,7 +69,10 @@ export function getPartitionKey(isQuickstart?: boolean): string {
return "";
}
export function isFreeTierAccount(): boolean {
export function isFreeTierAccount(targetAccountOverride?: AccountOverride): boolean {
if (targetAccountOverride) {
return targetAccountOverride.enableFreeTier ?? false;
}
return userContext.databaseAccount?.properties?.enableFreeTier;
}
@@ -130,7 +134,16 @@ export function AnalyticalStorageContent(): JSX.Element {
);
}
export function isSynapseLinkEnabled(): boolean {
export function isSynapseLinkEnabled(targetAccountOverride?: AccountOverride): boolean {
if (targetAccountOverride) {
if (targetAccountOverride.enableAnalyticalStorage) {
return true;
}
return targetAccountOverride.capabilities?.some(
(capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics,
);
}
if (!userContext.databaseAccount) {
return false;
}
@@ -0,0 +1,218 @@
import { act, fireEvent, render, screen } from "@testing-library/react";
import { initiateDataTransfer } from "Common/dataAccess/dataTransfers";
import { DatabaseAccount } from "Contracts/DataModels";
import * as ViewModels from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer";
import * as React from "react";
import { updateUserContext } from "UserContext";
import { ChangePartitionKeyPane } from "./ChangePartitionKeyPane";
jest.mock("Common/ErrorHandlingUtils", () => ({
handleError: jest.fn(),
getErrorMessage: jest.fn().mockReturnValue("error"),
getErrorStack: jest.fn().mockReturnValue(""),
}));
jest.mock("Common/dataAccess/createCollection", () => ({
createCollection: jest.fn().mockResolvedValue({}),
}));
jest.mock("Common/dataAccess/readDatabases", () => ({
readDatabases: jest.fn().mockResolvedValue([]),
}));
jest.mock("Common/dataAccess/dataTransfers", () => ({
initiateDataTransfer: jest.fn().mockResolvedValue({}),
}));
jest.mock("Utils/arm/databaseAccountUtils", () => ({
fetchDatabaseAccount: jest.fn().mockResolvedValue(null),
}));
jest.mock("Utils/arm/generatedClients/cosmos/databaseAccounts", () => ({
update: jest.fn().mockResolvedValue({}),
}));
jest.mock("hooks/useSidePanel", () => ({
useSidePanel: {
getState: () => ({
closeSidePanel: jest.fn(),
openSidePanel: jest.fn(),
}),
},
}));
jest.mock("Explorer/useDatabases", () => {
const state: Record<string, unknown> = {
databases: [],
resourceTokenCollection: undefined,
resourceTokenDatabase: undefined,
sampleDataResourceTokenCollection: undefined,
};
const mockStore = Object.assign(
jest.fn(() => state),
{
getState: () => state,
setState: jest.fn(),
subscribe: jest.fn(),
destroy: jest.fn(),
},
);
return { useDatabases: mockStore };
});
jest.mock("Common/LoadingOverlay", () => {
return {
__esModule: true,
default: ({ isLoading, label }: { isLoading: boolean; label: string }) =>
isLoading ? <div data-testid="loading-overlay">{label}</div> : null,
};
});
const createMockCollection = (id: string): ViewModels.Collection => {
const mockCollection = {
id: jest.fn().mockReturnValue(id),
offer: jest.fn().mockReturnValue(undefined),
partitionKey: { kind: "Hash", paths: ["/id"], version: 2 },
partitionKeyProperties: ["id"],
databaseId: "testDb",
} as unknown as ViewModels.Collection;
return mockCollection;
};
const createMockDatabase = (id: string, collections: ViewModels.Collection[] = []): ViewModels.Database => {
return {
id: jest.fn().mockReturnValue(id),
collections: jest.fn().mockReturnValue(collections),
} as unknown as ViewModels.Database;
};
describe("ChangePartitionKeyPane", () => {
const mockExplorer = new Explorer();
const mockOnClose = jest.fn().mockResolvedValue(undefined);
const mockCollection = createMockCollection("testCollection");
const mockDatabase = createMockDatabase("testDb", [mockCollection]);
beforeEach(() => {
jest.clearAllMocks();
updateUserContext({
databaseAccount: {
name: "testAccount",
id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/testAccount",
properties: {
documentEndpoint: "https://test.documents.azure.com",
capabilities: [],
backupPolicy: { type: "Periodic" },
},
} as unknown as DatabaseAccount,
subscriptionId: "sub1",
resourceGroup: "rg1",
apiType: "SQL",
});
});
const renderPane = () => {
return render(
<ChangePartitionKeyPane
sourceDatabase={mockDatabase}
sourceCollection={mockCollection}
explorer={mockExplorer}
onClose={mockOnClose}
/>,
);
};
it("renders migration type choice group", () => {
renderPane();
expect(screen.getByText("Migration type")).toBeTruthy();
expect(screen.getByText("Offline mode")).toBeTruthy();
expect(screen.getByText("Online mode")).toBeTruthy();
});
it("defaults to offline migration type", () => {
renderPane();
const offlineRadio = screen.getByRole("radio", { name: "Offline mode" }) as HTMLInputElement;
expect(offlineRadio.checked).toBe(true);
});
it("does not show online prerequisites section when offline is selected", () => {
const { container } = renderPane();
expect(container.querySelector("[data-test='online-prerequisites-section']")).toBeNull();
});
it("shows online prerequisites section when online is selected", () => {
renderPane();
const onlineRadio = screen.getByRole("radio", { name: "Online mode" });
fireEvent.click(onlineRadio);
expect(screen.getByText("Online container copy")).toBeTruthy();
expect(screen.getByText("Point In Time Restore enabled")).toBeTruthy();
expect(screen.getByText("Online copy enabled")).toBeTruthy();
});
it("shows prerequisite sections when online prerequisites are not met", () => {
renderPane();
const onlineRadio = screen.getByRole("radio", { name: "Online mode" });
fireEvent.click(onlineRadio);
// When prerequisites aren't met, the enable buttons should be visible
expect(screen.getByText("Enable Point In Time Restore")).toBeTruthy();
expect(screen.getAllByRole("button", { name: "Enable Online Copy" }).length).toBeGreaterThan(0);
});
it("shows enable PITR button when PITR is not enabled", () => {
renderPane();
const onlineRadio = screen.getByRole("radio", { name: "Online mode" });
fireEvent.click(onlineRadio);
expect(screen.getByText("Enable Point In Time Restore")).toBeTruthy();
});
it("does not show PITR enable button when PITR is already enabled", () => {
updateUserContext({
databaseAccount: {
name: "testAccount",
id: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.DocumentDB/databaseAccounts/testAccount",
properties: {
documentEndpoint: "https://test.documents.azure.com",
capabilities: [],
backupPolicy: { type: "Continuous" },
},
} as unknown as DatabaseAccount,
});
renderPane();
const onlineRadio = screen.getByRole("radio", { name: "Online mode" });
fireEvent.click(onlineRadio);
expect(screen.queryByText("Enable Point In Time Restore")).toBeNull();
});
it("disables online copy button when PITR is not enabled", () => {
renderPane();
const onlineRadio = screen.getByRole("radio", { name: "Online mode" });
fireEvent.click(onlineRadio);
const enableOnlineCopyBtns = screen.getAllByRole("button", { name: "Enable Online Copy" });
expect(enableOnlineCopyBtns.length).toBeGreaterThan(0);
expect((enableOnlineCopyBtns[0] as HTMLButtonElement).disabled).toBe(true);
});
it("passes mode to initiateDataTransfer when submitting", async () => {
const mockInitiateDataTransfer = jest.mocked(initiateDataTransfer);
// Mock refreshAllDatabases on the prototype to catch all calls
const refreshSpy = jest.spyOn(Explorer.prototype, "refreshAllDatabases").mockResolvedValue();
const { container } = renderPane();
// Fill in partition key (required for createContainer — state starts undefined)
const partitionKeyInput = container.querySelector("#addCollection-partitionKeyValue") as HTMLInputElement;
expect(partitionKeyInput).not.toBeNull();
fireEvent.change(partitionKeyInput, { target: { value: "/myKey" } });
const form = container.querySelector("form");
expect(form).not.toBeNull();
await act(async () => {
fireEvent.submit(form!);
});
expect(mockInitiateDataTransfer).toHaveBeenCalled();
expect(mockInitiateDataTransfer.mock.calls[0][0].mode).toBe("Offline");
refreshSpy.mockRestore();
});
});
@@ -7,16 +7,24 @@ import {
IconButton,
Link,
MessageBar,
MessageBarType,
PrimaryButton,
Stack,
Text,
TooltipHost,
} from "@fluentui/react";
import MarkdownRender from "@nteract/markdown";
import * as Constants from "Common/Constants";
import { CapabilityNames } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import LoadingOverlay from "Common/LoadingOverlay";
import { logError } from "Common/Logger";
import { createCollection } from "Common/dataAccess/createCollection";
import { DataTransferParams, initiateDataTransfer } from "Common/dataAccess/dataTransfers";
import * as DataModels from "Contracts/DataModels";
import * as ViewModels from "Contracts/ViewModels";
import { buildResourceLink } from "Explorer/ContainerCopy/CopyJobUtils";
import { BackupPolicyType } from "Explorer/ContainerCopy/Enums/CopyJobEnums";
import {
getPartitionKeyName,
getPartitionKeyPlaceHolder,
@@ -30,6 +38,8 @@ import { Keys, t } from "Localization";
import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import { fetchDatabaseAccount } from "Utils/arm/databaseAccountUtils";
import { update as updateDatabaseAccount } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useSidePanel } from "hooks/useSidePanel";
import * as React from "react";
@@ -40,6 +50,15 @@ export interface ChangePartitionKeyPaneProps {
onClose: () => Promise<void>;
}
const checkPitrEnabled = (account: DataModels.DatabaseAccount): boolean => {
return account?.properties?.backupPolicy?.type === BackupPolicyType.Continuous;
};
const checkOnlineCopyEnabled = (account: DataModels.DatabaseAccount): boolean => {
const capabilities = account?.properties?.capabilities ?? [];
return capabilities.some((cap) => cap.name === CapabilityNames.EnableOnlineCopyFeature);
};
export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
sourceDatabase,
sourceCollection,
@@ -52,6 +71,118 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
const [isExecuting, setIsExecuting] = React.useState<boolean>(false);
const [subPartitionKeys, setSubPartitionKeys] = React.useState<string[]>([]);
const [partitionKey, setPartitionKey] = React.useState<string>();
const [onlineMode, setOnlineMode] = React.useState<boolean>(false);
// Pane-local account state for tracking prerequisite enablement
const [localAccount, setLocalAccount] = React.useState<DataModels.DatabaseAccount>(userContext.databaseAccount);
const [isEnablingPrerequisite, setIsEnablingPrerequisite] = React.useState<boolean>(false);
const [prerequisiteLoaderMessage, setPrerequisiteLoaderMessage] = React.useState<string>("");
const pitrEnabled = checkPitrEnabled(localAccount);
const onlineCopyFeatureEnabled = checkOnlineCopyEnabled(localAccount);
const onlinePrerequisitesMet = pitrEnabled && onlineCopyFeatureEnabled;
const accountName = localAccount?.name ?? "";
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const intervalRef = React.useRef<NodeJS.Timeout | null>(null);
const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
React.useEffect(() => {
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const refreshAccount = async (): Promise<DataModels.DatabaseAccount | null> => {
try {
const account = await fetchDatabaseAccount(subscriptionId, resourceGroup, accountName);
if (account) {
setLocalAccount(account);
}
return account;
} catch (error) {
logError(
error.message || "Error fetching account after enabling prerequisite.",
"ChangePartitionKey/refreshAccount",
);
return null;
}
};
const clearPollingTimers = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
};
const startPollingForAccountUpdate = () => {
intervalRef.current = setInterval(() => {
refreshAccount();
}, 30 * 1000);
timeoutRef.current = setTimeout(
() => {
clearPollingTimers();
setIsEnablingPrerequisite(false);
},
10 * 60 * 1000,
);
};
const handleEnablePitr = () => {
const sourceAccountLink = buildResourceLink(localAccount);
const featureUrl = `${sourceAccountLink}/backupRestore`;
setIsEnablingPrerequisite(true);
setPrerequisiteLoaderMessage(t(Keys.containerCopy.popoverOverlaySpinnerLabel));
window.open(featureUrl, "_blank");
startPollingForAccountUpdate();
};
const handleEnableOnlineCopy = async () => {
setIsEnablingPrerequisite(true);
try {
setPrerequisiteLoaderMessage(
t(Keys.containerCopy.onlineCopyEnabled.validateAllVersionsAndDeletesChangeFeedSpinnerLabel),
);
const currentAccount = await fetchDatabaseAccount(subscriptionId, resourceGroup, accountName);
if (!currentAccount?.properties?.enableAllVersionsAndDeletesChangeFeed) {
setPrerequisiteLoaderMessage(
t(Keys.containerCopy.onlineCopyEnabled.enablingAllVersionsAndDeletesChangeFeedSpinnerLabel),
);
await updateDatabaseAccount(subscriptionId, resourceGroup, accountName, {
properties: {
enableAllVersionsAndDeletesChangeFeed: true,
},
});
}
const capabilities = currentAccount?.properties?.capabilities ?? [];
setPrerequisiteLoaderMessage(
t(Keys.containerCopy.onlineCopyEnabled.enablingOnlineCopySpinnerLabel, { accountName }),
);
await updateDatabaseAccount(subscriptionId, resourceGroup, accountName, {
properties: {
capabilities: [...capabilities, { name: CapabilityNames.EnableOnlineCopyFeature }],
},
});
startPollingForAccountUpdate();
} catch (error) {
logError(error.message || "Failed to enable online copy feature.", "ChangePartitionKey/handleEnableOnlineCopy");
setFormError("Failed to enable online copy feature. Please try again.");
setIsEnablingPrerequisite(false);
}
};
const getCollectionOptions = (): IDropdownOption[] => {
return sourceDatabase
@@ -84,9 +215,17 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
setFormError("Choose an existing container");
return false;
}
if (onlineMode && !onlinePrerequisitesMet) {
setFormError("Online migration prerequisites must be enabled before proceeding.");
return false;
}
return true;
};
const getModeForApi = (): "Offline" | "Online" => {
return onlineMode ? "Online" : "Offline";
};
const createDataTransferJob = async () => {
const jobName = `Portal_${targetCollectionId}_${Math.floor(Date.now() / 1000)}`;
const dataTransferParams: DataTransferParams = {
@@ -99,6 +238,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
sourceCollectionName: sourceCollection.id(),
targetDatabaseName: sourceDatabase.id(),
targetCollectionName: targetCollectionId,
mode: getModeForApi(),
};
await initiateDataTransfer(dataTransferParams);
};
@@ -133,12 +273,18 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
return !!selectedDatabase?.offer();
};
const isSubmitDisabled = onlineMode && !onlinePrerequisitesMet;
const migrationTypeLowercase = getModeForApi().toLowerCase() as keyof typeof Keys.containerCopy.migrationType;
const migrationTypeContent = Keys.containerCopy.migrationType[migrationTypeLowercase];
return (
<RightPaneForm
formError={formError}
isExecuting={isExecuting}
onSubmit={submit}
submitButtonText={t(Keys.common.ok)}
isSubmitButtonDisabled={isSubmitDisabled}
>
<Stack tokens={{ childrenGap: 10 }} className="panelMainContent">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
@@ -151,11 +297,58 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
{t(Keys.common.learnMore)}
</Link>
</Text>
{/* Migration Type */}
<Stack data-test="migration-type-section">
<Text className="panelTextBold" variant="small" style={{ marginBottom: 4 }}>
{t(Keys.containerCopy.migrationTypeTitle)}
</Text>
<Stack className="panelGroupSpacing" horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={!onlineMode}
aria-label="Offline mode"
aria-checked={!onlineMode}
name="migrationType"
type="radio"
role="radio"
id="migrationTypeOffline"
tabIndex={0}
onChange={() => setOnlineMode(false)}
/>
<span className="panelRadioBtnLabel">{t(Keys.containerCopy.migrationType.offline.title)}</span>
<input
className="panelRadioBtn"
checked={onlineMode}
aria-label="Online mode"
aria-checked={onlineMode}
name="migrationType"
type="radio"
role="radio"
tabIndex={0}
onChange={() => setOnlineMode(true)}
/>
<span className="panelRadioBtnLabel">{t(Keys.containerCopy.migrationType.online.title)}</span>
</div>
</Stack>
{migrationTypeContent && (
<Text
variant="small"
style={{ color: "var(--colorNeutralForeground1)", marginTop: 4 }}
data-test={`migration-type-description-${migrationTypeLowercase}`}
>
<MarkdownRender source={t(migrationTypeContent.description)} linkTarget="_blank" />
</Text>
)}
</Stack>
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Database id
{t(Keys.panes.addDatabase.databaseIdLabel)}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
@@ -420,6 +613,89 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
/>
</Stack>
)}
{/* Online prerequisites section */}
{onlineMode && (
<Stack data-test="online-prerequisites-section" tokens={{ childrenGap: 10 }}>
<LoadingOverlay isLoading={isEnablingPrerequisite} label={prerequisiteLoaderMessage} />
<Text className="panelTextBold" variant="small">
{t(Keys.containerCopy.assignPermissions.onlineConfiguration.title)}
</Text>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{t(Keys.containerCopy.assignPermissions.onlineConfiguration.description, { accountName })}
</Text>
{/* Point In Time Restore */}
<Stack tokens={{ childrenGap: 5 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 5 }}>
<Icon
iconName={pitrEnabled ? "SkypeCircleCheck" : "StatusCircleRing"}
styles={{
root: { color: pitrEnabled ? "green" : "var(--colorNeutralForeground1)", fontSize: 16 },
}}
/>
<Text variant="small" style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>
{t(Keys.containerCopy.pointInTimeRestore.title)}
</Text>
</Stack>
{!pitrEnabled && (
<Stack tokens={{ childrenGap: 10, padding: "0 0 0 20px" }}>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{t(Keys.containerCopy.pointInTimeRestore.description, { accessName: accountName })}
</Text>
<PrimaryButton
data-test="enable-pitr-button"
text={t(Keys.containerCopy.pointInTimeRestore.buttonText)}
disabled={isEnablingPrerequisite}
onClick={handleEnablePitr}
styles={{ root: { width: "fit-content" } }}
/>
</Stack>
)}
</Stack>
{/* Online Copy Enabled */}
<Stack tokens={{ childrenGap: 5 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 5 }}>
<Icon
iconName={onlineCopyFeatureEnabled ? "SkypeCircleCheck" : "StatusCircleRing"}
styles={{
root: {
color: onlineCopyFeatureEnabled ? "green" : "var(--colorNeutralForeground1)",
fontSize: 16,
},
}}
/>
<Text variant="small" style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>
{t(Keys.containerCopy.onlineCopyEnabled.title)}
</Text>
</Stack>
{!onlineCopyFeatureEnabled && (
<Stack tokens={{ childrenGap: 10, padding: "0 0 0 20px" }}>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{t(Keys.containerCopy.onlineCopyEnabled.description, { accountName })}&ensp;
<Link href={t(Keys.containerCopy.onlineCopyEnabled.href)} target="_blank" rel="noopener noreferrer">
{t(Keys.containerCopy.onlineCopyEnabled.hrefText)}
</Link>
</Text>
<PrimaryButton
data-test="enable-online-copy-button"
text={t(Keys.containerCopy.onlineCopyEnabled.buttonText)}
disabled={isEnablingPrerequisite || !pitrEnabled}
onClick={handleEnableOnlineCopy}
styles={{ root: { width: "fit-content" } }}
/>
</Stack>
)}
</Stack>
{!onlinePrerequisitesMet && (
<MessageBar messageBarType={MessageBarType.warning} data-test="online-prerequisites-warning">
{t(Keys.containerCopy.onlineCopyEnabled.onlineMigrationPrerequisitesMessage)}
</MessageBar>
)}
</Stack>
)}
</Stack>
</RightPaneForm>
);
+168 -7
View File
@@ -34,6 +34,8 @@
"browse": "Procházet",
"increaseValueBy1": "Zvýšit hodnotu o 1",
"decreaseValueBy1": "Snížit hodnotu o 1",
"on": "Zapnuto",
"off": "Vypnuto",
"preview": "Preview"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Vytvoření tabulky a interakce s daty pomocí rozhraní prostředí PostgreSQL"
},
"vcoreMongo": {
"title": "Prostředí Mongo",
"title": "Mongo Shell",
"description": "Vytvořte kolekci a pracujte s daty pomocí rozhraní prostředí MongoDB"
}
},
@@ -414,7 +416,7 @@
"refreshGridFailed": "Nepovedlo se aktualizovat mřížku dokumentů"
},
"mongoShell": {
"title": "Prostředí Mongo"
"title": "Mongo Shell"
}
},
"panes": {
@@ -762,7 +764,7 @@
"computedProperties": "Vypočítané vlastnosti",
"containerPolicies": "Zásady kontejneru",
"throughputBuckets": "Kbelíky propustnosti",
"globalSecondaryIndexPreview": "Globální sekundární index (Preview)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Zásady maskování"
},
"mongoNotifications": {
@@ -795,7 +797,7 @@
"perMonth": "/měs."
},
"throughput": {
"manualToAutoscaleDisclaimer": "Počáteční maximální hodnota RU/s pro automatické škálování bude určena systémem na základě nastavení aktuální manuální propustnosti a úložiště vašeho prostředku. Po povolení automatického škálování můžete změnit maximální počet RU/s.",
"manualToAutoscaleDisclaimer": "Počáteční maximální hodnota RU/s pro automatické škálování bude určena systémem na základě nastavení aktuální ruční propustnosti a úložiště vašeho prostředku. Po povolení automatického škálování můžete změnit maximální počet RU/s.",
"ttlWarningText": "Systém bude položky automaticky odstraňovat na základě hodnoty TTL (v sekundách), kterou zadáte, aniž by klientská aplikace musela výslovně provést operaci odstranění. Další informace viz",
"ttlWarningLinkText": "Hodnota TTL (Time to Live) v Azure Cosmos DB",
"unsavedIndexingPolicy": "zásada indexování",
@@ -813,8 +815,8 @@
"saveThroughputWarning": "Změna nastavení propustnosti ovlivní výši vaší faktury. Před uložením změn si prosím projděte aktualizovaný odhad nákladů uvedený níže.",
"currentAutoscaleThroughput": "Aktuální propustnost automatického škálování:",
"targetAutoscaleThroughput": "Cílová propustnost automatického škálování:",
"currentManualThroughput": "Aktuální manuální propustnost:",
"targetManualThroughput": "Cílová manuální propustnost:",
"currentManualThroughput": "Aktuální ruční propustnost:",
"targetManualThroughput": "Cílová ruční propustnost:",
"applyDelayedMessage": "Žádost o zvýšení propustnosti se úspěšně odeslala. Dokončení této operace bude trvat 1 až 3 pracovní dny. Nejnovější stav najdete v části Oznámení.",
"databaseLabel": "Databáze:",
"containerLabel": "Kontejner:",
@@ -947,7 +949,7 @@
"instant": "Okamžité",
"fourToSixHrs": "46 hodin",
"autoscaleDescription": "Na základě využití se vaše propustnost {{resourceType}} bude škálovat od",
"freeTierWarning": "Fakturace začne, pokud zřídíte více než {{ru}} RU/s manuální propustnosti nebo pokud se prostředek při automatickém škálování bude škálovat nad {{ru}} RU/s.",
"freeTierWarning": "Fakturace začne, pokud zřídíte více než {{ru}} RU/s ruční propustnosti nebo pokud se prostředek při automatickém škálování bude škálovat nad {{ru}} RU/s.",
"capacityCalculator": "Odhadněte požadovanou hodnotu RU/s pomocí",
"capacityCalculatorLink": " kalkulačka kapacity",
"fixedStorageNote": "Při použití kolekce s pevnou kapacitou úložiště můžete nastavit až 10 000 RU/s.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "Velikost kvantování v bajtech musí být větší než 0 a menší nebo rovna 512.",
"indexingSearchListSizeRangeError": "Velikost seznamu prohledávání indexu musí být větší nebo rovna 25 a menší nebo rovna 500."
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Zpětná vazba",
"feedbackButtonAriaLabel": "Poskytnout zpětnou vazbu k úlohám kopírování",
"refreshButtonAriaLabel": "Aktualizovat úlohy kopírování",
"createCopyJobButtonLabel": "Vytvořit úlohu kopírování",
"createCopyJobButtonAriaLabel": "Vytvořit novou úlohu kopírování kontejneru"
},
"noCopyJobs": {
"title": "Žádné úlohy kopírování k zobrazení",
"createCopyJobButtonText": "Vytvořit úlohu kopírování kontejneru"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Podrobnosti úlohy",
"errorTitle": "Podrobnosti o chybě",
"selectedContainers": "Vybrané kontejnery"
},
"createCopyJob": {
"panelTitle": "Vytvořit úlohu kopírování"
},
"selectAccount": {
"description": "Vyberte cílový účet, do kterého chcete kopírovat.",
"subscriptionDropdownLabel": "Předplatné",
"subscriptionDropdownPlaceholder": "Vyberte předplatné",
"accountDropdownLabel": "Účet",
"accountDropdownPlaceholder": "Vybrat účet"
},
"migrationType": {
"offline": {
"title": "Offline režim",
"description": "Úlohy offline kopírování kontejnerů umožňují kopírovat data ze zdrojového kontejneru do cílového kontejneru Cosmos DB pro podporovaná rozhraní API. Pro zajištění integrity dat mezi zdrojem a cílem doporučujeme před vytvořením úlohy kopírování zastavit aktualizace zdrojového kontejneru. Další informace o [úlohách offline kopírování](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Online režim",
"description": "Úlohy online kopírování kontejnerů umožňují kopírovat data ze zdrojového kontejneru do cílového kontejneru rozhraní API NoSQL služby Cosmos DB pomocí kanálu změn [Všechny verze a odstranění](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Díky tomu můžou aktualizace ve zdroji pokračovat i během kopírování dat. Na konci je potřeba krátký výpadek, aby bylo možné bezpečně přepnout klientské aplikace na cílový kontejner. Další informace o [úlohách online kopírování](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Vyberte zdrojový a cílový kontejner, do kterého chcete kopírovat.",
"sourceContainerSubHeading": "Zdrojový kontejner",
"targetContainerSubHeading": "Cílový kontejner",
"databaseDropdownLabel": "Databáze",
"databaseDropdownPlaceholder": "Vyberte databázi",
"containerDropdownLabel": "Kontejner",
"containerDropdownPlaceholder": "Vybrat kontejner",
"createNewContainerSubHeading": "Nakonfigurujte vlastnosti nového kontejneru v cílovém účtu {{accountName}}.",
"createNewContainerSubHeadingDefault": "Nakonfigurujte vlastnosti nového kontejneru.",
"createContainerButtonLabel": "Vytvořit nový kontejner",
"createContainerHeading": "Vytvořit nový kontejner"
},
"preview": {
"jobNameLabel": "Název úlohy",
"subscriptionLabel": "Cílové předplatné",
"accountLabel": "Cílový účet",
"sourceDatabaseLabel": "Zdrojová databáze",
"sourceContainerLabel": "Zdrojový kontejner",
"targetDatabaseLabel": "Cílová databáze",
"targetContainerLabel": "Cílový kontejner"
},
"assignPermissions": {
"crossAccountDescription": "Pokud chcete zkopírovat data ze zdrojového do cílového kontejneru, podle následujících kroků zajistěte, aby spravovaná identita zdrojového účtu měla přístup ke čtení a zápisu k cílovému účtu.",
"intraAccountOnlineDescription": "Podle následujících kroků povolte online kopírování pro účet {{accountName}}.",
"crossAccountConfiguration": {
"title": "Kopírování kontejnerů mezi účty",
"description": "Podle pokynů níže udělte potřebná oprávnění ke kopírování dat z {{sourceAccount}} do {{destinationAccount}}."
},
"onlineConfiguration": {
"title": "Online kopírování kontejneru",
"description": "Podle pokynů níže povolte online kopírování pro účet {{accountName}}."
}
},
"popoverOverlaySpinnerLabel": "Počkejte prosím, než zpracujeme váš požadavek...",
"addManagedIdentity": {
"title": "Spravovaná identita přiřazená systémem je povolená.",
"description": "Spravovaná identita přiřazená systémem je omezená na jednu na prostředek a je svázaná s životním cyklem tohoto prostředku. Po povolení můžete spravované identitě udělit oprávnění pomocí řízení přístupu na základě role Azure (Azure RBAC). Spravovaná identita se ověřuje pomocí Microsoft Entra ID, abyste žádné přihlašovací údaje nemuseli ukládat do kódu.",
"descriptionHrefText": "Další informace o spravovaných identitách.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Spravovaná identita přiřazená systémem",
"tooltipContent": "Přečtěte si další informace o",
"tooltipHrefText": "Spravované identity.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Můžete vybrat existující identitu přiřazenou uživatelem nebo vytvořit novou.",
"userAssignedIdentityLabel": "Můžete také vybrat spravovanou identitu přiřazenou uživatelem.",
"createUserAssignedIdentityLink": "Vytvořit spravovanou identitu přiřazenou uživatelem",
"enablementTitle": "Povolit spravovanou identitu přiřazenou systémem",
"enablementDescription": "Povolte spravovanou identitu přiřazenou systémem pro {{accountName}}. Potvrďte kliknutím na tlačítko Ano."
},
"defaultManagedIdentity": {
"title": "Spravovaná identita přiřazená systémem je nastavená jako výchozí.",
"description": "Zapněte spravovanou identitu přiřazenou systémem a nastavte ji jako výchozí pro {{accountName}}.",
"tooltipContent": "Přečtěte si další informace o",
"tooltipHrefText": "Výchozí spravované identity.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Spravovaná identita přiřazená systémem nastavená jako výchozí",
"popoverDescription": "Nastavte spravovanou identitu přiřazenou systémem jako výchozí pro {{accountName}}. Potvrďte kliknutím na tlačítko Ano. "
},
"readWritePermissionAssigned": {
"title": "Výchozí identitě byla přiřazena oprávnění ke čtení a zápisu.",
"description": "Pokud chcete povolit kopírování dat ze zdrojového do cílového kontejneru, udělte výchozí identitě zdrojového účtu přístup ke čtení a zápisu v cílovém účtu.",
"tooltipContent": "Přečtěte si další informace o",
"tooltipHrefText": "Oprávnění ke čtení a zápisu.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Přiřaďte výchozí identitě oprávnění ke čtení a zápisu.",
"popoverDescription": "Přiřaďte výchozí identitě zdrojového účtu oprávnění ke čtení a zápisu v cílovém účtu. Potvrďte kliknutím na tlačítko Ano."
},
"pointInTimeRestore": {
"title": "Obnovení k určitému bodu v čase je povolené",
"description": "Aby bylo možné používat úlohy online kopírování kontejnerů, aktualizujte zásady zálohování účtu {{accessName}} z pravidelného na průběžné zálohování. Pro tuto funkci je potřeba povolit průběžné zálohování.",
"tooltipContent": "Přečtěte si další informace o",
"tooltipHrefText": "Průběžné zálohování",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Povolit obnovení k určitému bodu v čase"
},
"onlineCopyEnabled": {
"title": "Online kopírování je povolené",
"description": "Povolte online kopírování kontejneru kliknutím na tlačítko níže v účtu {{accountName}}.",
"hrefText": "Další informace o úlohách online kopírování",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Povolit online kopírování",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Ověřuje se režim kanálu změn Všechny verze a odstranění (Preview)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Povoluje se režim kanálu změn Všechny verze a odstranění (Preview)...",
"enablingOnlineCopySpinnerLabel": "Povoluje se online kopírování pro účet {{accountName}}..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Datum a čas",
"name": "Název úlohy",
"status": "Stav",
"completionPercentage": "Dokončení v %",
"duration": "Doba trvání",
"error": "Chybová zpráva",
"mode": "Režim",
"actions": "Akce"
},
"actions": {
"pause": "Pozastavit",
"resume": "Obnovit",
"complete": "Dokončeno",
"viewDetails": "Zobrazit podrobnosti"
},
"status": {
"pending": "Ve frontě",
"inProgress": "Běží",
"running": "Běží",
"partitioning": "Běží",
"paused": "Pozastaveno",
"completed": "Dokončeno",
"failed": "Neúspěšné",
"faulted": "Neúspěšné",
"skipped": "Zrušeno",
"cancelled": "Zrušeno"
},
"dialog": {
"confirmButtonText": "Potvrdit",
"cancelButtonText": "Zrušit"
}
}
}
}
+174 -13
View File
@@ -34,6 +34,8 @@
"browse": "Durchsuchen",
"increaseValueBy1": "Wert um 1 erhöhen",
"decreaseValueBy1": "Wert um 1 verringern",
"on": "Ein",
"off": "Aus",
"preview": "Vorschau"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Erstellen Sie eine Tabelle und interagieren Sie mit Daten über die Shellschnittstelle von PostgreSQL."
},
"vcoreMongo": {
"title": "Mongo-Shell",
"title": "Mongo Shell",
"description": "Erstellen Sie eine Sammlung und interagieren Sie mit Daten über die Shellschnittstelle von MongoDB."
}
},
@@ -469,7 +471,7 @@
"sharded": "Mit Sharding",
"addPartitionKey": "Hierarchischen Partitionsschlüssel hinzufügen",
"hierarchicalPartitionKeyInfo": "Mit diesem Feature können Sie Ihre Daten mit bis zu drei Schlüsselebenen partitionieren, um eine bessere Datenverteilung zu erzielen. Erfordert .NET V3, Java V4 SDK oder JavaScript V3 SDK (Vorschauversion).",
"provisionDedicatedThroughput": "Dedizierten Durchsatz für {{collectionName}} bereitstellen",
"provisionDedicatedThroughput": "Dedizierten Durchsatz für diese {{collectionName}} bereitstellen",
"provisionDedicatedThroughputTooltip": "Sie können optional dedizierten Durchsatz für eine {{collectionName}} in einer Datenbank bereitstellen, für die Durchsatz bereitgestellt wurde. Dieser dedizierte Durchsatz wird nicht für andere {{collectionNamePlural}} in der Datenbank freigegeben und zählt nicht zum Durchsatz, den Sie für die Datenbank bereitgestellt haben. Diese Durchsatzmenge wird zusätzlich zu dem Durchsatz, den Sie auf Datenbankebene bereitgestellt haben, in Rechnung gestellt.",
"uniqueKeysPlaceholderMongo": "Durch Trennzeichen getrennte Pfade, z. B. firstName,address.zipCode",
"uniqueKeysPlaceholderSql": "Durch Trennzeichen getrennte Pfade, z. B. /firstName,/address/zipCode",
@@ -735,7 +737,7 @@
"addProperty": "Eigenschaft hinzufügen"
},
"addGlobalSecondaryIndex": {
"globalSecondaryIndexId": "Container-ID des globalen sekundären Indexes",
"globalSecondaryIndexId": "Container-ID des globalen sekundären Index",
"globalSecondaryIndexIdPlaceholder": "Beispiel: indexbyEmailId",
"projectionQuery": "Projektionsabfrage",
"projectionQueryPlaceholder": "SELECT c.email, c.accountId FROM c",
@@ -762,7 +764,7 @@
"computedProperties": "Berechnete Eigenschaften",
"containerPolicies": "Containerrichtlinien",
"throughputBuckets": "Durchsatzbuckets",
"globalSecondaryIndexPreview": "Globaler sekundärer Index (Vorschau)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Maskierungsrichtlinie"
},
"mongoNotifications": {
@@ -795,7 +797,7 @@
"perMonth": "/Mo."
},
"throughput": {
"manualToAutoscaleDisclaimer": "Die maximale RU/s-Anzahl für die Autoskalierung wird vom System basierend auf den aktuellen manuellen Durchsatzeinstellungen und dem Speicher Ihrer Ressource festgelegt. Nachdem die Autoskalierung aktiviert wurde, können Sie die maximale RU/s-Anzahl ändern.",
"manualToAutoscaleDisclaimer": "Der Ausgangswert für maximale RU/s für die Autoskalierung wird vom System basierend auf den aktuellen Einstellungen für den manuellen Durchsatz und dem Speicher Ihrer Ressource festgelegt. Nachdem die Autoskalierung aktiviert wurde, können Sie den Wert für maximale RU/s ändern.",
"ttlWarningText": "Das System löscht Elemente automatisch auf Grundlage des von Ihnen angegebenen TTL-Werts (in Sekunden), ohne dass eine Löschoperation explizit von einer Clientanwendung angefordert werden muss. Weitere Informationen finden Sie unter",
"ttlWarningLinkText": "Gültigkeitsdauer (TTL) in Azure Cosmos DB",
"unsavedIndexingPolicy": "Indizierungsrichtlinie",
@@ -807,12 +809,12 @@
"scalingUpDelayMessage": "Das Hochskalieren dauert 46 Stunden, da es die sofortige Unterstützung von Azure Cosmos DB basierend auf Ihrer Anzahl physischer Partitionen übersteigt. Sie können Ihren Durchsatz sofort auf {{instantMaximumThroughput}} erhöhen oder mit dem aktuellen Wert fortfahren und warten, bis das Hochskalieren abgeschlossen ist.",
"exceedPreAllocatedMessage": "Ihre Anfrage zur Erhöhung des Durchsatzes übersteigt die vorab zugewiesene Kapazität, was zu einer längeren Bearbeitungszeit als erwartet führen kann. Sie können aus drei Optionen wählen, um fortzufahren:",
"instantScaleOption": "Sie können sofort auf {{instantMaximumThroughput}} RU/s hochskalieren.",
"asyncScaleOption": "Sie können asynchron innerhalb von 46 Stunden auf einen beliebigen Wert unter {{maximumThroughput}} RU/s skalieren.",
"quotaMaxOption": "Ihr aktuelles Kontingent liegt bei {{maximumThroughput}} RU/s. Um dieses Limit zu überschreiten, müssen Sie eine Kontingenterhöhung anfordern und das Azure Cosmos DB-Team wird diese prüfen.",
"asyncScaleOption": "Sie können asynchron innerhalb von 46 Stunden auf einen beliebigen Wert unter {{maximumThroughput}} RU/s hochskalieren.",
"quotaMaxOption": "Ihr aktuelles Kontingent liegt bei {{maximumThroughput}} RU/s. Um dieses Limit zu überschreiten, müssen Sie eine Kontingenterhöhung anfordern, und das Azure Cosmos DB-Team wird diese prüfen.",
"belowMinimumMessage": "Sie können den Durchsatz nicht unter den aktuellen Mindestwert von {{minimum}} RU/s senken. Weitere Informationen zu diesem Limit finden Sie in unserer Dokumentation zum Serviceangebot.",
"saveThroughputWarning": "Ihre Rechnung wird sich ändern, wenn Sie Ihre Durchsatzeinstellungen aktualisieren. Bitte überprüfen Sie die untenstehende aktualisierte Kostenschätzung, bevor Sie Ihre Änderungen speichern.",
"currentAutoscaleThroughput": "Aktueller Durchsatz der Autoskalierung:",
"targetAutoscaleThroughput": "Zieldurchsatz für Autoskalierung:",
"currentAutoscaleThroughput": "Aktueller Autoskalierungsdurchsatz:",
"targetAutoscaleThroughput": "Autoskalierungs-Zieldurchsatz:",
"currentManualThroughput": "Aktueller manueller Durchsatz:",
"targetManualThroughput": "Manueller Zieldurchsatz:",
"applyDelayedMessage": "Die Anforderung zur Erhöhung des Durchsatzes wurde erfolgreich übermittelt. Dieser Vorgang dauert 13 Werktage. Die neuesten Statusinformationen finden Sie in den Benachrichtigungen.",
@@ -843,7 +845,7 @@
"disclaimerCompoundIndexesLink": " Zusammengesetzte Indizes ",
"disclaimerSuffix": "werden nur zum Sortieren von Abfrageergebnissen verwendet. Wenn Sie einen zusammengesetzten Index hinzufügen müssen, können Sie diesen mit der Mongo Shell erstellen.",
"compoundNotSupported": "Sammlungen mit zusammengesetzten Indizes werden im Indizierungseditor derzeit noch nicht unterstützt. Um die Indizierungsrichtlinie für diese Sammlung zu ändern, verwenden Sie die Mongo Shell.",
"aadError": "Um den Indexierungsrichtlinien-Editor zu verwenden, melden Sie sich bei",
"aadError": "Um den Indizierungsrichtlinien-Editor zu verwenden, bitte anmelden bei",
"aadErrorLink": "Azure-Portal.",
"refreshingProgress": "Fortschritt der Indextransformation wird aktualisiert",
"canMakeMoreChangesZero": "Sobald die aktuelle Indextransformation abgeschlossen ist, können Sie weitere Änderungen an der Indizierung vornehmen. ",
@@ -925,10 +927,10 @@
},
"globalSecondaryIndex": {
"indexesDefined": "Für diesen Container sind die folgenden Indizes definiert.",
"learnMoreSuffix": "über die Definition globaler Sekundärindizes und deren Verwendung.",
"learnMoreSuffix": "über die Definition globaler sekundärer Indizes und ihre Verwendung.",
"jsonAriaLabel": "JSON für globalen sekundären Index",
"addIndex": "Index hinzufügen",
"settingsTitle": "Globale Einstellungen für sekundären Index",
"settingsTitle": "Einstellungen für globalen sekundären Index",
"sourceContainer": "Quellcontainer",
"indexDefinition": "Definition des globalen sekundären Index"
},
@@ -947,7 +949,7 @@
"instant": "Sofort",
"fourToSixHrs": "46 Stunden",
"autoscaleDescription": "Basierend auf der Nutzung wird Ihr {{resourceType}}-Durchsatz von",
"freeTierWarning": "Die Abrechnung erfolgt, wenn Sie mehr als {{ru}} RU/s manuellen Durchsatz bereitstellen oder wenn die Ressource mit Autoscale über {{ru}} RU/s skaliert.",
"freeTierWarning": "Eine Abrechnung findet statt, wenn Sie einen manuellen Durchsatz von mehr als {{ru}} RU/s bereitstellen oder wenn die Ressource mit Autoskalierung auf über {{ru}} RU/s skaliert wird.",
"capacityCalculator": "Schätzen Sie Ihren erforderlichen RU/s mit",
"capacityCalculatorLink": " Kapazitätsrechner",
"fixedStorageNote": "Wenn Sie eine Sammlung mit fester Speicherkapazität verwenden, können Sie bis zu 10.000 RU/s festlegen.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "Die Quantisierungsbytegröße muss größer als 0 und kleiner oder gleich 512 sein.",
"indexingSearchListSizeRangeError": "Die Größe der Indizierungssuchliste muss größer oder gleich 25 und kleiner oder gleich 500 sein."
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Feedback",
"feedbackButtonAriaLabel": "Feedback zu Kopieraufträgen geben",
"refreshButtonAriaLabel": "Kopieraufträge aktualisieren",
"createCopyJobButtonLabel": "Kopierauftrag erstellen",
"createCopyJobButtonAriaLabel": "Neuen Containerkopierauftrag erstellen"
},
"noCopyJobs": {
"title": "Keine Kopieraufträge zum Anzeigen vorhanden",
"createCopyJobButtonText": "Containerkopierauftrag erstellen"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Auftragsdetails",
"errorTitle": "Fehlerdetails",
"selectedContainers": "Ausgewählte Container"
},
"createCopyJob": {
"panelTitle": "Kopierauftrag erstellen"
},
"selectAccount": {
"description": "Wählen Sie ein Zielkonto aus, in das kopiert werden soll.",
"subscriptionDropdownLabel": "Abonnement",
"subscriptionDropdownPlaceholder": "Abonnement auswählen",
"accountDropdownLabel": "Konto",
"accountDropdownPlaceholder": "Konto auswählen"
},
"migrationType": {
"offline": {
"title": "Offlinemodus",
"description": "Mit Offline-Containerkopieraufträgen können Sie Daten aus einem Quellcontainer in einen Cosmos DB-Zielcontainer für unterstützte APIs kopieren. Um die Datenintegrität zwischen Quelle und Ziel sicherzustellen, wird empfohlen, vor dem Erstellen des Kopierauftrags Updates für den Quellcontainer zu beenden. Weitere Informationen zu [Offlinekopieraufträgen](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Onlinemodus",
"description": "Mit Onlinecontainerkopieraufträgen können Sie Daten mithilfe des Änderungsfeeds [Alle Versionen und Löschvorgänge](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) aus einem Quellcontainer in einen Cosmos DB NoSQL-API-Zielcontainer kopieren. Dadurch können Updates in der Quelle fortgesetzt werden, während Daten kopiert werden. Am Ende ist eine kurze Downtime erforderlich, um sicher über Clientanwendungen zum Zielcontainer zu wechseln. Weitere Informationen zu [Onlinekopieraufträgen](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Wählen Sie einen Quellcontainer und einen Zielcontainer aus, in den kopiert werden soll.",
"sourceContainerSubHeading": "Quellcontainer",
"targetContainerSubHeading": "Zielcontainer",
"databaseDropdownLabel": "Datenbank",
"databaseDropdownPlaceholder": "Datenbank auswählen",
"containerDropdownLabel": "Container",
"containerDropdownPlaceholder": "Container auswählen",
"createNewContainerSubHeading": "Konfigurieren Sie die Eigenschaften für den neuen Container im Zielkonto „{{accountName}}“.",
"createNewContainerSubHeadingDefault": "Konfigurieren Sie die Eigenschaften für den neuen Container.",
"createContainerButtonLabel": "Neuen Container erstellen",
"createContainerHeading": "Neuen Container erstellen"
},
"preview": {
"jobNameLabel": "Auftragsname",
"subscriptionLabel": "Zielabonnement",
"accountLabel": "Zielkonto",
"sourceDatabaseLabel": "Quelldatenbank",
"sourceContainerLabel": "Quellcontainer",
"targetDatabaseLabel": "Zieldatenbank",
"targetContainerLabel": "Zielcontainer"
},
"assignPermissions": {
"crossAccountDescription": "Um Daten aus der Quelle in den Zielcontainer zu kopieren, stellen Sie sicher, dass die verwaltete Identität des Quellkontos Lese-/Schreibzugriff auf das Zielkonto hat, indem Sie die folgenden Schritte ausführen.",
"intraAccountOnlineDescription": "Führen Sie die folgenden Schritte aus, um das Onlinekopieren für Ihr Konto „{{accountName}}“ zu aktivieren.",
"crossAccountConfiguration": {
"title": "Kontoübergreifende Containerkopie",
"description": "Befolgen Sie die Anweisungen unten, um die erforderlichen Berechtigungen zum Kopieren von Daten von „{{sourceAccount}}“ nach „{{destinationAccount}}“ zu erteilen."
},
"onlineConfiguration": {
"title": "Onlinecontainerkopie",
"description": "Befolgen Sie die nachstehenden Anweisungen, um das Onlinekopieren für Ihr Konto „{{accountName}}“ zu aktivieren."
}
},
"popoverOverlaySpinnerLabel": "Bitte warten Sie, während wir Ihre Anforderung verarbeiten ...",
"addManagedIdentity": {
"title": "Systemseitig zugewiesene verwaltete Identität aktiviert.",
"description": "Eine systemseitig zugewiesene verwaltete Identität ist auf eine pro Ressource beschränkt und an den Lebenszyklus dieser Ressource gebunden. Nach ihrer Aktivierung können Sie der verwalteten Identität mithilfe der rollenbasierten Azure-Zugriffssteuerung(Azure Role Based Access Control, Azure RBAC) Berechtigungen erteilen. Die verwaltete Identität über Microsoft Entra ID authentifiziert wird, sodass Sie keine Anmeldeinformationen im Code speichern müssen.",
"descriptionHrefText": "Weitere Informationen zu verwalteten Identitäten.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Systemseitig zugewiesene verwaltete Identität",
"tooltipContent": "Weitere Informationen zu",
"tooltipHrefText": "Verwaltete Identitäten.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Sie können eine vorhandene benutzerseitig zugewiesene Identität auswählen oder eine neue erstellen.",
"userAssignedIdentityLabel": "Sie können auch eine benutzerseitig zugewiesene verwaltete Identität auswählen.",
"createUserAssignedIdentityLink": "Benutzerseitig zugewiesene verwaltete Identität erstellen",
"enablementTitle": "Systemseitig zugewiesene verwaltete Identität aktivieren",
"enablementDescription": "Aktivieren Sie die systemseitig zugewiesene verwaltete Identität für {{accountName}}. Klicken Sie zur Bestätigung auf die Schaltfläche „Ja“."
},
"defaultManagedIdentity": {
"title": "Systemseitig zugewiesene verwaltete Identität wurde als Standard festgelegt.",
"description": "Legen Sie die systemseitig zugewiesene verwaltete Identität als Standard für „{{accountName}}“ fest, indem Sie sie aktivieren.",
"tooltipContent": "Weitere Informationen zu",
"tooltipHrefText": "Verwaltete Standardidentitäten.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Systemseitig zugewiesene verwaltete Identität als Standard festlegen",
"popoverDescription": "Weisen Sie die systemseitig zugewiesene verwaltete Identität als Standard für „{{accountName}}“ zu. Klicken Sie zur Bestätigung auf die Schaltfläche „Ja“. "
},
"readWritePermissionAssigned": {
"title": "Der Standardidentität wurden Lese-/Schreibberechtigungen zugewiesen.",
"description": "Um das Kopieren von Daten aus der Quelle in den Zielcontainer zuzulassen, gewähren Sie der Standardidentität des Quellkontos Lese- und Schreibzugriff auf das Zielkonto.",
"tooltipContent": "Weitere Informationen zu",
"tooltipHrefText": "Lese-/Schreibberechtigungen.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Weisen Sie der Standardidentität Lese-/Schreibberechtigungen zu.",
"popoverDescription": "Weisen Sie der Standardidentität des Quellkontos Lese-/Schreibberechtigungen für das Zielkonto zu. Klicken Sie zur Bestätigung auf die Schaltfläche „Ja“."
},
"pointInTimeRestore": {
"title": "Point-in-Time-Wiederherstellung aktiviert",
"description": "Um Aufträge zum Kopieren von Onlinecontainern zu vereinfachen, aktualisieren Sie Ihre Sicherungsrichtlinie „{{accessName}}“ von regelmäßigen auf fortlaufende Sicherungen. Für diese Funktionalität ist die Aktivierung der fortlaufenden Sicherung erforderlich.",
"tooltipContent": "Weitere Informationen zu",
"tooltipHrefText": "Fortlaufende Sicherung",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Zeitpunktwiederherstellung aktivieren"
},
"onlineCopyEnabled": {
"title": "Onlinekopie aktiviert",
"description": "Aktivieren Sie das Kopieren von Onlinecontainern, indem Sie auf die Schaltfläche unten in Ihrem Konto „{{accountName}}“ klicken.",
"hrefText": "Weitere Informationen zu Onlinekopieraufträgen",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Onlinekopie aktivieren",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Änderungsfeedmodus „Alle Versionen und Löschvorgänge“ (Vorschau) wird validiert ...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Änderungsfeedmodus „Alle Versionen und Löschvorgänge“ (Vorschau) wird aktiviert",
"enablingOnlineCopySpinnerLabel": "Onlinekopie wird für Ihr Konto „{{accountName}}“ aktiviert ..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Datum und Uhrzeit",
"name": "Auftragsname",
"status": "Status",
"completionPercentage": "Abschluss %",
"duration": "Dauer",
"error": "Fehlermeldung",
"mode": "Modus",
"actions": "Aktionen"
},
"actions": {
"pause": "Anhalten",
"resume": "Fortsetzen",
"complete": "Abschließen",
"viewDetails": "Details anzeigen"
},
"status": {
"pending": "In Warteschlange",
"inProgress": "Wird ausgeführt",
"running": "Wird ausgeführt",
"partitioning": "Wird ausgeführt",
"paused": "Angehalten",
"completed": "Abgeschlossen",
"failed": "Fehler",
"faulted": "Fehler",
"skipped": "Abgebrochen",
"cancelled": "Abgebrochen"
},
"dialog": {
"confirmButtonText": "Bestätigen",
"cancelButtonText": "Abbrechen"
}
}
}
}
+178 -2
View File
@@ -34,6 +34,8 @@
"browse": "Browse",
"increaseValueBy1": "Increase value by 1",
"decreaseValueBy1": "Decrease value by 1",
"on": "On",
"off": "Off",
"preview": "Preview"
},
"splashScreen": {
@@ -758,11 +760,10 @@
"settings": "Settings",
"indexingPolicy": "Indexing Policy",
"partitionKeys": "Partition Keys",
"partitionKeysPreview": "Partition Keys (preview)",
"computedProperties": "Computed Properties",
"containerPolicies": "Container Policies",
"throughputBuckets": "Throughput Buckets",
"globalSecondaryIndexPreview": "Global Secondary Index (Preview)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Masking Policy"
},
"mongoNotifications": {
@@ -893,6 +894,10 @@
},
"partitionKeyEditor": {
"changePartitionKey": "Change {{partitionKeyName}}",
"confirmCancel1": "You are about to cancel the following copy job.",
"confirmCancel2": "Cancelling will stop the job immediately.",
"confirmComplete1": "You are about to complete the following copy job.",
"confrimComplete2": "Once completed, continuous data copy will stop after any pending documents are processed. To maintain data integrity, we recommend stopping updates to the source container before completing the job.",
"currentPartitionKey": "Current {{partitionKeyName}}",
"partitioning": "Partitioning",
"hierarchical": "Hierarchical",
@@ -992,5 +997,176 @@
"quantizationByteSizeRangeError": "Quantization byte size must be greater than 0 and less than or equal to 512",
"indexingSearchListSizeRangeError": "Indexing search list size must be greater than or equal to 25 and less than or equal to 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Feedback",
"feedbackButtonAriaLabel": "Provide feedback on copy jobs",
"refreshButtonAriaLabel": "Refresh copy jobs",
"createCopyJobButtonLabel": "Create Copy Job",
"createCopyJobButtonAriaLabel": "Create a new container copy job"
},
"noCopyJobs": {
"title": "No copy jobs to show",
"createCopyJobButtonText": "Create a container copy job"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Job Details",
"errorTitle": "Error Details",
"selectedContainers": "Selected Containers"
},
"createCopyJob": {
"panelTitle": "Create copy job"
},
"selectAccount": {
"description": "Please select a destination account to copy to.",
"subscriptionDropdownLabel": "Subscription",
"subscriptionDropdownPlaceholder": "Select a subscription",
"accountDropdownLabel": "Account",
"accountDropdownPlaceholder": "Select an account"
},
"migrationType": {
"offline": {
"title": "Offline mode",
"description": "Offline container copy jobs let you copy data from a source container to a destination Cosmos DB container for supported APIs. To ensure data integrity between the source and destination, we recommend stopping updates on the source container before creating the copy job. Learn more about [offline copy jobs](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Online mode",
"description": "Online container copy jobs let you copy data from a source container to a destination Cosmos DB NoSQL API container using the [All Versions and Delete](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) change feed. This allows updates to continue on the source while data is copied. A brief downtime is required at the end to safely switch over client applications to the destination container. Learn more about [online copy jobs](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"migrationTypeTitle": "Migration type",
"selectContainers": {
"description": "Please select a source container and a destination container to copy to.",
"sourceContainerSubHeading": "Source container",
"targetContainerSubHeading": "Destination container",
"databaseDropdownLabel": "Database",
"databaseDropdownPlaceholder": "Select a database",
"containerDropdownLabel": "Container",
"containerDropdownPlaceholder": "Select a container",
"createNewContainerSubHeading": "Configure the properties for the new container on destination account \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Configure the properties for the new container.",
"createContainerButtonLabel": "Create a new container",
"createContainerHeading": "Create new container"
},
"preview": {
"jobNameLabel": "Job name",
"subscriptionLabel": "Destination subscription",
"accountLabel": "Destination account",
"sourceDatabaseLabel": "Source database",
"sourceContainerLabel": "Source container",
"targetDatabaseLabel": "Destination database",
"targetContainerLabel": "Destination container"
},
"assignPermissions": {
"crossAccountDescription": "To copy data from the source to the destination container, ensure that the managed identity of the source account has read-write access to the destination account by completing the following steps.",
"intraAccountOnlineDescription": "Follow the steps below to enable online copy on your \"{{accountName}}\" account.",
"crossAccountConfiguration": {
"title": "Cross-account container copy",
"description": "Please follow the instruction below to grant requisite permissions to copy data from \"{{sourceAccount}}\" to \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Online container copy",
"description": "Please follow the instructions below to enable online copy on your \"{{accountName}}\" account."
}
},
"popoverOverlaySpinnerLabel": "Please wait while we process your request...",
"addManagedIdentity": {
"title": "System-assigned managed identity enabled.",
"description": "A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you don't have to store any credentials in code.",
"descriptionHrefText": "Learn more about Managed identities.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "System assigned managed identity",
"tooltipContent": "Learn more about",
"tooltipHrefText": "Managed Identities.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "You can select an existing user assigned identity or create a new one.",
"userAssignedIdentityLabel": "You may also select a user assigned managed identity.",
"createUserAssignedIdentityLink": "Create User Assigned Managed Identity",
"enablementTitle": "Enable system assigned managed identity",
"enablementDescription": "Enable system-assigned managed identity on the {{accountName}}. To confirm, click the \"Yes\" button."
},
"defaultManagedIdentity": {
"title": "System-assigned managed identity set as default.",
"description": "Set the system-assigned managed identity as default for \"{{accountName}}\" by switching it on.",
"tooltipContent": "Learn more about",
"tooltipHrefText": "Default Managed Identities.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "System assigned managed identity set as default",
"popoverDescription": "Assign the system-assigned managed identity as the default for \"{{accountName}}\". To confirm, click the \"Yes\" button. "
},
"readWritePermissionAssigned": {
"title": "Read-write permissions assigned to the default identity.",
"description": "To allow data copy from source to the destination container, provide read-write access on the destination account to the default identity of the source account.",
"tooltipContent": "Learn more about",
"tooltipHrefText": "Read-write permissions.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Assign read-write permissions to default identity.",
"popoverDescription": "Assign read-write permissions on the destination account to the default identity of the source account. To confirm, click the \"Yes\" button."
},
"pointInTimeRestore": {
"title": "Point In Time Restore enabled",
"description": "To facilitate online container copy jobs, please update your \"{{accessName}}\" backup policy from periodic to continuous backup. Enabling continuous backup is required for this functionality.",
"tooltipContent": "Learn more about",
"tooltipHrefText": "Continuous Backup",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Enable Point In Time Restore"
},
"onlineCopyEnabled": {
"title": "Online copy enabled",
"description": "Enable online container copy by clicking the button below on your \"{{accountName}}\" account.",
"hrefText": "Learn more about online copy jobs",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Enable Online Copy",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Validating All versions and deletes change feed mode (preview)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Enabling All versions and deletes change feed mode (preview)...",
"enablingOnlineCopySpinnerLabel": "Enabling online copy on your \"{{accountName}}\" account ...",
"onlineMigrationPrerequisitesMessage": "Online migration prerequisites must be enabled before proceeding."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Date & time",
"name": "Job name",
"status": "Status",
"completionPercentage": "Completion %",
"duration": "Duration",
"error": "Error message",
"mode": "Mode",
"actions": "Actions"
},
"actions": {
"pause": "Pause",
"resume": "Resume",
"complete": "Complete",
"viewDetails": "View Details"
},
"status": {
"pending": "Queued",
"inProgress": "Running",
"running": "Running",
"partitioning": "Running",
"paused": "Paused",
"completed": "Completed",
"failed": "Failed",
"faulted": "Failed",
"skipped": "Cancelled",
"cancelled": "Cancelled"
},
"dialog": {
"confirmButtonText": "Confirm",
"cancelButtonText": "Cancel"
}
},
"dataTransfers": {
"polling": {
"cancelConsoleMessage": "Data transfer job \"{{jobName}}\" cancelled",
"completedConsoleMessage": "Data transfer job \"{{jobName}}\" completed",
"defaultErrorMessage": "Operation could not be completed",
"errorConsoleMessage": "Data transfer job \"{{jobName}}\" failed: {{errorMessage}}",
"pauseConsoleMessage": "Data transfer job \"{{jobName}}\" paused",
"retryConsoleMessage": "Data transfer job \"{{jobName}}\" in progress, total count: {{totalCount}}, processed count: {{processedCount}}"
}
}
}
}
+162 -1
View File
@@ -34,6 +34,8 @@
"browse": "Examinar",
"increaseValueBy1": "Aumentar valor en 1",
"decreaseValueBy1": "Disminuir valor en 1",
"on": "Activado",
"off": "Desactivado",
"preview": "Versión preliminar"
},
"splashScreen": {
@@ -762,7 +764,7 @@
"computedProperties": "Propiedades calculadas",
"containerPolicies": "Directivas de contenedor",
"throughputBuckets": "Depósitos de rendimiento",
"globalSecondaryIndexPreview": "Índice secundario global (versión preliminar)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Directiva de enmascaramiento"
},
"mongoNotifications": {
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "El tamaño de bytes de cuantificación debe ser mayor que 0 y menor o igual que 512",
"indexingSearchListSizeRangeError": "El tamaño de la lista de búsqueda de indexación debe ser mayor o igual que 25 y menor o igual que 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Comentarios",
"feedbackButtonAriaLabel": "Proporcionar comentarios sobre los trabajos de copia",
"refreshButtonAriaLabel": "Actualizar trabajos de copia",
"createCopyJobButtonLabel": "Crear trabajo de copia",
"createCopyJobButtonAriaLabel": "Creación de un nuevo trabajo de copia de contenedor"
},
"noCopyJobs": {
"title": "No hay trabajos de copia que mostrar",
"createCopyJobButtonText": "Creación de un trabajo de copia de contenedor"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Detalles del trabajo",
"errorTitle": "Detalles del error",
"selectedContainers": "Contenedores seleccionados"
},
"createCopyJob": {
"panelTitle": "Crear trabajo de copia"
},
"selectAccount": {
"description": "Seleccione una cuenta de destino en la que copiar.",
"subscriptionDropdownLabel": "Suscripción",
"subscriptionDropdownPlaceholder": "Seleccionar una suscripción",
"accountDropdownLabel": "Cuenta",
"accountDropdownPlaceholder": "Seleccione una cuenta"
},
"migrationType": {
"offline": {
"title": "Modo sin conexión",
"description": "Los trabajos de copia de contenedor sin conexión permiten copiar datos de un contenedor de origen a un contenedor de Cosmos DB de destino para las API admitidas. Para garantizar la integridad de los datos entre el origen y el destino, se recomienda detener las actualizaciones en el contenedor de origen antes de crear el trabajo de copia. Obtenga más información sobre [trabajos de copia sin conexión](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Modo en línea",
"description": "Los trabajos de copia de contenedores en línea permiten copiar datos de un contenedor de origen a un contenedor de destino de la API NoSQL de Cosmos DB mediante la fuente de cambios [Todas las versiones y eliminación](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Esto permite que las actualizaciones continúen en el origen mientras se copian los datos. Se requiere un breve tiempo de inactividad al final para cambiar de forma segura las aplicaciones cliente al contenedor de destino. Obtenga más información sobre [trabajos de copia online](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Seleccione un contenedor de origen y un contenedor de destino en el que copiar.",
"sourceContainerSubHeading": "Contenedor de origen",
"targetContainerSubHeading": "Contenedor de destino",
"databaseDropdownLabel": "Base de datos",
"databaseDropdownPlaceholder": "Seleccionar una base de datos",
"containerDropdownLabel": "Contenedor",
"containerDropdownPlaceholder": "Seleccione un contenedor",
"createNewContainerSubHeading": "Configure las propiedades del nuevo contenedor en la cuenta de destino \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Configure las propiedades del nuevo contenedor.",
"createContainerButtonLabel": "Crear contenedor nuevo",
"createContainerHeading": "Crear nuevo contenedor"
},
"preview": {
"jobNameLabel": "Nombre del trabajo",
"subscriptionLabel": "Suscripción de destino",
"accountLabel": "Cuenta de destino",
"sourceDatabaseLabel": "Base de datos de origen",
"sourceContainerLabel": "Contenedor de origen",
"targetDatabaseLabel": "Base de datos de destino",
"targetContainerLabel": "Contenedor de destino"
},
"assignPermissions": {
"crossAccountDescription": "Para copiar datos desde el contenedor de origen al de destino, asegúrese de que la identidad administrada de la cuenta de origen tiene acceso de lectura y escritura a la cuenta de destino mediante los pasos siguientes.",
"intraAccountOnlineDescription": "Siga los pasos que se indican a continuación para habilitar la copia en línea en su cuenta \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Copia de contenedor entre cuentas",
"description": "Siga las instrucciones siguientes para conceder los permisos necesarios para copiar datos de \"{{sourceAccount}}\" a \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Copia de contenedor en línea",
"description": "Siga las instrucciones siguientes para habilitar la copia en línea en su cuenta \"{{accountName}}\"."
}
},
"popoverOverlaySpinnerLabel": "Espere mientras procesamos su solicitud...",
"addManagedIdentity": {
"title": "Identidad administrada asignada por el sistema habilitada.",
"description": "Una identidad administrada asignada por el sistema está restringida a una por recurso y está vinculada al ciclo de vida de este recurso. Una vez habilitado, puede conceder permisos a la identidad administrada mediante el control de acceso basado en rol de Azure (RBAC de Azure). La identidad administrada se autentica con Microsoft Entra ID, de modo que no tiene que almacenar credenciales en el código.",
"descriptionHrefText": "Obtenga más información sobre las identidades administradas.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identidad administrada asignada por el sistema",
"tooltipContent": "Más información sobre",
"tooltipHrefText": "Identidades administradas.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Puede seleccionar una identidad asignada por el usuario existente o crear una nueva.",
"userAssignedIdentityLabel": "También puede seleccionar una identidad administrada asignada por el usuario.",
"createUserAssignedIdentityLink": "Crear identidad administrada asignada por el usuario",
"enablementTitle": "Habilitar identidad administrada asignada por el sistema",
"enablementDescription": "Habilite la identidad administrada asignada por el sistema en el {{accountName}}. Para confirmar, haga clic en el botón \"Sí\"."
},
"defaultManagedIdentity": {
"title": "Identidad administrada asignada por el sistema establecida como predeterminada.",
"description": "Establezca la identidad administrada asignada por el sistema como predeterminada para \"{{accountName}}\" cambiándola a activada.",
"tooltipContent": "Más información sobre",
"tooltipHrefText": "Identidades administradas predeterminadas.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identidad administrada asignada por el sistema establecida como predeterminada",
"popoverDescription": "Asigne la identidad administrada asignada por el sistema como valor predeterminado para \"{{accountName}}\". Para confirmar, haga clic en el botón \"Sí\". "
},
"readWritePermissionAssigned": {
"title": "Permisos de lectura y escritura asignados a la identidad predeterminada.",
"description": "Para permitir la copia de datos desde el origen al contenedor de destino, proporcione acceso de lectura y escritura en la cuenta de destino a la identidad predeterminada de la cuenta de origen.",
"tooltipContent": "Más información sobre",
"tooltipHrefText": "Permisos de lectura y escritura.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Asigne permisos de lectura y escritura a la identidad predeterminada.",
"popoverDescription": "Asigne permisos de lectura y escritura en la cuenta de destino a la identidad predeterminada de la cuenta de origen. Para confirmar, haga clic en el botón \"Sí\"."
},
"pointInTimeRestore": {
"title": "Restauración a un momento dado habilitada",
"description": "Para facilitar los trabajos de copia de contenedor en línea, actualice la directiva de periódica \"{{accessName}}\" a copia de seguridad continua. Se requiere habilitar la copia de seguridad continua para esta funcionalidad.",
"tooltipContent": "Más información sobre",
"tooltipHrefText": "Copia de seguridad continua",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Habilitar restauración a un momento dado"
},
"onlineCopyEnabled": {
"title": "Copia en línea habilitada",
"description": "Habilite la copia de contenedor en línea haciendo clic en el botón siguiente en su cuenta \"{{accountName}}\".",
"hrefText": "Más información sobre los trabajos de copia en línea",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Habilitar copia en línea",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Validando todas las versiones y elimina el modo de fuente de cambios (versión preliminar)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Habilitando todas las versiones y elimina el modo de fuente de cambios (versión preliminar)...",
"enablingOnlineCopySpinnerLabel": "Habilitando la copia en línea en su cuenta \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Fecha y hora",
"name": "Nombre del trabajo",
"status": "Estado",
"completionPercentage": "Porcentaje de finalización",
"duration": "Duración",
"error": "Mensaje de error",
"mode": "Modo",
"actions": "Acciones"
},
"actions": {
"pause": "Pausar",
"resume": "Reanudar",
"complete": "Completado",
"viewDetails": "Ver detalles"
},
"status": {
"pending": "En cola",
"inProgress": "En ejecución",
"running": "En ejecución",
"partitioning": "En ejecución",
"paused": "En pausa",
"completed": "Completado",
"failed": "Erróneo",
"faulted": "Erróneo",
"skipped": "Cancelado",
"cancelled": "Cancelado"
},
"dialog": {
"confirmButtonText": "Confirmar",
"cancelButtonText": "Cancelar"
}
}
}
}
+175 -14
View File
@@ -34,6 +34,8 @@
"browse": "Parcourir",
"increaseValueBy1": "Augmenter la valeur de 1",
"decreaseValueBy1": "Diminuer la valeur de 1",
"on": "Activé",
"off": "Désactivé",
"preview": "Aperçu"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Créer un tableau et interagir avec les données à laide de linterface dinterpréteur PostgreSQL"
},
"vcoreMongo": {
"title": "Interpréteur de commandes Mongo",
"title": "Mongo Shell",
"description": "Créer une collection et interagir avec les données à laide de linterface dinterpréteur MongoDB"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "Supprimer {{containerName}}",
"newSqlQuery": "Nouvelle requête SQL",
"newQuery": "Nouvelle requête",
"openMongoShell": "Ouvrir linterpréteur de commandes Mongo",
"openMongoShell": "Ouvrir Mongo Shell",
"newShell": "Nouvel interpréteur de commandes",
"openCassandraShell": "Ouvrir linterpréteur de commandes Cassandra",
"newStoredProcedure": "Nouvelle procédure stockée",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Nous navons pas pu actualiser la grille des documents"
},
"mongoShell": {
"title": "Interpréteur de commandes Mongo"
"title": "Mongo Shell"
}
},
"panes": {
@@ -488,7 +490,7 @@
"legacySdkInfo": "Pour garantir la compatibilité avec les anciens Kits de développement logiciels (SDK), le conteneur créé utilise un schéma de partitionnement hérité qui prend en charge des valeurs de clés de partition dune taille maximale de 101 octets. Si cette option est activée, vous ne pouvez pas utiliser de clés de partition hiérarchiques.",
"indexingOnInfo": "Toutes les propriétés de vos documents seront indexées par défaut pour permettre des requêtes flexibles et efficaces.",
"indexingOffInfo": "Lindexation sera désactivée. Nous recommandons cette option si vous navez pas besoin dexécuter des requêtes ou si vous neffectuez que des opérations clé-valeur.",
"indexingOffWarning": "En créant ce conteneur avec lindexation désactivée, vous ne pouvez pas modifier la stratégie dindexation. Les modifications dindexation ne sont autorisées que sur un conteneur disposant dune stratégie dindexation.",
"indexingOffWarning": "En créant ce conteneur avec lindexation désactivée, vous ne pouvez pas modifier la politique dindexation. Les modifications dindexation ne sont autorisées que sur un conteneur disposant dune politique dindexation.",
"acknowledgeSpendErrorMonthly": "Prenez en compte lestimation des dépenses mensuelles.",
"acknowledgeSpendErrorDaily": "Prenez en compte lestimation des dépenses quotidiennes.",
"unshardedMaxRuError": "Les collections non partitionnées prennent en charge un maximum de 10 000 RU",
@@ -756,13 +758,13 @@
"scale": "Mise à l’échelle",
"conflictResolution": "Résolution des conflits",
"settings": "Paramètres",
"indexingPolicy": "Stratégie d'indexation",
"indexingPolicy": "Politique d'indexation",
"partitionKeys": "Clés de partition",
"partitionKeysPreview": "Clés de partition (aperçu)",
"computedProperties": "Propriétés calculées",
"containerPolicies": "Stratégies relatives aux conteneurs",
"throughputBuckets": "Bacs de débit",
"globalSecondaryIndexPreview": "Indice mondial du secteur secondaire (Aperçu)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Stratégie de masquage"
},
"mongoNotifications": {
@@ -798,7 +800,7 @@
"manualToAutoscaleDisclaimer": "Le débit maximal initial de RU/s de mise à l'échelle automatique sera déterminé par le système, en fonction des paramètres de débit manuels actuels et du stockage de votre ressource. Une fois la mise à l'échelle automatique activée, vous pouvez modifier le nombre maximal d'unités de requête par seconde (RU/s).",
"ttlWarningText": "Le système supprimera automatiquement les éléments en fonction de la valeur TTL (en secondes) que vous fournissez, sans qu'une opération de suppression explicite soit nécessaire de la part d'une application cliente. Pour plus d'informations, consultez,",
"ttlWarningLinkText": "Durée de vie (TTL) dans Azure Cosmos DB",
"unsavedIndexingPolicy": "stratégie d'indexation",
"unsavedIndexingPolicy": "politique d'indexation",
"unsavedDataMaskingPolicy": "Stratégie de masquage des données",
"unsavedComputedProperties": "propriétés calculées",
"unsavedEditorWarningPrefix": "Vous n'avez pas enregistré les dernières modifications apportées à votre",
@@ -841,9 +843,9 @@
"mongoIndexing": {
"disclaimer": "Pour les requêtes qui filtrent sur plusieurs propriétés, créez plusieurs index à champ unique au lieu d'un index composé.",
"disclaimerCompoundIndexesLink": " Indices composés ",
"disclaimerSuffix": "ne servent qu'à trier les résultats des requêtes. Si vous devez ajouter un index composé, vous pouvez en créer un à l'aide du shell Mongo.",
"compoundNotSupported": "Les collections avec index composés ne sont pas encore prises en charge dans l'éditeur d'indexation. Pour modifier la stratégie d'indexation de cette collection, utilisez le shell Mongo.",
"aadError": "Pour utiliser l'éditeur de stratégie d'indexation, veuillez vous connecter à",
"disclaimerSuffix": "ne servent qu'à trier les résultats des requêtes. Si vous devez ajouter un index composé, vous pouvez en créer un à l'aide de Mongo Shell.",
"compoundNotSupported": "Les collections avec index composés ne sont pas encore prises en charge dans l'éditeur d'indexation. Pour modifier la politique d'indexation de cette collection, utilisez le shell Mongo.",
"aadError": "Pour utiliser l'éditeur de politique d'indexation, veuillez vous connecter à",
"aadErrorLink": "Portail Azure.",
"refreshingProgress": "Actualisation de la progression de la transformation de l'index",
"canMakeMoreChangesZero": "Vous pourrez apporter d'autres modifications d'indexation une fois la transformation d'index actuelle terminée. ",
@@ -909,7 +911,7 @@
"learnMorePrefix": "sur la manière de définir les propriétés calculées et de les utiliser."
},
"indexingPolicy": {
"ariaLabel": "Stratégie d'indexation"
"ariaLabel": "Politique d'indexation"
},
"dataMasking": {
"ariaLabel": "Stratégie de masquage des données",
@@ -926,11 +928,11 @@
"globalSecondaryIndex": {
"indexesDefined": "Ce conteneur possède les index suivants définis pour lui.",
"learnMoreSuffix": "sur la manière de définir les index secondaires globaux et de les utiliser.",
"jsonAriaLabel": "Index secondaire mondial JSON",
"jsonAriaLabel": "Index secondaire global JSON",
"addIndex": "Ajouter un index",
"settingsTitle": "Paramètres d'indexation secondaire globale",
"settingsTitle": "Paramètres de l'index secondaire global",
"sourceContainer": "Conteneur source",
"indexDefinition": "Définition globale des indices secondaires"
"indexDefinition": "Définition de l'index secondaire global"
},
"indexingPolicyRefresh": {
"refreshFailed": "L'actualisation de la progression de la transformation de l'index a échoué"
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "La taille en octets de quantification doit être supérieure à 0 et inférieure ou égale à 512",
"indexingSearchListSizeRangeError": "La taille de la liste de recherche dindexation doit être comprise entre 25 et 500 inclus"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Commentaires",
"feedbackButtonAriaLabel": "Fournir des commentaires sur les travaux de copie",
"refreshButtonAriaLabel": "Actualiser les travaux de copie",
"createCopyJobButtonLabel": "Créer un travail de copie",
"createCopyJobButtonAriaLabel": "Créer un travail de copie de conteneur"
},
"noCopyJobs": {
"title": "Aucun travail de copie à afficher",
"createCopyJobButtonText": "Créer un travail de copie de conteneur"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Détails du travail",
"errorTitle": "Détails de lerreur",
"selectedContainers": "Conteneurs sélectionnés"
},
"createCopyJob": {
"panelTitle": "Créer un travail de copie"
},
"selectAccount": {
"description": "Sélectionnez un compte de destination vers lequel effectuer la copie.",
"subscriptionDropdownLabel": "Abonnement",
"subscriptionDropdownPlaceholder": "Sélectionner un abonnement",
"accountDropdownLabel": "Compte",
"accountDropdownPlaceholder": "Sélectionner un compte"
},
"migrationType": {
"offline": {
"title": "Mode hors connexion",
"description": "Les travaux de copie hors connexion de conteneur vous permettent de copier des données dun conteneur source vers un conteneur Cosmos DB cible pour les API prises en charge. Pour garantir lintégrité des données entre la source et la destination, nous vous recommandons darrêter les mises à jour sur le conteneur source avant de créer le travail de copie. En savoir plus sur les [travaux de copie hors ligne](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Mode en ligne",
"description": "Les travaux de copie en ligne de conteneur vous permettent de copier des données dun conteneur source vers un conteneur cible dAPI NoSQL de Cosmos DB à laide du flux de modification [All Versions and Delete](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Cela vous permet de continuer à mettre à jour la source pendant la copie des données. Un bref temps darrêt est nécessaire à la fin pour basculer en toute sécurité les applications clientes vers le conteneur cible. En savoir plus sur les [travaux de copie en ligne](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Veuillez sélectionner un conteneur source et un conteneur de destination vers lequel effectuer la copie.",
"sourceContainerSubHeading": "Conteneur source",
"targetContainerSubHeading": "Conteneur de destination",
"databaseDropdownLabel": "Base de données",
"databaseDropdownPlaceholder": "Sélectionner une base de données",
"containerDropdownLabel": "Conteneur",
"containerDropdownPlaceholder": "Sélectionner un conteneur",
"createNewContainerSubHeading": "Configurez les propriétés du nouveau conteneur sur le compte de destination « {{accountName}} ».",
"createNewContainerSubHeadingDefault": "Configurez les propriétés du nouveau conteneur.",
"createContainerButtonLabel": "Créer un conteneur",
"createContainerHeading": "Créer un conteneur"
},
"preview": {
"jobNameLabel": "Nom de travail",
"subscriptionLabel": "Abonnement de destination",
"accountLabel": "Compte de destination",
"sourceDatabaseLabel": "Base de données source",
"sourceContainerLabel": "Conteneur source",
"targetDatabaseLabel": "Base de données de destinations",
"targetContainerLabel": "Conteneur de destination"
},
"assignPermissions": {
"crossAccountDescription": "Pour copier des données de la source vers le conteneur de destination, vérifiez que lidentité managée du compte source dispose dun accès en lecture-écriture au compte de destination en effectuant les étapes suivantes.",
"intraAccountOnlineDescription": "Suivez les étapes ci-dessous pour activer la copie en ligne sur votre compte « {{accountName}} ».",
"crossAccountConfiguration": {
"title": "Copie de conteneur entre comptes",
"description": "Veuillez suivre les instructions ci-dessous pour accorder les autorisations nécessaires afin de copier des données de « {{sourceAccount}} » vers « {{destinationAccount}} »."
},
"onlineConfiguration": {
"title": "Copie de conteneur en ligne",
"description": "Suivre les instructions ci-dessous pour activer la copie en ligne sur votre compte « {{accountName}} »."
}
},
"popoverOverlaySpinnerLabel": "Veuillez patienter pendant que nous traitons votre requête...",
"addManagedIdentity": {
"title": "Identité managée affectée par le système activée.",
"description": "Une identité managée affectée par le système est limitée à une par ressource et est liée au cycle de vie de cette ressource. Une fois activée, vous pouvez accorder des autorisations à lidentité managée à laide du contrôle daccès en fonction du rôle Azure (RBAC Azure). Lidentité managée est authentifiée avec Microsoft Entra ID, si bien que vous navez pas à stocker dinformations didentification dans le code.",
"descriptionHrefText": "En savoir plus sur les identités managées.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identité managée affectée par le système",
"tooltipContent": "Découvrir plus dinformations sur",
"tooltipHrefText": "Identités managées.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Vous pouvez sélectionner une identité managée attribuée par lutilisateur existante ou en créer une nouvelle.",
"userAssignedIdentityLabel": "Vous pouvez également sélectionner une identité managée affectée par lutilisateur(-trice).",
"createUserAssignedIdentityLink": "Créer une identité managée affectée par lutilisateur",
"enablementTitle": "Activer lidentité managée affectée par le système",
"enablementDescription": "Activer lidentité managée affectée par le système sur le {{accountName}}. Pour confirmer, cliquez sur le bouton « Oui »."
},
"defaultManagedIdentity": {
"title": "Identité managée affectée par le système définie par défaut.",
"description": "Définissez lidentité managée affectée par le système comme identité par défaut pour « {{accountName}} » en lactivant.",
"tooltipContent": "Découvrir plus dinformations sur",
"tooltipHrefText": "Identités managées par défaut.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identité managée affectée par le système définie par défaut",
"popoverDescription": "Attribuer lidentité managée affectée par le système comme identité par défaut pour « {{accountName}} ». Pour confirmer, cliquez sur le bouton « Oui ». "
},
"readWritePermissionAssigned": {
"title": "Autorisations en lecture-écriture attribuées à lidentité par défaut.",
"description": "Pour autoriser la copie des données de la source vers le conteneur de destination, accordez un accès en lecture-écriture au compte de destination à lidentité par défaut du compte source.",
"tooltipContent": "Découvrir plus dinformations sur",
"tooltipHrefText": "Autorisation de lecture et d’écriture.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Attribuer des autorisations de lecture et d’écriture à lidentité par défaut.",
"popoverDescription": "Attribuer des autorisations de lecture-écriture sur le compte de destination à lidentité par défaut du compte source. Pour confirmer, cliquez sur le bouton « Oui »."
},
"pointInTimeRestore": {
"title": "Restauration à un moment donné activée",
"description": "Pour faciliter les opérations de copie de conteneur en ligne, mettez à jour la stratégie de sauvegarde de votre compte « {{accessName}} » de la sauvegarde périodique à la sauvegarde continue. Lactivation de la sauvegarde continue est requise pour cette fonctionnalité.",
"tooltipContent": "Découvrir plus dinformations sur",
"tooltipHrefText": "Sauvegarde continue",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Activer la restauration à un moment donné"
},
"onlineCopyEnabled": {
"title": "Copie en ligne activée",
"description": "Activez la copie de conteneur en ligne en cliquant sur le bouton ci-dessous dans votre compte « {{accountName}} ».",
"hrefText": "En savoir plus sur les travaux de copie en ligne",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Activer la copie en ligne",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Validation en cours du Mode de flux de modification de toutes les versions et suppressions (préversion)",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Activation en cours du Mode de flux de modification de toutes les versions et suppressions (préversion)...",
"enablingOnlineCopySpinnerLabel": "Activation de la copie en ligne sur votre compte « {{accountName}} » ..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Date et heure",
"name": "Nom de travail",
"status": "Statut",
"completionPercentage": "% dachèvement",
"duration": "Durée",
"error": "Message derreur",
"mode": "Mode",
"actions": "Actions"
},
"actions": {
"pause": "Suspendre",
"resume": "Reprendre",
"complete": "Terminé",
"viewDetails": "Afficher les détails"
},
"status": {
"pending": "En file dattente",
"inProgress": "Exécution",
"running": "Exécution",
"partitioning": "Exécution",
"paused": "En pause",
"completed": "Terminé",
"failed": "Échec",
"faulted": "Échec",
"skipped": "Annulé",
"cancelled": "Annulé"
},
"dialog": {
"confirmButtonText": "Confirmer",
"cancelButtonText": "Annuler"
}
}
}
}
+169 -8
View File
@@ -34,6 +34,8 @@
"browse": "Tallózás",
"increaseValueBy1": "Érték növelése 1-gyel",
"decreaseValueBy1": "Érték csökkentése 1-gyel",
"on": "Be",
"off": "Ki",
"preview": "Előnézet"
},
"splashScreen": {
@@ -303,7 +305,7 @@
"deleteContainer": "{{containerName}} törlése",
"newSqlQuery": "Új SQL-lekérdezés",
"newQuery": "Új lekérdezés",
"openMongoShell": "Mongo-felület megnyitása",
"openMongoShell": "Mongo Shell megnyitása",
"newShell": "Új felület",
"openCassandraShell": "Cassandra-felület megnyitása",
"newStoredProcedure": "Új tárolt eljárás",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Nem sikerült frissíteni a dokumentumrácsot"
},
"mongoShell": {
"title": "Mongo-felület"
"title": "Mongo Shell"
}
},
"panes": {
@@ -488,7 +490,7 @@
"legacySdkInfo": "A régebbi SDK-kkal való kompatibilitás biztosítása érdekében a létrehozott tároló egy örökölt particionálási sémát fog használni, amely legfeljebb 101 bájt méretű partíciókulcs-értékeket támogat. Ha ez engedélyezve van, nem használhat hierarchikus partíciókulcsokat.",
"indexingOnInfo": "A dokumentumok összes tulajdonsága alapértelmezés szerint indexelve lesz a rugalmas és hatékony lekérdezések érdekében.",
"indexingOffInfo": "Az indexelés ki lesz kapcsolva. Akkor ajánlott, ha nem kell lekérdezéseket futtatnia, vagy csak kulcsérték-műveletekkel rendelkezik.",
"indexingOffWarning": "Ha úgy hozza létre ezt a tárolót, hogy az indexelés ki van kapcsolva, nem fogja tudni módosítani az indexelési szabályzatot. Az indexelési módosítások csak indexelési szabályzattal rendelkező tárolókon engedélyezettek.",
"indexingOffWarning": "Ha úgy hozza létre ezt a tárolót, hogy az indexelés ki van kapcsolva, nem fogja tudni módosítani az indexelési házirendet. Az indexelési módosítások csak indexelési szabályzattal rendelkező tárolókon engedélyezettek.",
"acknowledgeSpendErrorMonthly": "Nyugtázza a becsült havi ráfordítást.",
"acknowledgeSpendErrorDaily": "Nyugtázza a becsült napi ráfordítást.",
"unshardedMaxRuError": "A horizontálisan nem skálázott gyűjtemények legfeljebb 10 000 kérelemegységet támogatnak",
@@ -619,7 +621,7 @@
"accountId": "Fiókazonosító",
"sessionId": "Munkamenet-azonosító",
"popupsDisabledError": "Nem sikerült hitelesítést létrehozni ehhez a fiókhoz, mert a böngészőben le vannak tiltva az előugró ablakok.\nEngedélyezze az előugró ablakokat ehhez a webhelyhez, majd kattintson az Entra ID bejelentkezés gombra",
"failedToAcquireTokenError": "Nem sikerült automatikusan beszerezni az engedélyezési jogkivonatot. Kattintson az Entra ID bejelentkezés gombra az Entra ID RBAC-műveletek engedélyezéséhez"
"failedToAcquireTokenError": "Nem sikerült automatikusan beszerezni a hozzáférési jogkivonatot. Kattintson az Entra ID bejelentkezés gombra az Entra ID RBAC-műveletek engedélyezéséhez"
},
"saveQuery": {
"panelTitle": "Lekérdezés mentése",
@@ -756,13 +758,13 @@
"scale": "Skálázás",
"conflictResolution": "Ütközésfeloldás",
"settings": "Beállítások",
"indexingPolicy": "Indexelési szabályzat",
"indexingPolicy": "Indexelési házirend",
"partitionKeys": "Partíciókulcsok",
"partitionKeysPreview": "Partíciókulcsok (előzetes verzió)",
"computedProperties": "Számított tulajdonságok",
"containerPolicies": "Tárolószabályzatok",
"throughputBuckets": "Átviteli sebesség gyűjtői",
"globalSecondaryIndexPreview": "Globális másodlagos index (előzetes verzió)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Maszkolási szabályzat"
},
"mongoNotifications": {
@@ -841,7 +843,7 @@
"mongoIndexing": {
"disclaimer": "Több tulajdonságra szűrő lekérdezések esetén összetett index helyett hozzon létre több egymezős indexet.",
"disclaimerCompoundIndexesLink": " Összetett indexek ",
"disclaimerSuffix": "csak a lekérdezési eredmények rendezésére szolgál. Ha összetett indexet kell hozzáadnia, a Mongo-felület használatával hozhat létre egyet.",
"disclaimerSuffix": "csak a lekérdezési eredmények rendezésére szolgál. Ha összetett indexet kell hozzáadnia, a Mongo Shell használatával hozhat létre egyet.",
"compoundNotSupported": "Az összetett indexekkel rendelkező gyűjtemények még nem támogatottak az indexelőszerkesztőben. A gyűjtemény indexelési házirendjének módosításához használja a Mongo-felületet.",
"aadError": "Az indexelési házirendszerkesztő használatához jelentkezzen be:",
"aadErrorLink": "Azure Portal.",
@@ -909,7 +911,7 @@
"learnMorePrefix": "a számított tulajdonságok definiálásáról és azok használatáról."
},
"indexingPolicy": {
"ariaLabel": "Indexelési szabályzat"
"ariaLabel": "Indexelési házirend"
},
"dataMasking": {
"ariaLabel": "Adatmaszkolási házirend",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "A kvantálási bájtméretnek nagyobbnak kell lennie 0-nál, és legfeljebb 512 lehet",
"indexingSearchListSizeRangeError": "Az indexelő keresési lista méretének 25-tel egyenlőnek vagy annál nagyobbnak és ugyanakkor 500-nál kisebbnek vagy azzal egyenlőnek kell lennie"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Visszajelzés",
"feedbackButtonAriaLabel": "Visszajelzés küldése a másolási feladatokról",
"refreshButtonAriaLabel": "Másolási feladatok frissítése",
"createCopyJobButtonLabel": "Másolási feladat létrehozása",
"createCopyJobButtonAriaLabel": "Új tárolómásolási feladat létrehozása"
},
"noCopyJobs": {
"title": "Nincsenek megjeleníthető másolási feladatok",
"createCopyJobButtonText": "Tárolómásolási feladat létrehozása"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Feladat részletei",
"errorTitle": "Hiba részletei",
"selectedContainers": "Kiválasztott tárolók"
},
"createCopyJob": {
"panelTitle": "Másolási feladat létrehozása"
},
"selectAccount": {
"description": "Válassza ki a célfiókot, amelybe másolni szeretne.",
"subscriptionDropdownLabel": "Előfizetés",
"subscriptionDropdownPlaceholder": "Előfizetés kiválasztása",
"accountDropdownLabel": "Fiók",
"accountDropdownPlaceholder": "Fiók kiválasztása"
},
"migrationType": {
"offline": {
"title": "Offline mód",
"description": "Az offline tárolómásolási feladatok lehetővé teszik az adatok másolását egy forrástárolóból egy cél Cosmos DB-tárolóba a támogatott API-k esetében. Az adatintegritás megőrzése érdekében a forrás és a cél között javasoljuk, hogy a másolási feladat létrehozása előtt állítsa le a forrástároló frissítéseit. További információ az [offline másolási feladatokról](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Online üzemmód",
"description": "Az online tárolómásolási feladatok lehetővé teszik, hogy adatokat másoljon egy forrástárolóból egy cél Cosmos DB NoSQL API-tárolóba a [Minden verzió és törlés](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) változáscsatorna használatával. Ez lehetővé teszi, hogy a frissítések az adatok másolása közben is folytatódjanak a forráson. A végén rövid leállásra van szükség ahhoz, hogy biztonságosan átválthassa az ügyfélalkalmazásokat a céltárolóra. További információ az [online másolási feladatokról](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Válasszon ki egy forrástárolót és egy céltárolót, amelybe másolni szeretne.",
"sourceContainerSubHeading": "Forrástároló",
"targetContainerSubHeading": "Céloldali tároló",
"databaseDropdownLabel": "Adatbázis",
"databaseDropdownPlaceholder": "Adatbázis kiválasztása",
"containerDropdownLabel": "Tároló",
"containerDropdownPlaceholder": "Válasszon ki egy tárolót",
"createNewContainerSubHeading": "Konfigurálja az új tároló tulajdonságait a(z) „{{accountName}}” célfiókban.",
"createNewContainerSubHeadingDefault": "Konfigurálja az új tároló tulajdonságait.",
"createContainerButtonLabel": "Új tároló létrehozása",
"createContainerHeading": "Új tároló létrehozása"
},
"preview": {
"jobNameLabel": "Feladat neve",
"subscriptionLabel": "Célelőfizetés",
"accountLabel": "Célfiók",
"sourceDatabaseLabel": "Forrásadatbázis",
"sourceContainerLabel": "Forrástároló",
"targetDatabaseLabel": "Céladatbázis neve: {0}",
"targetContainerLabel": "Céloldali tároló"
},
"assignPermissions": {
"crossAccountDescription": "Ahhoz, hogy adatokat másolhasson a forrásból a céltárolóba, az alábbi lépések végrehajtásával győződjön meg arról, hogy a forrásfiók felügyelt identitása olvasási-írási hozzáféréssel rendelkezik a célfiókhoz.",
"intraAccountOnlineDescription": "Kövesse az alábbi lépéseket az online másolás engedélyezéséhez a(z) „{{accountName}}” fiókjában.",
"crossAccountConfiguration": {
"title": "Fiókközi tárolómásolás",
"description": "Kövesse az alábbi utasítást ahhoz, hogy megadja a(z) „{{sourceAccount}}” helyről a(z) „{{destinationAccount}}” helyre végzett másoláshoz szükséges engedélyeket."
},
"onlineConfiguration": {
"title": "Online tárolómásolás",
"description": "Kövesse az alábbi utasításokat az online másolás engedélyezéséhez a(z) „{{accountName}}” fiókjában."
}
},
"popoverOverlaySpinnerLabel": "Kis türelmet, amíg feldolgozzuk a kérését...",
"addManagedIdentity": {
"title": "Rendszer által hozzárendelt felügyelt identitás engedélyezve.",
"description": "Erőforrásonként csak egy rendszer által hozzárendelt felügyelt identitás adható meg, és ennek életciklusa megegyezik az adott erőforráséval. Az engedélyezés után a felügyelt identitáshoz az Azure szerepköralapú hozzáférés-vezérlési (Azure RBAC) funkciójával rendelhet hozzá engedélyeket. A felügyelt identitást a Microsoft Entra ID hitelesíti, így nem kell hitelesítő adatokat tárolnia a programkódban.",
"descriptionHrefText": "További információ a felügyelt identitásokról.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Rendszer által hozzárendelt felügyelt identitás",
"tooltipContent": "További információ",
"tooltipHrefText": "Felügyelt identitások.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Kiválaszthat egy meglévő felhasználó által hozzárendelt identitást, vagy létrehozhat egy újat.",
"userAssignedIdentityLabel": "Felhasználó által hozzárendelt felügyelt identitást is választhat.",
"createUserAssignedIdentityLink": "Felhasználó által hozzárendelt felügyelt identitás létrehozása",
"enablementTitle": "Rendszer által hozzárendelt felügyelt identitás engedélyezése",
"enablementDescription": "Rendszer által hozzárendelt felügyelt identitás engedélyezése a következőn: {{accountName}}. A megerősítéshez kattintson az „Igen” gombra."
},
"defaultManagedIdentity": {
"title": "Rendszer által hozzárendelt felügyelt identitás alapértelmezettként beállítva.",
"description": "Állítsa be a rendszer által hozzárendelt felügyelt identitást alapértelmezettként a(z) „{{accountName}}” fiókban a bekapcsolásával.",
"tooltipContent": "További információ",
"tooltipHrefText": "Alapértelmezett felügyelt identitások.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Rendszer által hozzárendelt felügyelt identitás alapértelmezettként beállítva",
"popoverDescription": "Rendelje hozzá alapértelmezettként a rendszer által hozzárendelt felügyelt identitást ehhez: „{{accountName}}”. A megerősítéshez kattintson az „Igen” gombra. "
},
"readWritePermissionAssigned": {
"title": "Az alapértelmezett identitáshoz rendelt olvasási-írási engedélyek.",
"description": "Ahhoz, hogy az adatokat a forrásból a céltárolóba másolhassa, adjon olvasási-írási hozzáférést a célfiókhoz a forrásfiók alapértelmezett identitása számára.",
"tooltipContent": "További információ",
"tooltipHrefText": "Olvasási-írási engedélyek.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Olvasási és írási engedélyek hozzárendelése az alapértelmezett identitáshoz.",
"popoverDescription": "Rendeljen olvasási-írási engedélyeket a célfiókhoz a forrásfiók alapértelmezett identitása számára. A megerősítéshez kattintson az „Igen” gombra."
},
"pointInTimeRestore": {
"title": "Időponthoz kötött helyreállítás engedélyezve",
"description": "Az online tárolómásolási feladatok támogatásához frissítse a(z) „{{accessName}}” biztonsági mentési szabályzatát időszakosról folyamatos biztonsági mentésre. A folyamatos biztonsági mentést engedélyezni kell ehhez a funkcióhoz.",
"tooltipContent": "További információ",
"tooltipHrefText": "Folyamatos biztonsági mentés",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Időponthoz kötött helyreállítás engedélyezése"
},
"onlineCopyEnabled": {
"title": "Online másolás engedélyezve",
"description": "Engedélyezze az online tárolómásolást az alábbi gombra kattintva a(z) „{{accountName}}” fiókjában.",
"hrefText": "További információ az online másolási feladatokról",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Online másolás engedélyezése",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Az összes verzió és a törlések változáscsatorna-módjának ellenőrzése",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Az összes verzió és a törlések változáscsatorna-módjának engedélyezése...",
"enablingOnlineCopySpinnerLabel": "Az online másolás engedélyezése a(z) „{{accountName}}” fiókban ..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Dátum és idő",
"name": "Feladat neve",
"status": "Állapot",
"completionPercentage": "Befejezés %",
"duration": "Időtartam",
"error": "Hibaüzenet",
"mode": "Mód",
"actions": "Műveletek"
},
"actions": {
"pause": "Szüneteltetés",
"resume": "Folytatás",
"complete": "Kész",
"viewDetails": "Részletek megtekintése"
},
"status": {
"pending": "Várólistára helyezve",
"inProgress": "Fut",
"running": "Fut",
"partitioning": "Fut",
"paused": "Felfüggesztve",
"completed": "Kész",
"failed": "Hibás",
"faulted": "Hibás",
"skipped": "Megszakítva",
"cancelled": "Megszakítva"
},
"dialog": {
"confirmButtonText": "Megerősítés",
"cancelButtonText": "Mégse"
}
}
}
}
+164 -3
View File
@@ -34,6 +34,8 @@
"browse": "Telusuri",
"increaseValueBy1": "Tambah nilai sebesar 1",
"decreaseValueBy1": "Kurangi nilai sebesar 1",
"on": "Aktif",
"off": "Nonaktif",
"preview": "Pratinjau"
},
"splashScreen": {
@@ -762,7 +764,7 @@
"computedProperties": "Properti Terkomputasi",
"containerPolicies": "Kebijakan Kontainer",
"throughputBuckets": "Wadah Throughput",
"globalSecondaryIndexPreview": "Indeks Sekunder Global (Pratinjau)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Kebijakan Masking"
},
"mongoNotifications": {
@@ -811,8 +813,8 @@
"quotaMaxOption": "Maksimum kuota Anda saat ini adalah {{maximumThroughput}} RU/dtk. Untuk menambah di atas batas ini, Anda harus mengajukan permintaan penambahan kuota dan tim Azure Cosmos DB akan meninjaunya.",
"belowMinimumMessage": "Anda tidak dapat menurunkan throughput di bawah minimum {{minimum}} RU/dtk saat ini. Untuk informasi selengkapnya tentang batas ini, lihat dokumentasi kutipan layanan kami.",
"saveThroughputWarning": "Tagihan Anda akan terpengaruh jika Anda memperbarui pengaturan throughput. Periksa perkiraan biaya yang baru di bawah ini sebelum menyimpan perubahan Anda",
"currentAutoscaleThroughput": "Throughput skala otomatis saat ini:",
"targetAutoscaleThroughput": "Throughput skala otomatis target:",
"currentAutoscaleThroughput": "Throughput penskalaan otomatis saat ini:",
"targetAutoscaleThroughput": "Throughput penskalaan otomatis target:",
"currentManualThroughput": "Throughput manual saat ini:",
"targetManualThroughput": "Throughput manual target:",
"applyDelayedMessage": "Permintaan untuk menambah throughput berhasil dikirimkan. Operasi ini akan memakan waktu 1-3 hari kerja. Tampilkan status terbaru di Pemberitahuan.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "Ukuran byte kuantisasi harus lebih besar dari 0 dan kurang dari atau sama dengan 512",
"indexingSearchListSizeRangeError": "Ukuran daftar pencarian pengindeks harus lebih besar dari atau sama dengan 25 dan kurang dari atau sama dengan 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Umpan balik",
"feedbackButtonAriaLabel": "Berikan umpan balik tentang pekerjaan penyalinan",
"refreshButtonAriaLabel": "Refresh pekerjaan penyalinan",
"createCopyJobButtonLabel": "Buat Pekerjaan Penyalinan",
"createCopyJobButtonAriaLabel": "Buat pekerjaan penyalinan kontainer baru"
},
"noCopyJobs": {
"title": "Tidak ada pekerjaan penyalinan untuk ditampilkan",
"createCopyJobButtonText": "Buat pekerjaan penyalinan kontainer"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Detail Pekerjaan",
"errorTitle": "Detail Kesalahan",
"selectedContainers": "Kontainer yang Dipilih"
},
"createCopyJob": {
"panelTitle": "Buat pekerjaan penyalinan"
},
"selectAccount": {
"description": "Pilih akun tujuan untuk penyalinan.",
"subscriptionDropdownLabel": "Langganan",
"subscriptionDropdownPlaceholder": "Pilih langganan",
"accountDropdownLabel": "Akun",
"accountDropdownPlaceholder": "Pilih akun"
},
"migrationType": {
"offline": {
"title": "Modus offline",
"description": "Pekerjaan penyalinan kontainer offline memungkinkan Anda menyalin data dari kontainer sumber ke kontainer Cosmos DB tujuan untuk API yang didukung. Untuk memastikan integritas data antara sumber dan tujuan, sebaiknya hentikan pembaruan pada kontainer sumber sebelum membuat pekerjaan penyalinan. Pelajari selengkapnya tentang [pekerjaan penyalinan offline](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Mode online",
"description": "Pekerjaan penyalinan kontainer online memungkinkan Anda menyalin data dari kontainer sumber ke kontainer API Cosmos DB NoSQL tujuan menggunakan umpan perubahan [Semua Versi dan Hapus](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Hal ini memungkinkan pembaruan untuk melanjutkan sumber saat data disalin. Waktu henti singkat diperlukan di akhir untuk mengalihkan aplikasi klien ke kontainer tujuan dengan aman. Pelajari selengkapnya tentang [pekerjaan penyalinan online](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Pilih kontainer sumber dan kontainer tujuan yang akan digunakan untuk penyalinan.",
"sourceContainerSubHeading": "Kontainer sumber",
"targetContainerSubHeading": "Kontainer tujuan",
"databaseDropdownLabel": "Database",
"databaseDropdownPlaceholder": "Pilih database",
"containerDropdownLabel": "Kontainer",
"containerDropdownPlaceholder": "Pilih kontainer",
"createNewContainerSubHeading": "Konfigurasikan properti untuk kontainer baru pada akun tujuan \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Konfigurasikan properti untuk kontainer baru.",
"createContainerButtonLabel": "Buat kontainer baru",
"createContainerHeading": "Buat kontainer baru"
},
"preview": {
"jobNameLabel": "Nama pekerjaan",
"subscriptionLabel": "Langganan tujuan",
"accountLabel": "Akun tujuan",
"sourceDatabaseLabel": "Database sumber",
"sourceContainerLabel": "Kontainer sumber",
"targetDatabaseLabel": "Database tujuan",
"targetContainerLabel": "Kontainer tujuan"
},
"assignPermissions": {
"crossAccountDescription": "Untuk menyalin data dari sumber ke kontainer tujuan, pastikan bahwa identitas terkelola akun sumber memiliki akses baca-tulis ke akun tujuan dengan menyelesaikan langkah-langkah berikut.",
"intraAccountOnlineDescription": "Ikuti langkah-langkah di bawah ini untuk mengaktifkan penyalinan online di akun \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Salinan kontainer lintas akun",
"description": "Ikuti instruksi di bawah ini untuk memberikan izin yang diperlukan untuk menyalin data dari \"{{sourceAccount}}\" ke \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Penyalinan kontainer online",
"description": "Ikuti petunjuk di bawah ini untuk mengaktifkan penyalinan online di akun \"{{accountName}}\" Anda."
}
},
"popoverOverlaySpinnerLabel": "Harap tunggu sementara kami memproses permintaan Anda...",
"addManagedIdentity": {
"title": "Identitas terkelola yang ditetapkan sistem diaktifkan.",
"description": "Identitas terkelola yang ditetapkan sistem dibatasi hanya satu identitas per sumber daya dan dikaitkan dengan siklus hidup sumber daya ini. Setelah diaktifkan, Anda dapat memberikan izin ke identitas terkelola menggunakan kontrol akses berbasis peran Azure (Azure RBAC). Identitas terkelola diautentikasi dengan Microsoft Entra ID sehingga Anda tidak perlu menyimpan kredensial apa pun dalam kode.",
"descriptionHrefText": "Pelajari selengkapnya tentang Identitas terkelola.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identitas terkelola yang ditetapkan sistem",
"tooltipContent": "Pelajari selengkapnya tentang",
"tooltipHrefText": "Identitas Terkelola.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Anda dapat memilih identitas yang ditetapkan pengguna yang ada atau membuat identitas baru.",
"userAssignedIdentityLabel": "Anda juga dapat memilih identitas terkelola yang ditetapkan pengguna.",
"createUserAssignedIdentityLink": "Buat Identitas Terkelola yang Ditetapkan Pengguna",
"enablementTitle": "Aktifkan identitas terkelola yang ditetapkan sistem",
"enablementDescription": "Aktifkan identitas terkelola yang ditetapkan sistem pada {{accountName}}. Untuk mengonfirmasi, klik tombol \"Ya\"."
},
"defaultManagedIdentity": {
"title": "Identitas terkelola yang ditetapkan sistem ditetapkan sebagai default.",
"description": "Atur identitas terkelola yang ditetapkan sistem sebagai default untuk \"{{accountName}}\" dengan mengaktifkannya.",
"tooltipContent": "Pelajari selengkapnya tentang",
"tooltipHrefText": "Identitas Terkelola Default.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identitas terkelola yang ditetapkan sistem ditetapkan sebagai default",
"popoverDescription": "Tetapkan identitas terkelola yang ditetapkan sistem sebagai default untuk \"{{accountName}}\". Untuk mengonfirmasi, klik tombol \"Ya\". "
},
"readWritePermissionAssigned": {
"title": "Izin baca-tulis ditetapkan ke identitas default.",
"description": "Untuk mengizinkan penyalinan data dari sumber ke kontainer tujuan, berikan akses baca-tulis pada akun tujuan ke identitas default akun sumber.",
"tooltipContent": "Pelajari selengkapnya tentang",
"tooltipHrefText": "Izin baca-tulis.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Tetapkan izin baca-tulis ke identitas default.",
"popoverDescription": "Tetapkan izin baca-tulis pada akun tujuan ke identitas default akun sumber. Untuk mengonfirmasi, klik tombol \"Ya\"."
},
"pointInTimeRestore": {
"title": "Pemulihan Titik Waktu diaktifkan",
"description": "Untuk memfasilitasi pekerjaan penyalinan kontainer online, perbarui kebijakan pencadangan \"{{accessName}}\" Anda dari berkala menjadi pencadangan berkelanjutan. Pengaktifan pencadangan berkelanjutan diperlukan untuk fungsionalitas ini.",
"tooltipContent": "Pelajari selengkapnya tentang",
"tooltipHrefText": "Pencadangan Berkelanjutan",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Aktifkan Pemulihan Titik Waktu"
},
"onlineCopyEnabled": {
"title": "Penyalinan online diaktifkan",
"description": "Aktifkan penyalinan kontainer online dengan mengeklik tombol di bawah ini pada akun \"{{accountName}}\".",
"hrefText": "Pelajari selengkapnya tentang pekerjaan penyalinan online",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Aktifkan Penyalinan Online",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Memvalidasi Semua versi dan penghapusan mengubah mode umpan (pratinjau)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Mengaktifkan Semua versi dan menghapus mengubah mode umpan (pratinjau)...",
"enablingOnlineCopySpinnerLabel": "Mengaktifkan penyalinan online di akun \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Tanggal dan waktu",
"name": "Nama pekerjaan",
"status": "Status",
"completionPercentage": "% Penyelesaian",
"duration": "Durasi",
"error": "Pesan kesalahan",
"mode": "Mode",
"actions": "Tindakan"
},
"actions": {
"pause": "Jeda",
"resume": "Lanjutkan",
"complete": "Selesai",
"viewDetails": "Tampilkan Detail"
},
"status": {
"pending": "Diantrekan",
"inProgress": "Berjalan",
"running": "Berjalan",
"partitioning": "Berjalan",
"paused": "Dijeda",
"completed": "Selesai",
"failed": "Gagal",
"faulted": "Gagal",
"skipped": "Dibatalkan",
"cancelled": "Dibatalkan"
},
"dialog": {
"confirmButtonText": "Konfirmasikan",
"cancelButtonText": "Batal"
}
}
}
}
+191 -30
View File
@@ -34,6 +34,8 @@
"browse": "Sfoglia",
"increaseValueBy1": "Aumentare il valore di 1",
"decreaseValueBy1": "Diminuisci il valore di 1",
"on": "Attivato",
"off": "Disattivato",
"preview": "Anteprima"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Creare una tabella e interagire con i dati usando l'interfaccia shell di PostgreSQL"
},
"vcoreMongo": {
"title": "Shell Mongo",
"title": "Mongo Shell",
"description": "Creare una raccolta e interagire con i dati usando l'interfaccia shell di MongoDB"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "Elimina {{containerName}}",
"newSqlQuery": "Nuova query SQL",
"newQuery": "Nuova query",
"openMongoShell": "Apri shell Mongo",
"openMongoShell": "Apri Mongo Shell",
"newShell": "Nuova Shell",
"openCassandraShell": "Apri shell Cassandra",
"newStoredProcedure": "Nuova stored procedure",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Aggiornamento griglia documenti non riuscito"
},
"mongoShell": {
"title": "Shell Mongo"
"title": "Mongo Shell"
}
},
"panes": {
@@ -442,7 +444,7 @@
"keyspaceIdLabel": "ID keyspace",
"databaseIdPlaceholder": "Digitare un nuovo ID {{databaseLabel}}",
"databaseTooltip": "Un {{databaseLabel}} è un contenitore logico di una o più {{collectionsLabel}}",
"greaterThanError": "Immettere un valore maggiore di {{minValue}} per la velocità effettiva di Autopilot",
"greaterThanError": "Immetti un valore maggiore di {{minValue}} per la velocità effettiva di Autopilot",
"acknowledgeSpendError": "Confermare la spesa stimata di {{period}}.",
"acknowledgeSpendErrorMonthly": "Confermare la spesa mensile stimata.",
"acknowledgeSpendErrorDaily": "Confermare la spesa giornaliera stimata."
@@ -469,7 +471,7 @@
"sharded": "Partizionato",
"addPartitionKey": "Aggiungi chiave di partizione gerarchica",
"hierarchicalPartitionKeyInfo": "Questa funzionalità consente di partizionare i dati con fino a tre livelli di chiavi per una migliore distribuzione dei dati. Richiede .NET V3, Java V4 SDK o JavaScript V3 SDK in anteprima.",
"provisionDedicatedThroughput": "Eseguire il provisioning della velocità effettiva dedicata per {{collectionName}}",
"provisionDedicatedThroughput": "Esegui il provisioning della velocità effettiva dedicata per {{collectionName}}",
"provisionDedicatedThroughputTooltip": "È possibile effettuare facoltativamente il provisioning di una velocità effettiva dedicata per un {{collectionName}} di un database che ha già una velocità effettiva con provisioning. Questa velocità effettiva dedicata non sarà condivisa con altri {{collectionNamePlural}} nel database e non verrà conteggiata nella velocità effettiva con provisioning per il database. Questa velocità effettiva verrà fatturata in aggiunta a quella con provisioning a livello di database.",
"uniqueKeysPlaceholderMongo": "Percorsi separati da virgole, ad esempio, firstName,address.zipCode",
"uniqueKeysPlaceholderSql": "Percorsi separati da virgole, ad esempio /firstName,/address/zipCode",
@@ -492,7 +494,7 @@
"acknowledgeSpendErrorMonthly": "Confermare la spesa mensile stimata.",
"acknowledgeSpendErrorDaily": "Confermare la spesa giornaliera stimata.",
"unshardedMaxRuError": "Le raccolte non partizionate supportano fino a 10.000 UR",
"acknowledgeShareThroughputError": "Confermare il costo stimato di questa velocità effettiva dedicata.",
"acknowledgeShareThroughputError": "Conferma il costo stimato di questa velocità effettiva dedicata.",
"vectorPolicyError": "Correggere gli errori nei criteri sul vettore contenitore",
"fullTextSearchPolicyError": "Correggere gli errori nei criteri di ricerca full-text del contenitore",
"addingSampleDataSet": "Aggiunta dei set di dati di esempio",
@@ -708,7 +710,7 @@
"tableIdLabel": "Immettere il comando CQL per creare la tabella.",
"enterTableId": "Immetti ID tabella",
"tableSchemaAriaLabel": "Schema della tabella",
"provisionDedicatedThroughput": "Eseguire il provisioning della velocità effettiva dedicata per questa tabella",
"provisionDedicatedThroughput": "Esegui il provisioning della velocità effettiva dedicata per questa tabella",
"provisionDedicatedThroughputTooltip": "È possibile effettuare facoltativamente il provisioning di una capacità effettiva dedicata per una tabella all'interno di un keyspace che ha già una capacità con provisioning. Questa capacità effettiva dedicata non sarà condivisa con altre tabelle nel keyspace e non verrà conteggiata nella capacità con provisioning per il keyspace. Questa capacità effettiva verrà fatturata in aggiunta a quella con provisioning a livello di keyspace."
},
"tables": {
@@ -762,7 +764,7 @@
"computedProperties": "Proprietà calcolate",
"containerPolicies": "Criteri contenitore",
"throughputBuckets": "Bucket di velocità effettiva",
"globalSecondaryIndexPreview": "Indice secondario globale (anteprima)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Criteri di maschera"
},
"mongoNotifications": {
@@ -795,7 +797,7 @@
"perMonth": "/mese"
},
"throughput": {
"manualToAutoscaleDisclaimer": "Il numero massimo di UR/sec con scalabilità automatica iniziale verrà determinato dal sistema, in base alle impostazioni correnti della velocità effettiva manuale e all'archiviazione della risorsa. Dopo aver abilitato la scalabilità automatica, è possibile modificare il numero massimo di UR/sec.",
"manualToAutoscaleDisclaimer": "Il numero massimo di UR/s con scalabilità automatica iniziale verrà determinato dal sistema, in base alle impostazioni correnti della velocità effettiva configurata manualmente e all'archiviazione della risorsa. Dopo aver abilitato la scalabilità automatica, è possibile modificare il numero massimo di UR/s.",
"ttlWarningText": "Il sistema eliminerà automaticamente gli elementi in base al valore TTL (in secondi) specificato, senza la necessità di un'operazione di eliminazione eseguita in modo esplicito da un'applicazione client. Per altre informazioni, vedere",
"ttlWarningLinkText": "Durata (TTL) in Azure Cosmos DB",
"unsavedIndexingPolicy": "criteri di indicizzazione",
@@ -803,24 +805,24 @@
"unsavedComputedProperties": "proprietà calcolate",
"unsavedEditorWarningPrefix": "Non sono state salvate le modifiche più recenti apportate a",
"unsavedEditorWarningSuffix": ". Fare clic su Salva per confermare le modifiche.",
"updateDelayedApplyWarning": "Si sta per richiedere un aumento della velocità effettiva oltre la capacità preallocata. Il completamento dell'operazione potrebbe richiedere alcuni minuti.",
"scalingUpDelayMessage": "L'aumento richiederà 4-6 ore perché supera quello che Azure Cosmos DB può attualmente supportare immediatamente in base al numero di partizioni fisiche. È possibile aumentare immediatamente la velocità effettiva a {{instantMaximumThroughput}} o procedere con questo valore e attendere il completamento dell'aumento.",
"exceedPreAllocatedMessage": "La richiesta di aumento della velocità effettiva supera la capacità preallocata, quindi potrebbe richiedere più tempo del previsto. Per continuare, è possibile scegliere tra tre opzioni:",
"instantScaleOption": "È possibile aumentare immediatamente fino a {{instantMaximumThroughput}} UR/sec.",
"asyncScaleOption": "È possibile aumentare in modo asincrono fino a qualsiasi valore inferiore a {{maximumThroughput}} UR/sec in 4-6 ore.",
"updateDelayedApplyWarning": "Stai per richiedere un aumento della velocità effettiva oltre la capacità preallocata. Il completamento dell'operazione potrebbe richiedere alcuni minuti.",
"scalingUpDelayMessage": "L'aumento richiederà 4-6 ore perché supera quello che Azure Cosmos DB può attualmente supportare in base al numero di partizioni fisiche. È possibile aumentare immediatamente la velocità effettiva a {{instantMaximumThroughput}} o procedere con questo valore e attendere il completamento dell'aumento.",
"exceedPreAllocatedMessage": "La richiesta di aumento della velocità effettiva supera la capacità preallocata, quindi potrebbe richiedere più tempo del previsto. Per continuare, puoi scegliere tra tre opzioni:",
"instantScaleOption": "È possibile aumentare immediatamente fino a {{instantMaximumThroughput}} UR/s.",
"asyncScaleOption": "È possibile aumentare in modo asincrono fino a qualsiasi valore inferiore a {{maximumThroughput}} UR/s in 4-6 ore.",
"quotaMaxOption": "Il valore massimo della quota corrente è {{maximumThroughput}} UR/s. Per superare questo limite, è necessario richiedere un aumento della quota e il team di Azure Cosmos DB esaminerà la richiesta.",
"belowMinimumMessage": "Non è possibile ridurre la velocità effettiva al di sotto del valore minimo corrente di {{minimum}} UR/sec. Per altre informazioni su questo limite, fare riferimento alla documentazione relativa alle quote dei servizi.",
"saveThroughputWarning": "La fattura sarà interessata dall'aggiornamento delle impostazioni della velocità effettiva. Prima di salvare le modifiche, esaminare la stima dei costi aggiornata riportata di seguito",
"belowMinimumMessage": "Non è possibile ridurre la velocità effettiva al di sotto del valore minimo corrente di {{minimum}} UR/s. Per altre informazioni su questo limite, fai riferimento alla documentazione relativa alle quote dei servizi.",
"saveThroughputWarning": "La fattura sarà interessata dall'aggiornamento delle impostazioni della velocità effettiva. Prima di salvare le modifiche, esamina la stima dei costi aggiornata riportata di seguito",
"currentAutoscaleThroughput": "Velocità effettiva con scalabilità automatica corrente:",
"targetAutoscaleThroughput": "Velocità effettiva con scalabilità automatica di destinazione:",
"currentManualThroughput": "Velocità effettiva manuale corrente:",
"targetManualThroughput": "Velocità effettiva manuale di destinazione:",
"applyDelayedMessage": "La richiesta di aumento della velocità effettiva è stata inviata. Il completamento dell'operazione richiederà da 1 a 3 giorni lavorativi. Visualizzare lo stato più recente nelle notifiche.",
"currentManualThroughput": "Velocità effettiva corrente configurata manualmente:",
"targetManualThroughput": "Velocità effettiva di destinazione configurata manualmente:",
"applyDelayedMessage": "La richiesta di aumento della velocità effettiva è stata inviata. Il completamento dell'operazione richiederà da 1 a 3 giorni lavorativi. Visualizza lo stato più recente nelle notifiche.",
"databaseLabel": "Database:",
"containerLabel": "Contenitore:",
"applyShortDelayMessage": "È attualmente in corso una richiesta di aumento della velocità effettiva. Il completamento dell'operazione potrebbe richiedere alcuni minuti.",
"applyLongDelayMessage": "È attualmente in corso una richiesta di aumento della velocità effettiva. Il completamento dell'operazione richiederà da 1 a 3 giorni lavorativi. Visualizzare lo stato più recente nelle notifiche.",
"throughputCapError": "L'account è attualmente configurato con un limite di velocità effettiva totale di {{throughputCap}} UR/sec. Questo aggiornamento non è possibile perché aumenterebbe la velocità effettiva totale fino a {{newTotalThroughput}} UR/sec. Modificare il limite di velocità effettiva totale nella gestione dei costi.",
"applyLongDelayMessage": "È attualmente in corso una richiesta di aumento della velocità effettiva. Il completamento dell'operazione richiederà da 1 a 3 giorni lavorativi. Visualizza lo stato più recente nelle notifiche.",
"throughputCapError": "L'account è attualmente configurato con un limite di velocità effettiva totale di {{throughputCap}} UR/s. Questo aggiornamento non è possibile perché aumenterebbe la velocità effettiva totale fino a {{newTotalThroughput}} UR/s. Modifica il limite di velocità effettiva totale nella gestione dei costi.",
"throughputIncrementError": "Il valore della velocità effettiva deve essere in incrementi di 1000"
},
"conflictResolution": {
@@ -841,9 +843,9 @@
"mongoIndexing": {
"disclaimer": "Per le query che filtrano in base a più proprietà, creare più indici di campi singoli anziché un indice composto.",
"disclaimerCompoundIndexesLink": " Indici composti ",
"disclaimerSuffix": "vengono usati solo per l'ordinamento dei risultati della query. Se è necessario aggiungere un indice composto, è possibile crearne uno usando la shell Mongo.",
"compoundNotSupported": "Le raccolte con indici composti non sono ancora supportate nell'editor di indicizzazione. Per modificare i criteri di indicizzazione per questa raccolta, usare la shell Mongo.",
"aadError": "Per usare l'editor dei criteri di indicizzazione, accedere a",
"disclaimerSuffix": "vengono usati solo per l'ordinamento dei risultati della query. Se è necessario aggiungere un indice composto, è possibile crearne uno usando Mongo Shell.",
"compoundNotSupported": "Le raccolte con indici composti non sono ancora supportate nell'editor di indicizzazione. Per modificare i criteri di indicizzazione per questa raccolta, usa Mongo Shell.",
"aadError": "Per usare l'editor dei criteri di indicizzazione, accedi a",
"aadErrorLink": "portale di Azure.",
"refreshingProgress": "Aggiornamento dello stato di avanzamento della trasformazione dell'indice",
"canMakeMoreChangesZero": "Al termine della trasformazione dell'indice corrente, è possibile apportare altre modifiche all'indicizzazione. ",
@@ -885,10 +887,10 @@
"scale": {
"freeTierInfo": "Con il livello gratuito si otterranno gratuitamente le prime {{ru}} UR/s e {{storage}} GB di spazio di archiviazione in questo account. Per mantenere l'account gratuito, mantenere il totale delle UR/sec in tutte le risorse dell'account al di sotto di {{ru}} UR/sec.",
"freeTierLearnMore": "Altre informazioni.",
"throughputRuS": "Unità elaborate (UR/sec)",
"autoScaleCustomSettings": "L'account ha impostazioni personalizzate che impediscono l'impostazione della velocità effettiva a livello di contenitore. Collaborare con il punto di contatto del team tecnico di Cosmos DB per apportare modifiche.",
"throughputRuS": "Velocità effettiva (UR/s)",
"autoScaleCustomSettings": "L'account ha impostazioni personalizzate che impediscono la configurazione della velocità effettiva a livello di contenitore. Collabora con il punto di contatto del team tecnico di Cosmos DB per apportare modifiche.",
"keyspaceSharedThroughput": "La capacità effettiva condivisa di questa tabella è configurata nel keyspace",
"throughputRangeLabel": "Velocità effettiva ({{min}}-{{max}} UR/s)",
"throughputRangeLabel": "Velocità effettiva ({{min}} - {{max}} UR/s)",
"unlimited": "senza limiti"
},
"partitionKeyEditor": {
@@ -928,7 +930,7 @@
"learnMoreSuffix": "su come definire gli indici secondari globali e su come usarli.",
"jsonAriaLabel": "JSON indice secondario globale",
"addIndex": "Aggiungi indice",
"settingsTitle": "Impostazioni globali dell'indice secondario",
"settingsTitle": "Impostazioni dell'indice secondario globale",
"sourceContainer": "Contenitore di origine",
"indexDefinition": "Definizione di indice secondario globale"
},
@@ -947,7 +949,7 @@
"instant": "Immediata",
"fourToSixHrs": "4-6 ore",
"autoscaleDescription": "In base all'utilizzo, la velocità effettiva {{resourceType}} verrà ridimensionata da",
"freeTierWarning": "La fatturazione verrà applicata se si effettua il provisioning di più di {{ru}} UR/sec di velocità effettiva manuale o se la risorsa viene ridimensionata oltre a {{ru}} UR/sec con scalabilità automatica.",
"freeTierWarning": "La fatturazione verrà applicata se si effettua il provisioning di più di {{ru}} UR/s di velocità effettiva configurata manualmente o se la risorsa viene ridimensionata oltre a {{ru}} UR/s con scalabilità automatica.",
"capacityCalculator": "Stima le UR/sec necessarie con",
"capacityCalculatorLink": " calcolatore capacità",
"fixedStorageNote": "Quando si usa una raccolta con capacità di archiviazione fissa, è possibile impostare fino a 10.000 UR/sec.",
@@ -961,7 +963,7 @@
"active": "Attivo",
"inactive": "Inattivo",
"defaultBucketLabel": "Contenitore di velocità effettiva predefinito",
"defaultBucketPlaceholder": "Selezionare un contenitore di velocità effettiva predefinito",
"defaultBucketPlaceholder": "Seleziona un contenitore di velocità effettiva predefinito",
"defaultBucketTooltip": "Il contenitore di velocità effettiva predefinito viene usato per le operazioni che non specificano un contenitore particolare.",
"defaultBucketTooltipLearnMore": "Altre informazioni.",
"noDefaultBucketSelected": "Nessun contenitore di velocità effettiva predefinito selezionato",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "La dimensione di quantizzazione in byte deve essere maggiore di 0 e minore o uguale a 512",
"indexingSearchListSizeRangeError": "La dimensione dell'elenco di ricerca di indicizzazione deve essere maggiore o uguale a 25 e minore o uguale a 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Feedback",
"feedbackButtonAriaLabel": "Fornisci feedback sui processi di copia",
"refreshButtonAriaLabel": "Aggiorna processi di copia",
"createCopyJobButtonLabel": "Crea processo di copia",
"createCopyJobButtonAriaLabel": "Crea un nuovo processo di copia del contenitore"
},
"noCopyJobs": {
"title": "Nessun processo di copia da visualizzare",
"createCopyJobButtonText": "Crea un processo di copia del contenitore"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Dettagli processo",
"errorTitle": "Dettagli errore",
"selectedContainers": "Contenitori selezionati"
},
"createCopyJob": {
"panelTitle": "Crea processo di copia"
},
"selectAccount": {
"description": "Selezionare un account di destinazione in cui copiare.",
"subscriptionDropdownLabel": "Sottoscrizione",
"subscriptionDropdownPlaceholder": "Selezionare una sottoscrizione",
"accountDropdownLabel": "Account",
"accountDropdownPlaceholder": "Selezionare un account"
},
"migrationType": {
"offline": {
"title": "Modalità offline",
"description": "I processi di copia dei contenitori offline consentono di copiare i dati da un contenitore di origine a un contenitore di destinazione di Cosmos DB per le API supportate. Per garantire l'integrità dei dati tra origine e destinazione, è consigliabile arrestare gli aggiornamenti nel contenitore di origine prima di creare il processo di copia. Altre informazioni sui [processi di copia offline](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Modalità online",
"description": "I processi di copia dei contenitori online consentono di copiare i dati da un contenitore di origine a un contenitore di destinazione dell'API NoSQL di Cosmos DB usando il feed di modifiche [Tutte le versioni ed eliminazioni](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). In questo modo gli aggiornamenti possono continuare nell'origine durante la copia dei dati. Alla fine è necessario un breve periodo di inattività per passare in modo sicuro le applicazioni client al contenitore di destinazione. Altre informazioni sui [processi di copia online](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Selezionare un contenitore di origine e un contenitore di destinazione in cui copiare.",
"sourceContainerSubHeading": "Contenitore di origine",
"targetContainerSubHeading": "Contenitore di destinazione",
"databaseDropdownLabel": "Database",
"databaseDropdownPlaceholder": "Selezionare un database",
"containerDropdownLabel": "Contenitore",
"containerDropdownPlaceholder": "Selezionare un contenitore",
"createNewContainerSubHeading": "Configurare le proprietà del nuovo contenitore nell'account di destinazione \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Configurare le proprietà per il nuovo contenitore.",
"createContainerButtonLabel": "Crea un nuovo contenitore",
"createContainerHeading": "Crea nuovo contenitore"
},
"preview": {
"jobNameLabel": "Nome processo",
"subscriptionLabel": "Sottoscrizione di destinazione",
"accountLabel": "Account di destinazione",
"sourceDatabaseLabel": "Database di origine",
"sourceContainerLabel": "Contenitore di origine",
"targetDatabaseLabel": "Database di destinazione",
"targetContainerLabel": "Contenitore di destinazione"
},
"assignPermissions": {
"crossAccountDescription": "Per copiare i dati dal contenitore di origine a quello di destinazione, assicurarsi che l'identità gestita dell'account di origine disponga dell'accesso in lettura/scrittura all'account di destinazione completando la procedura seguente.",
"intraAccountOnlineDescription": "Seguire questa procedura per abilitare la copia online nell'account \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Copia contenitore tra account",
"description": "Seguire le istruzioni indicate di seguito per concedere le autorizzazioni necessarie a copiare i dati da \"{{sourceAccount}}\" a \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Copia contenitore online",
"description": "Seguire le istruzioni seguenti per abilitare la copia online nell'account \"{{accountName}}\"."
}
},
"popoverOverlaySpinnerLabel": "Attendere il completamento dell'elaborazione della richiesta...",
"addManagedIdentity": {
"title": "Identità gestita assegnata dal sistema abilitata.",
"description": "Un'identità gestita assegnata dal sistema è limitata a una per risorsa ed è legata al ciclo di vita di tale risorsa. Dopo l'abilitazione, è possibile concedere le autorizzazioni all'identità gestita usando il controllo degli accessi in base al ruolo di Azure (RBAC di Azure). L'identità gestita viene autenticata con Microsoft Entra ID, quindi non è necessario archiviare credenziali nel codice.",
"descriptionHrefText": "Altre informazioni sulle identità gestite.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identità gestita assegnata dal sistema",
"tooltipContent": "Altre informazioni",
"tooltipHrefText": "Identità gestite.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "È possibile selezionare un'identità assegnata dall'utente esistente o crearne una nuova.",
"userAssignedIdentityLabel": "È anche possibile selezionare un'identità gestita assegnata dall'utente.",
"createUserAssignedIdentityLink": "Crea identità gestita assegnata dall'utente",
"enablementTitle": "Abilitare l'identità gestita assegnata dal sistema",
"enablementDescription": "Abilitare l'identità gestita assegnata dal sistema nel {{accountName}}. Per confermare, fare clic sul pulsante \"Sì\"."
},
"defaultManagedIdentity": {
"title": "Identità gestita assegnata dal sistema impostata come predefinita.",
"description": "Impostare l'identità gestita assegnata dal sistema come predefinita per \"{{accountName}}\" attivandola.",
"tooltipContent": "Altre informazioni",
"tooltipHrefText": "Identità gestite predefinite.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identità gestita assegnata dal sistema impostata come predefinita",
"popoverDescription": "Assegnare l'identità gestita assegnata dal sistema come predefinita per \"{{accountName}}\". Per confermare, fare clic sul pulsante \"Sì\". "
},
"readWritePermissionAssigned": {
"title": "Autorizzazioni di lettura/scrittura assegnate all'identità predefinita.",
"description": "Per consentire la copia dei dati dal contenitore di origine a quello di destinazione, concedere all'identità predefinita dell'account di origine l'accesso in lettura/scrittura dell'account di destinazione.",
"tooltipContent": "Altre informazioni",
"tooltipHrefText": "Autorizzazioni di lettura/scrittura.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Assegnare autorizzazioni di lettura/scrittura all'identità predefinita.",
"popoverDescription": "Assegnare all'identità predefinita dell'account di origine le autorizzazioni di lettura/scrittura dell'account di destinazione. Per confermare, fare clic sul pulsante \"Sì\"."
},
"pointInTimeRestore": {
"title": "Ripristino temporizzato abilitato",
"description": "Per agevolare i processi di copia online dei contenitori, aggiorna il criterio di backup \"{{accessName}}\" da backup periodico a backup continuo. Per questa funzionalità è necessario abilitare il backup continuo.",
"tooltipContent": "Altre informazioni",
"tooltipHrefText": "Backup continuo",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Abilita ripristino temporizzato"
},
"onlineCopyEnabled": {
"title": "Copia online abilitata",
"description": "Abilitare la copia online del contenitore facendo clic sul pulsante seguente nell'account \"{{accountName}}\".",
"hrefText": "Altre informazioni sui processi di copia online",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Abilita copia online",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Convalida della modalità feed di modifiche per tutte le versioni ed eliminazioni (anteprima)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Abilitazione della modalità feed di modifiche per tutte le versioni ed eliminazioni (anteprima)...",
"enablingOnlineCopySpinnerLabel": "Abilitazione della copia online nell'account \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Data e ora",
"name": "Nome processo",
"status": "Stato",
"completionPercentage": "% completamento",
"duration": "Durata",
"error": "Messaggio di errore",
"mode": "Modalità",
"actions": "Azioni"
},
"actions": {
"pause": "Sospendi",
"resume": "Riprendi",
"complete": "Completamento",
"viewDetails": "Visualizza dettagli"
},
"status": {
"pending": "In coda",
"inProgress": "In esecuzione",
"running": "In esecuzione",
"partitioning": "In esecuzione",
"paused": "In pausa",
"completed": "Completato",
"failed": "Non riuscito",
"faulted": "Non riuscito",
"skipped": "Annullato",
"cancelled": "Annullato"
},
"dialog": {
"confirmButtonText": "Conferma",
"cancelButtonText": "Annulla"
}
}
}
}
+167 -6
View File
@@ -34,6 +34,8 @@
"browse": "参照",
"increaseValueBy1": "値を 1 増加",
"decreaseValueBy1": "値を 1 減少",
"on": "オン",
"off": "オフ",
"preview": "プレビュー"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "PostgreSQL のシェル インターフェイスを使用して、テーブルを作成し、データを操作します"
},
"vcoreMongo": {
"title": "Mongo シェル",
"title": "Mongo Shell",
"description": "MongoDB のシェル インターフェイスを使用して、コレクションを作成し、データを操作します"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "{{containerName}} の削除",
"newSqlQuery": "新しい SQL クエリ",
"newQuery": "新しいクエリ",
"openMongoShell": "Mongo シェルを開く",
"openMongoShell": "Mongo Shell を開く",
"newShell": "新しいシェル",
"openCassandraShell": "Cassandra シェルを開く",
"newStoredProcedure": "新しいストアド プロシージャ",
@@ -414,7 +416,7 @@
"refreshGridFailed": "ドキュメント グリッドの更新に失敗しました"
},
"mongoShell": {
"title": "Mongo シェル"
"title": "Mongo Shell"
}
},
"panes": {
@@ -762,7 +764,7 @@
"computedProperties": "計算されたプロパティ",
"containerPolicies": "コンテナー ポリシー",
"throughputBuckets": "スループット バケット",
"globalSecondaryIndexPreview": "グローバル セカンダリ インデックス (プレビュー)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "マスキング ポリシー"
},
"mongoNotifications": {
@@ -841,8 +843,8 @@
"mongoIndexing": {
"disclaimer": "複数のプロパティでフィルター処理するクエリの場合は、複合インデックスではなく、複数の単一フィールド インデックスを作成してください。",
"disclaimerCompoundIndexesLink": " 複合インデックス ",
"disclaimerSuffix": "は、クエリ結果の並べ替えにのみ使用されます。複合インデックスを追加する必要がある場合は、Mongo シェルを使用して作成できます。",
"compoundNotSupported": "複合インデックスを持つコレクションは、インデックス作成エディターではまだサポートされていません。このコレクションのインデックス作成ポリシーを変更するには、Mongo シェルを使用してください。",
"disclaimerSuffix": "は、クエリ結果の並べ替えにのみ使用されます。複合インデックスを追加する必要がある場合は、Mongo Shell を使用して作成できます。",
"compoundNotSupported": "複合インデックスを持つコレクションは、インデックス作成エディターではまだサポートされていません。このコレクションのインデックス作成ポリシーを変更するには、Mongo Shell を使用してください。",
"aadError": "インデックス作成ポリシー エディターを使用するには、次にログインしてください:",
"aadErrorLink": "Azure portal。",
"refreshingProgress": "インデックス変換の進行状況を更新しています",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "量子化バイト サイズは 0 より大きく、512 以下である必要があります",
"indexingSearchListSizeRangeError": "インデックス検索リスト サイズは 25 以上 500 以下である必要があります"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "フィードバック",
"feedbackButtonAriaLabel": "コピー ジョブに関するフィードバックを提供する",
"refreshButtonAriaLabel": "コピー ジョブの更新",
"createCopyJobButtonLabel": "コピー ジョブの作成",
"createCopyJobButtonAriaLabel": "新しいコンテナー コピー ジョブの作成"
},
"noCopyJobs": {
"title": "表示するコピー ジョブはありません",
"createCopyJobButtonText": "コンテナー コピー ジョブの作成"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "ジョブの詳細",
"errorTitle": "エラーの詳細",
"selectedContainers": "選択したコンテナー"
},
"createCopyJob": {
"panelTitle": "コピー ジョブの作成"
},
"selectAccount": {
"description": "コピー先のアカウントを選択してください。",
"subscriptionDropdownLabel": "サブスクリプション",
"subscriptionDropdownPlaceholder": "サブスクリプションを選択します",
"accountDropdownLabel": "アカウント",
"accountDropdownPlaceholder": "アカウントの選択"
},
"migrationType": {
"offline": {
"title": "オフライン モード",
"description": "オフライン コンテナー コピー ジョブでは、サポートされている API のソース コンテナーからコピー先の Cosmos DB コンテナーにデータをコピーできます。ソースとコピー先の間でデータ整合性を確保するため、コピー ジョブを作成する前にソース コンテナーでの更新を停止することをお勧めします。[オフライン コピー ジョブ](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql) の詳細をご覧ください。"
},
"online": {
"title": "オンライン モード",
"description": "オンライン コンテナー コピー ジョブでは、[All Versions and Delete](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) 変更フィードを使用して、ソース コンテナーからコピー先の Cosmos DB NoSQL API コンテナーにデータをコピーできます。これにより、データのコピー中もソースで更新を継続できます。クライアント アプリケーションをコピー先コンテナーに安全に切り替えるには、最後に短時間のダウンタイムが必要です。[online copy jobs](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started) についての詳細をご覧ください。"
}
},
"selectContainers": {
"description": "コピー元のコンテナーとコピー先のコンテナーを選択してください。",
"sourceContainerSubHeading": "ソース コンテナー",
"targetContainerSubHeading": "コピー先のコンテナー",
"databaseDropdownLabel": "データベース",
"databaseDropdownPlaceholder": "データベースの選択",
"containerDropdownLabel": "コンテナー",
"containerDropdownPlaceholder": "コンテナーの選択",
"createNewContainerSubHeading": "コピー先のアカウント \"{{accountName}}\" の新しいコンテナーのプロパティを構成します。",
"createNewContainerSubHeadingDefault": "新しいコンテナーのプロパティを構成します。",
"createContainerButtonLabel": "新しいコンテナーを作成する",
"createContainerHeading": "新しいコンテナーの作成"
},
"preview": {
"jobNameLabel": "ジョブ名",
"subscriptionLabel": "変換先サービス",
"accountLabel": "コピー先のアカウント",
"sourceDatabaseLabel": "ソース データベース",
"sourceContainerLabel": "ソース コンテナー",
"targetDatabaseLabel": "転送先データベース:",
"targetContainerLabel": "コピー先のコンテナー"
},
"assignPermissions": {
"crossAccountDescription": "ソースからコピー先のコンテナーにデータをコピーするには、次の手順を実行して、ソース アカウントのマネージド ID にコピー先のアカウントへの読み取り/書き込みアクセス許可があることを確認します。",
"intraAccountOnlineDescription": "\"{{accountName}}\" アカウントでオンライン コピーを有効にするには、次の手順に従ってください。",
"crossAccountConfiguration": {
"title": "アカウント間でのコンテナーのコピー",
"description": "\"{{sourceAccount}}\" から \"{{destinationAccount}}\" にデータをコピーするために必要なアクセス許可を付与するには、以下の手順に従ってください。"
},
"onlineConfiguration": {
"title": "オンライン コンテナー コピー",
"description": "\"{{accountName}}\" アカウントでオンライン コピーを有効にするには、以下の手順に従ってください。"
}
},
"popoverOverlaySpinnerLabel": "要求を処理しています。しばらくお待ちください...",
"addManagedIdentity": {
"title": "システム割り当てマネージド ID が有効化されました。",
"description": "システム割り当てマネージド ID は 1 つのリソースにつき 1 つに限定されており、このリソースのライフサイクルに関連付けられています。有効にすると、Azure ロールベースのアクセス制御 (Azure RBAC) を使用して、マネージド ID にアクセス許可を付与できます。マネージド ID は Microsoft Entra ID で認証されるため、コード内に資格情報を格納する必要はありません。",
"descriptionHrefText": "マネージド ID に関する詳細情報。",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "システム割り当てマネージド ID",
"tooltipContent": "次に関する詳細をご確認ください:",
"tooltipHrefText": "マネージド ID。",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "既存のユーザー割り当てマネージド ID を選択するか、新しい ID を作成できます。",
"userAssignedIdentityLabel": "ユーザー割り当てマネージド ID を選択することもできます。",
"createUserAssignedIdentityLink": "ユーザー割り当てマネージド ID の作成",
"enablementTitle": "システム割り当てマネージド ID の有効化",
"enablementDescription": "{{accountName}} でシステム割り当てマネージド ID を有効にします。確認するには、[はい] ボタンをクリックします。"
},
"defaultManagedIdentity": {
"title": "システム割り当てマネージド ID は既定として設定されます。",
"description": "オンに切り替えて、システム割り当てマネージド ID を \"{{accountName}}\" の既定として設定します。",
"tooltipContent": "次に関する詳細をご確認ください:",
"tooltipHrefText": "既定のマネージド ID。",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "システム割り当てマネージド ID を既定として設定する",
"popoverDescription": "システム割り当てマネージド ID を \"{{accountName}}\" の既定として割り当てます。確認するには、[はい] ボタンをクリックします。"
},
"readWritePermissionAssigned": {
"title": "既定の ID に割り当てられた読み取り/書き込みアクセス許可。",
"description": "ソースからコピー先のコンテナーへのデータのコピーを許可するには、コピー先のアカウントでソース アカウントの既定の ID に読み取り/書き込みアクセス許可を付与します。",
"tooltipContent": "次に関する詳細をご確認ください:",
"tooltipHrefText": "読み取り/書き込みアクセス許可。",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "既定の ID に読み取り/書き込みアクセス許可を割り当てます。",
"popoverDescription": "コピー先アカウントの読み取り/書き込みアクセス許可を、ソース アカウントの既定の ID に割り当てます。確認するには、[はい] ボタンをクリックします。"
},
"pointInTimeRestore": {
"title": "ポイントインタイム リストアが有効です",
"description": "オンライン コンテナー コピー ジョブを有効にするには、\"{{accessName}}\" バックアップ ポリシーを定期バックアップから継続的バックアップに更新してください。この機能を使用するには、継続的バックアップを有効にする必要があります。",
"tooltipContent": "次に関する詳細をご確認ください:",
"tooltipHrefText": "継続的バックアップ",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "ポイントインタイム リストアを有効にする"
},
"onlineCopyEnabled": {
"title": "オンライン コピーが有効",
"description": "\"{{accountName}}\" アカウントで下のボタンをクリックして、オンライン コンテナー コピーを有効にします。",
"hrefText": "オンライン コピー ジョブの詳細情報",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "オンライン コピーを有効にする",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "すべてのバージョンを検証し、変更フィード モードを削除します (プレビュー)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "すべてのバージョンおよび削除の変更フィード モード (プレビュー) を有効にしています",
"enablingOnlineCopySpinnerLabel": "\"{{accountName}}\" アカウントでオンライン コピーを有効にしています..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "日付と時刻",
"name": "ジョブ名",
"status": "状態",
"completionPercentage": "完了率",
"duration": "期間",
"error": "エラー メッセージ",
"mode": "モード",
"actions": "アクション"
},
"actions": {
"pause": "一時停止",
"resume": "再開",
"complete": "完了",
"viewDetails": "詳細の表示"
},
"status": {
"pending": "キューに登録済み",
"inProgress": "実行中",
"running": "実行中",
"partitioning": "実行中",
"paused": "一時停止",
"completed": "完了済み",
"failed": "失敗",
"faulted": "失敗",
"skipped": "取り消し済み",
"cancelled": "取り消し済み"
},
"dialog": {
"confirmButtonText": "確認",
"cancelButtonText": "キャンセル"
}
}
}
}
+166 -5
View File
@@ -34,6 +34,8 @@
"browse": "찾아보기",
"increaseValueBy1": "값을 1만큼 늘리기",
"decreaseValueBy1": "값을 1만큼 줄이기",
"on": "켜기",
"off": "끄기",
"preview": "미리 보기"
},
"splashScreen": {
@@ -619,7 +621,7 @@
"accountId": "계정 ID",
"sessionId": "세션 ID",
"popupsDisabledError": "브라우저에서 팝업이 차단되어 이 계정에 대한 권한 부여를 설정할 수 없습니다.\n이 사이트에 대해 팝업을 허용한 후 \"Entra ID에 로그인\" 버튼을 클릭하세요.",
"failedToAcquireTokenError": "권한 부여 토큰을 자동으로 가져오지 못했습니다. Entra ID RBAC 작업을 사용하려면 \"Entra ID 로그인\" 버튼을 클릭하세요."
"failedToAcquireTokenError": "인증 토큰을 자동으로 가져오지 못했습니다. Entra ID RBAC 작업을 활성화하려면 \"Entra ID 로그인\" 버튼을 클릭하세요."
},
"saveQuery": {
"panelTitle": "쿼리 저장",
@@ -762,7 +764,7 @@
"computedProperties": "계산된 속성",
"containerPolicies": "컨테이너 정책",
"throughputBuckets": "처리량 버킷",
"globalSecondaryIndexPreview": "전역 보조 인덱스(미리 보기)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "마스킹 정책"
},
"mongoNotifications": {
@@ -807,7 +809,7 @@
"scalingUpDelayMessage": "실제 파티션 수에 따라 현재 Azure Cosmos DB가 즉시 지원할 수 있는 한도를 초과하므로 확장에는 4~6시간이 소요됩니다. 처리량을 {{instantMaximumThroughput}}(으)로 즉시 늘리거나, 이 값을 유지하며 확장이 완료될 때까지 기다릴 수 있습니다.",
"exceedPreAllocatedMessage": "처리량 증가 요청이 미리 할당된 용량을 초과하여 예상보다 오래 걸릴 수 있습니다. 계속하기 위해 선택할 수 있는 세 가지 옵션이 있습니다.",
"instantScaleOption": "{{instantMaximumThroughput}}RU/s까지 즉시 확장할 수 있습니다.",
"asyncScaleOption": "4~6시간 내에 {{maximumThroughput}}RU/s 이하의 값으로 비동기적으로 확장할 수 있습니다.",
"asyncScaleOption": "4~6시간 내에 {{maximumThroughput}}RU/s 미만의 모든 값으로 비동기적으로 확장할 수 있습니다.",
"quotaMaxOption": "현재 할당량 최댓값은 {{maximumThroughput}}RU/s입니다. 이 한도를 초과하려면 할당량 증가를 요청해야 하며, Azure Cosmos DB 팀에서 검토합니다.",
"belowMinimumMessage": "처리량을 현재 최소 {{minimum}}RU/s 미만으로 낮출 수 없습니다. 이 한도에 대한 자세한 내용은 서비스 견적 문서를 참고하세요.",
"saveThroughputWarning": "처리량 설정을 변경하면 청구에 영향이 있습니다. 변경 내용을 저장하기 전에 아래에서 업데이트된 예상 비용을 검토하세요.",
@@ -841,8 +843,8 @@
"mongoIndexing": {
"disclaimer": "여러 속성을 필터링하는 쿼리의 경우 복합 인덱스 대신 여러 개의 단일 필드 인덱스를 만드세요.",
"disclaimerCompoundIndexesLink": " 복합 인덱스 ",
"disclaimerSuffix": "쿼리 결과를 정렬하는 데만 사용됩니다. 복합 인덱스 추가가 필요한 경우 Mongo 셸을 사용하여 만들 수 있습니다.",
"compoundNotSupported": "복합 인덱스가 있는 컬렉션은 인덱싱 편집기에서 아직 지원되지 않습니다. 이 컬렉션의 인덱싱 정책을 수정하려면 Mongo 을 사용하세요.",
"disclaimerSuffix": "쿼리 결과를 정렬하는 데만 사용됩니다. 복합 인덱스 추가해야 하는 경우 MongoDB 셸을 사용하여 생성할 수 있습니다.",
"compoundNotSupported": "복합 인덱스가 있는 컬렉션은 아직 인덱싱 편집기에서 지원되지 않습니다. 이 컬렉션의 인덱싱 정책을 수정하려면 Mongo Shell을 사용하세요.",
"aadError": "인덱싱 정책 편집기를 사용하려면 다음에 로그인하세요.",
"aadErrorLink": "Azure Portal.",
"refreshingProgress": "인덱스 변환 진행률 새로 고침",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "양자화 바이트 크기는 0보다 크고 512 이하이어야 합니다.",
"indexingSearchListSizeRangeError": "인덱싱 검색 목록 크기는 25보다 크거나 같고 500보다 작거나 같아야 합니다."
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "피드백",
"feedbackButtonAriaLabel": "복사 작업에 대한 의견 보내기",
"refreshButtonAriaLabel": "복사 작업 새로 고침",
"createCopyJobButtonLabel": "복사 작업 만들기",
"createCopyJobButtonAriaLabel": "새 컨테이너 복사 작업 만들기"
},
"noCopyJobs": {
"title": "표시할 복사 작업이 없음",
"createCopyJobButtonText": "컨테이너 복사 작업 만들기"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "작업 정보",
"errorTitle": "오류 세부 정보",
"selectedContainers": "선택된 컨테이너"
},
"createCopyJob": {
"panelTitle": "복사 작업 만들기"
},
"selectAccount": {
"description": "복사할 대상 계정을 선택하세요.",
"subscriptionDropdownLabel": "구독",
"subscriptionDropdownPlaceholder": "구독 선택",
"accountDropdownLabel": "계정",
"accountDropdownPlaceholder": "계정 선택"
},
"migrationType": {
"offline": {
"title": "오프라인 모드",
"description": "오프라인 컨테이너 복사 작업을 사용하면 지원되는 API에 대해 원본 컨테이너의 데이터를 대상 Cosmos DB 컨테이너로 복사할 수 있습니다. 원본과 대상 간의 데이터 무결성을 보장하려면 복사 작업을 만들기 전에 원본 컨테이너에서 업데이트를 중지하는 것이 좋습니다. [오프라인 복사 작업](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)에서 자세히 알아보세요."
},
"online": {
"title": "온라인 모드",
"description": "온라인 컨테이너 복사 작업을 사용하면 [모든 버전 및 삭제](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) 변경 피드를 통해 원본 컨테이너의 데이터를 대상 Cosmos DB NoSQL API 컨테이너로 복사할 수 있습니다. 이렇게 하면 데이터가 복사되는 동안에도 원본에서 업데이트를 계속할 수 있습니다. 클라이언트 애플리케이션을 대상 컨테이너로 안전하게 전환하려면 마지막에 잠시 가동 중지 시간이 필요합니다. [온라인 복사 작업](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)에서 자세히 알아보세요."
}
},
"selectContainers": {
"description": "복사할 원본 컨테이너와 대상 컨테이너를 선택하세요.",
"sourceContainerSubHeading": "원본 컨테이너",
"targetContainerSubHeading": "대상 컨테이너",
"databaseDropdownLabel": "데이터베이스",
"databaseDropdownPlaceholder": "데이터베이스 선택",
"containerDropdownLabel": "컨테이너",
"containerDropdownPlaceholder": "컨테이너 선택",
"createNewContainerSubHeading": "대상 계정 '{{accountName}}'의 새 컨테이너의 속성을 구성합니다.",
"createNewContainerSubHeadingDefault": "새 컨테이너의 속성을 구성합니다.",
"createContainerButtonLabel": "새 컨테이너 만들기",
"createContainerHeading": "새 컨테이너 만들기"
},
"preview": {
"jobNameLabel": "작업 이름",
"subscriptionLabel": "대상 구독",
"accountLabel": "대상 계정",
"sourceDatabaseLabel": "원본 데이터베이스",
"sourceContainerLabel": "원본 컨테이너",
"targetDatabaseLabel": "대상 데이터베이스",
"targetContainerLabel": "대상 컨테이너"
},
"assignPermissions": {
"crossAccountDescription": "원본에서 대상 컨테이너로 데이터를 복사하려면 다음 단계를 완료하여 원본 계정의 관리 ID가 대상 계정에 대한 읽기-쓰기 액세스 권한을 갖도록 구성하세요.",
"intraAccountOnlineDescription": "'{{accountName}}' 계정에서 온라인 복사를 사용하도록 설정하려면 아래 단계를 따르세요.",
"crossAccountConfiguration": {
"title": "계정 간 컨테이너 복사",
"description": "아래 지침에 따라 '{{sourceAccount}}'에서 '{{destinationAccount}}'(으)로 데이터를 복사하는 데 필요한 권한을 부여하세요."
},
"onlineConfiguration": {
"title": "온라인 컨테이너 복사",
"description": "'{{accountName}}' 계정에서 온라인 복사를 사용하도록 설정하려면 아래 지침을 따르세요."
}
},
"popoverOverlaySpinnerLabel": "요청을 처리하는 동안 잠시 기다려 주세요...",
"addManagedIdentity": {
"title": "시스템 할당 관리 ID를 사용하도록 설정했습니다.",
"description": "시스템 할당 관리 ID는 리소스당 하나로 제한되며 이 리소스의 수명 주기에 연결됩니다. 사용하도록 설정하면 Azure RBAC(Azure 역할 기반 액세스 제어)를 사용하여 관리 ID에 권한을 부여할 수 있습니다. 관리 ID는 Microsoft Entra ID로 인증되므로 코드에 자격 증명을 저장할 필요가 없습니다.",
"descriptionHrefText": "관리 ID에 대해 자세히 알아보세요.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "시스템이 할당한 관리 ID",
"tooltipContent": "자세한 정보",
"tooltipHrefText": "관리 ID입니다.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "기존 사용자 할당 ID를 선택하거나 새 ID를 만들 수 있습니다.",
"userAssignedIdentityLabel": "사용자가 할당한 관리 ID를 선택할 수도 있습니다.",
"createUserAssignedIdentityLink": "사용자가 할당한 관리 ID 만들기",
"enablementTitle": "시스템이 할당한 관리 ID 사용",
"enablementDescription": "{{accountName}}에 시스템이 할당한 관리 ID를 사용하도록 설정합니다. 확인하려면 '예' 단추를 클릭하세요."
},
"defaultManagedIdentity": {
"title": "기본값으로 설정된 시스템 할당 관리 ID입니다.",
"description": "시스템이 할당한 관리 ID를 켜서 '{{accountName}}'의 기본값으로 설정합니다.",
"tooltipContent": "자세한 정보",
"tooltipHrefText": "기본 관리 ID입니다.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "기본값으로 설정된 시스템 할당 관리 ID",
"popoverDescription": "시스템이 할당한 관리 ID를 '{{accountName}}'의 기본값으로 설정합니다. 확인하려면 '예' 단추를 클릭하세요. "
},
"readWritePermissionAssigned": {
"title": "기본 ID에 할당된 읽기-쓰기 권한입니다.",
"description": "원본에서 대상 컨테이너로 데이터 복사를 허용하려면 원본 계정의 기본 ID에 대상 계정의 읽기-쓰기 액세스 권한을 제공하세요.",
"tooltipContent": "자세한 정보",
"tooltipHrefText": "읽기-쓰기 권한입니다.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "기본 ID에 읽기-쓰기 권한을 할당합니다.",
"popoverDescription": "대상 계정의 읽기-쓰기 권한을 원본 계정의 기본 ID에 할당합니다. 확인하려면 '예' 단추를 클릭하세요."
},
"pointInTimeRestore": {
"title": "지정 시간 복원 사용 설정됨",
"description": "온라인 컨테이너 복사 작업을 더 간편하게 수행하려면 '{{accessName}}' 백업 정책을 주기적 백업에서 지속적인 백업으로 업데이트하세요. 이 기능을 사용하려면 지속적인 백업을 사용하도록 설정해야 합니다.",
"tooltipContent": "자세한 정보",
"tooltipHrefText": "지속적인 백업",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "지정 시간 복원 사용"
},
"onlineCopyEnabled": {
"title": "온라인 복사 사용 설정됨",
"description": "'{{accountName}}' 계정에서 아래 단추를 클릭하여 온라인 컨테이너 복사를 사용하도록 설정하세요.",
"hrefText": "온라인 복사 작업에 대해 자세히 알아보기",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "온라인 복사 사용",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "모든 버전 및 삭제 변경 피드 모드(미리 보기) 유효성 검사 중...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "모든 버전 및 삭제 변경 피드 모드(미리 보기) 사용 설정 중...",
"enablingOnlineCopySpinnerLabel": "'{{accountName}}' 계정에서 온라인 복사를 사용하도록 설정하는 중..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "날짜 및 시간",
"name": "작업 이름",
"status": "상태",
"completionPercentage": "완료율",
"duration": "기간",
"error": "오류 메시지",
"mode": "모드",
"actions": "작업"
},
"actions": {
"pause": "일시 중지",
"resume": "다시 시작",
"complete": "완료",
"viewDetails": "세부 정보 보기"
},
"status": {
"pending": "큐에 대기됨",
"inProgress": "실행하는 중",
"running": "실행하는 중",
"partitioning": "실행하는 중",
"paused": "일시 중지됨",
"completed": "완료",
"failed": "실패",
"faulted": "실패",
"skipped": "취소됨",
"cancelled": "취소됨"
},
"dialog": {
"confirmButtonText": "확인",
"cancelButtonText": "취소"
}
}
}
}
+172 -11
View File
@@ -34,6 +34,8 @@
"browse": "Bladeren",
"increaseValueBy1": "Waarde verhogen met 1",
"decreaseValueBy1": "Waarde verlagen met 1",
"on": "Aan",
"off": "Uit",
"preview": "Voorbeeld"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Een tabel maken en communiceren met gegevens met de shell-interface van PostgreSQL"
},
"vcoreMongo": {
"title": "Mongo-shell",
"title": "Mongo Shell",
"description": "Een verzameling maken en communiceren met gegevens met behulp van de shell-interface van MongoDB"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "{{containerName}} verwijderen",
"newSqlQuery": "Nieuwe SQL-query",
"newQuery": "Nieuwe query",
"openMongoShell": "Mongo-shell openen",
"openMongoShell": "Mongo Shell openen",
"newShell": "Nieuwe shell",
"openCassandraShell": "Cassandra-shell openen",
"newStoredProcedure": "Nieuwe opgeslagen procedure",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Kan het raster met documenten niet vernieuwen"
},
"mongoShell": {
"title": "Mongo-shell"
"title": "Mongo Shell"
}
},
"panes": {
@@ -469,8 +471,8 @@
"sharded": "Shard",
"addPartitionKey": "Hiërarchische partitiesleutel toevoegen",
"hierarchicalPartitionKeyInfo": "Met deze functie kunt u uw gegevens partitioneren met maximaal drie niveaus aan sleutels voor een betere distributie. Hiervoor is .NET V3, Java V4 SDK of preview javaScript V3 SDK vereist.",
"provisionDedicatedThroughput": "Toegewezen doorvoer inrichten voor deze {{collectionName}}",
"provisionDedicatedThroughputTooltip": "U kunt optioneel toegewezen doorvoer inrichten voor een {{collectionName}} binnen een database waarvoor doorvoer is ingericht. Dit toegewezen doorvoeraantal wordt niet gedeeld met andere {{collectionNamePlural}} in de database en telt niet mee voor de doorvoer die u voor de database hebt ingericht. Dit doorvoeraantal wordt in rekening gebracht naast het doorvoeraantal die u hebt ingericht op databaseniveau.",
"provisionDedicatedThroughput": "Speciale doorvoer inrichten voor deze {{collectionName}}",
"provisionDedicatedThroughputTooltip": "U kunt optioneel speciale doorvoer inrichten voor een {{collectionName}} binnen een database waarvoor doorvoer is ingericht. Dit speciale-doorvoeraantal wordt niet gedeeld met andere {{collectionNamePlural}} in de database en telt niet mee voor de doorvoer die u voor de database hebt ingericht. Dit doorvoeraantal wordt in rekening gebracht naast het doorvoeraantal die u hebt ingericht op databaseniveau.",
"uniqueKeysPlaceholderMongo": "Door komma's gescheiden paden, bijvoorbeeld firstName,address.zipCode",
"uniqueKeysPlaceholderSql": "Door komma's gescheiden paden, bijvoorbeeld /firstName,/address/zipCode",
"addUniqueKey": "Unieke sleutel toevoegen",
@@ -492,7 +494,7 @@
"acknowledgeSpendErrorMonthly": "Bevestig de geschatte maandelijkse uitgaven.",
"acknowledgeSpendErrorDaily": "Bevestig de geschatte dagelijkse uitgaven.",
"unshardedMaxRuError": "Geen shard verzamelingen ondersteunen maximaal 10.000 RU's",
"acknowledgeShareThroughputError": "Bevestig de geschatte kosten van deze toegewezen doorvoer.",
"acknowledgeShareThroughputError": "Bevestig de kostenraming van deze speciale doorvoer.",
"vectorPolicyError": "Los fouten op in het containervectorbeleid",
"fullTextSearchPolicyError": "Los fouten op in het zoekbeleid voor volledige tekst van container",
"addingSampleDataSet": "Voorbeeldgegevensset toevoegen",
@@ -708,8 +710,8 @@
"tableIdLabel": "Voer de CQL-opdracht in om de tabel te maken.",
"enterTableId": "Tabel-id invoeren",
"tableSchemaAriaLabel": "Tabelschema",
"provisionDedicatedThroughput": "Toegewezen doorvoer inrichten voor deze tabel",
"provisionDedicatedThroughputTooltip": "U kunt optioneel toegewijde doorvoer instellen voor een tabel binnen een keyspace waarvoor doorvoer is ingesteld. Dit toegewezen doorvoeraantal wordt niet gedeeld met andere tabellen in de keyspace en telt niet mee voor de doorvoer die u hebt ingericht voor de keyspace. Dit doorvoeraantal wordt in rekening gebracht naast het doorvoeraantal die u hebt ingericht op het niveau van de keyspace."
"provisionDedicatedThroughput": "Speciale doorvoer inrichten voor deze tabel",
"provisionDedicatedThroughputTooltip": "U kunt optioneel speciale doorvoer instellen voor een tabel binnen een keyspace waarvoor doorvoer is ingesteld. Dit toegewezen doorvoeraantal wordt niet gedeeld met andere tabellen in de keyspace en telt niet mee voor de doorvoer die u hebt ingericht voor de keyspace. Dit doorvoeraantal wordt in rekening gebracht naast het doorvoeraantal die u hebt ingericht op het niveau van de keyspace."
},
"tables": {
"addProperty": "Eigenschap toevoegen",
@@ -762,7 +764,7 @@
"computedProperties": "Berekende eigenschappen",
"containerPolicies": "Containerbeleidsregels",
"throughputBuckets": "Doorvoerbuckets",
"globalSecondaryIndexPreview": "Globale secundaire index (preview)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Beleid voor maskering"
},
"mongoNotifications": {
@@ -811,8 +813,8 @@
"quotaMaxOption": "Uw huidige quotumlimiet is {{maximumThroughput}} RU/s. Om deze limiet te overschrijden, moet u een quotumverhoging aanvragen; het Azure Cosmos DB-team zal dit beoordelen.",
"belowMinimumMessage": "U kunt de doorvoer niet verlagen tot onder uw huidige minimum van {{minimum}} RU/s. Raadpleeg de documentatie bij de serviceofferte voor meer informatie over deze limiet.",
"saveThroughputWarning": "Uw factuur wordt beïnvloed wanneer u uw doorvoerinstellingen bijwerkt. Controleer de bijgewerkte kostenraming hieronder voordat u uw wijzigingen opslaat",
"currentAutoscaleThroughput": "Huidige doorvoer voor automatische schaalaanpassing:",
"targetAutoscaleThroughput": "Doeldoorvoer voor automatische schaalaanpassing:",
"currentAutoscaleThroughput": "Huidig automatisch schalen van doorvoer:",
"targetAutoscaleThroughput": "Automatisch schalen van doorvoer van doel:",
"currentManualThroughput": "Actuele handmatige doorvoer:",
"targetManualThroughput": "Handmatige doorvoer van doel:",
"applyDelayedMessage": "De aanvraag om de doorvoer te verhogen is ingediend. Deze bewerking duurt 1-3 werkdagen om te voltooien. Bekijk de meest recente status in Meldingen.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "De grootte van de kwantisatiebyte moet groter zijn dan 0 en kleiner dan of gelijk zijn aan 512",
"indexingSearchListSizeRangeError": "De grootte van de zoeklijst voor indexering moet groter zijn dan of gelijk zijn aan 25 en kleiner dan of gelijk zijn aan 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Feedback",
"feedbackButtonAriaLabel": "Feedback geven over kopieertaken",
"refreshButtonAriaLabel": "Kopieertaken vernieuwen",
"createCopyJobButtonLabel": "Kopieertaak maken",
"createCopyJobButtonAriaLabel": "Een nieuwe containerkopietaak maken"
},
"noCopyJobs": {
"title": "Geen kopieertaken om weer te geven",
"createCopyJobButtonText": "Een containerkopietaak maken"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Taakdetails",
"errorTitle": "Foutdetails",
"selectedContainers": "Geselecteerde containers"
},
"createCopyJob": {
"panelTitle": "Kopieertaak maken"
},
"selectAccount": {
"description": "Selecteer een doelaccount waarnaar u wilt kopiëren.",
"subscriptionDropdownLabel": "Abonnement",
"subscriptionDropdownPlaceholder": "Een abonnement selecteren",
"accountDropdownLabel": "Account",
"accountDropdownPlaceholder": "Een account selecteren"
},
"migrationType": {
"offline": {
"title": "Offlinemodus",
"description": "Met offlinetaken voor het kopiëren van containers kunt u gegevens kopiëren van een broncontainer naar een doelcontainer Cosmos DB voor ondersteunde API's. Om de gegevensintegriteit tussen de bron en het doel te garanderen, raden we u aan om updates op de broncontainer te stoppen voordat u de kopieertaak maakt. Meer informatie over [offline kopieertaken](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Onlinemodus",
"description": "Met online containerkopietaken kunt u gegevens kopiëren van een broncontainer naar een bestemming Cosmos DB NoSQL API-container met behulp van de wijzigingenfeed [Alle versies en verwijderen](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview) change feed. Hierdoor kunnen updates worden voortgezet op de bron terwijl gegevens worden gekopieerd. Aan het einde is een korte downtime vereist om veilig over te schakelen van clienttoepassingen naar de doelcontainer. Meer informatie over [online kopieertaken](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Selecteer een broncontainer en een doelcontainer waarnaar u wilt kopiëren.",
"sourceContainerSubHeading": "Broncontainer",
"targetContainerSubHeading": "Doelcontainer",
"databaseDropdownLabel": "Database",
"databaseDropdownPlaceholder": "Een database selecteren",
"containerDropdownLabel": "Container",
"containerDropdownPlaceholder": "Een container selecteren",
"createNewContainerSubHeading": "Configureer de eigenschappen voor de nieuwe container in doelaccount {{accountName}}.",
"createNewContainerSubHeadingDefault": "Configureer de eigenschappen voor de nieuwe container.",
"createContainerButtonLabel": "Een nieuwe container maken",
"createContainerHeading": "Nieuwe container maken"
},
"preview": {
"jobNameLabel": "Taaknaam",
"subscriptionLabel": "Doelabonnement",
"accountLabel": "Doelaccount",
"sourceDatabaseLabel": "Brondatabase",
"sourceContainerLabel": "Broncontainer",
"targetDatabaseLabel": "Doeldatabase",
"targetContainerLabel": "Doelcontainer"
},
"assignPermissions": {
"crossAccountDescription": "Als u gegevens wilt kopiëren van de bron naar de doelcontainer, moet u ervoor zorgen dat de beheerde identiteit van het bronaccount lees-/schrijftoegang heeft tot het doelaccount door de volgende stappen uit te voeren.",
"intraAccountOnlineDescription": "Volg de onderstaande stappen om online kopiëren in te schakelen voor uw {{accountName}} account.",
"crossAccountConfiguration": {
"title": "Containerkopie voor meerdere accounts",
"description": "Volg de onderstaande instructies om de vereiste machtigingen te verlenen voor het kopiëren van gegevens van {{sourceAccount}} naar {{destinationAccount}}."
},
"onlineConfiguration": {
"title": "Onlinecontainer kopiëren",
"description": "Volg de onderstaande instructies om online kopiëren in te schakelen voor uw account {{accountName}}."
}
},
"popoverOverlaySpinnerLabel": "Een ogenblik geduld. Uw aanvraag wordt verwerkt...",
"addManagedIdentity": {
"title": "De door het systeem toegewezen beheerde identiteit is ingeschakeld.",
"description": "Een door het systeem toegewezen beheerde identiteit is beperkt tot één per resource en is gekoppeld aan de levenscyclus van deze resource. Zodra deze functie is ingeschakeld, kunt u machtigingen verlenen aan de beheerde identiteit met behulp van op rollen gebaseerd toegangsbeheer van Azure (Azure RBAC). De beheerde identiteit wordt geverifieerd met Microsoft Entra ID, zodat je geen referenties in code hoeft op te slaan.",
"descriptionHrefText": "Meer informatie over beheerde identiteiten.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Door het systeem toegewezen beheerde identiteit",
"tooltipContent": "Meer informatie over",
"tooltipHrefText": "Beheerde identiteiten.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "U kunt een bestaande door de gebruiker toegewezen identiteit selecteren of een nieuwe identiteit maken.",
"userAssignedIdentityLabel": "U kunt ook een door de gebruiker toegewezen beheerde identiteit selecteren.",
"createUserAssignedIdentityLink": "Door de gebruiker toegewezen beheerde identiteit maken",
"enablementTitle": "De door het systeem toegewezen beheerde identiteit inschakelen",
"enablementDescription": "Door het systeem toegewezen beheerde identiteit inschakelen op de {{accountName}}. Klik op de knop Ja om te bevestigen."
},
"defaultManagedIdentity": {
"title": "Door het systeem toegewezen beheerde identiteit, ingesteld als standaard.",
"description": "Stel de door het systeem toegewezen beheerde identiteit in als standaard voor {{accountName}} door deze in te schakelen.",
"tooltipContent": "Meer informatie over",
"tooltipHrefText": "Standaard beheerde identiteiten.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Door het systeem toegewezen beheerde identiteit ingesteld als standaard",
"popoverDescription": "Wijs de door het systeem toegewezen beheerde identiteit toe als de standaardwaarde voor {{accountName}}. Klik op de knop Ja om te bevestigen. "
},
"readWritePermissionAssigned": {
"title": "Lees-/schrijfmachtigingen toegewezen aan de standaardidentiteit.",
"description": "Als u het kopiëren van gegevens van de bron naar de doelcontainer wilt toestaan, geeft u lees-/schrijftoegang voor het doelaccount op tot de standaardidentiteit van het bronaccount.",
"tooltipContent": "Meer informatie over",
"tooltipHrefText": "Lees-/schrijfmachtigingen.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Lees-/schrijfmachtigingen toewijzen aan de standaardidentiteit.",
"popoverDescription": "Wijs lees-/schrijfmachtigingen voor het doelaccount toe aan de standaardidentiteit van het bronaccount. Klik op de knop Ja om te bevestigen."
},
"pointInTimeRestore": {
"title": "Herstel naar een bepaald tijdstip ingeschakeld",
"description": "Werk uw {{accessName}} back-upbeleid bij van periodieke naar continue back-up om taken voor het kopiëren van onlinecontainers te vergemakkelijken. Voor deze functionaliteit is het inschakelen van continue back-up vereist.",
"tooltipContent": "Meer informatie over",
"tooltipHrefText": "Continue back-up",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Herstel naar een bepaald tijdstip inschakelen"
},
"onlineCopyEnabled": {
"title": "Online kopiëren is ingeschakeld",
"description": "Schakel onlinecontainerkopie in door op de onderstaande knop in uw {{accountName}} account te klikken.",
"hrefText": "Meer informatie over online kopieertaken",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Online kopiëren inschakelen",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Modus Alle versies en verwijderingen voor wijzigingenfeed (preview) valideren...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Inschakelen van Alle versies en verwijderen van de wijzigingenfeed (preview)",
"enablingOnlineCopySpinnerLabel": "Online kopiëren inschakelen voor uw {{accountName}}-account... ..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Datum en tijd",
"name": "Taaknaam",
"status": "Status",
"completionPercentage": "Voltooiings %",
"duration": "Duur",
"error": "Foutbericht",
"mode": "Modus",
"actions": "Acties"
},
"actions": {
"pause": "Onderbreken",
"resume": "Hervatten",
"complete": "Voltooien",
"viewDetails": "Details weergeven"
},
"status": {
"pending": "In de wachtrij",
"inProgress": "Uitvoeren...",
"running": "Uitvoeren...",
"partitioning": "Uitvoeren...",
"paused": "Onderbroken",
"completed": "Voltooid",
"failed": "Mislukt",
"faulted": "Mislukt",
"skipped": "Geannuleerd",
"cancelled": "Geannuleerd"
},
"dialog": {
"confirmButtonText": "Bevestigen",
"cancelButtonText": "Annuleren"
}
}
}
}
+180 -19
View File
@@ -34,6 +34,8 @@
"browse": "Przeglądaj",
"increaseValueBy1": "Zwiększ wartość o 1",
"decreaseValueBy1": "Zmniejsz wartość o 1",
"on": "Włączone",
"off": "Wyłączone",
"preview": "Podgląd"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Utwórz tabelę i pracuj z danymi za pomocą powłoki interfejsu PostgreSQL"
},
"vcoreMongo": {
"title": "Powłoka Mongo",
"title": "Mongo Shell",
"description": "Utwórz kolekcję i pracuj z danymi przy użyciu interfejsu powłoki bazy danych MongoDB"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "Usuń {{containerName}}",
"newSqlQuery": "Nowe zapytanie SQL",
"newQuery": "Nowe zapytanie",
"openMongoShell": "Otwórz powłokę Mongo",
"openMongoShell": "Otwórz Mongo Shell",
"newShell": "Nowa powłoka",
"openCassandraShell": "Otwórz powłokę Cassandra",
"newStoredProcedure": "Nowa procedura składowana",
@@ -326,7 +328,7 @@
"documentDeleted": "Dokument został pomyślnie usunięty.",
"deleteDocumentDialogTitle": "Usuń dokument",
"deleteDocumentsDialogTitle": "Usuń dokumenty",
"throttlingError": "Nie można usunąć niektórych dokumentów z powodu błędu ograniczenia szybkości. Spróbuj ponownie później. Aby temu zapobiec w przyszłości, rozważ zwiększenie przepustowości kontenera lub bazy danych.",
"throttlingError": "Nie można usunąć niektórych dokumentów z powodu błędu ograniczenia szybkości. Spróbuj ponownie później. Aby temu zapobiec w przyszłości, rozważ zwiększenie przepływności kontenera lub bazy danych.",
"deleteFailed": "Usuwanie dokumentów nie powiodło się ({{error}})",
"missingShardProperty": "W dokumencie brakuje właściwości fragmentu: {{partitionKeyProperty}}",
"refreshGridFailed": "Nie udało się odświeżyć siatki dokumentów",
@@ -353,7 +355,7 @@
"requestTooLargeBase": "Niektóre żądania usunięcia nie powiodły się z powodu wyjątku „Żądanie za duże” (429)",
"retriedSuccessfully": "ale pomyślnie ponowiono próbę.",
"retryingNow": "Ponawianie próby teraz.",
"increaseThroughputTip": "Aby temu zapobiec w przyszłości, rozważ zwiększenie przepustowości kontenera lub bazy danych.",
"increaseThroughputTip": "Aby temu zapobiec w przyszłości, rozważ zwiększenie przepływności kontenera lub bazy danych.",
"numberOfSelectedDocuments": "Liczba wybranych dokumentów: {{count}}",
"mongoFilterPlaceholder": "Wpisz predykat zapytania (np. {\"id\":\"foo\"}), wybierz go z listy rozwijanej lub pozostaw pusty, aby zapytać o wszystkie dokumenty.",
"sqlFilterPlaceholder": "Wpisz predykat zapytania (np. WHERE c.id=\"1\"), wybierz go z listy rozwijanej lub pozostaw pusty, aby zapytać o wszystkie dokumenty.",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Nie udało się odświeżyć siatki dokumentów"
},
"mongoShell": {
"title": "Powłoka Mongo"
"title": "Mongo Shell"
}
},
"panes": {
@@ -488,7 +490,7 @@
"legacySdkInfo": "Aby zapewnić zgodność ze starszymi zestawami SDK, utworzony kontener będzie korzystał ze starszego schematu partycjonowania, który obsługuje wartości klucza partycji o rozmiarze do 101 bajtów. Jeśli ta opcja jest włączona, nie będzie można używać hierarchicznych kluczy partycji.",
"indexingOnInfo": "Wszystkie właściwości w dokumentach będą domyślnie indeksowane, co umożliwia elastyczne i wydajne zapytania.",
"indexingOffInfo": "Indeksowanie zostanie wyłączone. Zalecane, jeśli nie musisz uruchamiać zapytań lub wykonujesz tylko operacje na wartościach klucza.",
"indexingOffWarning": "Tworząc ten kontener z wyłączonym indeksowaniem, nie będziesz mógł wprowadzać zmian w polityce indeksowania. Zmiany indeksowania są dozwolone tylko w kontenerze z zasadami indeksowania.",
"indexingOffWarning": "Tworząc ten kontener z wyłączonym indeksowaniem, nie będziesz mógł wprowadzać zmian w zasadach indeksowania. Zmiany indeksowania są dozwolone tylko w kontenerze z zasadami indeksowania.",
"acknowledgeSpendErrorMonthly": "Potwierdź szacowane miesięczne wydatki.",
"acknowledgeSpendErrorDaily": "Potwierdź szacowane dzienne wydatki.",
"unshardedMaxRuError": "Kolekcje bez fragmentów obsługują do 10 000 jednostek żądań",
@@ -508,7 +510,7 @@
"step1Body": "Baza danych jest nadrzędna względem kontenera. Możesz utworzyć nową bazę danych lub skorzystać z istniejącej. W tym poradniku tworzymy nową bazę o nazwie SampleDB.",
"step1LearnMore": "Dowiedz się więcej o zasobach.",
"step2Headline": "Ustawianie przepływności",
"step2Body": "Cosmos DB zaleca współdzielenie przepustowości na poziomie bazy danych. Autoskalowanie zapewnia elastyczną przepustowość w oparciu o maksymalną ustawioną wartość RU/s (Request Units).",
"step2Body": "Cosmos DB zaleca współdzielenie przepływności na poziomie bazy danych. Autoskalowanie zapewnia elastyczną przepływność w oparciu o maksymalną ustawioną wartość RU/s (Request Units).",
"step2LearnMore": "Dowiedz się więcej o jednostkach RU/s.",
"step3Headline": "Nadawanie nazwy kontenerowi",
"step3Body": "Nadaj nazwę kontenerowi",
@@ -762,7 +764,7 @@
"computedProperties": "Właściwości obliczane",
"containerPolicies": "Zasady kontenera",
"throughputBuckets": "Zasobniki przepływności",
"globalSecondaryIndexPreview": "Globalny indeks pomocniczy (wersja zapoznawcza)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Zasady maskowania"
},
"mongoNotifications": {
@@ -795,7 +797,7 @@
"perMonth": "/miesiąc"
},
"throughput": {
"manualToAutoscaleDisclaimer": "System określi początkową maksymalną wartość autoskalowania RU/s na podstawie bieżących ustawień ręcznej przepływności i pojemności magazynu zasobu. Po włączeniu autoskalowania można zmienić maksymalną liczbę jednostek RU/s.",
"manualToAutoscaleDisclaimer": "System określi początkową maksymalną wartość autoskalowania RU/s na podstawie bieżących ustawień ręcznej przepływności i pojemności magazynu zasobu. Po włączeniu przepływności z automatycznym skalowaniem można zmienić maksymalną liczbę jednostek RU/s.",
"ttlWarningText": "System automatycznie usunie elementy na podstawie podanej wartości TTL (w sekundach), bez konieczności wykonywania operacji usuwania przez aplikację kliencką. Aby uzyskać więcej informacji, zobacz",
"ttlWarningLinkText": "Czas wygaśnięcia (TTL) w usłudze Azure Cosmos DB",
"unsavedIndexingPolicy": "zasady indeksowania",
@@ -804,17 +806,17 @@
"unsavedEditorWarningPrefix": "Nie zapisano najnowszych zmian wprowadzonych w",
"unsavedEditorWarningSuffix": ". Kliknij przycisk Zapisz, aby potwierdzić zmiany.",
"updateDelayedApplyWarning": "Zamierzasz zażądać zwiększenia przepływności ponad wstępnie przydzieloną pojemność. Wykonanie tej operacji zajmie trochę czasu.",
"scalingUpDelayMessage": "Skalowanie w górę potrwa 46 godzin, ponieważ przekracza to, co Azure Cosmos DB może obecnie obsłużyć natychmiast, biorąc pod uwagę liczbę partycji fizycznych. Możesz natychmiast zwiększyć przepustowość do {{instantMaximumThroughput}} lub kontynuować z tą wartością i poczekać na zakończenie skalowania w górę.",
"scalingUpDelayMessage": "Skalowanie w górę potrwa 46 godzin, ponieważ przekracza to, co Azure Cosmos DB może obecnie obsłużyć natychmiast, biorąc pod uwagę liczbę partycji fizycznych. Możesz natychmiast zwiększyć przepływność do {{instantMaximumThroughput}} lub kontynuować z tą wartością i poczekać na zakończenie skalowania w górę.",
"exceedPreAllocatedMessage": "Twoje żądanie zwiększenia przepływności przekracza wstępnie przydzieloną pojemność, co może potrwać dłużej niż oczekiwano. Masz do wyboru trzy opcje, aby kontynuować:",
"instantScaleOption": "Możesz natychmiast skalować w górę do {{instantMaximumThroughput}} RU/s.",
"asyncScaleOption": "Możesz asynchronicznie skalować w górę do dowolnej wartości w {{maximumThroughput}} RU/s w ciągu 46 godzin.",
"quotaMaxOption": "Bieżący limit przydziału wynosi {{maximumThroughput}} RU/s. Aby przekroczony został ten limit, musisz zażądać zwiększenia limitu przydziału, a zespół usługi Azure Cosmos DB przeprowadzi przegląd.",
"belowMinimumMessage": "Nie można obniżyć przepływności poniżej bieżącego minimum wynoszącego {{minimum}} RU/s. Aby uzyskać więcej informacji na temat tego limitu, zapoznaj się z naszą dokumentacją oferty usługi.",
"saveThroughputWarning": "Twoje rozliczenie zmieni się po aktualizacji ustawień przepływności. Przejrzyj zaktualizowane oszacowanie kosztów poniżej przed zapisaniem zmian",
"currentAutoscaleThroughput": "Bieżąca przepływność autoskalowania:",
"targetAutoscaleThroughput": "Docelowa przepływność autoskalowania:",
"currentManualThroughput": "Bieżąca przepływność ręczna:",
"targetManualThroughput": "Docelowa przepływność ręczna:",
"currentAutoscaleThroughput": "Bieżąca przepływność z automatycznym skalowaniem:",
"targetAutoscaleThroughput": "Docelowa przepływność z automatycznym skalowaniem:",
"currentManualThroughput": "Bieżąca ręczna przepływność:",
"targetManualThroughput": "Docelowa ręczna przepływność:",
"applyDelayedMessage": "Żądanie zwiększenia przepływności zostało pomyślnie przesłane. Ta operacja potrwa od 1 do 3 dni roboczych. Najnowszy status sprawdzisz w obszarze Powiadomienia.",
"databaseLabel": "Baza danych:",
"containerLabel": "Kontener:",
@@ -841,8 +843,8 @@
"mongoIndexing": {
"disclaimer": "W przypadku zapytań filtrujących po wielu właściwościach utwórz wiele indeksów pojedynczych pól zamiast indeksu złożonego.",
"disclaimerCompoundIndexesLink": " Indeksy złożone ",
"disclaimerSuffix": "są używane tylko do sortowania wyników zapytania. Jeśli musisz dodać indeks złożony, możesz go utworzyć przy użyciu powłoki Mongo.",
"compoundNotSupported": "Kolekcje z indeksami złożonymi nie są jeszcze obsługiwane w edytorze indeksowania. Aby zmodyfikować zasady indeksowania dla tej kolekcji, użyj powłoki Mongo.",
"disclaimerSuffix": "są używane tylko do sortowania wyników zapytania. Jeśli musisz dodać indeks złożony, możesz go utworzyć przy użyciu Mongo Shell.",
"compoundNotSupported": "Kolekcje z indeksami złożonymi nie są jeszcze obsługiwane w edytorze indeksowania. Aby zmodyfikować zasady indeksowania dla tej kolekcji, użyj Mongo Shell.",
"aadError": "Aby użyć edytora zasad indeksowania, zaloguj się do",
"aadErrorLink": "azure portal.",
"refreshingProgress": "Odświeżanie postępu przekształcania indeksu",
@@ -928,7 +930,7 @@
"learnMoreSuffix": "o sposobie definiowania globalnych indeksów pomocniczych i sposobie ich używania.",
"jsonAriaLabel": "Kod JSON globalnego indeksu pomocniczego",
"addIndex": "Dodaj indeks",
"settingsTitle": "Globalne ustawienia indeksu pomocniczego",
"settingsTitle": "Ustawienia globalnego indeksu pomocniczego",
"sourceContainer": "Kontener źródłowy",
"indexDefinition": "Definicja globalnego indeksu pomocniczego"
},
@@ -946,8 +948,8 @@
"unlimited": "Bez ograniczeń",
"instant": "Natychmiastowe",
"fourToSixHrs": "46 godz.",
"autoscaleDescription": "Na podstawie użycia Twoja {{resourceType}} przepustowość będzie skalowana od",
"freeTierWarning": "Rozliczenia zostaną naliczone, jeśli przydzielisz więcej niż {{ru}} RU/s ręcznej przepływności lub jeśli zasób będzie skalowany powyżej {{ru}} RU/s za pomocą autoskalowania.",
"autoscaleDescription": "Na podstawie użycia Twoja {{resourceType}} przepływność będzie skalowana od",
"freeTierWarning": "Rozliczenia zostaną naliczone, jeśli przydzielisz więcej niż {{ru}} RU/s ręcznej przepływności lub jeśli zasób będzie skalowany powyżej {{ru}} RU/s przy wykorzystaniu przepływności z automatycznym skalowaniem.",
"capacityCalculator": "Oszacuj wymagane jednostki RU/s za pomocą",
"capacityCalculatorLink": " kalkulator pojemności",
"fixedStorageNote": "W przypadku korzystania z kolekcji ze stałą pojemnością magazynu można skonfigurować do 10 000 RU/s.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "Rozmiar bajtów kwantyzacji musi być większy od 0 i mniejszy lub równy 512",
"indexingSearchListSizeRangeError": "Rozmiar listy wyszukiwania indeksowania musi być większy lub równy 25 i mniejszy lub równy 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Opinia",
"feedbackButtonAriaLabel": "Przekaż opinię na temat zadań kopiowania",
"refreshButtonAriaLabel": "Odśwież zadania kopiowania",
"createCopyJobButtonLabel": "Utwórz zadanie kopiowania",
"createCopyJobButtonAriaLabel": "Utwórz nowe zadanie kopiowania kontenera"
},
"noCopyJobs": {
"title": "Brak zadań kopiowania do wyświetlenia",
"createCopyJobButtonText": "Utwórz zadanie kopiowania kontenera"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Szczegóły zadania",
"errorTitle": "Szczegóły błędu",
"selectedContainers": "Wybrane kontenery"
},
"createCopyJob": {
"panelTitle": "Tworzenie zadania kopiowania"
},
"selectAccount": {
"description": "Wybierz konto docelowe, do którego chcesz kopiować.",
"subscriptionDropdownLabel": "Subskrypcja",
"subscriptionDropdownPlaceholder": "Wybierz subskrypcję",
"accountDropdownLabel": "Konto",
"accountDropdownPlaceholder": "Wybierz konto"
},
"migrationType": {
"offline": {
"title": "Tryb offline",
"description": "Zadania kopiowania kontenerów w trybie offline umożliwiają kopiowanie danych z kontenera źródłowego do docelowego kontenera usługi Cosmos DB w przypadku obsługiwanych interfejsów API. Aby zapewnić integralność danych między źródłem i miejscem docelowym, zalecamy zatrzymanie aktualizacji w kontenerze źródłowym przed utworzeniem zadania kopiowania. Dowiedz się więcej o [zadaniach kopiowania w trybie offline](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Tryb online",
"description": "Zadania kopiowania kontenerów online umożliwiają kopiowanie danych z kontenera źródłowego do docelowego kontenera interfejsu API NoSQL usługi Cosmos DB przy użyciu zestawienia zmian [Wszystkie wersje i usuwanie](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Umożliwia to kontynuowanie aktualizacji w źródle podczas kopiowania danych. Na końcu jest wymagany krótki przestój, aby bezpiecznie przełączyć aplikacje klienckie do kontenera docelowego. Dowiedz się więcej o [zadaniach kopiowania online](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Wybierz kontener źródłowy i kontener docelowy do skopiowania.",
"sourceContainerSubHeading": "Kontener źródłowy",
"targetContainerSubHeading": "Kontener docelowy",
"databaseDropdownLabel": "Baza danych",
"databaseDropdownPlaceholder": "Wybierz bazę danych",
"containerDropdownLabel": "Kontener",
"containerDropdownPlaceholder": "Wybierz kontener",
"createNewContainerSubHeading": "Skonfiguruj właściwości nowego kontenera na koncie docelowym „{{accountName}}”.",
"createNewContainerSubHeadingDefault": "Skonfiguruj właściwości nowego kontenera.",
"createContainerButtonLabel": "Utwórz nowy kontener",
"createContainerHeading": "Utwórz nowy kontener"
},
"preview": {
"jobNameLabel": "Nazwa zadania",
"subscriptionLabel": "Subskrypcja docelowa",
"accountLabel": "Konto docelowe",
"sourceDatabaseLabel": "Źródłowa baza danych",
"sourceContainerLabel": "Kontener źródłowy",
"targetDatabaseLabel": "Docelowa baza danych",
"targetContainerLabel": "Kontener docelowy"
},
"assignPermissions": {
"crossAccountDescription": "Aby skopiować dane ze kontenera źródłowego do kontenera docelowego, upewnij się, że tożsamość zarządzana konta źródłowego ma dostęp do odczytu i zapisu na koncie docelowym, wykonując następujące kroki.",
"intraAccountOnlineDescription": "Wykonaj poniższe kroki, aby włączyć kopiowanie online na koncie „{{accountName}}”.",
"crossAccountConfiguration": {
"title": "Kopiowanie kontenera między kontami",
"description": "Postępuj zgodnie z instrukcjami poniżej, aby udzielić wymaganych uprawnień do kopiowania danych z lokalizacji konta źródłowego „{{sourceAccount}}” do konta docelowego „{{destinationAccount}}”."
},
"onlineConfiguration": {
"title": "Kopiowanie do kontenera w trybie online",
"description": "Postępuj zgodnie z poniższymi instrukcjami, aby włączyć kopiowanie online na koncie „{{accountName}}”."
}
},
"popoverOverlaySpinnerLabel": "Poczekaj na przetworzenie Twojego żądania...",
"addManagedIdentity": {
"title": "Włączono tożsamość zarządzaną przypisaną przez system.",
"description": "Tożsamość zarządzana przypisana przez system jest ograniczona do jednej na zasób i jest powiązana z cyklem życia danego zasobu. Po włączeniu uprawnienia dla tej tożsamości zarządzanej możesz przyznać za pomocą kontroli dostępu na podstawie ról na platformie Azure (Azure RBAC). Tożsamość zarządzana jest uwierzytelniana przy użyciu rozwiązania Microsoft Entra ID, więc nie trzeba przechowywać żadnych poświadczeń w kodzie.",
"descriptionHrefText": "Dowiedz się więcej o tożsamościach zarządzanych.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Tożsamość zarządzana przypisana przez system",
"tooltipContent": "Dowiedz się więcej o",
"tooltipHrefText": "Tożsamości zarządzane.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Możesz wybrać istniejącą tożsamość przypisaną przez użytkownika lub utworzyć nową.",
"userAssignedIdentityLabel": "Możesz również wybrać tożsamość zarządzaną przypisaną przez użytkownika.",
"createUserAssignedIdentityLink": "Tworzenie tożsamości zarządzanej przypisanej przez użytkownika",
"enablementTitle": "Włącz tożsamość zarządzaną przypisaną przez system",
"enablementDescription": "Włącz tożsamość zarządzaną przypisaną przez system na koncie „{{accountName}}”. Aby potwierdzić, kliknij przycisk „Tak”."
},
"defaultManagedIdentity": {
"title": "Tożsamość zarządzana przypisana przez system jako wartość domyślna.",
"description": "Ustaw tożsamość zarządzaną przypisaną przez system jako domyślną dla konta „{{accountName}}”, przełączając się na nią.",
"tooltipContent": "Dowiedz się więcej o",
"tooltipHrefText": "Domyślne tożsamości zarządzane.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Tożsamość zarządzana przypisana przez system jako domyślna",
"popoverDescription": "Przypisz tożsamość zarządzaną przypisaną przez system jako domyślną dla „{{accountName}}”. Aby potwierdzić, kliknij przycisk „Tak”. "
},
"readWritePermissionAssigned": {
"title": "Uprawnienia do odczytu i zapisu przypisane do tożsamości domyślnej.",
"description": "Aby zezwolić na kopiowanie danych ze źródła do kontenera docelowego, zapewnij dostęp do odczytu i zapisu na koncie docelowym do domyślnej tożsamości konta źródłowego.",
"tooltipContent": "Dowiedz się więcej o",
"tooltipHrefText": "Uprawnienia do odczytu i zapisu.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Przypisz uprawnienia do odczytu i zapisu do tożsamości domyślnej.",
"popoverDescription": "Przypisz uprawnienia do odczytu i zapisu na koncie docelowym do domyślnej tożsamości konta źródłowego. Aby potwierdzić, kliknij przycisk „Tak”."
},
"pointInTimeRestore": {
"title": "Włączono przywracanie do punktu w czasie",
"description": "Aby ułatwić wykonywanie zadań kopiowania kontenerów w trybie online, zaktualizuj zasady kopii zapasowych „{{accessName}}” z okresowych na ciągłe. Włączenie ciągłej kopii zapasowej jest wymagane dla tej funkcji.",
"tooltipContent": "Dowiedz się więcej o",
"tooltipHrefText": "Ciągła kopia zapasowa",
"tooltipHref": "https://learn.microsoft.com/pl-pl/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Włącz przywracanie do określonego punktu w czasie"
},
"onlineCopyEnabled": {
"title": "Włączono kopiowanie w trybie online",
"description": "Włącz kopiowanie kontenerów online, klikając przycisk poniżej na koncie „{{accountName}}”.",
"hrefText": "Dowiedz się więcej o zadaniach kopiowania w trybie online",
"href": "https://learn.microsoft.com/pl-pl/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Włącz kopiowanie online",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Trwa weryfikacja wszystkich wersji i usunięć w trybie zestawienia zmian (wersja zapoznawcza)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Trwa włączanie wszystkich wersji i usunięć w trybie zestawienia zmian (wersja zapoznawcza)...",
"enablingOnlineCopySpinnerLabel": "Trwa włączanie kopii online na koncie „{{accountName}}”..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Data i godzina",
"name": "Nazwa zadania",
"status": "Stan",
"completionPercentage": "Procent ukończenia",
"duration": "Czas trwania",
"error": "Komunikat o błędzie",
"mode": "Tryb",
"actions": "Akcje"
},
"actions": {
"pause": "Wstrzymaj",
"resume": "Wznów",
"complete": "Ukończone",
"viewDetails": "Wyświetl szczegóły"
},
"status": {
"pending": "W kolejce",
"inProgress": "Uruchomiono",
"running": "Uruchomiono",
"partitioning": "Uruchomiono",
"paused": "Wstrzymano",
"completed": "Ukończone",
"failed": "Zakończone niepowodzeniem",
"faulted": "Zakończone niepowodzeniem",
"skipped": "Anulowane",
"cancelled": "Anulowane"
},
"dialog": {
"confirmButtonText": "Potwierdź",
"cancelButtonText": "Anuluj"
}
}
}
}
+165 -4
View File
@@ -34,6 +34,8 @@
"browse": "Procurar",
"increaseValueBy1": "Aumentar o valor em 1",
"decreaseValueBy1": "Diminuir valor em 1",
"on": "Ativado",
"off": "Desligado",
"preview": "Pré-visualização"
},
"splashScreen": {
@@ -303,7 +305,7 @@
"deleteContainer": "Excluir {{containerName}}",
"newSqlQuery": "Nova Consulta SQL",
"newQuery": "Nova Consulta",
"openMongoShell": "Abrir o Shell Mongo",
"openMongoShell": "Abrir o Mongo Shell",
"newShell": "Novo Shell",
"openCassandraShell": "Abrir o Shell Cassandra",
"newStoredProcedure": "Novo Procedimento Armazenado",
@@ -762,7 +764,7 @@
"computedProperties": "Propriedades Calculadas",
"containerPolicies": "Políticas de Contêiner",
"throughputBuckets": "Buckets de Taxa de Transferência",
"globalSecondaryIndexPreview": "Índice Secundário Global (Versão Prévia)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Política de Mascaramento"
},
"mongoNotifications": {
@@ -811,7 +813,7 @@
"quotaMaxOption": "Sua cota máxima atual é de {{maximumThroughput}} RU/s. Para exceder esse limite, você deve solicitar um aumento de cota, que será revisado pela equipe do Azure Cosmos DB.",
"belowMinimumMessage": "Não é possível reduzir a taxa de transferência abaixo do mínimo atual de {{minimum}} RU/s. Para mais informações sobre esse limite, consulte nossa documentação de cotas de serviço.",
"saveThroughputWarning": "Sua cobrança será afetada à medida que você atualizar as configurações de taxa de transferência. Revise a estimativa de custo atualizada abaixo antes de salvar as alterações",
"currentAutoscaleThroughput": "Taxa de transferência atual da escala automática:",
"currentAutoscaleThroughput": "Taxa de transferência de dimensionamento automático:",
"targetAutoscaleThroughput": "Taxa de transferência de escala automática de destino:",
"currentManualThroughput": "Taxa de transferência manual atual:",
"targetManualThroughput": "Taxa de transferência manual de destino:",
@@ -841,7 +843,7 @@
"mongoIndexing": {
"disclaimer": "Para consultas que filtram várias propriedades, crie vários índices de campo único em vez de um índice composto.",
"disclaimerCompoundIndexesLink": " Índices compostos ",
"disclaimerSuffix": "são usados apenas para classificar os resultados da consulta. Se você precisar adicionar um índice composto, poderá criar um usando o shell do Mongo.",
"disclaimerSuffix": "são usados apenas para classificar os resultados da consulta. Se você precisar adicionar um índice composto, poderá criar um usando o Mongo Shell.",
"compoundNotSupported": "Coleções com índices compostos ainda não têm suporte no editor de indexação. Para modificar a política de indexação desta coleção, use o Mongo Shell.",
"aadError": "Para usar o editor de políticas de indexação, faça logon no",
"aadErrorLink": "portal do azure.",
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "O tamanho do byte de quantização deve ser maior que 0 e menor ou igual a 512",
"indexingSearchListSizeRangeError": "O tamanho da lista de pesquisa de indexação deve ser maior ou igual a 25 e menor ou igual a 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Comentários",
"feedbackButtonAriaLabel": "Fornecer comentários sobre trabalhos de cópia",
"refreshButtonAriaLabel": "Atualizar trabalhos de cópia",
"createCopyJobButtonLabel": "Criar Trabalho de Cópia",
"createCopyJobButtonAriaLabel": "Criar um novo trabalho de cópia de contêiner"
},
"noCopyJobs": {
"title": "Não há trabalhos de cópia para mostrar",
"createCopyJobButtonText": "Criar um trabalho de cópia de contêiner"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Detalhes do Trabalho",
"errorTitle": "Detalhes do Erro",
"selectedContainers": "Contêineres Selecionados"
},
"createCopyJob": {
"panelTitle": "Criar trabalho de cópia"
},
"selectAccount": {
"description": "Selecione uma conta de destino para a qual copiar.",
"subscriptionDropdownLabel": "Assinatura",
"subscriptionDropdownPlaceholder": "Selecione uma assinatura",
"accountDropdownLabel": "Conta",
"accountDropdownPlaceholder": "Selecionar uma conta"
},
"migrationType": {
"offline": {
"title": "Modo offline",
"description": "Os trabalhos de cópia de contêiner offline permitem copiar dados de um contêiner de origem para um contêiner de destino do Cosmos DB para APIs com suporte. Para garantir a integridade dos dados entre a origem e o destino, recomendamos interromper as atualizações no contêiner de origem antes de criar o trabalho de cópia. Saiba mais sobre [trabalhos de cópia offline](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Modo online",
"description": "Os trabalhos de cópia online de contêiner permitem copiar dados de um contêiner de origem para um contêiner de destino da API NoSQL do Cosmos DB usando o feed de alterações [Todas as Versões e Excluir](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Isso permite que as atualizações continuem na fonte enquanto os dados são copiados. Um breve tempo de inatividade é necessário no final para alternar aplicativos cliente com segurança para o contêiner de destino. Saiba mais sobre [trabalhos de cópia online](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Selecione um contêiner de origem e um contêiner de destino para o qual copiar.",
"sourceContainerSubHeading": "Contêiner de origem",
"targetContainerSubHeading": "Contêiner de destino",
"databaseDropdownLabel": "Banco de dados",
"databaseDropdownPlaceholder": "Selecionar um banco de dados",
"containerDropdownLabel": "Contêiner",
"containerDropdownPlaceholder": "Selecionar um contêiner",
"createNewContainerSubHeading": "Configure as propriedades do novo contêiner na conta de destino \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Configure as propriedades para o novo contêiner.",
"createContainerButtonLabel": "Criar um novo contêiner",
"createContainerHeading": "Criar novo contêiner"
},
"preview": {
"jobNameLabel": "Nome do trabalho",
"subscriptionLabel": "Assinatura de destino",
"accountLabel": "Conta de destino",
"sourceDatabaseLabel": "Banco de dados de origem",
"sourceContainerLabel": "Contêiner de origem",
"targetDatabaseLabel": "Banco de dados de destino",
"targetContainerLabel": "Contêiner de destino"
},
"assignPermissions": {
"crossAccountDescription": "Para copiar dados da origem para o contêiner de destino, certifique-se de que a identidade gerenciada da conta de origem tenha acesso de leitura/gravação à conta de destino concluindo as etapas a seguir.",
"intraAccountOnlineDescription": "Siga as etapas abaixo para habilitar a cópia online em sua conta \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Cópia de contêiner entre contas",
"description": "Siga as instruções abaixo para conceder permissões necessárias para copiar dados de \"{{sourceAccount}}\" para \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Cópia de contêiner online",
"description": "Siga as instruções abaixo para habilitar a cópia online em sua conta \"{{accountName}}\"."
}
},
"popoverOverlaySpinnerLabel": "Aguarde enquanto processemos a sua solicitação...",
"addManagedIdentity": {
"title": "Identidade gerenciada atribuída pelo sistema habilitada.",
"description": "A identidade gerenciada atribuída pelo sistema é restrita a uma por recurso e está vinculada ao ciclo de vida desse recurso. Uma vez habilitado, você pode conceder permissões à identidade gerenciada usando o controle de acesso baseado em função (RBAC) do Azure. A identidade gerenciada é autenticada com o Microsoft Entra ID, portanto, você não precisa armazenar credenciais no código.",
"descriptionHrefText": "Saiba mais sobre as Identidades gerenciadas.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identidade gerenciada atribuída ao sistema",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Identidades Gerenciadas.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Você pode selecionar uma identidade atribuída pelo usuário existente ou criar uma nova.",
"userAssignedIdentityLabel": "Você também pode selecionar uma identidade gerenciada atribuída pelo usuário.",
"createUserAssignedIdentityLink": "Criar uma identidade gerenciada atribuída pelo usuário",
"enablementTitle": "Habilitar a identidade gerenciada atribuída pelo sistema",
"enablementDescription": "Habilitar a identidade gerenciada atribuída pelo sistema no {{accountName}}. Para confirmar, clique no botão \"Sim\"."
},
"defaultManagedIdentity": {
"title": "Identidade gerenciada atribuída pelo sistema definida como padrão.",
"description": "Defina a identidade gerenciada atribuída pelo sistema como padrão para \"{{accountName}}\" ativando essa opção.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Identidades Gerenciadas Padrão.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identidade gerenciada atribuída pelo sistema definida como padrão",
"popoverDescription": "Atribua a identidade gerenciada atribuída pelo sistema como o padrão para \"{{accountName}}\". Para confirmar, clique no botão \"Sim\". "
},
"readWritePermissionAssigned": {
"title": "Permissões de leitura/gravação atribuídas à identidade padrão.",
"description": "Para permitir a cópia de dados da origem para o contêiner de destino, forneça acesso de leitura/gravação na conta de destino para a identidade padrão da conta de origem.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Permissões de leitura/gravação.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Atribua permissões de leitura/gravação à identidade padrão.",
"popoverDescription": "Atribua permissões de leitura/gravação na conta de destino à identidade padrão da conta de origem. Para confirmar, clique no botão \"Sim\"."
},
"pointInTimeRestore": {
"title": "Restauração pontual habilitada",
"description": "Para facilitar trabalhos de cópia de contêiner online, atualize a política de backup \"{{accessName}}\" de backup periódico para contínuo. Habilitar o backup contínuo é necessário para essa funcionalidade.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Backup Contínuo",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Habilitar Restauração Pontual"
},
"onlineCopyEnabled": {
"title": "Cópia online habilitada",
"description": "Habilite a cópia de contêiner online clicando no botão abaixo em sua conta \"{{accountName}}\".",
"hrefText": "Saiba mais sobre trabalhos de cópia online",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Habilitar Cópia Online",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Validando Todas as versões e exclui o modo de feed de alterações (versão prévia)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Habilitando Todas as versões e excluindo o modo de feed de alterações (versão prévia)...",
"enablingOnlineCopySpinnerLabel": "Habilitando a cópia online em sua conta \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Data e hora",
"name": "Nome do trabalho",
"status": "Status",
"completionPercentage": "% de Conclusão",
"duration": "Duração",
"error": "Mensagem de erro",
"mode": "Modo",
"actions": "Ações"
},
"actions": {
"pause": "Pausar",
"resume": "Retomar",
"complete": "Completar",
"viewDetails": "Exibir Detalhes"
},
"status": {
"pending": "Na fila",
"inProgress": "Em execução",
"running": "Em execução",
"partitioning": "Em execução",
"paused": "Em pausa",
"completed": "Concluído",
"failed": "Com falha",
"faulted": "Com falha",
"skipped": "Cancelado",
"cancelled": "Cancelado"
},
"dialog": {
"confirmButtonText": "Confirmar",
"cancelButtonText": "Cancelar"
}
}
}
}
+162 -1
View File
@@ -34,6 +34,8 @@
"browse": "Procurar",
"increaseValueBy1": "Aumentar valor em 1",
"decreaseValueBy1": "Diminuir valor em 1",
"on": "Ativado",
"off": "Desativado",
"preview": "Pré-visualização"
},
"splashScreen": {
@@ -762,7 +764,7 @@
"computedProperties": "Propriedades Calculadas",
"containerPolicies": "Políticas de Contentor",
"throughputBuckets": "Registos de Débito",
"globalSecondaryIndexPreview": "Índice Secundário Global (Pré-Visualização)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Política de Mascaramento"
},
"mongoNotifications": {
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "O tamanho do byte de quantização tem de ser superior a 0 e inferior ou igual a 512",
"indexingSearchListSizeRangeError": "O tamanho da lista de pesquisa de indexação tem de ser maior ou igual a 25 e menor ou igual a 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Feedback",
"feedbackButtonAriaLabel": "Fornecer feedback sobre tarefas de cópia",
"refreshButtonAriaLabel": "Atualizar tarefas de cópia",
"createCopyJobButtonLabel": "Criar Tarefa de Cópia",
"createCopyJobButtonAriaLabel": "Criar uma nova tarefa de cópia de contentor"
},
"noCopyJobs": {
"title": "Não existem tarefas de cópia para apresentar",
"createCopyJobButtonText": "Criar uma tarefa de cópia de contentor"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Detalhes da Tarefa",
"errorTitle": "Detalhes do Erro",
"selectedContainers": "Contentores selecionados"
},
"createCopyJob": {
"panelTitle": "Criar tarefa de cópia"
},
"selectAccount": {
"description": "Selecione uma conta de destino para copiar para ela.",
"subscriptionDropdownLabel": "Subscrição",
"subscriptionDropdownPlaceholder": "Selecionar uma subscrição",
"accountDropdownLabel": "Conta",
"accountDropdownPlaceholder": "Selecione uma conta"
},
"migrationType": {
"offline": {
"title": "Modo offline",
"description": "As tarefas de cópia de contentores offline permitem-lhe copiar dados de um contentor de origem para um contentor de destino do Cosmos DB para APIs suportadas. Para garantir a integridade dos dados entre a origem e o destino, recomendamos que pare as atualizações no contentor de origem antes de criar a tarefa de cópia. Saiba mais sobre [offline copy jobs](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Modo online",
"description": "As tarefas de cópia de contentores online permitem-lhe copiar dados de um contentor de origem para um contentor de destino da API NoSQL do Cosmos DB usando o feed de alterações [All Versions and Delete](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Isto permite que as atualizações continuem na origem enquanto os dados são copiados. É necessário um breve período de inatividade no final para mudar em segurança as aplicações cliente para o contentor de destino. Saiba mais sobre [online copy jobs](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Selecione um contentor de origem e um contentor de destino para o qual copiar.",
"sourceContainerSubHeading": "Contentor de origem",
"targetContainerSubHeading": "Contentor de destino",
"databaseDropdownLabel": "Base de dados",
"databaseDropdownPlaceholder": "Selecionar uma base de dados",
"containerDropdownLabel": "Contentor",
"containerDropdownPlaceholder": "Selecionar um contentor",
"createNewContainerSubHeading": "Configure as propriedades do novo contentor na conta de destino \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Configure as propriedades do novo contentor.",
"createContainerButtonLabel": "Criar novo contentor",
"createContainerHeading": "Criar novo contentor"
},
"preview": {
"jobNameLabel": "Nome da tarefa",
"subscriptionLabel": "Subscrição de destino",
"accountLabel": "Conta de destino",
"sourceDatabaseLabel": "Base de dados de origem",
"sourceContainerLabel": "Contentor de origem",
"targetDatabaseLabel": "Base de Dados de Destino",
"targetContainerLabel": "Contentor de destino"
},
"assignPermissions": {
"crossAccountDescription": "Para copiar dados do contentor de origem para o contentor de destino, certifique-se de que a identidade gerida da conta de origem tem acesso de leitura/escrita à conta de destino ao concluir os passos seguintes.",
"intraAccountOnlineDescription": "Siga os passos abaixo para ativar a cópia online na sua conta \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Cópia de contentor entre contas",
"description": "Siga as instruções abaixo para conceder as permissões necessárias para copiar dados de \"{{sourceAccount}}\" para \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Cópia de contentor online",
"description": "Siga as instruções abaixo para ativar a cópia online na sua conta \"{{accountName}}\"."
}
},
"popoverOverlaySpinnerLabel": "Aguarde enquanto processamos o seu pedido...",
"addManagedIdentity": {
"title": "Identidade gerida atribuída pelo sistema ativada",
"description": "Uma identidade gerida atribuída pelo sistema está restrita a uma por recurso e associada ao ciclo de vida deste recurso. Depois de ativada, pode conceder permissões à identidade gerida através do controlo de acesso baseado em funções do Azure (Azure RBAC). A identidade gerida é autenticada com o Microsoft Entra ID, pelo que não tem de armazenar credenciais em código.",
"descriptionHrefText": "Saiba mais sobre as Identidades geridas.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Identidade gerida atribuída pelo sistema",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Identidades geridas.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Pode selecionar uma identidade atribuída pelo utilizador existente ou criar uma nova.",
"userAssignedIdentityLabel": "Também pode selecionar uma identidade gerida atribuída pelo utilizador.",
"createUserAssignedIdentityLink": "Criar Identidade Gerida Atribuída pelo Utilizador",
"enablementTitle": "Ativar a identidade gerida atribuída pelo sistema",
"enablementDescription": "Ative a identidade gerida atribuída pelo sistema no {{accountName}} Para confirmar, clique no botão \"Sim\"."
},
"defaultManagedIdentity": {
"title": "Identidade gerida atribuída pelo sistema definida como predefinição.",
"description": "Defina a identidade gerida atribuída pelo sistema como predefinição para \"{{accountName}}\" ao ativá-la.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Identidades Gerida Predefinida.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Identidade gerida atribuída pelo sistema definida como predefinição",
"popoverDescription": "Atribua a identidade gerida atribuída pelo sistema como predefinição para \"{{accountName}}\". Para confirmar, clique no botão \"Sim\". "
},
"readWritePermissionAssigned": {
"title": "Permissões de leitura/escrita atribuídas à identidade predefinida.",
"description": "Para permitir a cópia de dados do contentor de origem para o contentor de destino, conceda acesso de leitura/escrita na conta de destino à identidade predefinida da conta de origem.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Permissões de leitura/escrita.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Atribua permissões de leitura/escrita à identidade predefinida.",
"popoverDescription": "Atribua permissões de leitura/escrita na conta de destino à identidade predefinida da conta de origem. Para confirmar, clique no botão \"Sim\"."
},
"pointInTimeRestore": {
"title": "Restauro para Um Ponto No Tempo ativado",
"description": "Para facilitar as tarefas de cópia online de contentores, atualize a sua política de cópia de segurança \"{{accessName}}\" de cópia de segurança periódica para cópia de segurança contínua. É necessário ativar a cópia de segurança contínua para esta funcionalidade.",
"tooltipContent": "Saiba mais sobre",
"tooltipHrefText": "Cópia de Segurança Contínua",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Ativar Restauro para Um Ponto No Tempo"
},
"onlineCopyEnabled": {
"title": "Cópia online ativada",
"description": "Ative a cópia online de contentores ao clicar no botão abaixo na sua conta \"{{accountName}}\".",
"hrefText": "Saiba mais sobre tarefas de cópia online",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Ativar Cópia Online",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "A validar o modo de feed de alterações para todas as versões e eliminações (pré-visualização)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "A ativar o modo de feed de alterações para todas as versões e eliminações (pré-visualização)...",
"enablingOnlineCopySpinnerLabel": "A ativar a cópia online na sua conta \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Data e hora",
"name": "Nome da tarefa",
"status": "Estado",
"completionPercentage": "Conclusão %",
"duration": "Duração",
"error": "Mensagem de erro",
"mode": "Modo",
"actions": "Ações"
},
"actions": {
"pause": "Colocar em pausa",
"resume": "Retomar",
"complete": "Completo",
"viewDetails": "Ver Detalhes"
},
"status": {
"pending": "Em fila",
"inProgress": "Em execução",
"running": "Em execução",
"partitioning": "Em execução",
"paused": "Em pausa",
"completed": "Concluído",
"failed": "Com falhas",
"faulted": "Com falhas",
"skipped": "Cancelado",
"cancelled": "Cancelado"
},
"dialog": {
"confirmButtonText": "Confirmar",
"cancelButtonText": "Cancelar"
}
}
}
}
+176 -15
View File
@@ -34,6 +34,8 @@
"browse": "Обзор",
"increaseValueBy1": "Увеличить значение на 1",
"decreaseValueBy1": "Уменьшить значение на 1",
"on": "Вкл",
"off": "Выкл",
"preview": "Предварительная версия"
},
"splashScreen": {
@@ -76,7 +78,7 @@
"description": "Создайте таблицу и взаимодействуйте с данными, используя командный интерфейс PostgreSQL"
},
"vcoreMongo": {
"title": "Оболочка Mongo",
"title": "Mongo Shell",
"description": "Создайте коллекцию и взаимодействуйте с данными, используя командный интерфейс MongoDB"
}
},
@@ -303,7 +305,7 @@
"deleteContainer": "Удалить {{containerName}}",
"newSqlQuery": "Новый SQL-запрос",
"newQuery": "Новый запрос",
"openMongoShell": "Открыть оболочку Mongo",
"openMongoShell": "Открыть Mongo Shell",
"newShell": "Новая оболочка",
"openCassandraShell": "Открыть оболочку Cassandra",
"newStoredProcedure": "Новая хранимая процедура",
@@ -414,7 +416,7 @@
"refreshGridFailed": "Не удалось обновить сетку документов"
},
"mongoShell": {
"title": "Оболочка Mongo"
"title": "Mongo Shell"
}
},
"panes": {
@@ -488,7 +490,7 @@
"legacySdkInfo": "Чтобы обеспечить совместимость со старыми SDK, созданный контейнер будет использовать устаревшую схему секционирования, которая поддерживает значения ключа секционирования размером не более 101 байта. Если эта возможность включена, использование иерархических ключей секционирования будет недоступно.",
"indexingOnInfo": "По умолчанию все свойства в документах будут индексироваться для гибкости и эффективности запросов.",
"indexingOffInfo": "Индексация будет отключена. Рекомендуется, если не требуется выполнять запросы или используются только операции с ключами.",
"indexingOffWarning": "Если создать контейнер с отключённым индексированием, изменения политики индексирования будут недоступны. Изменения разрешены только для контейнеров с включённой политикой индексирования.",
"indexingOffWarning": "Если создать контейнер с отключённым индексированием, изменения политики индексации будут недоступны. Изменения разрешены только для контейнеров с включённой политикой индексации.",
"acknowledgeSpendErrorMonthly": "Подтвердите, что осведомлены о смете ежемесячных расходов.",
"acknowledgeSpendErrorDaily": "Подтвердите, что осведомлены о смете ежемесячных расходов за день.",
"unshardedMaxRuError": "Неэкстентированные коллекции поддерживают до 10 000 ЕЗ",
@@ -762,7 +764,7 @@
"computedProperties": "Вычисленные свойства",
"containerPolicies": "Правила перевозки контейнеров",
"throughputBuckets": "Группы пропускной способности",
"globalSecondaryIndexPreview": "Глобальный вторичный индекс (предварительный просмотр)",
"globalSecondaryIndexPreview": "Global Secondary Index",
"maskingPolicyPreview": "Политика маскирования"
},
"mongoNotifications": {
@@ -795,17 +797,17 @@
"perMonth": "в месяц"
},
"throughput": {
"manualToAutoscaleDisclaimer": "Начальный максимальный объем автоматически масштабируемых RU/s будет определяться системой на основе текущих настроек пропускной способности, заданных вручную, и объема хранилища ваших ресурсов. После включения автомасштабирования вы можете изменить максимальное значение RU/s.",
"manualToAutoscaleDisclaimer": "Начальный максимальный объем автоматически масштабируемых RU/s будет определяться системой на основе текущих настроек пропускной способности вручную и объема хранилища ваших ресурсов. После включения автомасштабирования вы можете изменить максимальное значение RU/s.",
"ttlWarningText": "Система будет автоматически удалять элементы на основе указанного вами значения TTL (в секундах), без необходимости явного запуска операции удаления клиентским приложением. Для получения более подробной информации см.",
"ttlWarningLinkText": "Время жизни (TTL) в Azure Cosmos DB",
"unsavedIndexingPolicy": "политика индексирования",
"unsavedIndexingPolicy": "политика индексации",
"unsavedDataMaskingPolicy": "политика маскирования данных",
"unsavedComputedProperties": "вычисленные свойства",
"unsavedEditorWarningPrefix": "Вы не сохранили последние изменения, внесенные в ваш",
"unsavedEditorWarningSuffix": ". Нажмите \"Сохранить\", чтобы подтвердить изменения.",
"updateDelayedApplyWarning": "Вы собираетесь запросить увеличение пропускной способности сверх предварительно выделенной мощности. Эта операция займет некоторое время.",
"updateDelayedApplyWarning": "Вы собираетесь запросить увеличение пропускной способности сверх предварительно выделенной емкости. Эта операция займет некоторое время.",
"scalingUpDelayMessage": "Масштабирование займет 4-6 часов, поскольку оно превышает возможности Azure Cosmos DB по мгновенному масштабированию, исходя из количества физических разделов. Вы можете мгновенно увеличить пропускную способность до {{instantMaximumThroughput}} или продолжить с этим значением и дождаться завершения масштабирования.",
"exceedPreAllocatedMessage": "Ваш запрос на увеличение пропускной способности превышает предварительно выделенную мощность, что может занять больше времени, чем ожидалось. Для продолжения вы можете выбрать один из трех вариантов:",
"exceedPreAllocatedMessage": "Ваш запрос на увеличение пропускной способности превышает предварительно выделенную емкость, что может занять больше времени, чем ожидалось. Для продолжения вы можете выбрать один из трех вариантов:",
"instantScaleOption": "Вы можете мгновенно увеличить масштаб до {{instantMaximumThroughput}} RU/s.",
"asyncScaleOption": "Вы можете асинхронно масштабировать систему до любого значения менее {{maximumThroughput}} RU/s за 4-6 часов.",
"quotaMaxOption": "Ваш текущий максимальный лимит квоты составляет {{maximumThroughput}} RU/s. Чтобы превысить этот лимит, необходимо запросить увеличение квоты, и команда Azure Cosmos DB рассмотрит этот запрос.",
@@ -813,8 +815,8 @@
"saveThroughputWarning": "Изменение настроек пропускной способности повлияет на ваш счет за электроэнергию. Перед сохранением изменений ознакомьтесь с обновленной сметой расходов ниже",
"currentAutoscaleThroughput": "Текущая пропускная способность автомасштабирования:",
"targetAutoscaleThroughput": "Целевая пропускная способность автомасштабирования:",
"currentManualThroughput": "Текущая производительность при ручном вводе данных:",
"targetManualThroughput": "Целевой показатель производительности при ручном вводе данных:",
"currentManualThroughput": "Текущая пропускная способность вручную:",
"targetManualThroughput": "Целевой показатель пропускной способности вручную:",
"applyDelayedMessage": "Запрос на увеличение пропускной способности успешно подан. Выполнение этой операции займет от 1 до 3 рабочих дней. Просмотрите актуальный статус в разделе \"Уведомления\".",
"databaseLabel": "База данных:",
"containerLabel": "Контейнер:",
@@ -841,9 +843,9 @@
"mongoIndexing": {
"disclaimer": "Для запросов, фильтрующих по нескольким свойствам, создавайте несколько индексов для отдельных полей вместо составного индекса.",
"disclaimerCompoundIndexesLink": " Составные индексы ",
"disclaimerSuffix": "используются только для сортировки результатов запроса. Если вам нужно добавить составной индекс, вы можете создать его с помощью оболочки Mongo.",
"compoundNotSupported": "В редакторе индексирования пока не поддерживаются коллекции со сложными индексами. Для изменения политики индексирования этой коллекции используйте оболочку Mongo.",
"aadError": "Для использования редактора политик индексирования войдите в",
"disclaimerSuffix": "используются только для сортировки результатов запроса. Если вам нужно добавить составной индекс, вы можете создать его с помощью Mongo Shell.",
"compoundNotSupported": "В редакторе индексации пока не поддерживаются коллекции со сложными индексами. Для изменения политики индексации этой коллекции используйте Mongo Shell.",
"aadError": "Для использования редактора политик индексации войдите в",
"aadErrorLink": "Портал Azure.",
"refreshingProgress": "Обновление хода преобразования индекса",
"canMakeMoreChangesZero": "Дополнительные изменения в индексировании можно внести после завершения текущего преобразования индекса. ",
@@ -928,7 +930,7 @@
"learnMoreSuffix": "о том, как определять глобальные вторичные индексы и как их использовать.",
"jsonAriaLabel": "Глобальный вторичный индекс JSON",
"addIndex": "Добавить индекс",
"settingsTitle": "Глобальные настройки вторичного индекса",
"settingsTitle": "Настройки глобального вторичного индекса",
"sourceContainer": "Исходный контейнер",
"indexDefinition": "Определение глобального вторичного индекса"
},
@@ -992,5 +994,164 @@
"quantizationByteSizeRangeError": "Размер байта квантования должен быть больше 0 и меньше или равен 512",
"indexingSearchListSizeRangeError": "Размер списка поиска при индексировании должен быть не меньше 25 и не больше 500"
}
},
"containerCopy": {
"commandBar": {
"feedbackButtonLabel": "Отзыв",
"feedbackButtonAriaLabel": "Оставить отзыв о заданиях копирования",
"refreshButtonAriaLabel": "Обновить задания копирования",
"createCopyJobButtonLabel": "Создать задание копирования",
"createCopyJobButtonAriaLabel": "Создать задание копирования контейнера"
},
"noCopyJobs": {
"title": "Нет заданий копирования для отображения",
"createCopyJobButtonText": "Создать задание копирования контейнера"
},
"jobDetails": {
"panelTitle": "{{jobName}}",
"panelTitleDefault": "Сведения о задании",
"errorTitle": "Сведения об ошибке",
"selectedContainers": "Выбранные контейнеры"
},
"createCopyJob": {
"panelTitle": "Создать задание копирования"
},
"selectAccount": {
"description": "Выберите целевую учетную запись, в которую нужно выполнить копирование.",
"subscriptionDropdownLabel": "Подписка",
"subscriptionDropdownPlaceholder": "Выберите подписку",
"accountDropdownLabel": "Учетная запись",
"accountDropdownPlaceholder": "Выберите учетную запись"
},
"migrationType": {
"offline": {
"title": "Автономный режим",
"description": "Автономные задания копирования контейнеров позволяют копировать данные из исходного контейнера в целевой контейнер Cosmos DB для поддерживаемых API. Чтобы обеспечить целостность данных между исходным и целевым контейнерами, рекомендуется остановить обновление исходного контейнера перед созданием задания копирования. Подробнее о [заданиях автономного копирования](https://learn.microsoft.com/azure/cosmos-db/how-to-container-copy?tabs=offline-copy&pivots=api-nosql)."
},
"online": {
"title": "Онлайн-режим",
"description": "Онлайн-задания копирования контейнеров позволяют копировать данные из исходного контейнера в целевой контейнер API NoSQL Cosmos DB с помощью канала изменений [Все версии и удаление](https://learn.microsoft.com/azure/cosmos-db/change-feed-modes?tabs=all-versions-and-deletes#all-versions-and-deletes-change-feed-mode-preview). Это позволяет продолжать обновление в источнике во время копирования данных. В конце потребуется кратковременный простой, чтобы безопасно переключить клиентские приложения на целевой контейнер. Подробнее об [заданиях онлайн-копирования](https://learn.microsoft.com/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#getting-started)."
}
},
"selectContainers": {
"description": "Выберите исходный и целевой контейнер для копирования.",
"sourceContainerSubHeading": "Исходный контейнер",
"targetContainerSubHeading": "Целевой контейнер",
"databaseDropdownLabel": "База данных",
"databaseDropdownPlaceholder": "Выберите базу данных",
"containerDropdownLabel": "Контейнер",
"containerDropdownPlaceholder": "Выберите контейнер",
"createNewContainerSubHeading": "Настройте свойства нового контейнера в целевой учетной записи \"{{accountName}}\".",
"createNewContainerSubHeadingDefault": "Настройте свойства нового контейнера.",
"createContainerButtonLabel": "Создать контейнер",
"createContainerHeading": "Создать контейнер"
},
"preview": {
"jobNameLabel": "Имя задания",
"subscriptionLabel": "Целевая подписка",
"accountLabel": "Целевая учетная запись",
"sourceDatabaseLabel": "База данных-источник",
"sourceContainerLabel": "Исходный контейнер",
"targetDatabaseLabel": "Целевая база данных",
"targetContainerLabel": "Целевой контейнер"
},
"assignPermissions": {
"crossAccountDescription": "Чтобы скопировать данные из исходного в целевой контейнер, выполните следующие действия и убедитесь, что управляемое удостоверение исходной учетной записи обладает доступом на чтение и запись в целевой учетной записи.",
"intraAccountOnlineDescription": "Выполните следующие действия, чтобы включить онлайн-копирование в учетной записи \"{{accountName}}\".",
"crossAccountConfiguration": {
"title": "Копирование контейнера между учетными записями",
"description": "Следуйте приведенным ниже инструкциям, чтобы предоставить необходимые разрешения для копирования данных из \"{{sourceAccount}}\" в \"{{destinationAccount}}\"."
},
"onlineConfiguration": {
"title": "Онлайн-копирование контейнера",
"description": "Следуйте инструкциям ниже, чтобы включить онлайн-копирование в учетной записи \"{{accountName}}\"."
}
},
"popoverOverlaySpinnerLabel": "Подождите, пока мы обрабатываем ваш запрос...",
"addManagedIdentity": {
"title": "Управляемое удостоверение, назначаемое системой, включено.",
"description": "Количество управляемых удостоверений, назначаемых системой, ограничено одним удостоверением на ресурс, привязанным к жизненному циклу этого ресурса. После включения вы можете предоставить разрешения управляемому удостоверению с помощью управления доступом на основе ролей Azure (Azure RBAC). Для управляемого удостоверения проверка подлинности выполняется с помощью Microsoft Entra ID, что избавляет вас от необходимости хранить учетные данные в программном коде.",
"descriptionHrefText": "Дополнительные сведения об управляемых удостоверениях.",
"descriptionHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"toggleLabel": "Управляемое удостоверение, назначаемое системой",
"tooltipContent": "Подробнее о",
"tooltipHrefText": "Управляемые удостоверения.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"userAssignedIdentityTooltip": "Вы можете выбрать существующее удостоверение, назначаемое пользователем, или создать новое.",
"userAssignedIdentityLabel": "Вы также можете выбрать управляемое удостоверение, назначаемое пользователем.",
"createUserAssignedIdentityLink": "Создание управляемого удостоверения, назначаемого пользователем",
"enablementTitle": "Включение управляемого удостоверения, назначаемого системой",
"enablementDescription": "Включить управляемое удостоверение, назначаемое системой, в {{accountName}}. Для подтверждения нажмите кнопку \"Да\"."
},
"defaultManagedIdentity": {
"title": "Управляемое удостоверение, назначаемое системой, настроено в качестве используемого по умолчанию.",
"description": "Назначьте управляемое удостоверение, назначаемое системой, в качестве используемого по умолчанию для \"{{accountName}}\", включив его.",
"tooltipContent": "Подробнее о",
"tooltipHrefText": "Управляемые удостоверения по умолчанию.",
"tooltipHref": "https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview",
"popoverTitle": "Управляемое удостоверение, назначаемое системой, настроено в качестве используемого по умолчанию",
"popoverDescription": "Назначьте управляемое удостоверение, назначаемое системой, в качестве используемого по умолчанию для \"{{accountName}}\". Для подтверждения нажмите кнопку \"Да\". "
},
"readWritePermissionAssigned": {
"title": "Разрешения на чтение и запись, назначенные удостоверению, используемому по умолчанию.",
"description": "Чтобы разрешить копирование данных из исходного в целевой контейнер, предоставьте удостоверению по умолчанию исходной учетной записи доступ на чтение и запись в целевой учетной записи.",
"tooltipContent": "Подробнее о",
"tooltipHrefText": "Разрешения на чтение и записи.",
"tooltipHref": "https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-connect-role-based-access-control",
"popoverTitle": "Назначьте удостоверению по умолчанию разрешения на чтение и запись.",
"popoverDescription": "Назначьте удостоверению по умолчанию исходной учетной записи разрешения на чтение и запись в целевой учетной записи. Для подтверждения нажмите кнопку \"Да\"."
},
"pointInTimeRestore": {
"title": "Восстановление до точки во времени включено",
"description": "Чтобы упростить задания онлайн-копирования контейнеров, обновите политику резервного копирования \"{{accessName}}\" с периодического на непрерывное резервное копирование. Для этой функции необходимо включить непрерывное резервное копирование.",
"tooltipContent": "Подробнее о",
"tooltipHrefText": "Непрерывное резервное копирование",
"tooltipHref": "https://learn.microsoft.com/en-us/azure/cosmos-db/continuous-backup-restore-introduction",
"buttonText": "Включить восстановление до точки во времени"
},
"onlineCopyEnabled": {
"title": "Онлайн-копирование включено",
"description": "Включите онлайн-копирование контейнера, нажав кнопку ниже в учетной записи \"{{accountName}}\".",
"hrefText": "Подробнее о заданиях онлайн-копирования",
"href": "https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy?tabs=online-copy&pivots=api-nosql#enable-online-copy",
"buttonText": "Включить онлайн-копирование",
"validateAllVersionsAndDeletesChangeFeedSpinnerLabel": "Проверка режима канала изменений для всех версий и удалений (предварительная версия)...",
"enablingAllVersionsAndDeletesChangeFeedSpinnerLabel": "Включение режима канала изменений для всех версий и удалений (предварительная версия)...",
"enablingOnlineCopySpinnerLabel": "Включение онлайн-копирования для вашей учетной записи \"{{accountName}}\"..."
},
"monitorJobs": {
"columns": {
"lastUpdatedTime": "Дата и время",
"name": "Имя задания",
"status": "Состояние",
"completionPercentage": "Процент выполнения",
"duration": "Длительность",
"error": "Сообщение об ошибке",
"mode": "Режим",
"actions": "Действия"
},
"actions": {
"pause": "Приостановить",
"resume": "Возобновить",
"complete": "Выполнить",
"viewDetails": "Просмотреть подробности"
},
"status": {
"pending": "В очереди",
"inProgress": "Выполняется",
"running": "Выполняется",
"partitioning": "Выполняется",
"paused": "Приостановлено",
"completed": "Выполнено",
"failed": "Сбой",
"faulted": "Сбой",
"skipped": "Отменено",
"cancelled": "Отменено"
},
"dialog": {
"confirmButtonText": "Подтвердить",
"cancelButtonText": "Отмена"
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More