Compare commits

..

1 Commits

Author SHA1 Message Date
Sakshi Gupta
8eade71456 dark mode for header 2026-01-02 14:36:42 +05:30
55 changed files with 429 additions and 980 deletions

View File

@@ -164,8 +164,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
shardTotal: [20] shardTotal: [16]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Use Node.js 18.x - 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) 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 "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV 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) 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 "::add-mask::$CASSANDRA_TESTACCOUNT_TOKEN"
# echo CASSANDRA_TESTACCOUNT_TOKEN=$CASSANDRA_TESTACCOUNT_TOKEN >> $GITHUB_ENV 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) 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 "::add-mask::$MONGO_TESTACCOUNT_TOKEN"
# echo MONGO_TESTACCOUNT_TOKEN=$MONGO_TESTACCOUNT_TOKEN >> $GITHUB_ENV 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) 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 "::add-mask::$MONGO32_TESTACCOUNT_TOKEN"
# echo MONGO32_TESTACCOUNT_TOKEN=$MONGO32_TESTACCOUNT_TOKEN >> $GITHUB_ENV 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) 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 "::add-mask::$MONGO_READONLY_TESTACCOUNT_TOKEN"
# echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV echo MONGO_READONLY_TESTACCOUNT_TOKEN=$MONGO_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} - name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3 run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
- name: Upload blob report to GitHub Actions Artifacts - name: Upload blob report to GitHub Actions Artifacts
@@ -250,4 +250,4 @@ jobs:
with: with:
name: html-report--attempt-${{ github.run_attempt }} name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report path: playwright-report
retention-days: 14 retention-days: 14

View File

@@ -6,8 +6,8 @@ on:
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
schedule: schedule:
# Once every two hours # Once every hour
- cron: "0 */2 * * *" - cron: "0 15 * * *"
permissions: permissions:
id-token: write id-token: write
@@ -36,4 +36,4 @@ jobs:
with: with:
node-version: 18.x node-version: 18.x
- run: npm ci - run: npm ci
- run: node utils/cleanupDBs.js - run: node utils/cleanupDBs.js

View File

@@ -218,7 +218,6 @@ a:focus {
.tabPanesContainer { .tabPanesContainer {
overflow: auto !important; overflow: auto !important;
display: flex;
} }
.tabs-container { .tabs-container {

View File

@@ -17,6 +17,38 @@
position: fixed; position: fixed;
top: -200px; top: -200px;
} }
body.isDarkMode .ms-Layer {
.ms-Callout-main {
background-color: @BaseHigh !important;
}
.ms-Callout-beak {
background-color: @BaseHigh !important;
}
.ms-ContextualMenu {
background-color: @BaseHigh !important;
}
.ms-Dropdown-items {
background-color: @BaseHigh !important;
}
.ms-Dropdown-item {
background-color: @BaseHigh !important;
color: @BaseLight !important;
&:hover {
background-color: @BaseMediumHigh !important;
color: @BaseLight !important;
}
&.is-selected {
background-color: @BaseMediumHigh !important;
color: @BaseLight !important;
}
}
}
html { html {
font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@@ -129,6 +161,65 @@ body {
} }
} }
&.isDarkMode .accountSwitchContextualMenu {
background-color: @BaseHigh;
.ms-Callout-main {
background-color: @BaseHigh;
}
.ms-ContextualMenu-item {
background-color: @BaseHigh;
}
.ms-Dropdown {
.ms-Dropdown-title {
background-color: @BaseDark;
color: @BaseLight;
border-color: @BaseMediumHigh;
}
.ms-Dropdown-caretDownWrapper {
color: @BaseLight;
}
&:hover .ms-Dropdown-title {
background-color: @BaseHigh;
color: @BaseLight;
border-color: @BaseMedium;
}
}
.ms-Label {
color: @BaseLight;
}
}
&.isDarkMode .ms-Dropdown-callout {
.ms-Callout-main {
background-color: @BaseHigh;
}
.ms-Dropdown-items {
background-color: @BaseHigh;
}
.ms-Dropdown-item {
background-color: @BaseHigh;
color: @BaseLight;
&:hover {
background-color: @BaseMediumHigh;
color: @BaseLight;
}
&.is-selected {
background-color: @BaseMediumHigh;
color: @BaseLight;
}
}
}
.fixedleftpane { .fixedleftpane {
background: @BaseLow; background: @BaseLow;
height: 100vh; height: 100vh;

2
package-lock.json generated
View File

@@ -116,8 +116,8 @@
"tinykeys": "2.1.0", "tinykeys": "2.1.0",
"underscore": "1.12.1", "underscore": "1.12.1",
"utility-types": "3.10.0", "utility-types": "3.10.0",
"uuid": "9.0.0",
"web-vitals": "4.2.4", "web-vitals": "4.2.4",
"uuid": "9.0.0",
"zustand": "3.5.0" "zustand": "3.5.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -11,8 +11,8 @@ export default defineConfig({
reporter: process.env.CI ? "blob" : "html", reporter: process.env.CI ? "blob" : "html",
timeout: 10 * 60 * 1000, timeout: 10 * 60 * 1000,
use: { use: {
trace: "retain-on-failure", trace: "off",
video: "retain-on-failure", video: "off",
screenshot: "on", screenshot: "on",
testIdAttribute: "data-test", testIdAttribute: "data-test",
contextOptions: { contextOptions: {

View File

@@ -13,7 +13,6 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) =>
return ( return (
<Overlay <Overlay
data-test="loading-overlay"
styles={{ styles={{
root: { root: {
backgroundColor: "rgba(255,255,255,0.9)", backgroundColor: "rgba(255,255,255,0.9)",

View File

@@ -1,65 +0,0 @@
import { IButtonStyles, IStackStyles, ITextStyles } from "@fluentui/react";
import * as React from "react";
export const getDropdownButtonStyles = (disabled: boolean): IButtonStyles => ({
root: {
width: "100%",
height: "32px",
padding: "0 28px 0 8px",
border: "1px solid #8a8886",
background: "#fff",
color: "#323130",
textAlign: "left",
cursor: disabled ? "not-allowed" : "pointer",
position: "relative",
},
label: {
fontWeight: "normal",
fontSize: "14px",
},
});
export const buttonLabelStyles: ITextStyles = {
root: {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
display: "block",
},
};
export const chevronStyles: React.CSSProperties = {
position: "absolute",
right: "8px",
top: "50%",
transform: "translateY(-50%)",
pointerEvents: "none",
};
export const calloutContentStyles: IStackStyles = {
root: {
display: "flex",
flexDirection: "column",
},
};
export const listContainerStyles: IStackStyles = {
root: {
maxHeight: "300px",
overflowY: "auto",
},
};
export const getItemStyles = (isSelected: boolean): React.CSSProperties => ({
padding: "8px 12px",
cursor: "pointer",
fontSize: "14px",
backgroundColor: isSelected ? "#e6e6e6" : "transparent",
});
export const emptyMessageStyles: ITextStyles = {
root: {
padding: "8px 12px",
color: "#605e5c",
},
};

View File

@@ -1,200 +0,0 @@
import { fireEvent, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import React from "react";
import { SearchableDropdown } from "./SearchableDropdown";
interface TestItem {
id: string;
name: string;
}
describe("SearchableDropdown", () => {
const mockItems: TestItem[] = [
{ id: "1", name: "Item One" },
{ id: "2", name: "Item Two" },
{ id: "3", name: "Item Three" },
];
const defaultProps = {
label: "Test Label",
items: mockItems,
selectedItem: null,
onSelect: jest.fn(),
getKey: (item: TestItem) => item.id,
getDisplayText: (item: TestItem) => item.name,
placeholder: "Select an item",
filterPlaceholder: "Filter items",
className: "test-dropdown",
};
beforeEach(() => {
jest.clearAllMocks();
});
it("should render with label and placeholder", () => {
render(<SearchableDropdown {...defaultProps} />);
expect(screen.getByText("Test Label")).toBeInTheDocument();
expect(screen.getByText("Select an item")).toBeInTheDocument();
});
it("should display selected item", () => {
const propsWithSelection = {
...defaultProps,
selectedItem: mockItems[0],
};
render(<SearchableDropdown {...propsWithSelection} />);
expect(screen.getByText("Item One")).toBeInTheDocument();
});
it("should show 'No items found' when items array is empty", () => {
const propsWithEmptyItems = {
...defaultProps,
items: [],
};
render(<SearchableDropdown {...propsWithEmptyItems} />);
expect(screen.getByText("No Test Labels Found")).toBeInTheDocument();
});
it("should open dropdown when button is clicked", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
expect(screen.getByPlaceholderText("Filter items")).toBeInTheDocument();
});
it("should filter items based on search text", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
const searchBox = screen.getByPlaceholderText("Filter items");
fireEvent.change(searchBox, { target: { value: "Two" } });
expect(screen.getByText("Item Two")).toBeInTheDocument();
expect(screen.queryByText("Item One")).not.toBeInTheDocument();
expect(screen.queryByText("Item Three")).not.toBeInTheDocument();
});
it("should call onSelect when an item is clicked", () => {
const onSelectMock = jest.fn();
const propsWithMock = {
...defaultProps,
onSelect: onSelectMock,
};
render(<SearchableDropdown {...propsWithMock} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
const item = screen.getByText("Item Two");
fireEvent.click(item);
expect(onSelectMock).toHaveBeenCalledWith(mockItems[1]);
});
it("should close dropdown after selecting an item", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
expect(screen.getByPlaceholderText("Filter items")).toBeInTheDocument();
const item = screen.getByText("Item One");
fireEvent.click(item);
expect(screen.queryByPlaceholderText("Filter items")).not.toBeInTheDocument();
});
it("should disable button when disabled prop is true", () => {
const propsWithDisabled = {
...defaultProps,
disabled: true,
};
render(<SearchableDropdown {...propsWithDisabled} />);
const button = screen.getByRole("button");
expect(button).toBeDisabled();
});
it("should not open dropdown when disabled", () => {
const propsWithDisabled = {
...defaultProps,
disabled: true,
};
render(<SearchableDropdown {...propsWithDisabled} />);
const button = screen.getByRole("button");
fireEvent.click(button);
expect(screen.queryByPlaceholderText("Filter items")).not.toBeInTheDocument();
});
it("should show 'No items found' when search yields no results", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
const searchBox = screen.getByPlaceholderText("Filter items");
fireEvent.change(searchBox, { target: { value: "Nonexistent" } });
expect(screen.getByText("No items found")).toBeInTheDocument();
});
it("should handle case-insensitive filtering", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
const searchBox = screen.getByPlaceholderText("Filter items");
fireEvent.change(searchBox, { target: { value: "two" } });
expect(screen.getByText("Item Two")).toBeInTheDocument();
expect(screen.queryByText("Item One")).not.toBeInTheDocument();
});
it("should clear filter text when dropdown is closed and reopened", () => {
render(<SearchableDropdown {...defaultProps} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
const searchBox = screen.getByPlaceholderText("Filter items");
fireEvent.change(searchBox, { target: { value: "Two" } });
// Close dropdown by selecting an item
const item = screen.getByText("Item Two");
fireEvent.click(item);
// Reopen dropdown
fireEvent.click(button);
// Filter text should be cleared
const reopenedSearchBox = screen.getByPlaceholderText("Filter items");
expect(reopenedSearchBox).toHaveValue("");
});
it("should use custom placeholder text", () => {
const propsWithCustomPlaceholder = {
...defaultProps,
placeholder: "Choose an option",
};
render(<SearchableDropdown {...propsWithCustomPlaceholder} />);
expect(screen.getByText("Choose an option")).toBeInTheDocument();
});
it("should use custom filter placeholder text", () => {
const propsWithCustomFilterPlaceholder = {
...defaultProps,
filterPlaceholder: "Search here",
};
render(<SearchableDropdown {...propsWithCustomFilterPlaceholder} />);
const button = screen.getByText("Select an item");
fireEvent.click(button);
expect(screen.getByPlaceholderText("Search here")).toBeInTheDocument();
});
});

View File

@@ -1,155 +0,0 @@
import {
Callout,
DefaultButton,
DirectionalHint,
ISearchBoxStyles,
Label,
SearchBox,
Stack,
Text,
} from "@fluentui/react";
import * as React from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import {
buttonLabelStyles,
calloutContentStyles,
chevronStyles,
emptyMessageStyles,
getDropdownButtonStyles,
getItemStyles,
listContainerStyles,
} from "./SearchableDropdown.styles";
interface SearchableDropdownProps<T> {
label: string;
items: T[];
selectedItem: T | null;
onSelect: (item: T) => void;
getKey: (item: T) => string;
getDisplayText: (item: T) => string;
placeholder?: string;
filterPlaceholder?: string;
className?: string;
disabled?: boolean;
onDismiss?: () => void;
searchBoxStyles?: Partial<ISearchBoxStyles>;
}
export const SearchableDropdown = <T,>({
label,
items,
selectedItem,
onSelect,
getKey,
getDisplayText,
placeholder = "Select an item",
filterPlaceholder = "Filter items",
className,
disabled = false,
onDismiss,
searchBoxStyles: customSearchBoxStyles,
}: SearchableDropdownProps<T>): React.ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const [filterText, setFilterText] = useState("");
const buttonRef = useRef<HTMLDivElement>(null);
const closeDropdown = useCallback(() => {
setIsOpen(false);
setFilterText("");
}, []);
const filteredItems = useMemo(
() => items?.filter((item) => getDisplayText(item).toLowerCase().includes(filterText.toLowerCase())),
[items, filterText, getDisplayText],
);
const handleDismiss = useCallback(() => {
closeDropdown();
onDismiss?.();
}, [closeDropdown, onDismiss]);
const handleButtonClick = useCallback(() => {
if (disabled) {
return;
}
setIsOpen(!isOpen);
}, [isOpen, disabled]);
const handleSelect = useCallback(
(item: T) => {
onSelect(item);
closeDropdown();
},
[onSelect, closeDropdown],
);
const buttonLabel = selectedItem
? getDisplayText(selectedItem)
: items?.length === 0
? `No ${label}s Found`
: placeholder;
const buttonId = `${className}-button`;
const buttonStyles = getDropdownButtonStyles(disabled);
return (
<Stack>
<Label htmlFor={buttonId}>{label}</Label>
<div ref={buttonRef}>
<DefaultButton
id={buttonId}
className={className}
onClick={handleButtonClick}
styles={buttonStyles}
disabled={disabled}
>
<Text styles={buttonLabelStyles}>{buttonLabel}</Text>
<span style={chevronStyles}></span>
</DefaultButton>
</div>
{isOpen && (
<Callout
target={buttonRef.current}
onDismiss={handleDismiss}
directionalHint={DirectionalHint.bottomLeftEdge}
isBeakVisible={false}
gapSpace={0}
setInitialFocus
>
<Stack styles={calloutContentStyles} style={{ width: buttonRef.current?.offsetWidth || 300 }}>
<SearchBox
placeholder={filterPlaceholder}
value={filterText}
onChange={(_, newValue) => setFilterText(newValue || "")}
styles={customSearchBoxStyles}
/>
<Stack styles={listContainerStyles}>
{filteredItems && filteredItems.length > 0 ? (
filteredItems.map((item) => {
const key = getKey(item);
const isSelected = selectedItem ? getKey(selectedItem) === key : false;
return (
<div
key={key}
onClick={() => handleSelect(item)}
style={getItemStyles(isSelected)}
onMouseEnter={(e) => (e.currentTarget.style.backgroundColor = "#f3f2f1")}
onMouseLeave={(e) =>
(e.currentTarget.style.backgroundColor = isSelected ? "#e6e6e6" : "transparent")
}
>
<Text>{getDisplayText(item)}</Text>
</div>
);
})
) : (
<Text styles={emptyMessageStyles}>No items found</Text>
)}
</Stack>
</Stack>
</Callout>
)}
</Stack>
);
};

View File

@@ -3,7 +3,6 @@
exports[`LoadingOverlay should handle long labels properly 1`] = ` exports[`LoadingOverlay should handle long labels properly 1`] = `
<div <div
class="ms-Overlay root-109" class="ms-Overlay root-109"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-111" class="ms-Spinner root-111"
@@ -23,7 +22,6 @@ exports[`LoadingOverlay should handle long labels properly 1`] = `
exports[`LoadingOverlay should render loading overlay when isLoading is true 1`] = ` exports[`LoadingOverlay should render loading overlay when isLoading is true 1`] = `
<div <div
class="ms-Overlay root-109" class="ms-Overlay root-109"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-111" class="ms-Spinner root-111"
@@ -43,7 +41,6 @@ exports[`LoadingOverlay should render loading overlay when isLoading is true 1`]
exports[`LoadingOverlay should render loading overlay with custom label 1`] = ` exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
<div <div
class="ms-Overlay root-109" class="ms-Overlay root-109"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-111" class="ms-Spinner root-111"
@@ -63,7 +60,6 @@ exports[`LoadingOverlay should render loading overlay with custom label 1`] = `
exports[`LoadingOverlay should render loading overlay with empty label 1`] = ` exports[`LoadingOverlay should render loading overlay with empty label 1`] = `
<div <div
class="ms-Overlay root-109" class="ms-Overlay root-109"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-111" class="ms-Spinner root-111"

View File

@@ -1,4 +1,3 @@
import { configContext } from "ConfigContext";
import { ApiType, userContext } from "UserContext"; import { ApiType, userContext } from "UserContext";
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import { import {
@@ -15,12 +14,9 @@ import {
DataTransferJobFeedResults, DataTransferJobFeedResults,
DataTransferJobGetResults, DataTransferJobGetResults,
} from "Utils/arm/generatedClients/dataTransferService/types"; } from "Utils/arm/generatedClients/dataTransferService/types";
import { armRequest } from "Utils/arm/request";
import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs"; import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs";
import promiseRetry, { AbortError, FailedAttemptError } from "p-retry"; import promiseRetry, { AbortError, FailedAttemptError } from "p-retry";
export const DATA_TRANSFER_JOB_API_VERSION = "2025-05-01-preview";
export interface DataTransferParams { export interface DataTransferParams {
jobName: string; jobName: string;
apiType: ApiType; apiType: ApiType;
@@ -37,34 +33,26 @@ export const getDataTransferJobs = async (
subscriptionId: string, subscriptionId: string,
resourceGroup: string, resourceGroup: string,
accountName: string, accountName: string,
signal?: AbortSignal,
): Promise<DataTransferJobGetResults[]> => { ): Promise<DataTransferJobGetResults[]> => {
let dataTransferJobs: DataTransferJobGetResults[] = []; let dataTransferJobs: DataTransferJobGetResults[] = [];
let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount( let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount(
subscriptionId, subscriptionId,
resourceGroup, resourceGroup,
accountName, accountName,
signal,
); );
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])]; dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
while (dataTransferFeeds?.nextLink) { while (dataTransferFeeds?.nextLink) {
/** const nextResponse = await window.fetch(dataTransferFeeds.nextLink, {
* The `nextLink` URL returned by the Cosmos DB SQL API pointed to an incorrect endpoint, causing timeouts. headers: {
* (i.e: https://cdbmgmtprodby.documents.azure.com:450/subscriptions/{subId}/resourceGroups/{rg}/providers/Microsoft.DocumentDB/databaseAccounts/{account}/sql/dataTransferJobs?$top=100&$skiptoken=...) Authorization: userContext.authorizationToken,
* 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,
}); });
dataTransferJobs.push(...(dataTransferFeeds?.value || [])); if (nextResponse.ok) {
dataTransferFeeds = await nextResponse.json();
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
} else {
break;
}
} }
return dataTransferJobs; return dataTransferJobs;
}; };

View File

@@ -35,7 +35,6 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
<InfoTooltip content={managedIdentityTooltip} /> <InfoTooltip content={managedIdentityTooltip} />
</Text> </Text>
<Toggle <Toggle
data-test="btn-toggle"
checked={systemAssigned} checked={systemAssigned}
onText={ContainerCopyMessages.toggleBtn.onText} onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText} offText={ContainerCopyMessages.toggleBtn.offText}

View File

@@ -65,7 +65,6 @@ const AddReadPermissionToDefaultIdentity: React.FC<AddReadPermissionToDefaultIde
<InfoTooltip content={TooltipContent} /> <InfoTooltip content={TooltipContent} />
</Text> </Text>
<Toggle <Toggle
data-test="btn-toggle"
checked={readPermissionAssigned} checked={readPermissionAssigned}
onText={ContainerCopyMessages.toggleBtn.onText} onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText} offText={ContainerCopyMessages.toggleBtn.offText}

View File

@@ -12,7 +12,7 @@ import { useCopyJobPrerequisitesCache } from "../../Utils/useCopyJobPrerequisite
import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection"; import usePermissionSections, { PermissionGroupConfig, PermissionSectionConfig } from "./hooks/usePermissionsSection";
const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => ( const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Component, completed, disabled }) => (
<AccordionItem key={id} value={id} disabled={disabled} data-test="accordion-item"> <AccordionItem key={id} value={id} disabled={disabled}>
<AccordionHeader className="accordionHeader"> <AccordionHeader className="accordionHeader">
<Text className="accordionHeaderText" variant="medium"> <Text className="accordionHeaderText" variant="medium">
{title} {title}
@@ -25,13 +25,13 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({ id, title, Compo
height={completed ? 20 : 24} height={completed ? 20 : 24}
/> />
</AccordionHeader> </AccordionHeader>
<AccordionPanel aria-disabled={disabled} className="accordionPanel" data-test="accordion-panel"> <AccordionPanel aria-disabled={disabled} className="accordionPanel">
<Component /> <Component />
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
); );
const PermissionGroup: React.FC<PermissionGroupConfig> = ({ id, title, description, sections }) => { const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description, sections }) => {
const [openItems, setOpenItems] = React.useState<string[]>([]); const [openItems, setOpenItems] = React.useState<string[]>([]);
useEffect(() => { useEffect(() => {
@@ -44,7 +44,6 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ id, title, descripti
return ( return (
<Stack <Stack
data-test={`permission-group-container-${id}`}
tokens={{ childrenGap: 15 }} tokens={{ childrenGap: 15 }}
styles={{ styles={{
root: { root: {
@@ -100,11 +99,7 @@ const AssignPermissions = () => {
}, []); }, []);
return ( return (
<Stack <Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}>
data-test="Panel:AssignPermissionsContainer"
className="assignPermissionsContainer"
tokens={{ childrenGap: 20 }}
>
<Text variant="medium"> <Text variant="medium">
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online {isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription( ? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(

View File

@@ -31,7 +31,6 @@ const DefaultManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
<InfoTooltip content={managedIdentityTooltip} /> <InfoTooltip content={managedIdentityTooltip} />
</div> </div>
<Toggle <Toggle
data-test="btn-toggle"
checked={defaultSystemAssigned} checked={defaultSystemAssigned}
onText={ContainerCopyMessages.toggleBtn.onText} onText={ContainerCopyMessages.toggleBtn.onText}
offText={ContainerCopyMessages.toggleBtn.offText} offText={ContainerCopyMessages.toggleBtn.offText}

View File

@@ -127,7 +127,6 @@ const PointInTimeRestore: React.FC = () => {
<Stack.Item> <Stack.Item>
{showRefreshButton ? ( {showRefreshButton ? (
<PrimaryButton <PrimaryButton
data-test="pointInTimeRestore:RefreshBtn"
className="fullWidth" className="fullWidth"
text={ContainerCopyMessages.refreshButtonLabel} text={ContainerCopyMessages.refreshButtonLabel}
iconProps={{ iconName: "Refresh" }} iconProps={{ iconName: "Refresh" }}
@@ -135,7 +134,6 @@ const PointInTimeRestore: React.FC = () => {
/> />
) : ( ) : (
<PrimaryButton <PrimaryButton
data-test="pointInTimeRestore:PrimaryBtn"
className="fullWidth" className="fullWidth"
text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText} text={loading ? "" : ContainerCopyMessages.pointInTimeRestore.buttonText}
{...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})} {...(loading ? { iconProps: { iconName: "SyncStatusSolid" } } : {})}

View File

@@ -67,7 +67,6 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
class="ms-Toggle-background pill-117" class="ms-Toggle-background pill-117"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle1" id="Toggle1"
role="switch" role="switch"
type="button" type="button"
@@ -155,7 +154,6 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
class="ms-Toggle-background pill-121" class="ms-Toggle-background pill-121"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle11" id="Toggle11"
role="switch" role="switch"
type="button" type="button"
@@ -175,12 +173,10 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
</div> </div>
<div <div
class="ms-Stack popover-container foreground loading css-123" class="ms-Stack popover-container foreground loading css-123"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<div <div
class="ms-Overlay root-135" class="ms-Overlay root-135"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-137" class="ms-Spinner root-137"
@@ -327,7 +323,6 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
class="ms-Toggle-background pill-121" class="ms-Toggle-background pill-121"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle3" id="Toggle3"
role="switch" role="switch"
type="button" type="button"
@@ -347,7 +342,6 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
</div> </div>
<div <div
class="ms-Stack popover-container foreground css-123" class="ms-Stack popover-container foreground css-123"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span

View File

@@ -41,7 +41,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle17" id="Toggle17"
role="switch" role="switch"
type="button" type="button"
@@ -104,7 +103,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Edge Cases should handle m
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle16" id="Toggle16"
role="switch" role="switch"
type="button" type="button"
@@ -167,7 +165,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle3" id="Toggle3"
role="switch" role="switch"
type="button" type="button"
@@ -230,7 +227,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
class="ms-Toggle-background pill-119" class="ms-Toggle-background pill-119"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle1" id="Toggle1"
role="switch" role="switch"
type="button" type="button"
@@ -318,7 +314,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle0" id="Toggle0"
role="switch" role="switch"
type="button" type="button"
@@ -381,7 +376,6 @@ exports[`AddReadPermissionToDefaultIdentity Component Rendering should render co
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle2" id="Toggle2"
role="switch" role="switch"
type="button" type="button"

View File

@@ -4,7 +4,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -16,7 +15,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-testGroup"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -38,7 +36,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -88,7 +85,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -138,7 +134,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div> <div>
Incomplete Component Incomplete Component
@@ -147,7 +142,6 @@ exports[`AssignPermissions Component Accordion Behavior should render accordion
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw" class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
@@ -207,7 +201,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -219,7 +212,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-testGroup"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -241,7 +233,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -291,7 +282,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -341,7 +331,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div> <div>
Incomplete Component Incomplete Component
@@ -350,7 +339,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw" class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
@@ -410,7 +398,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -422,7 +409,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-testGroup"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -444,7 +430,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -494,7 +479,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -544,7 +528,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div> <div>
Incomplete Component Incomplete Component
@@ -553,7 +536,6 @@ exports[`AssignPermissions Component Edge Cases should calculate correct indent
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw" class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
@@ -613,7 +595,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -625,7 +606,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-testGroup"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -647,7 +627,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -697,7 +676,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -747,7 +725,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div> <div>
Incomplete Component Incomplete Component
@@ -756,7 +733,6 @@ exports[`AssignPermissions Component Edge Cases should handle missing account na
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw" class="fui-AccordionHeader accordionHeader ___lyghz50_53x5ri0 f1s2aq7o f1c21dwh f1s184ao ft85np5 fwrgwhw"
@@ -816,7 +792,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -828,7 +803,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-crossAccountConfigs"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -850,7 +824,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -902,7 +875,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
</div> </div>
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-onlineConfigs"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -924,7 +896,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -974,7 +945,6 @@ exports[`AssignPermissions Component Permission Groups should render multiple pe
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div <div
data-testid="online-copy-enabled" data-testid="online-copy-enabled"
@@ -994,7 +964,6 @@ exports[`AssignPermissions Component Permission Groups should render online migr
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -1006,7 +975,6 @@ exports[`AssignPermissions Component Permission Groups should render online migr
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-onlineConfigs"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -1028,7 +996,6 @@ exports[`AssignPermissions Component Permission Groups should render online migr
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -1078,7 +1045,6 @@ exports[`AssignPermissions Component Permission Groups should render online migr
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -1128,7 +1094,6 @@ exports[`AssignPermissions Component Permission Groups should render online migr
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div <div
data-testid="online-copy-enabled" data-testid="online-copy-enabled"
@@ -1148,7 +1113,6 @@ exports[`AssignPermissions Component Permission Groups should render permission
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -1160,7 +1124,6 @@ exports[`AssignPermissions Component Permission Groups should render permission
> >
<div <div
class="ms-Stack css-112" class="ms-Stack css-112"
data-test="permission-group-container-crossAccountConfigs"
> >
<div <div
class="ms-Stack css-113" class="ms-Stack css-113"
@@ -1182,7 +1145,6 @@ exports[`AssignPermissions Component Permission Groups should render permission
> >
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -1232,7 +1194,6 @@ exports[`AssignPermissions Component Permission Groups should render permission
</div> </div>
<div <div
class="fui-AccordionItem" class="fui-AccordionItem"
data-test="accordion-item"
> >
<div <div
class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5" class="fui-AccordionHeader accordionHeader ___kex8dp0_1udlp87 f19n0e5 f1c21dwh f1s184ao ft85np5"
@@ -1282,7 +1243,6 @@ exports[`AssignPermissions Component Permission Groups should render permission
<div <div
aria-disabled="false" aria-disabled="false"
class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu" class="fui-AccordionPanel accordionPanel ___1rufncu_1hx1scr f1axvtxu"
data-test="accordion-panel"
> >
<div <div
data-testid="add-read-permission" data-testid="add-read-permission"
@@ -1302,7 +1262,6 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"
@@ -1324,7 +1283,6 @@ exports[`AssignPermissions Component Rendering should render without crashing wi
<div> <div>
<div <div
class="ms-Stack assignPermissionsContainer css-109" class="ms-Stack assignPermissionsContainer css-109"
data-test="Panel:AssignPermissionsContainer"
> >
<span <span
class="css-110" class="css-110"

View File

@@ -41,7 +41,6 @@ exports[`DefaultManagedIdentity Edge Cases should handle missing account name gr
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle14" id="Toggle14"
role="switch" role="switch"
type="button" type="button"
@@ -104,7 +103,6 @@ exports[`DefaultManagedIdentity Edge Cases should handle null account 1`] = `
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle15" id="Toggle15"
role="switch" role="switch"
type="button" type="button"
@@ -167,7 +165,6 @@ exports[`DefaultManagedIdentity Loading States should render loading state snaps
class="ms-Toggle-background pill-119" class="ms-Toggle-background pill-119"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle10" id="Toggle10"
role="switch" role="switch"
type="button" type="button"
@@ -259,7 +256,6 @@ exports[`DefaultManagedIdentity Rendering should render correctly with default s
class="ms-Toggle-background pill-115" class="ms-Toggle-background pill-115"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle0" id="Toggle0"
role="switch" role="switch"
type="button" type="button"
@@ -322,7 +318,6 @@ exports[`DefaultManagedIdentity Toggle Interactions should render toggle with ch
class="ms-Toggle-background pill-119" class="ms-Toggle-background pill-119"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="btn-toggle"
id="Toggle7" id="Toggle7"
role="switch" role="switch"
type="button" type="button"

View File

@@ -56,7 +56,6 @@ exports[`PointInTimeRestore Initial Render should render correctly with default
<button <button
class="ms-Button ms-Button--primary fullWidth root-115" class="ms-Button ms-Button--primary fullWidth root-115"
data-is-focusable="true" data-is-focusable="true"
data-test="pointInTimeRestore:PrimaryBtn"
type="button" type="button"
> >
<span <span
@@ -132,7 +131,6 @@ exports[`PointInTimeRestore Initial Render should render with empty account name
<button <button
class="ms-Button ms-Button--primary fullWidth root-115" class="ms-Button ms-Button--primary fullWidth root-115"
data-is-focusable="true" data-is-focusable="true"
data-test="pointInTimeRestore:PrimaryBtn"
type="button" type="button"
> >
<span <span
@@ -163,7 +161,6 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
> >
<div <div
class="ms-Overlay root-123" class="ms-Overlay root-123"
data-test="loading-overlay"
> >
<div <div
class="ms-Spinner root-125" class="ms-Spinner root-125"
@@ -226,7 +223,6 @@ exports[`PointInTimeRestore Snapshots should match snapshot in loading state 1`]
aria-disabled="true" aria-disabled="true"
class="ms-Button ms-Button--primary is-disabled fullWidth root-128" class="ms-Button ms-Button--primary is-disabled fullWidth root-128"
data-is-focusable="false" data-is-focusable="false"
data-test="pointInTimeRestore:PrimaryBtn"
disabled="" disabled=""
type="button" type="button"
> >
@@ -305,7 +301,6 @@ exports[`PointInTimeRestore Snapshots should match snapshot with refresh button
<button <button
class="ms-Button ms-Button--primary fullWidth root-115" class="ms-Button ms-Button--primary fullWidth root-115"
data-is-focusable="true" data-is-focusable="true"
data-test="pointInTimeRestore:RefreshBtn"
type="button" type="button"
> >
<span <span

View File

@@ -19,21 +19,9 @@ const NavigationControls: React.FC<NavigationControlsProps> = ({
isPreviousDisabled, isPreviousDisabled,
}) => ( }) => (
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20 }}>
<PrimaryButton <PrimaryButton text={primaryBtnText} onClick={onPrimary} allowDisabledFocus disabled={isPrimaryDisabled} />
data-test="copy-job-primary" <DefaultButton text="Previous" onClick={onPrevious} allowDisabledFocus disabled={isPreviousDisabled} />
text={primaryBtnText} <DefaultButton text="Cancel" onClick={onCancel} />
onClick={onPrimary}
allowDisabledFocus
disabled={isPrimaryDisabled}
/>
<DefaultButton
data-test="copy-job-previous"
text="Previous"
onClick={onPrevious}
allowDisabledFocus
disabled={isPreviousDisabled}
/>
<DefaultButton data-test="copy-job-cancel" text="Cancel" onClick={onCancel} />
</Stack> </Stack>
); );

View File

@@ -17,7 +17,6 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
({ isLoading = false, title, children, onPrimary, onCancel }) => { ({ isLoading = false, title, children, onPrimary, onCancel }) => {
return ( return (
<Stack <Stack
data-test="popover-container"
className={`popover-container foreground ${isLoading ? "loading" : ""}`} className={`popover-container foreground ${isLoading ? "loading" : ""}`}
tokens={{ childrenGap: 20 }} tokens={{ childrenGap: 20 }}
style={{ maxWidth: 450 }} style={{ maxWidth: 450 }}

View File

@@ -4,7 +4,6 @@ exports[`PopoverMessage Component Edge Cases should handle empty string title 1`
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -72,7 +71,6 @@ exports[`PopoverMessage Component Edge Cases should handle null children 1`] = `
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -135,7 +133,6 @@ exports[`PopoverMessage Component Edge Cases should handle undefined children 1`
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -198,7 +195,6 @@ exports[`PopoverMessage Component Edge Cases should handle very long title 1`] =
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -270,7 +266,6 @@ exports[`PopoverMessage Component Rendering should render correctly when visible
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -340,7 +335,6 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -415,7 +409,6 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
<div> <div>
<div <div
class="ms-Stack popover-container foreground css-109" class="ms-Stack popover-container foreground css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
@@ -485,7 +478,6 @@ exports[`PopoverMessage Component Rendering should render correctly with loading
<div> <div>
<div <div
class="ms-Stack popover-container foreground loading css-109" class="ms-Stack popover-container foreground loading css-109"
data-test="popover-container"
style="max-width: 450px;" style="max-width: 450px;"
> >
<div <div

View File

@@ -22,7 +22,6 @@ const CreateCopyJobScreens: React.FC = () => {
<Stack.Item className="createCopyJobScreensContent"> <Stack.Item className="createCopyJobScreensContent">
{contextError && ( {contextError && (
<MessageBar <MessageBar
data-test="Panel:ErrorContainer"
className="createCopyJobErrorMessageBar" className="createCopyJobErrorMessageBar"
messageBarType={MessageBarType.blocked} messageBarType={MessageBarType.blocked}
isMultiline={false} isMultiline={false}

View File

@@ -31,17 +31,17 @@ const PreviewCopyJob: React.FC = () => {
})); }));
}; };
return ( return (
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer" data-test="Panel:PreviewCopyJob"> <Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer">
<FieldRow label={ContainerCopyMessages.jobNameLabel}> <FieldRow label={ContainerCopyMessages.jobNameLabel}>
<TextField data-test="job-name-textfield" value={jobName} onChange={onJobNameChange} /> <TextField value={jobName} onChange={onJobNameChange} />
</FieldRow> </FieldRow>
<Stack> <Stack>
<Text className="bold">{ContainerCopyMessages.sourceSubscriptionLabel}</Text> <Text className="bold">{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
<Text data-test="source-subscription-name">{copyJobState.source?.subscription?.displayName}</Text> <Text>{copyJobState.source?.subscription?.displayName}</Text>
</Stack> </Stack>
<Stack> <Stack>
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text> <Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text>
<Text data-test="source-account-name">{copyJobState.source?.account?.name}</Text> <Text>{copyJobState.source?.account?.name}</Text>
</Stack> </Stack>
<Stack> <Stack>
<DetailsList <DetailsList

View File

@@ -3,7 +3,6 @@
exports[`PreviewCopyJob should handle special characters in database and container names 1`] = ` exports[`PreviewCopyJob should handle special characters in database and container names 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -33,7 +32,6 @@ exports[`PreviewCopyJob should handle special characters in database and contain
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField84" id="TextField84"
type="text" type="text"
value="job-with@special#chars_123" value="job-with@special#chars_123"
@@ -53,7 +51,6 @@ exports[`PreviewCopyJob should handle special characters in database and contain
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -68,7 +65,6 @@ exports[`PreviewCopyJob should handle special characters in database and contain
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -325,7 +321,6 @@ exports[`PreviewCopyJob should handle special characters in database and contain
exports[`PreviewCopyJob should render component with cross-subscription setup 1`] = ` exports[`PreviewCopyJob should render component with cross-subscription setup 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -355,7 +350,6 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField96" id="TextField96"
type="text" type="text"
value="" value=""
@@ -375,7 +369,6 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -390,7 +383,6 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -647,7 +639,6 @@ exports[`PreviewCopyJob should render component with cross-subscription setup 1`
exports[`PreviewCopyJob should render with default state and empty job name 1`] = ` exports[`PreviewCopyJob should render with default state and empty job name 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -677,7 +668,6 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField0" id="TextField0"
type="text" type="text"
value="" value=""
@@ -697,7 +687,6 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -712,7 +701,6 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -969,7 +957,6 @@ exports[`PreviewCopyJob should render with default state and empty job name 1`]
exports[`PreviewCopyJob should render with long subscription and account names 1`] = ` exports[`PreviewCopyJob should render with long subscription and account names 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -999,7 +986,6 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField60" id="TextField60"
type="text" type="text"
value="" value=""
@@ -1019,7 +1005,6 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
This is a very long subscription name that might cause display issues if not handled properly This is a very long subscription name that might cause display issues if not handled properly
</span> </span>
@@ -1034,7 +1019,6 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
this-is-a-very-long-database-account-name-that-might-cause-display-issues this-is-a-very-long-database-account-name-that-might-cause-display-issues
</span> </span>
@@ -1291,7 +1275,6 @@ exports[`PreviewCopyJob should render with long subscription and account names 1
exports[`PreviewCopyJob should render with missing source account information 1`] = ` exports[`PreviewCopyJob should render with missing source account information 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -1321,7 +1304,6 @@ exports[`PreviewCopyJob should render with missing source account information 1`
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField36" id="TextField36"
type="text" type="text"
value="" value=""
@@ -1341,7 +1323,6 @@ exports[`PreviewCopyJob should render with missing source account information 1`
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -1607,7 +1588,6 @@ exports[`PreviewCopyJob should render with missing source account information 1`
exports[`PreviewCopyJob should render with missing source subscription information 1`] = ` exports[`PreviewCopyJob should render with missing source subscription information 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -1637,7 +1617,6 @@ exports[`PreviewCopyJob should render with missing source subscription informati
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField24" id="TextField24"
type="text" type="text"
value="" value=""
@@ -1666,7 +1645,6 @@ exports[`PreviewCopyJob should render with missing source subscription informati
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -1923,7 +1901,6 @@ exports[`PreviewCopyJob should render with missing source subscription informati
exports[`PreviewCopyJob should render with online migration type 1`] = ` exports[`PreviewCopyJob should render with online migration type 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -1953,7 +1930,6 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField72" id="TextField72"
type="text" type="text"
value="online-migration-job" value="online-migration-job"
@@ -1973,7 +1949,6 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -1988,7 +1963,6 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -2245,7 +2219,6 @@ exports[`PreviewCopyJob should render with online migration type 1`] = `
exports[`PreviewCopyJob should render with pre-filled job name 1`] = ` exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -2275,7 +2248,6 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField12" id="TextField12"
type="text" type="text"
value="custom-job-name-123" value="custom-job-name-123"
@@ -2295,7 +2267,6 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -2310,7 +2281,6 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>
@@ -2567,7 +2537,6 @@ exports[`PreviewCopyJob should render with pre-filled job name 1`] = `
exports[`PreviewCopyJob should render with undefined database and container names 1`] = ` exports[`PreviewCopyJob should render with undefined database and container names 1`] = `
<div <div
class="ms-Stack previewCopyJobContainer css-109" class="ms-Stack previewCopyJobContainer css-109"
data-test="Panel:PreviewCopyJob"
> >
<div <div
class="ms-Stack flex-row css-110" class="ms-Stack flex-row css-110"
@@ -2597,7 +2566,6 @@ exports[`PreviewCopyJob should render with undefined database and container name
<input <input
aria-invalid="false" aria-invalid="false"
class="ms-TextField-field field-115" class="ms-TextField-field field-115"
data-test="job-name-textfield"
id="TextField48" id="TextField48"
type="text" type="text"
value="" value=""
@@ -2617,7 +2585,6 @@ exports[`PreviewCopyJob should render with undefined database and container name
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-subscription-name"
> >
Test Subscription Test Subscription
</span> </span>
@@ -2632,7 +2599,6 @@ exports[`PreviewCopyJob should render with undefined database and container name
</span> </span>
<span <span
class="css-125" class="css-125"
data-test="source-account-name"
> >
test-account test-account
</span> </span>

View File

@@ -9,7 +9,7 @@ import ContainerCopyMessages from "../../../../ContainerCopyMessages";
import { CopyJobContext } from "../../../../Context/CopyJobContext"; import { CopyJobContext } from "../../../../Context/CopyJobContext";
import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums"; import { CopyJobMigrationType } from "../../../../Enums/CopyJobEnums";
import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes"; import { CopyJobContextProviderType, CopyJobContextState } from "../../../../Types/CopyJobTypes";
import { AccountDropdown, normalizeAccountId } from "./AccountDropdown"; import { AccountDropdown } from "./AccountDropdown";
jest.mock("../../../../../../hooks/useDatabaseAccounts"); jest.mock("../../../../../../hooks/useDatabaseAccounts");
jest.mock("../../../../../../UserContext", () => ({ jest.mock("../../../../../../UserContext", () => ({
@@ -202,16 +202,13 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState); const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toEqual({ expect(newState.source.account).toBe(mockDatabaseAccount1);
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
});
}); });
it("should auto-select predefined account from userContext if available", async () => { it("should auto-select predefined account from userContext if available", async () => {
const userContextAccount = { const userContextAccount = {
...mockDatabaseAccount2, ...mockDatabaseAccount2,
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account2", id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account2",
}; };
(userContext as any).databaseAccount = userContextAccount; (userContext as any).databaseAccount = userContextAccount;
@@ -226,10 +223,7 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(mockCopyJobState); const newState = stateUpdateFunction(mockCopyJobState);
expect(newState.source.account).toEqual({ expect(newState.source.account).toBe(mockDatabaseAccount2);
...mockDatabaseAccount2,
id: normalizeAccountId(mockDatabaseAccount2.id),
});
}); });
it("should keep current account if it exists in the filtered list", async () => { it("should keep current account if it exists in the filtered list", async () => {
@@ -254,16 +248,7 @@ describe("AccountDropdown", () => {
const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0]; const stateUpdateFunction = mockSetCopyJobState.mock.calls[0][0];
const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState); const newState = stateUpdateFunction(contextWithSelectedAccount.copyJobState);
expect(newState).toEqual({ expect(newState).toBe(contextWithSelectedAccount.copyJobState);
...contextWithSelectedAccount.copyJobState,
source: {
...contextWithSelectedAccount.copyJobState.source,
account: {
...mockDatabaseAccount1,
id: normalizeAccountId(mockDatabaseAccount1.id),
},
},
});
}); });
it("should handle account change when user selects different account", async () => { it("should handle account change when user selects different account", async () => {
@@ -287,7 +272,7 @@ describe("AccountDropdown", () => {
it("should normalize account ID for Portal platform", () => { it("should normalize account ID for Portal platform", () => {
const portalAccount = { const portalAccount = {
...mockDatabaseAccount1, ...mockDatabaseAccount1,
id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDB/databaseAccounts/account1", id: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.DocumentDb/databaseAccounts/account1",
}; };
(configContext as any).platform = Platform.Portal; (configContext as any).platform = Platform.Portal;

View File

@@ -12,7 +12,7 @@ import FieldRow from "../../Components/FieldRow";
interface AccountDropdownProps {} interface AccountDropdownProps {}
export const normalizeAccountId = (id: string = "") => { const normalizeAccountId = (id: string) => {
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/"); return id.replace("/Microsoft.DocumentDb/", "/Microsoft.DocumentDB/");
} else if (configContext.platform === Platform.Hosted) { } else if (configContext.platform === Platform.Hosted) {
@@ -27,12 +27,7 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId; const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId); const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
const sqlApiOnlyAccounts = (allAccounts || []) const sqlApiOnlyAccounts: DatabaseAccount[] = (allAccounts || []).filter((account) => apiType(account) === "SQL");
.filter((account) => apiType(account) === "SQL")
.map((account) => ({
...account,
id: normalizeAccountId(account.id),
}));
const updateCopyJobState = (newAccount: DatabaseAccount) => { const updateCopyJobState = (newAccount: DatabaseAccount) => {
setCopyJobState((prevState) => { setCopyJobState((prevState) => {
@@ -52,7 +47,7 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
useEffect(() => { useEffect(() => {
if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) { if (sqlApiOnlyAccounts && sqlApiOnlyAccounts.length > 0 && selectedSubscriptionId) {
const currentAccountId = copyJobState?.source?.account?.id; const currentAccountId = copyJobState?.source?.account?.id;
const predefinedAccountId = normalizeAccountId(userContext.databaseAccount?.id); const predefinedAccountId = userContext.databaseAccount?.id;
const selectedAccountId = currentAccountId || predefinedAccountId; const selectedAccountId = currentAccountId || predefinedAccountId;
const targetAccount: DatabaseAccount | null = const targetAccount: DatabaseAccount | null =
@@ -63,7 +58,7 @@ export const AccountDropdown: React.FC<AccountDropdownProps> = () => {
const accountOptions = const accountOptions =
sqlApiOnlyAccounts?.map((account) => ({ sqlApiOnlyAccounts?.map((account) => ({
key: account.id, key: normalizeAccountId(account.id),
text: account.name, text: account.name,
data: account, data: account,
})) || []; })) || [];

View File

@@ -10,7 +10,7 @@ interface MigrationTypeCheckboxProps {
} }
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => ( export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => (
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow" data-test="migration-type-checkbox"> <Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
<Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} /> <Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} />
</Stack> </Stack>
)); ));

View File

@@ -3,7 +3,6 @@
exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = ` exports[`MigrationTypeCheckbox Component Rendering should render in checked state 1`] = `
<div <div
class="ms-Stack migrationTypeRow css-109" class="ms-Stack migrationTypeRow css-109"
data-test="migration-type-checkbox"
> >
<div <div
class="ms-Checkbox is-checked is-enabled root-119" class="ms-Checkbox is-checked is-enabled root-119"
@@ -44,7 +43,6 @@ exports[`MigrationTypeCheckbox Component Rendering should render in checked stat
exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = ` exports[`MigrationTypeCheckbox Component Rendering should render with default props (unchecked state) 1`] = `
<div <div
class="ms-Stack migrationTypeRow css-109" class="ms-Stack migrationTypeRow css-109"
data-test="migration-type-checkbox"
> >
<div <div
class="ms-Checkbox is-enabled root-110" class="ms-Checkbox is-enabled root-110"

View File

@@ -47,11 +47,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
const onDropdownChange = dropDownChangeHandler(setCopyJobState); const onDropdownChange = dropDownChangeHandler(setCopyJobState);
return ( return (
<Stack <Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>
data-test="Panel:SelectSourceAndTargetContainers"
className="selectSourceAndTargetContainers"
tokens={{ childrenGap: 25 }}
>
<span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span> <span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
<DatabaseContainerSection <DatabaseContainerSection
heading={ContainerCopyMessages.sourceContainerSubHeading} heading={ContainerCopyMessages.sourceContainerSubHeading}
@@ -63,7 +59,6 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
selectedContainer={source?.containerId} selectedContainer={source?.containerId}
containerDisabled={!source?.databaseId} containerDisabled={!source?.databaseId}
containerOnChange={onDropdownChange("sourceContainer")} containerOnChange={onDropdownChange("sourceContainer")}
sectionType="source"
/> />
<DatabaseContainerSection <DatabaseContainerSection
heading={ContainerCopyMessages.targetContainerSubHeading} heading={ContainerCopyMessages.targetContainerSubHeading}
@@ -76,7 +71,6 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
containerDisabled={!target?.databaseId} containerDisabled={!target?.databaseId}
containerOnChange={onDropdownChange("targetContainer")} containerOnChange={onDropdownChange("targetContainer")}
handleOnDemandCreateContainer={showAddCollectionPanel} handleOnDemandCreateContainer={showAddCollectionPanel}
sectionType="target"
/> />
</Stack> </Stack>
); );

View File

@@ -32,7 +32,6 @@ describe("DatabaseContainerSection", () => {
selectedContainer: "container1", selectedContainer: "container1",
containerDisabled: false, containerDisabled: false,
containerOnChange: mockContainerOnChange, containerOnChange: mockContainerOnChange,
sectionType: "source",
}; };
beforeEach(() => { beforeEach(() => {
@@ -293,7 +292,6 @@ describe("DatabaseContainerSection", () => {
containerOptions: mockContainerOptions, containerOptions: mockContainerOptions,
selectedContainer: "container1", selectedContainer: "container1",
containerOnChange: mockContainerOnChange, containerOnChange: mockContainerOnChange,
sectionType: "source",
}; };
render(<DatabaseContainerSection {...minimalProps} />); render(<DatabaseContainerSection {...minimalProps} />);
@@ -395,7 +393,6 @@ describe("DatabaseContainerSection", () => {
containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }], containerOptions: [{ key: "c1", text: "Container 1", data: { id: "c1" } }],
selectedContainer: "c1", selectedContainer: "c1",
containerOnChange: jest.fn(), containerOnChange: jest.fn(),
sectionType: "source",
}; };
const { container } = render(<DatabaseContainerSection {...minimalProps} />); const { container } = render(<DatabaseContainerSection {...minimalProps} />);
@@ -414,7 +411,6 @@ describe("DatabaseContainerSection", () => {
containerDisabled: false, containerDisabled: false,
containerOnChange: jest.fn(), containerOnChange: jest.fn(),
handleOnDemandCreateContainer: jest.fn(), handleOnDemandCreateContainer: jest.fn(),
sectionType: "target",
}; };
const { container } = render(<DatabaseContainerSection {...fullProps} />); const { container } = render(<DatabaseContainerSection {...fullProps} />);
@@ -432,7 +428,6 @@ describe("DatabaseContainerSection", () => {
selectedContainer: "container1", selectedContainer: "container1",
containerDisabled: true, containerDisabled: true,
containerOnChange: jest.fn(), containerOnChange: jest.fn(),
sectionType: "target",
}; };
const { container } = render(<DatabaseContainerSection {...disabledProps} />); const { container } = render(<DatabaseContainerSection {...disabledProps} />);
@@ -448,7 +443,6 @@ describe("DatabaseContainerSection", () => {
containerOptions: [], containerOptions: [],
selectedContainer: "", selectedContainer: "",
containerOnChange: jest.fn(), containerOnChange: jest.fn(),
sectionType: "target",
}; };
const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />); const { container } = render(<DatabaseContainerSection {...emptyOptionsProps} />);

View File

@@ -15,7 +15,6 @@ export const DatabaseContainerSection = ({
containerDisabled, containerDisabled,
containerOnChange, containerOnChange,
handleOnDemandCreateContainer, handleOnDemandCreateContainer,
sectionType,
}: DatabaseContainerSectionProps) => ( }: DatabaseContainerSectionProps) => (
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection"> <Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
<label className="subHeading">{heading}</label> <label className="subHeading">{heading}</label>
@@ -28,7 +27,6 @@ export const DatabaseContainerSection = ({
disabled={!!databaseDisabled} disabled={!!databaseDisabled}
selectedKey={selectedDatabase} selectedKey={selectedDatabase}
onChange={databaseOnChange} onChange={databaseOnChange}
data-test={`${sectionType}-databaseDropdown`}
/> />
</FieldRow> </FieldRow>
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}> <FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
@@ -41,7 +39,6 @@ export const DatabaseContainerSection = ({
disabled={!!containerDisabled} disabled={!!containerDisabled}
selectedKey={selectedContainer} selectedKey={selectedContainer}
onChange={containerOnChange} onChange={containerOnChange}
data-test={`${sectionType}-containerDropdown`}
/> />
{handleOnDemandCreateContainer && ( {handleOnDemandCreateContainer && (
<ActionButton className="create-container-link-btn" onClick={() => handleOnDemandCreateContainer()}> <ActionButton className="create-container-link-btn" onClick={() => handleOnDemandCreateContainer()}>

View File

@@ -37,7 +37,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="target-databaseDropdown"
id="Dropdown98" id="Dropdown98"
role="combobox" role="combobox"
tabindex="0" tabindex="0"
@@ -95,7 +94,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with all pro
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="target-containerDropdown"
id="Dropdown99" id="Dropdown99"
role="combobox" role="combobox"
tabindex="0" tabindex="0"
@@ -184,7 +182,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
class="ms-Dropdown is-disabled is-required dropdown-143" class="ms-Dropdown is-disabled is-required dropdown-143"
data-is-focusable="false" data-is-focusable="false"
data-ktp-target="true" data-ktp-target="true"
data-test="target-databaseDropdown"
id="Dropdown103" id="Dropdown103"
role="combobox" role="combobox"
tabindex="-1" tabindex="-1"
@@ -242,7 +239,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with disable
class="ms-Dropdown is-disabled is-required dropdown-143" class="ms-Dropdown is-disabled is-required dropdown-143"
data-is-focusable="false" data-is-focusable="false"
data-ktp-target="true" data-ktp-target="true"
data-test="target-containerDropdown"
id="Dropdown104" id="Dropdown104"
role="combobox" role="combobox"
tabindex="-1" tabindex="-1"
@@ -310,7 +306,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="target-databaseDropdown"
id="Dropdown105" id="Dropdown105"
role="combobox" role="combobox"
tabindex="0" tabindex="0"
@@ -368,7 +363,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with empty o
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="target-containerDropdown"
id="Dropdown106" id="Dropdown106"
role="combobox" role="combobox"
tabindex="0" tabindex="0"
@@ -436,7 +430,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="source-databaseDropdown"
id="Dropdown96" id="Dropdown96"
role="combobox" role="combobox"
tabindex="0" tabindex="0"
@@ -494,7 +487,6 @@ exports[`DatabaseContainerSection Snapshot Testing matches snapshot with minimal
class="ms-Dropdown is-required dropdown-112" class="ms-Dropdown is-required dropdown-112"
data-is-focusable="true" data-is-focusable="true"
data-ktp-target="true" data-ktp-target="true"
data-test="source-containerDropdown"
id="Dropdown97" id="Dropdown97"
role="combobox" role="combobox"
tabindex="0" tabindex="0"

View File

@@ -83,7 +83,6 @@ const CopyJobActionMenu: React.FC<CopyJobActionMenuProps> = ({ job, handleClick
return ( return (
<IconButton <IconButton
data-test={`CopyJobActionMenu/Button:${job.Name}`}
role="button" role="button"
iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }} iconProps={{ iconName: "More", styles: { root: { fontSize: "20px", fontWeight: "bold" } } }}
menuProps={{ items: getMenuItems() }} menuProps={{ items: getMenuItems() }}

View File

@@ -80,7 +80,6 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
<Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}> <Stack.Item verticalFill={true} grow={1} shrink={1} style={styles.stackItem}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}> <ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<ShimmeredDetailsList <ShimmeredDetailsList
className="CopyJobListContainer"
onRenderRow={_onRenderRow} onRenderRow={_onRenderRow}
checkboxVisibility={2} checkboxVisibility={2}
columns={columns} columns={columns}

View File

@@ -49,7 +49,6 @@ export interface DatabaseContainerSectionProps {
containerDisabled?: boolean; containerDisabled?: boolean;
containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void; containerOnChange: (ev: React.FormEvent<HTMLDivElement>, option: DropdownOptionType) => void;
handleOnDemandCreateContainer?: () => void; handleOnDemandCreateContainer?: () => void;
sectionType: "source" | "target";
} }
export interface CopyJobContextState { export interface CopyJobContextState {

View File

@@ -437,14 +437,13 @@ export default class Explorer {
public onRefreshResourcesClick = async (): Promise<void> => { public onRefreshResourcesClick = async (): Promise<void> => {
if (isFabricMirroredKey()) { if (isFabricMirroredKey()) {
scheduleRefreshFabricToken(true).then(() => this.refreshAllDatabases()); scheduleRefreshFabricToken(true).then(() => this.refreshAllDatabases());
} else { return;
await (userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases());
await this.refreshNotebookList();
} }
logConsoleInfo("Successfully refreshed databases"); await (userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases());
await this.refreshNotebookList();
}; };
// Facade // Facade

View File

@@ -1,5 +1,6 @@
import { initializeIcons } from "@fluentui/react"; import { initializeIcons } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import { MessageTypes } from "Contracts/MessageTypes";
import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure"; import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure";
import * as React from "react"; import * as React from "react";
import { render } from "react-dom"; import { render } from "react-dom";
@@ -21,9 +22,33 @@ import "./Shared/appInsights";
import { useAADAuth } from "./hooks/useAADAuth"; import { useAADAuth } from "./hooks/useAADAuth";
import { useConfig } from "./hooks/useConfig"; import { useConfig } from "./hooks/useConfig";
import { useTokenMetadata } from "./hooks/usePortalAccessToken"; import { useTokenMetadata } from "./hooks/usePortalAccessToken";
import { THEME_MODE_DARK, useThemeStore } from "./hooks/useTheme";
initializeIcons(); initializeIcons();
if (typeof window !== "undefined") {
window.addEventListener("message", (event) => {
const messageData = event.data?.data || event.data;
const messageType = messageData?.type;
if (messageType === MessageTypes.UpdateTheme) {
const themeData = messageData?.params?.theme || messageData?.theme;
if (themeData && themeData.mode !== undefined) {
const isDark = themeData.mode === THEME_MODE_DARK;
useThemeStore.setState({
isDarkMode: isDark,
themeMode: themeData.mode,
});
if (isDark) {
document.body.classList.add("isDarkMode");
} else {
document.body.classList.remove("isDarkMode");
}
}
}
});
}
const App: React.FunctionComponent = () => { const App: React.FunctionComponent = () => {
// For handling encrypted portal tokens sent via query paramter // For handling encrypted portal tokens sent via query paramter
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);

View File

@@ -1,7 +1,7 @@
// TODO: Renable this rule for the file or turn it off everywhere // TODO: Renable this rule for the file or turn it off everywhere
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
import { DefaultButton, IButtonStyles, IContextualMenuItem } from "@fluentui/react"; import { DefaultButton, IButtonStyles, IContextualMenuItem, IContextualMenuProps } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { FunctionComponent, useEffect, useState } from "react"; import { FunctionComponent, useEffect, useState } from "react";
import { StyleConstants } from "../../../Common/StyleConstants"; import { StyleConstants } from "../../../Common/StyleConstants";
@@ -92,14 +92,16 @@ export const AccountSwitcher: FunctionComponent<Props> = ({ armToken, setDatabas
}, },
]; ];
const menuProps: IContextualMenuProps = {
directionalHintFixed: true,
className: "accountSwitchContextualMenu",
items,
};
return ( return (
<DefaultButton <DefaultButton
text={buttonText} text={buttonText}
menuProps={{ menuProps={menuProps}
directionalHintFixed: true,
className: "accountSwitchContextualMenu",
items,
}}
styles={buttonStyles} styles={buttonStyles}
className="accountSwitchButton" className="accountSwitchButton"
id="accountSwitchButton" id="accountSwitchButton"

View File

@@ -1,6 +1,6 @@
import { Dropdown } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { FunctionComponent } from "react"; import { FunctionComponent } from "react";
import { SearchableDropdown } from "../../../Common/SearchableDropdown";
import { DatabaseAccount } from "../../../Contracts/DataModels"; import { DatabaseAccount } from "../../../Contracts/DataModels";
interface Props { interface Props {
@@ -17,18 +17,20 @@ export const SwitchAccount: FunctionComponent<Props> = ({
dismissMenu, dismissMenu,
}: Props) => { }: Props) => {
return ( return (
<SearchableDropdown<DatabaseAccount> <Dropdown
label="Cosmos DB Account Name" label="Cosmos DB Account Name"
items={accounts}
selectedItem={selectedAccount}
onSelect={(account) => setSelectedAccountName(account.name)}
getKey={(account) => account.name}
getDisplayText={(account) => account.name}
placeholder="Select an Account"
filterPlaceholder="Filter accounts"
className="accountSwitchAccountDropdown" className="accountSwitchAccountDropdown"
disabled={!accounts || accounts.length === 0} options={accounts?.map((account) => ({
onDismiss={dismissMenu} key: account.name,
text: account.name,
data: account,
}))}
onChange={(_, option) => {
setSelectedAccountName(String(option?.key));
dismissMenu();
}}
defaultSelectedKey={selectedAccount?.name}
placeholder={accounts && accounts.length === 0 ? "No Accounts Found" : "Select an Account"}
/> />
); );
}; };

View File

@@ -1,6 +1,6 @@
import { Dropdown } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { FunctionComponent } from "react"; import { FunctionComponent } from "react";
import { SearchableDropdown } from "../../../Common/SearchableDropdown";
import { Subscription } from "../../../Contracts/DataModels"; import { Subscription } from "../../../Contracts/DataModels";
interface Props { interface Props {
@@ -15,16 +15,21 @@ export const SwitchSubscription: FunctionComponent<Props> = ({
selectedSubscription, selectedSubscription,
}: Props) => { }: Props) => {
return ( return (
<SearchableDropdown<Subscription> <Dropdown
label="Subscription" label="Subscription"
items={subscriptions}
selectedItem={selectedSubscription}
onSelect={(sub) => setSelectedSubscriptionId(sub.subscriptionId)}
getKey={(sub) => sub.subscriptionId}
getDisplayText={(sub) => sub.displayName}
placeholder="Select a Subscription"
filterPlaceholder="Filter subscriptions"
className="accountSwitchSubscriptionDropdown" className="accountSwitchSubscriptionDropdown"
options={subscriptions?.map((sub) => {
return {
key: sub.subscriptionId,
text: sub.displayName,
data: sub,
};
})}
onChange={(_, option) => {
setSelectedSubscriptionId(String(option?.key));
}}
defaultSelectedKey={selectedSubscription?.subscriptionId}
placeholder={subscriptions && subscriptions.length === 0 ? "No Subscriptions Found" : "Select a Subscription"}
/> />
); );
}; };

View File

@@ -8,8 +8,7 @@ test("Cassandra keyspace and table CRUD", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.Cassandra); const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
const newTableButton = await explorer.globalCommandButton("New Table"); await explorer.globalCommandButton("New Table").click();
await newTableButton.click();
await explorer.whilePanelOpen( await explorer.whilePanelOpen(
"Add Table", "Add Table",
async (panel, okButton) => { async (panel, okButton) => {

View File

@@ -352,9 +352,8 @@ export class DataExplorer {
* *
* There's only a single "primary" button, but we still require you to pass the label to confirm you're selecting the right button. * There's only a single "primary" button, but we still require you to pass the label to confirm you're selecting the right button.
*/ */
async globalCommandButton(label: string): Promise<Locator> { globalCommandButton(label: string): Locator {
await this.frame.getByTestId("GlobalCommands").click(); return this.frame.getByTestId("GlobalCommands").getByText(label);
return this.frame.getByRole("menuitem", { name: label });
} }
/** Select the command bar button with the specified label */ /** Select the command bar button with the specified label */
@@ -460,15 +459,6 @@ export class DataExplorer {
const containerNode = await this.waitForContainerNode(context.database.id, context.container.id); const containerNode = await this.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand(); await containerNode.expand();
// refresh tree to remove deleted database
const consoleMessages = await this.getNotificationConsoleMessages();
const refreshButton = this.frame.getByTestId("Sidebar/RefreshButton");
await refreshButton.click();
await expect(consoleMessages).toContainText("Successfully refreshed databases", {
timeout: ONE_MINUTE_MS,
});
await this.collapseNotificationConsole();
const scaleAndSettingsButton = this.frame.getByTestId( const scaleAndSettingsButton = this.frame.getByTestId(
`TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`, `TreeNode:${context.database.id}/${context.container.id}/Scale & Settings`,
); );
@@ -476,35 +466,10 @@ export class DataExplorer {
} }
/** Gets the console message element */ /** Gets the console message element */
getConsoleHeaderStatus(): Locator { getConsoleMessage(): Locator {
return this.frame.getByTestId("notification-console/header-status"); return this.frame.getByTestId("notification-console/header-status");
} }
async expandNotificationConsole(): Promise<void> {
await this.setNotificationConsoleExpanded(true);
}
async collapseNotificationConsole(): Promise<void> {
await this.setNotificationConsoleExpanded(false);
}
async setNotificationConsoleExpanded(expanded: boolean): Promise<void> {
const notificationConsoleToggleButton = this.frame.getByTestId("NotificationConsole/ExpandCollapseButton");
const alt = await notificationConsoleToggleButton.locator("img").getAttribute("alt");
// When expanded, the icon says "Collapse icon"
if (expanded && alt === "Expand icon") {
await notificationConsoleToggleButton.click();
} else if (!expanded && alt === "Collapse icon") {
await notificationConsoleToggleButton.click();
}
}
async getNotificationConsoleMessages(): Promise<Locator> {
await this.setNotificationConsoleExpanded(true);
return this.frame.getByTestId("NotificationConsole/Contents");
}
async getDropdownItemByName(name: string, ariaLabel?: string): Promise<Locator> { async getDropdownItemByName(name: string, ariaLabel?: string): Promise<Locator> {
const dropdownItemsWrapper = this.frame.locator("div.ms-Dropdown-items"); const dropdownItemsWrapper = this.frame.locator("div.ms-Dropdown-items");
if (ariaLabel) { if (ariaLabel) {

View File

@@ -9,8 +9,7 @@ test("Gremlin graph CRUD", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.Gremlin); const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
// Create new database and graph // Create new database and graph
const newGraphButton = await explorer.globalCommandButton("New Graph"); await explorer.globalCommandButton("New Graph").click();
await newGraphButton.click();
await explorer.whilePanelOpen( await explorer.whilePanelOpen(
"New Graph", "New Graph",
async (panel, okButton) => { async (panel, okButton) => {

View File

@@ -14,8 +14,7 @@ import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUnique
const explorer = await DataExplorer.open(page, accountType); const explorer = await DataExplorer.open(page, accountType);
const newCollectionButton = await explorer.globalCommandButton("New Collection"); await explorer.globalCommandButton("New Collection").click();
await newCollectionButton.click();
await explorer.whilePanelOpen( await explorer.whilePanelOpen(
"New Collection", "New Collection",
async (panel, okButton) => { async (panel, okButton) => {

View File

@@ -8,8 +8,7 @@ test("SQL database and container CRUD", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.SQL); const explorer = await DataExplorer.open(page, TestAccount.SQL);
const newContainerButton = await explorer.globalCommandButton("New Container"); await explorer.globalCommandButton("New Container").click();
await newContainerButton.click();
await explorer.whilePanelOpen( await explorer.whilePanelOpen(
"New Container", "New Container",
async (panel, okButton) => { async (panel, okButton) => {

View File

@@ -30,12 +30,9 @@ test.beforeEach("Open new query tab", async ({ page }) => {
await explorer.frame.getByTestId("NotificationConsole/Contents").waitFor(); await explorer.frame.getByTestId("NotificationConsole/Contents").waitFor();
}); });
// Delete database only if not running in CI test.afterAll("Delete Test Database", async () => {
if (!process.env.CI) { await context?.dispose();
test.afterAll("Delete Test Database", async () => { });
await context?.dispose();
});
}
test("Query results", async () => { test("Query results", async () => {
// Run the query and verify the results // Run the query and verify the results

View File

@@ -1,100 +1,98 @@
// import { expect, test } from "@playwright/test"; import { expect, Page, test } from "@playwright/test";
// import { DataExplorer, TestAccount } from "../../fx"; import { DataExplorer, TestAccount } from "../../fx";
// import { createTestSQLContainer, TestContainerContext } from "../../testData"; import { createTestSQLContainer, TestContainerContext } from "../../testData";
// test.describe("Change Partition Key", () => { test.describe("Change Partition Key", () => {
// let context: TestContainerContext = null!; let pageInstance: Page;
// let explorer: DataExplorer = null!; let context: TestContainerContext = null!;
// const newPartitionKeyPath = "newPartitionKey"; let explorer: DataExplorer = null!;
// const newContainerId = "testcontainer_1"; const newPartitionKeyPath = "/newPartitionKey";
const newContainerId = "testcontainer_1";
// test.beforeAll("Create Test Database", async () => { test.beforeAll("Create Test Database", async () => {
// context = await createTestSQLContainer(); context = await createTestSQLContainer();
// }); });
// test.beforeEach("Open container settings", async ({ page }) => { test.beforeEach("Open container settings", async ({ page }) => {
// explorer = await DataExplorer.open(page, TestAccount.SQL); pageInstance = page;
explorer = await DataExplorer.open(page, TestAccount.SQL);
// // Click Scale & Settings and open Partition Key tab // Click Scale & Settings and open Partition Key tab
// await explorer.openScaleAndSettings(context); await explorer.openScaleAndSettings(context);
// const PartitionKeyTab = explorer.frame.getByTestId("settings-tab-header/PartitionKeyTab"); const PartitionKeyTab = explorer.frame.getByTestId("settings-tab-header/PartitionKeyTab");
// await expect(PartitionKeyTab).toBeVisible(); await PartitionKeyTab.click();
// await PartitionKeyTab.click(); });
// });
// // Delete database only if not running in CI test.afterAll("Delete Test Database", async () => {
// if (!process.env.CI) { await context?.dispose();
// test.afterEach("Delete Test Database", async () => { });
// await context?.dispose();
// });
// }
// test("Change partition key path", async () => { test("Change partition key path", async () => {
// await expect(explorer.frame.getByText("/partitionKey")).toBeVisible(); await expect(explorer.frame.getByText("/partitionKey")).toBeVisible();
// await expect(explorer.frame.getByText("Change partition key")).toBeVisible(); await expect(explorer.frame.getByText("Change partition key")).toBeVisible();
// await expect(explorer.frame.getByText(/To safeguard the integrity of/)).toBeVisible(); await expect(explorer.frame.getByText(/To safeguard the integrity of/)).toBeVisible();
// await expect(explorer.frame.getByText(/To change the partition key/)).toBeVisible(); await expect(explorer.frame.getByText(/To change the partition key/)).toBeVisible();
// const changePartitionKeyButton = explorer.frame.getByTestId("change-partition-key-button"); const changePartitionKeyButton = explorer.frame.getByTestId("change-partition-key-button");
// expect(changePartitionKeyButton).toBeVisible(); expect(changePartitionKeyButton).toBeVisible();
// await changePartitionKeyButton.click(); await changePartitionKeyButton.click();
// // Fill out new partition key form in the panel // Fill out new partition key form in the panel
// const changePkPanel = explorer.frame.getByTestId(`Panel:Change partition key`); const changePkPanel = explorer.frame.getByTestId(`Panel:Change partition key`);
// await expect(changePkPanel.getByText(context.database.id)).toBeVisible(); await expect(changePkPanel.getByText(context.database.id)).toBeVisible();
// await expect(explorer.frame.getByRole("heading", { name: "Change partition key" })).toBeVisible(); await expect(explorer.frame.getByRole("heading", { name: "Change partition key" })).toBeVisible();
// await expect(explorer.frame.getByText(/When changing a container/)).toBeVisible(); await expect(explorer.frame.getByText(/When changing a container/)).toBeVisible();
// // Try to switch to new container // Try to switch to new container
// await expect(changePkPanel.getByText("New container")).toBeVisible(); await expect(changePkPanel.getByText("New container")).toBeVisible();
// await expect(changePkPanel.getByText("Existing container")).toBeVisible(); await expect(changePkPanel.getByText("Existing container")).toBeVisible();
// await expect(changePkPanel.getByTestId("new-container-id-input")).toBeVisible(); await expect(changePkPanel.getByTestId("new-container-id-input")).toBeVisible();
// changePkPanel.getByTestId("new-container-id-input").fill(newContainerId); changePkPanel.getByTestId("new-container-id-input").fill(newContainerId);
// await expect(changePkPanel.getByTestId("new-container-partition-key-input")).toBeVisible(); await expect(changePkPanel.getByTestId("new-container-partition-key-input")).toBeVisible();
// changePkPanel.getByTestId("new-container-partition-key-input").fill(newPartitionKeyPath); changePkPanel.getByTestId("new-container-partition-key-input").fill(newPartitionKeyPath);
// await expect(changePkPanel.getByTestId("add-sub-partition-key-button")).toBeVisible(); await expect(changePkPanel.getByTestId("add-sub-partition-key-button")).toBeVisible();
// changePkPanel.getByTestId("add-sub-partition-key-button").click(); changePkPanel.getByTestId("add-sub-partition-key-button").click();
// await expect(changePkPanel.getByTestId("new-container-sub-partition-key-input-0")).toBeVisible(); await expect(changePkPanel.getByTestId("new-container-sub-partition-key-input-0")).toBeVisible();
// await expect(changePkPanel.getByTestId("remove-sub-partition-key-button-0")).toBeVisible(); await expect(changePkPanel.getByTestId("remove-sub-partition-key-button-0")).toBeVisible();
// await expect(changePkPanel.getByTestId("hierarchical-partitioning-info-text")).toBeVisible(); await expect(changePkPanel.getByTestId("hierarchical-partitioning-info-text")).toBeVisible();
// await changePkPanel.getByTestId("remove-sub-partition-key-button-0").click(); changePkPanel.getByTestId("new-container-sub-partition-key-input-0").fill("/customerId");
// await changePkPanel.getByTestId("Panel/OkButton").click(); await changePkPanel.getByTestId("Panel/OkButton").click();
// await expect(changePkPanel).not.toBeVisible({ timeout: 5 * 60 * 1000 }); await pageInstance.waitForLoadState("networkidle");
await expect(changePkPanel).not.toBeVisible({ timeout: 60 * 1000 });
// // Verify partition key change job // Verify partition key change job
// const jobText = explorer.frame.getByText(/Partition key change job/); const jobText = explorer.frame.getByText(/Partition key change job/);
// await expect(jobText).toBeVisible(); await expect(jobText).toBeVisible();
// await expect(explorer.frame.locator(".ms-ProgressIndicator-itemName")).toContainText("Portal_testcontainer_1"); await expect(explorer.frame.locator(".ms-ProgressIndicator-itemName")).toContainText("Portal_testcontainer_1");
// const jobRow = explorer.frame.locator(".ms-ProgressIndicator-itemDescription"); const jobRow = explorer.frame.locator(".ms-ProgressIndicator-itemDescription");
// // await expect(jobRow.getByText("Pending")).toBeVisible({ timeout: 30 * 1000 }); await expect(jobRow.getByText("Completed")).toBeVisible({ timeout: 30 * 1000 });
// await expect(jobRow.getByText("Completed")).toBeVisible({ timeout: 5 * 60 * 1000 });
// const newContainerNode = await explorer.waitForContainerNode(context.database.id, newContainerId); const newContainerNode = await explorer.waitForContainerNode(context.database.id, newContainerId);
// expect(newContainerNode).not.toBeNull(); expect(newContainerNode).not.toBeNull();
// // Now try to switch to existing container // Now try to switch to existing container
// await changePartitionKeyButton.click(); await changePartitionKeyButton.click();
// await changePkPanel.getByText("Existing container").click(); await changePkPanel.getByText("Existing container").click();
// await changePkPanel.getByLabel("Use existing container").check(); await changePkPanel.getByLabel("Use existing container").check();
// await changePkPanel.getByText("Choose an existing container").click(); await changePkPanel.getByText("Choose an existing container").click();
// const containerDropdownItem = await explorer.getDropdownItemByName(newContainerId, "Existing Containers"); const containerDropdownItem = await explorer.getDropdownItemByName(newContainerId, "Existing Containers");
// await containerDropdownItem.click(); await containerDropdownItem.click();
// await changePkPanel.getByTestId("Panel/OkButton").click(); await changePkPanel.getByTestId("Panel/OkButton").click();
// await explorer.frame.getByRole("button", { name: "Cancel" }).click(); await explorer.frame.getByRole("button", { name: "Cancel" }).click();
// // Dismiss overlay if it appears // Dismiss overlay if it appears
// const overlayFrame = explorer.frame.locator("#webpack-dev-server-client-overlay").first(); const overlayFrame = explorer.frame.locator("#webpack-dev-server-client-overlay").first();
// if (await overlayFrame.count()) { if (await overlayFrame.count()) {
// await overlayFrame.contentFrame().getByLabel("Dismiss").click(); await overlayFrame.contentFrame().getByLabel("Dismiss").click();
// } }
// const cancelledJobRow = explorer.frame.getByTestId("Tab:tab0"); const cancelledJobRow = explorer.frame.getByTestId("Tab:tab0");
// await expect(cancelledJobRow.getByText("Cancelled")).toBeVisible({ timeout: 30 * 1000 }); await expect(cancelledJobRow.getByText("Cancelled")).toBeVisible({ timeout: 30 * 1000 });
// }); });
// }); });

View File

@@ -1,4 +1,4 @@
import { Browser, expect, Locator, Page, test } from "@playwright/test"; import { expect, Locator, test } from "@playwright/test";
import { import {
CommandBarButton, CommandBarButton,
DataExplorer, DataExplorer,
@@ -9,116 +9,121 @@ import {
} from "../../fx"; } from "../../fx";
import { createTestSQLContainer, TestContainerContext } from "../../testData"; import { createTestSQLContainer, TestContainerContext } from "../../testData";
interface SetupResult { test.describe("Autoscale and Manual throughput", () => {
context: TestContainerContext; let context: TestContainerContext = null!;
page: Page; let explorer: DataExplorer = null!;
explorer: DataExplorer;
}
test.describe("Autoscale throughput", () => { test.beforeAll("Create Test Database", async () => {
let setup: SetupResult; context = await createTestSQLContainer({ includeTestData: true });
test.beforeAll(async ({ browser }) => {
setup = await openScaleTab(browser);
// Switch manual -> autoscale once for this suite
const autoscaleRadioButton = setup.explorer.frame.getByText("Autoscale", { exact: true });
await autoscaleRadioButton.click();
await expect(setup.explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
await setup.explorer.commandBarButton(CommandBarButton.Save).click();
await expect(setup.explorer.getConsoleHeaderStatus()).toContainText(
`Successfully updated offer for collection ${setup.context.container.id}`,
{ timeout: 2 * ONE_MINUTE_MS },
);
}); });
test.afterAll(async () => { test.beforeEach("Open container settings", async ({ page }) => {
await cleanup(setup); explorer = await DataExplorer.open(page, TestAccount.SQL);
// Click Scale & Settings and open Scale tab
await explorer.openScaleAndSettings(context);
const scaleTab = explorer.frame.getByTestId("settings-tab-header/ScaleTab");
await scaleTab.click();
});
test.afterAll("Delete Test Database", async () => {
await context?.dispose();
}); });
test("Update autoscale max throughput", async () => { test("Update autoscale max throughput", async () => {
await getThroughputInput(setup.explorer, "autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString()); // By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await setup.explorer.commandBarButton(CommandBarButton.Save).click(); await switchManualToAutoscaleThroughput();
await expect(setup.explorer.getConsoleHeaderStatus()).toContainText( // Update autoscale max throughput
`Successfully updated offer for collection ${setup.context.container.id}`, await getThroughputInput("autopilot").fill(TEST_AUTOSCALE_MAX_THROUGHPUT_RU_2K.toString());
{ timeout: 2 * ONE_MINUTE_MS },
// Save
await explorer.commandBarButton(CommandBarButton.Save).click();
// Read console message
await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: 2 * ONE_MINUTE_MS,
},
); );
}); });
test("Update autoscale max throughput passed allowed limit", async () => { test("Update autoscale max throughput passed allowed limit", async () => {
const softAllowedMaxThroughputString = await setup.explorer.frame // By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await switchManualToAutoscaleThroughput();
// Get soft allowed max throughput and remove commas
const softAllowedMaxThroughputString = await explorer.frame
.getByTestId("soft-allowed-maximum-throughput") .getByTestId("soft-allowed-maximum-throughput")
.innerText(); .innerText();
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, "")); const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));
await getThroughputInput(setup.explorer, "autopilot").fill((softAllowedMaxThroughput * 10).toString()); // Try to set autoscale max throughput above allowed limit
await expect(setup.explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled(); await getThroughputInput("autopilot").fill((softAllowedMaxThroughput * 10).toString());
await expect(delayedApplyWarning(setup.explorer)).toBeVisible(); await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
"This update isn't possible because it would increase the total throughput",
);
}); });
test("Update autoscale max throughput with invalid increment", async () => { test("Update autoscale max throughput with invalid increment", async () => {
await getThroughputInput(setup.explorer, "autopilot").fill("1100"); // By default the created container has manual throughput (Containers created via JS SDK v4.7.0 cannot be created with autoscale throughput)
await expect(setup.explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled(); await switchManualToAutoscaleThroughput();
await expect(getThroughputInputErrorMessage(setup.explorer, "autopilot")).toContainText(
// Try to set autoscale max throughput with invalid increment
await getThroughputInput("autopilot").fill("1100");
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("autopilot")).toContainText(
"Throughput value must be in increments of 1000", "Throughput value must be in increments of 1000",
); );
}); });
});
test.describe("Manual throughput", () => {
let setup: SetupResult;
test.beforeAll(async ({ browser }) => {
setup = await openScaleTab(browser);
});
test.afterAll(async () => {
await cleanup(setup);
});
test("Update manual throughput", async () => { test("Update manual throughput", async () => {
await getThroughputInput(setup.explorer, "manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString()); await getThroughputInput("manual").fill(TEST_MANUAL_THROUGHPUT_RU_2K.toString());
await setup.explorer.commandBarButton(CommandBarButton.Save).click(); await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(setup.explorer.getConsoleHeaderStatus()).toContainText( await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${setup.context.container.id}`, `Successfully updated offer for collection ${context.container.id}`,
{ timeout: 2 * ONE_MINUTE_MS }, {
timeout: 2 * ONE_MINUTE_MS,
},
); );
}); });
test("Update manual throughput passed allowed limit", async () => { test("Update manual throughput passed allowed limit", async () => {
const softAllowedMaxThroughputString = await setup.explorer.frame // Get soft allowed max throughput and remove commas
const softAllowedMaxThroughputString = await explorer.frame
.getByTestId("soft-allowed-maximum-throughput") .getByTestId("soft-allowed-maximum-throughput")
.innerText(); .innerText();
const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, "")); const softAllowedMaxThroughput = Number(softAllowedMaxThroughputString.replace(/,/g, ""));
await getThroughputInput(setup.explorer, "manual").fill((softAllowedMaxThroughput * 10).toString()); // Try to set manual throughput above allowed limit
await expect(delayedApplyWarning(setup.explorer)).toBeVisible(); await getThroughputInput("manual").fill((softAllowedMaxThroughput * 10).toString());
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeDisabled();
await expect(getThroughputInputErrorMessage("manual")).toContainText(
"This update isn't possible because it would increase the total throughput",
);
}); });
// Helper methods
const getThroughputInput = (type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input`);
};
const getThroughputInputErrorMessage = (type: "manual" | "autopilot"): Locator => {
return explorer.frame.getByTestId(`${type}-throughput-input-error`);
};
const switchManualToAutoscaleThroughput = async (): Promise<void> => {
const autoscaleRadioButton = explorer.frame.getByText("Autoscale", { exact: true });
await autoscaleRadioButton.click();
await expect(explorer.commandBarButton(CommandBarButton.Save)).toBeEnabled();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(
`Successfully updated offer for collection ${context.container.id}`,
{
timeout: ONE_MINUTE_MS,
},
);
};
}); });
const delayedApplyWarning = (explorer: DataExplorer): Locator =>
explorer.frame.locator("#updateThroughputDelayedApplyWarningMessage");
const getThroughputInput = (explorer: DataExplorer, type: "manual" | "autopilot"): Locator =>
explorer.frame.getByTestId(`${type}-throughput-input`);
const getThroughputInputErrorMessage = (explorer: DataExplorer, type: "manual" | "autopilot"): Locator =>
explorer.frame.getByTestId(`${type}-throughput-input-error`);
async function openScaleTab(browser: Browser): Promise<SetupResult> {
const context = await createTestSQLContainer();
const page = await browser.newPage();
const explorer = await DataExplorer.open(page, TestAccount.SQL);
await explorer.openScaleAndSettings(context);
await explorer.frame.getByTestId("settings-tab-header/ScaleTab").click();
return { context, page, explorer };
}
async function cleanup({ context }: Partial<SetupResult>) {
if (!process.env.CI) {
await context?.dispose();
}
}

View File

@@ -6,10 +6,14 @@ test.describe("Settings under Scale & Settings", () => {
let context: TestContainerContext = null!; let context: TestContainerContext = null!;
let explorer: DataExplorer = null!; let explorer: DataExplorer = null!;
test.beforeAll("Create Test Database & Open Settings tab", async ({ browser }) => { test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(); context = await createTestSQLContainer({ includeTestData: true });
const page = await browser.newPage(); });
test.beforeEach("Open Settings tab under Scale & Settings", async ({ page }) => {
explorer = await DataExplorer.open(page, TestAccount.SQL); explorer = await DataExplorer.open(page, TestAccount.SQL);
const containerNode = await explorer.waitForContainerNode(context.database.id, context.container.id);
await containerNode.expand();
// Click Scale & Settings and open Scale tab // Click Scale & Settings and open Scale tab
await explorer.openScaleAndSettings(context); await explorer.openScaleAndSettings(context);
@@ -17,24 +21,18 @@ test.describe("Settings under Scale & Settings", () => {
await settingsTab.click(); await settingsTab.click();
}); });
// Delete database only if not running in CI test.afterAll("Delete Test Database", async () => {
if (!process.env.CI) { await context?.dispose();
test.afterAll("Delete Test Database", async () => { });
await context?.dispose();
});
}
test("Update TTL to On (no default)", async () => { test("Update TTL to On (no default)", async () => {
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" }); const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
await ttlOnNoDefaultRadioButton.click(); await ttlOnNoDefaultRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click(); await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleHeaderStatus()).toContainText( await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
`Successfully updated container ${context.container.id}`, timeout: ONE_MINUTE_MS,
{ });
timeout: 2 * ONE_MINUTE_MS,
},
);
}); });
test("Update TTL to On (with user entry)", async () => { test("Update TTL to On (with user entry)", async () => {
@@ -46,11 +44,27 @@ test.describe("Settings under Scale & Settings", () => {
await ttlInput.fill("30000"); await ttlInput.fill("30000");
await explorer.commandBarButton(CommandBarButton.Save).click(); await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleHeaderStatus()).toContainText( await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
`Successfully updated container ${context.container.id}`, timeout: ONE_MINUTE_MS,
{ });
timeout: 2 * ONE_MINUTE_MS, });
},
); test("Update TTL to Off", async () => {
// By default TTL is set to off so we need to first set it to On
const ttlOnNoDefaultRadioButton = explorer.frame.getByRole("radio", { name: "ttl-on-no-default-option" });
await ttlOnNoDefaultRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
// Set it to Off
const ttlOffRadioButton = explorer.frame.getByRole("radio", { name: "ttl-off-option" });
await ttlOffRadioButton.click();
await explorer.commandBarButton(CommandBarButton.Save).click();
await expect(explorer.getConsoleMessage()).toContainText(`Successfully updated container ${context.container.id}`, {
timeout: ONE_MINUTE_MS,
});
}); });
}); });

View File

@@ -7,8 +7,7 @@ test("Tables CRUD", async ({ page }) => {
const explorer = await DataExplorer.open(page, TestAccount.Tables); const explorer = await DataExplorer.open(page, TestAccount.Tables);
const newTableButton = explorer.frame.getByTestId("GlobalCommands").getByRole("button", { name: "New Table" }); await explorer.globalCommandButton("New Table").click();
await newTableButton.click();
await explorer.whilePanelOpen( await explorer.whilePanelOpen(
"New Table", "New Table",
async (panel, okButton) => { async (panel, okButton) => {

View File

@@ -74,50 +74,17 @@ async function main() {
} }
} else if (account.kind === "GlobalDocumentDB") { } else if (account.kind === "GlobalDocumentDB") {
const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name); const sqlDatabases = await client.sqlResources.listSqlDatabases(resourceGroupName, account.name);
const sqlDatabasesToDelete = sqlDatabases.map(async (database) => { for (const database of sqlDatabases) {
await deleteWithRetry(client, database, account.name); const timestamp = Number(database.resource._ts) * 1000;
}); if (timestamp && timestamp < thirtyMinutesAgo) {
await Promise.all(sqlDatabasesToDelete); await client.sqlResources.deleteSqlDatabase(resourceGroupName, account.name, database.name);
} console.log(`DELETED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
} } else {
} console.log(`SKIPPED: ${account.name} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
}
// Retry logic for handling throttling
async function deleteWithRetry(client, database, accountName) {
const maxRetries = 5;
let attempt = 0;
let backoffTime = 1000; // Start with 1 second
while (attempt < maxRetries) {
try {
const timestamp = Number(database.resource._ts) * 1000;
if (timestamp && timestamp < thirtyMinutesAgo) {
await client.sqlResources.deleteSqlDatabase(resourceGroupName, accountName, database.name);
console.log(`DELETED: ${accountName} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
} else {
console.log(`SKIPPED: ${accountName} | ${database.name} | Age: ${friendlyTime(Date.now() - timestamp)}`);
}
return;
} catch (error) {
if (error.statusCode === 429) {
// Throttling error (HTTP 429), apply exponential backoff
console.log(`Throttling detected, retrying ${database.name}... (Attempt ${attempt + 1})`);
await delay(backoffTime);
attempt++;
backoffTime *= 2; // Exponential backoff
} else {
// For other errors, log and break
console.error(`Error deleting ${database.name}:`, error);
break;
} }
} }
} }
console.log(`Failed to delete ${database.name} after ${maxRetries} attempts.`);
}
// Helper function to delay the retry attempts
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
} }
main() main()
@@ -129,4 +96,4 @@ main()
console.log(err); console.log(err);
console.log("Cleanup failed! Exiting with success. Cleanup should always fail safe."); console.log("Cleanup failed! Exiting with success. Cleanup should always fail safe.");
process.exit(0); process.exit(0);
}); });