diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 284405b54..5d8db8a78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,8 +164,8 @@ jobs: strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] - shardTotal: [16] + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + shardTotal: [20] steps: - uses: actions/checkout@v4 - name: Use Node.js 18.x @@ -198,18 +198,18 @@ jobs: GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken) echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN" echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV - CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN" - echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN" - echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN" - echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV - MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken) - echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN" - echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV + # CASSANDRA_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-cassandra.documents.azure.com/.default" -o tsv --query accessToken) + # echo "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN" + # echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV + # MONGO_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo.documents.azure.com/.default" -o tsv --query accessToken) + # echo "::add-mask::$MONGO_TESTACCOUNT_TOKEN" + # echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV + # MONGO32_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo32.documents.azure.com/.default" -o tsv --query accessToken) + # echo "::add-mask::$MONGO32_TESTACCOUNT_TOKEN" + # echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV + # MONGO_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-mongo-readonly.documents.azure.com/.default" -o tsv --query accessToken) + # echo "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN" + # echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV - name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3 - name: Upload blob report to GitHub Actions Artifacts @@ -250,4 +250,4 @@ jobs: with: name: html-report--attempt-${{ github.run_attempt }} path: playwright-report - retention-days: 14 + retention-days: 14 \ No newline at end of file diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 6eed6ca0b..ece8c8dba 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -6,8 +6,8 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: schedule: - # Once every hour - - cron: "0 15 * * *" + # Once every two hours + - cron: "0 */2 * * *" permissions: id-token: write @@ -36,4 +36,4 @@ jobs: with: node-version: 18.x - run: npm ci - - run: node utils/cleanupDBs.js + - run: node utils/cleanupDBs.js \ No newline at end of file diff --git a/less/documentDBFabric.less b/less/documentDBFabric.less index 7e3c15429..c1c0ec00c 100644 --- a/less/documentDBFabric.less +++ b/less/documentDBFabric.less @@ -218,6 +218,7 @@ a:focus { .tabPanesContainer { overflow: auto !important; + display: flex; } .tabs-container { diff --git a/package-lock.json b/package-lock.json index 0663caa6d..b3f8d655e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,8 +116,8 @@ "tinykeys": "2.1.0", "underscore": "1.12.1", "utility-types": "3.10.0", - "web-vitals": "4.2.4", "uuid": "9.0.0", + "web-vitals": "4.2.4", "zustand": "3.5.0" }, "devDependencies": { diff --git a/playwright.config.ts b/playwright.config.ts index b1f6a622d..228a9373b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -11,8 +11,8 @@ export default defineConfig({ reporter: process.env.CI ? "blob" : "html", timeout: 10 * 60 * 1000, use: { - trace: "off", - video: "off", + trace: "retain-on-failure", + video: "retain-on-failure", screenshot: "on", testIdAttribute: "data-test", contextOptions: { diff --git a/src/Common/LoadingOverlay.tsx b/src/Common/LoadingOverlay.tsx index 320576533..2cbf34213 100644 --- a/src/Common/LoadingOverlay.tsx +++ b/src/Common/LoadingOverlay.tsx @@ -13,6 +13,7 @@ const LoadingOverlay: React.FC = ({ isLoading, label }) => return (
{ const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`); try { + let resource: StoredProcedureDefinition & Resource; if ( userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations && @@ -60,14 +61,16 @@ export async function createStoredProcedure( storedProcedure.id, createSprocParams, ); - return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource); + resource = rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource); + } else { + const response = await client() + .database(databaseId) + .container(collectionId) + .scripts.storedProcedures.create(storedProcedure); + resource = response.resource; } - - const response = await client() - .database(databaseId) - .container(collectionId) - .scripts.storedProcedures.create(storedProcedure); - return response?.resource; + logConsoleInfo(`Successfully created stored procedure ${storedProcedure.id}`); + return resource; } catch (error) { handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`); throw error; diff --git a/src/Common/dataAccess/dataTransfers.ts b/src/Common/dataAccess/dataTransfers.ts index e639f9965..3e8829486 100644 --- a/src/Common/dataAccess/dataTransfers.ts +++ b/src/Common/dataAccess/dataTransfers.ts @@ -1,3 +1,4 @@ +import { configContext } from "ConfigContext"; import { ApiType, userContext } from "UserContext"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils"; import { @@ -14,9 +15,12 @@ import { DataTransferJobFeedResults, DataTransferJobGetResults, } from "Utils/arm/generatedClients/dataTransferService/types"; +import { armRequest } from "Utils/arm/request"; import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs"; import promiseRetry, { AbortError, FailedAttemptError } from "p-retry"; +export const DATA_TRANSFER_JOB_API_VERSION = "2025-05-01-preview"; + export interface DataTransferParams { jobName: string; apiType: ApiType; @@ -33,26 +37,34 @@ export const getDataTransferJobs = async ( subscriptionId: string, resourceGroup: string, accountName: string, + signal?: AbortSignal, ): Promise => { let dataTransferJobs: DataTransferJobGetResults[] = []; let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount( subscriptionId, resourceGroup, accountName, + signal, ); dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])]; while (dataTransferFeeds?.nextLink) { - const nextResponse = await window.fetch(dataTransferFeeds.nextLink, { - headers: { - Authorization: userContext.authorizationToken, - }, + /** + * The `nextLink` URL returned by the Cosmos DB SQL API pointed to an incorrect endpoint, causing timeouts. + * (i.e: https://cdbmgmtprodby.documents.azure.com:450/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.DocumentDB/databaseAccounts/{account}/sql/dataTransferJobs?$top=100&$skiptoken=...) + * We manipulate the URL by parsing it to extract the path and query parameters, + * then construct the correct URL for the Azure Resource Manager (ARM) API. + * This ensures that the request is made to the correct base URL (`configContext.ARM_ENDPOINT`), + * which is required for ARM operations. + */ + const parsedUrl = new URL(dataTransferFeeds.nextLink); + const nextUrlPath = parsedUrl.pathname + parsedUrl.search; + dataTransferFeeds = await armRequest({ + host: configContext.ARM_ENDPOINT, + path: nextUrlPath, + method: "GET", + apiVersion: DATA_TRANSFER_JOB_API_VERSION, }); - if (nextResponse.ok) { - dataTransferFeeds = await nextResponse.json(); - dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])]; - } else { - break; - } + dataTransferJobs.push(...(dataTransferFeeds?.value || [])); } return dataTransferJobs; }; diff --git a/src/Common/dataAccess/deleteTrigger.ts b/src/Common/dataAccess/deleteTrigger.ts index 22b77f009..568f4cefe 100644 --- a/src/Common/dataAccess/deleteTrigger.ts +++ b/src/Common/dataAccess/deleteTrigger.ts @@ -24,6 +24,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr } else { await client().database(databaseId).container(collectionId).scripts.trigger(triggerId).delete(); } + logConsoleProgress(`Successfully deleted trigger ${triggerId}`); } catch (error) { handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`); throw error; diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx index 1cff2c213..76dca972b 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/AddManagedIdentity.tsx @@ -35,6 +35,7 @@ const AddManagedIdentity: React.FC = () => { = ({ id, title, Component, completed, disabled }) => ( - + {title} @@ -25,13 +25,13 @@ const PermissionSection: React.FC = ({ id, title, Compo height={completed ? 20 : 24} /> - + ); -const PermissionGroup: React.FC = ({ title, description, sections }) => { +const PermissionGroup: React.FC = ({ id, title, description, sections }) => { const [openItems, setOpenItems] = React.useState([]); useEffect(() => { @@ -44,6 +44,7 @@ const PermissionGroup: React.FC = ({ title, description, return ( { }, []); return ( - + {isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online ? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription( diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx index 69e12e72e..3eeb60bbf 100644 --- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx +++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx @@ -31,6 +31,7 @@ const DefaultManagedIdentity: React.FC = () => {
{ {showRefreshButton ? ( { /> ) : (
Incomplete Component @@ -142,6 +147,7 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
Incomplete Component @@ -339,6 +350,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
Incomplete Component @@ -536,6 +553,7 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
Incomplete Component @@ -733,6 +756,7 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
@@ -301,6 +305,7 @@ exports[`PointInTimeRestore Snapshots should match snapshot with refresh button