Compare commits

...

7 Commits

Author SHA1 Message Date
Sakshi Gupta
91cd61d387 Merge branch 'master' into users/sakshig/darkthemeCopyJobs-4050631 2025-12-29 21:51:37 +05:30
BChoudhury-ms
6167f94bc3 fix: restore SidePanel component for Container Copy feature (#2295) 2025-12-29 21:46:47 +05:30
BChoudhury-ms
be89c634f3 Add E2E tests for partition key change workflow (#2293) 2025-12-29 15:08:54 +05:30
Sakshi Gupta
e9151bcaf0 Fix formatting in Utils.test.ts 2025-12-29 13:13:23 +05:30
Sakshi Gupta
72debd0778 added a dark theme toggle button on Copyjobs next to refresh button and covered full feature 2025-12-29 12:57:26 +05:30
sakshigupta12feb
42e230b88b Few more UI observation (fixes) (#2283)
* fixed bottom border for fabric

* fixed scrollbar

* reverted last

* updated the review comments

* Fixed scroll , updated the home page UI box shadow, header font weight, margin between boxed , subtab underline for fabric fixed

---------

Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2025-12-17 23:54:30 +05:30
sakshigupta12feb
6196ba4722 Fixed bottom border for fabric and small UI changes (#2282)
* fixed bottom border for fabric

* updated the review comments

---------

Co-authored-by: Sakshi Gupta <sakshig@microsoft.com>
2025-12-16 21:35:08 +05:30
47 changed files with 468 additions and 172 deletions

View File

@@ -128,7 +128,7 @@
@provisionDatabaseThroughputInfo: 200px; @provisionDatabaseThroughputInfo: 200px;
//tabs container //tabs container
@ActiveTabHeight: 31px; @ActiveTabHeight: 32px;
@ActiveTabWidth: 141px; @ActiveTabWidth: 141px;
@TabsHeight: 30px; @TabsHeight: 30px;
@TabsWidth: 140px; @TabsWidth: 140px;

View File

@@ -406,7 +406,11 @@ body {
width: 440px; width: 440px;
min-height: 565px; min-height: 565px;
} }
.dataExplorerLoaderforcopyJobs{
width: 100%;
min-height: 565px;
right: 0;
}
.dataExplorerTabLoaderContainer { .dataExplorerTabLoaderContainer {
left: initial; left: initial;
top: initial; top: initial;
@@ -2643,7 +2647,7 @@ a:link {
.tabPanesContainer { .tabPanesContainer {
flex-grow: 1; flex-grow: 1;
overflow-y: scroll; overflow: hidden;
background-color: var(--colorNeutralBackground1); background-color: var(--colorNeutralBackground1);
color: var(--colorNeutralForeground1); color: var(--colorNeutralForeground1);
} }
@@ -2651,6 +2655,7 @@ a:link {
.tabs-container { .tabs-container {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow-y: auto;
} }
.paddingspan4 { .paddingspan4 {
@@ -2677,7 +2682,7 @@ a:link {
width: @ActiveTabWidth; width: @ActiveTabWidth;
} }
.nav-tabs > li.active .contentWrapper { .nav-tabs > li.active .contentWrapper .tabNavText {
border-bottom: 2px solid var(--colorCompoundBrandBackground); border-bottom: 2px solid var(--colorCompoundBrandBackground);
} }

View File

@@ -7,6 +7,7 @@ html {
body { body {
font-family: @FabricFont; font-family: @FabricFont;
background-color: #f5f5f5; background-color: #f5f5f5;
--colorCompoundBrandBackground: @FabricAccentMedium;
} }
a { a {
@@ -41,7 +42,7 @@ a:focus {
} }
.nav-tabs-margin { .nav-tabs-margin {
padding-top: 5px; padding-top: 0px;
background-color: #ffffff; background-color: #ffffff;
} }
@@ -68,17 +69,20 @@ a:focus {
} }
.nav-tabs > li > .tabNavContentContainer > .tab_Content:hover { .nav-tabs > li > .tabNavContentContainer > .tab_Content:hover {
border-bottom: 2px solid #e0e0e0; border-bottom: none;
} }
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content, .nav-tabs > li.active > .tabNavContentContainer > .tab_Content,
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content:hover { .nav-tabs > li.active > .tabNavContentContainer > .tab_Content:hover {
border-bottom: 2px solid @FabricAccentMedium; border-bottom: none;
} }
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText { .nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
border-bottom: 0px none transparent; border-bottom: 0px none transparent;
} }
.nav-tabs > li.active .contentWrapper .tabNavText {
border-bottom: 2px solid @FabricAccentMedium;
}
.tabNavContentContainer { .tabNavContentContainer {
padding: @SmallSpace 0px @SmallSpace 0px; padding: @SmallSpace 0px @SmallSpace 0px;

View File

@@ -1,4 +1,5 @@
import { Overlay, Spinner, SpinnerSize } from "@fluentui/react"; import { Overlay, Spinner, SpinnerSize } from "@fluentui/react";
import { useThemeStore } from "hooks/useTheme";
import React from "react"; import React from "react";
interface LoadingOverlayProps { interface LoadingOverlayProps {
@@ -7,6 +8,7 @@ interface LoadingOverlayProps {
} }
const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) => { const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) => {
const isDarkMode = useThemeStore((state) => state.isDarkMode);
if (!isLoading) { if (!isLoading) {
return null; return null;
} }
@@ -15,7 +17,7 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) =>
<Overlay <Overlay
styles={{ styles={{
root: { root: {
backgroundColor: "rgba(255,255,255,0.9)", backgroundColor: isDarkMode ? "rgba(32, 31, 30, 0.9)" : "rgba(255,255,255,0.9)",
zIndex: 9999, zIndex: 9999,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
@@ -23,7 +25,11 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({ isLoading, label }) =>
}, },
}} }}
> >
<Spinner size={SpinnerSize.large} label={label} styles={{ label: { fontWeight: 600 } }} /> <Spinner
size={SpinnerSize.large}
label={label}
styles={{ label: { fontWeight: 600, color: isDarkMode ? "#ffffff" : "#323130" } }}
/>
</Overlay> </Overlay>
); );
}; };

View File

@@ -11,3 +11,14 @@
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
/* Override dark mode inherit for pagination icons */
body.isDarkMode .pager-container .ms-Button .ms-Button-icon,
body.isDarkMode .pager-container .ms-Button i {
color: var(--colorBrandForeground1);
}
body.isDarkMode .pager-container .ms-Button:disabled .ms-Button-icon,
body.isDarkMode .pager-container .ms-Button:disabled i {
color: var(--colorNeutralForegroundDisabled);
}

View File

@@ -31,6 +31,7 @@ const iconButtonStyles = {
outline: "none", outline: "none",
}, },
}; };
const textStyle: React.CSSProperties = { color: "var(--colorNeutralForeground1)" };
const Pager: React.FC<PagerProps> = ({ const Pager: React.FC<PagerProps> = ({
startIndex, startIndex,
@@ -59,7 +60,7 @@ const Pager: React.FC<PagerProps> = ({
return ( return (
<div className={className || "pager-container"}> <div className={className || "pager-container"}>
{showItemCount && ( {showItemCount && (
<Text> <Text style={textStyle}>
Showing {startIndex + 1} - {endIndex} of {totalCount} items Showing {startIndex + 1} - {endIndex} of {totalCount} items
</Text> </Text>
)} )}
@@ -82,7 +83,7 @@ const Pager: React.FC<PagerProps> = ({
disabled={disabled || currentPage === 1} disabled={disabled || currentPage === 1}
styles={iconButtonStyles} styles={iconButtonStyles}
/> />
<Text> <Text style={textStyle}>
Page {currentPage} of {totalPages} Page {currentPage} of {totalPages}
</Text> </Text>
<IconButton <IconButton

View File

@@ -1,24 +1,28 @@
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react"; import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
import React from "react"; import React from "react";
import { StyleConstants } from "../../../Common/StyleConstants"; import { useThemeStore } from "../../../hooks/useTheme";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import * as CommandBarUtil from "../../Menus/CommandBar/CommandBarUtil"; import * as CommandBarUtil from "../../Menus/CommandBar/CommandBarUtil";
import { getThemeTokens } from "../../Theme/ThemeUtil";
import { ContainerCopyProps } from "../Types/CopyJobTypes"; import { ContainerCopyProps } from "../Types/CopyJobTypes";
import { getCommandBarButtons } from "./Utils"; import { getCommandBarButtons } from "./Utils";
const backgroundColor = StyleConstants.BaseLight;
const rootStyle = {
root: {
backgroundColor: backgroundColor,
},
};
const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ explorer }) => { const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ explorer }) => {
const isDarkMode = useThemeStore((state) => state.isDarkMode);
const themeTokens = getThemeTokens(isDarkMode);
const backgroundColor = themeTokens.colorNeutralBackground1;
const rootStyle = {
root: {
backgroundColor: backgroundColor,
},
};
const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(explorer); const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(explorer);
const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor); const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor);
return ( return (
<div className="commandBarContainer"> <div className="commandBarContainer" style={{ backgroundColor }}>
<FluentCommandBar <FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands" ariaLabel="Use left and right arrow keys to navigate between commands"
styles={rootStyle} styles={rootStyle}

View File

@@ -82,9 +82,9 @@ describe("CommandBar Utils", () => {
it("should include feedback button when platform is Portal", () => { it("should include feedback button when platform is Portal", () => {
const buttons = getCommandBarButtons(mockExplorer); const buttons = getCommandBarButtons(mockExplorer);
expect(buttons.length).toBe(3); expect(buttons.length).toBe(4);
const feedbackButton = buttons[2]; const feedbackButton = buttons[3];
expect(feedbackButton).toBeDefined(); expect(feedbackButton).toBeDefined();
expect(feedbackButton.ariaLabel).toBe("Provide feedback on copy jobs"); expect(feedbackButton.ariaLabel).toBe("Provide feedback on copy jobs");
expect(feedbackButton.tooltipText).toBe("Feedback"); expect(feedbackButton.tooltipText).toBe("Feedback");
@@ -107,7 +107,7 @@ describe("CommandBar Utils", () => {
const { getCommandBarButtons: getCommandBarButtonsEmulator } = await import("./Utils"); const { getCommandBarButtons: getCommandBarButtonsEmulator } = await import("./Utils");
const buttons = getCommandBarButtonsEmulator(mockExplorer); const buttons = getCommandBarButtonsEmulator(mockExplorer);
expect(buttons.length).toBe(2); expect(buttons.length).toBe(3);
}); });
it("should call openCreateCopyJobPanel when create button is clicked", () => { it("should call openCreateCopyJobPanel when create button is clicked", () => {
@@ -131,7 +131,7 @@ describe("CommandBar Utils", () => {
it("should call openContainerCopyFeedbackBlade when feedback button is clicked", () => { it("should call openContainerCopyFeedbackBlade when feedback button is clicked", () => {
const buttons = getCommandBarButtons(mockExplorer); const buttons = getCommandBarButtons(mockExplorer);
const feedbackButton = buttons[2]; const feedbackButton = buttons[3];
feedbackButton.onCommandClick({} as React.SyntheticEvent); feedbackButton.onCommandClick({} as React.SyntheticEvent);
@@ -148,7 +148,10 @@ describe("CommandBar Utils", () => {
expect(buttons[1].iconAlt).toBe("Refresh"); expect(buttons[1].iconAlt).toBe("Refresh");
expect(buttons[2].iconSrc).toBeDefined(); expect(buttons[2].iconSrc).toBeDefined();
expect(buttons[2].iconAlt).toBe("Feedback"); expect(buttons[2].iconAlt).toBe("Dark Theme");
expect(buttons[3].iconSrc).toBeDefined();
expect(buttons[3].iconAlt).toBe("Feedback");
}); });
it("should handle null MonitorCopyJobsRefState ref gracefully", () => { it("should handle null MonitorCopyJobsRefState ref gracefully", () => {
@@ -202,12 +205,13 @@ describe("CommandBar Utils", () => {
}); });
}); });
it("should maintain button order: create, refresh, feedback", () => { it("should maintain button order: create, refresh, themeToggle, feedback", () => {
const buttons = getCommandBarButtons(mockExplorer); const buttons = getCommandBarButtons(mockExplorer);
expect(buttons[0].tooltipText).toBe("Create Copy Job"); expect(buttons[0].tooltipText).toBe("Create Copy Job");
expect(buttons[1].tooltipText).toBe("Refresh"); expect(buttons[1].tooltipText).toBe("Refresh");
expect(buttons[2].tooltipText).toBe("Feedback"); expect(buttons[2].tooltipText).toBe("Dark Theme");
expect(buttons[3].tooltipText).toBe("Feedback");
}); });
}); });
@@ -229,7 +233,7 @@ describe("CommandBar Utils", () => {
buttons[1].onCommandClick({} as React.SyntheticEvent); buttons[1].onCommandClick({} as React.SyntheticEvent);
expect(mockRefreshJobList).toHaveBeenCalled(); expect(mockRefreshJobList).toHaveBeenCalled();
buttons[2].onCommandClick({} as React.SyntheticEvent); buttons[3].onCommandClick({} as React.SyntheticEvent);
expect(mockOpenContainerCopyFeedbackBlade).toHaveBeenCalled(); expect(mockOpenContainerCopyFeedbackBlade).toHaveBeenCalled();
}); });
}); });

View File

@@ -1,7 +1,10 @@
import AddIcon from "../../../../images/Add.svg"; import AddIcon from "../../../../images/Add.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import MoonIcon from "../../../../images/MoonIcon.svg";
import RefreshIcon from "../../../../images/refresh-cosmos.svg"; import RefreshIcon from "../../../../images/refresh-cosmos.svg";
import SunIcon from "../../../../images/SunIcon.svg";
import { configContext, Platform } from "../../../ConfigContext"; import { configContext, Platform } from "../../../ConfigContext";
import { useThemeStore } from "../../../hooks/useTheme";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import * as Actions from "../Actions/CopyJobActions"; import * as Actions from "../Actions/CopyJobActions";
@@ -11,6 +14,7 @@ import { CopyJobCommandBarBtnType } from "../Types/CopyJobTypes";
function getCopyJobBtns(explorer: Explorer): CopyJobCommandBarBtnType[] { function getCopyJobBtns(explorer: Explorer): CopyJobCommandBarBtnType[] {
const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref); const monitorCopyJobsRef = MonitorCopyJobsRefState((state) => state.ref);
const isDarkMode = useThemeStore.getState().isDarkMode;
const buttons: CopyJobCommandBarBtnType[] = [ const buttons: CopyJobCommandBarBtnType[] = [
{ {
key: "createCopyJob", key: "createCopyJob",
@@ -26,7 +30,15 @@ function getCopyJobBtns(explorer: Explorer): CopyJobCommandBarBtnType[] {
ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel, ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel,
onClick: () => monitorCopyJobsRef?.refreshJobList(), onClick: () => monitorCopyJobsRef?.refreshJobList(),
}, },
{
key: "themeToggle",
iconSrc: isDarkMode ? SunIcon : MoonIcon,
label: isDarkMode ? "Light Theme" : "Dark Theme",
ariaLabel: isDarkMode ? "Switch to Light Theme" : "Switch to Dark Theme",
onClick: () => useThemeStore.getState().toggleTheme(),
},
]; ];
if (configContext.platform === Platform.Portal) { if (configContext.platform === Platform.Portal) {
buttons.push({ buttons.push({
key: "feedback", key: "feedback",

View File

@@ -12,7 +12,12 @@ import useToggle from "./hooks/useToggle";
const managedIdentityTooltip = ( const managedIdentityTooltip = (
<Text> <Text>
{ContainerCopyMessages.addManagedIdentity.tooltip.content} &nbsp; {ContainerCopyMessages.addManagedIdentity.tooltip.content} &nbsp;
<Link href={ContainerCopyMessages.addManagedIdentity.tooltip.href} target="_blank" rel="noopener noreferrer"> <Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.addManagedIdentity.tooltip.href}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.addManagedIdentity.tooltip.hrefText} {ContainerCopyMessages.addManagedIdentity.tooltip.hrefText}
</Link> </Link>
</Text> </Text>
@@ -26,7 +31,7 @@ const AddManagedIdentity: React.FC<AddManagedIdentityProps> = () => {
return ( return (
<Stack className="addManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}> <Stack className="addManagedIdentityContainer" tokens={{ childrenGap: 15, padding: "0 0 0 20px" }}>
<Text> <Text className="themeText">
{ContainerCopyMessages.addManagedIdentity.description}&ensp; {ContainerCopyMessages.addManagedIdentity.description}&ensp;
<Link href={ContainerCopyMessages.addManagedIdentity.descriptionHref} target="_blank" rel="noopener noreferrer"> <Link href={ContainerCopyMessages.addManagedIdentity.descriptionHref} target="_blank" rel="noopener noreferrer">
{ContainerCopyMessages.addManagedIdentity.descriptionHrefText} {ContainerCopyMessages.addManagedIdentity.descriptionHrefText}

View File

@@ -13,7 +13,12 @@ import useToggle from "./hooks/useToggle";
const TooltipContent = ( const TooltipContent = (
<Text> <Text>
{ContainerCopyMessages.readPermissionAssigned.tooltip.content} &nbsp; {ContainerCopyMessages.readPermissionAssigned.tooltip.content} &nbsp;
<Link href={ContainerCopyMessages.readPermissionAssigned.tooltip.href} target="_blank" rel="noopener noreferrer"> <Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.readPermissionAssigned.tooltip.href}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText} {ContainerCopyMessages.readPermissionAssigned.tooltip.hrefText}
</Link> </Link>
</Text> </Text>

View File

@@ -47,8 +47,8 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description,
tokens={{ childrenGap: 15 }} tokens={{ childrenGap: 15 }}
styles={{ styles={{
root: { root: {
background: "#fafafa", background: "var(--colorNeutralBackground2)",
border: "1px solid #e1e1e1", border: "1px solid var(--colorNeutralStroke1)",
borderRadius: 8, borderRadius: 8,
padding: 16, padding: 16,
boxShadow: "0 1px 3px rgba(0,0,0,0.1)", boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
@@ -56,11 +56,11 @@ const PermissionGroup: React.FC<PermissionGroupConfig> = ({ title, description,
}} }}
> >
<Stack tokens={{ childrenGap: 5 }}> <Stack tokens={{ childrenGap: 5 }}>
<Text variant="medium" style={{ fontWeight: 600 }}> <Text variant="medium" style={{ fontWeight: 600, color: "var(--colorNeutralForeground1)" }}>
{title} {title}
</Text> </Text>
{description && ( {description && (
<Text variant="small" styles={{ root: { color: "#605E5C" } }}> <Text variant="small" styles={{ root: { color: "var(--colorNeutralForeground2)" } }}>
{description} {description}
</Text> </Text>
)} )}
@@ -100,7 +100,7 @@ const AssignPermissions = () => {
return ( return (
<Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}> <Stack className="assignPermissionsContainer" tokens={{ childrenGap: 20 }}>
<Text variant="medium"> <Text variant="medium" style={{ color: "var(--colorNeutralForeground1)" }}>
{isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online {isSameAccount && copyJobState.migrationType === CopyJobMigrationType.Online
? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription( ? ContainerCopyMessages.assignPermissions.intraAccountOnlineDescription(
copyJobState?.source?.account?.name || "", copyJobState?.source?.account?.name || "",

View File

@@ -12,7 +12,12 @@ import useToggle from "./hooks/useToggle";
const managedIdentityTooltip = ( const managedIdentityTooltip = (
<Text> <Text>
{ContainerCopyMessages.defaultManagedIdentity.tooltip.content} &nbsp; {ContainerCopyMessages.defaultManagedIdentity.tooltip.content} &nbsp;
<Link href={ContainerCopyMessages.defaultManagedIdentity.tooltip.href} target="_blank" rel="noopener noreferrer"> <Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.defaultManagedIdentity.tooltip.href}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText} {ContainerCopyMessages.defaultManagedIdentity.tooltip.hrefText}
</Link> </Link>
</Text> </Text>

View File

@@ -13,7 +13,12 @@ import InfoTooltip from "../Components/InfoTooltip";
const tooltipContent = ( const tooltipContent = (
<Text> <Text>
{ContainerCopyMessages.pointInTimeRestore.tooltip.content} &nbsp; {ContainerCopyMessages.pointInTimeRestore.tooltip.content} &nbsp;
<Link href={ContainerCopyMessages.pointInTimeRestore.tooltip.href} target="_blank" rel="noopener noreferrer"> <Link
style={{ color: "var(--colorBrandForeground1)" }}
href={ContainerCopyMessages.pointInTimeRestore.tooltip.href}
target="_blank"
rel="noopener noreferrer"
>
{ContainerCopyMessages.pointInTimeRestore.tooltip.hrefText} {ContainerCopyMessages.pointInTimeRestore.tooltip.hrefText}
</Link> </Link>
</Text> </Text>

View File

@@ -5,7 +5,7 @@ exports[`AddManagedIdentity Snapshot Tests renders initial state correctly 1`] =
class="ms-Stack addManagedIdentityContainer css-109" class="ms-Stack addManagedIdentityContainer css-109"
> >
<span <span
class="css-110" class="themeText css-110"
> >
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code. A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
@@ -92,7 +92,7 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
class="ms-Stack addManagedIdentityContainer css-109" class="ms-Stack addManagedIdentityContainer css-109"
> >
<span <span
class="css-110" class="themeText css-110"
> >
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code. A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
@@ -192,13 +192,13 @@ exports[`AddManagedIdentity Snapshot Tests renders loading state 1`] = `
</div> </div>
</div> </div>
<span <span
class="css-124" class="themeText css-124"
style="font-weight: 600;" style="font-weight: 600;"
> >
Enable system assigned managed identity Enable system assigned managed identity
</span> </span>
<span <span
class="css-110" class="themeText css-110"
> >
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button. Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
</span> </span>
@@ -261,7 +261,7 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
class="ms-Stack addManagedIdentityContainer css-109" class="ms-Stack addManagedIdentityContainer css-109"
> >
<span <span
class="css-110" class="themeText css-110"
> >
A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code. A system-assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. Once enabled, you can grant permissions to the managed identity by using Azure role-based access control (Azure RBAC). The managed identity is authenticated with Microsoft Entra ID, so you dont have to store any credentials in code.
@@ -345,13 +345,13 @@ exports[`AddManagedIdentity Snapshot Tests renders with toggle on and popover vi
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-124" class="themeText css-124"
style="font-weight: 600;" style="font-weight: 600;"
> >
Enable system assigned managed identity Enable system assigned managed identity
</span> </span>
<span <span
class="css-110" class="themeText css-110"
> >
Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button. Enable system-assigned managed identity on the test-target-account. To confirm, click the "Yes" button.
</span> </span>

View File

@@ -22,10 +22,10 @@ const PopoverContainer: React.FC<PopoverContainerProps> = React.memo(
style={{ maxWidth: 450 }} style={{ maxWidth: 450 }}
> >
<LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} /> <LoadingOverlay isLoading={isLoading} label={ContainerCopyMessages.popoverOverlaySpinnerLabel} />
<Text variant="mediumPlus" style={{ fontWeight: 600 }}> <Text variant="mediumPlus" className="themeText" style={{ fontWeight: 600 }}>
{title} {title}
</Text> </Text>
<Text>{children}</Text> <Text className="themeText">{children}</Text>
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20 }}>
<PrimaryButton text={"Yes"} onClick={onPrimary} disabled={isLoading} /> <PrimaryButton text={"Yes"} onClick={onPrimary} disabled={isLoading} />
<DefaultButton text="No" onClick={onCancel} disabled={isLoading} /> <DefaultButton text="No" onClick={onCancel} disabled={isLoading} />

View File

@@ -7,11 +7,11 @@ exports[`PopoverMessage Component Edge Cases should handle empty string title 1`
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
/> />
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
Test content Test content
@@ -74,7 +74,7 @@ exports[`PopoverMessage Component Edge Cases should handle null children 1`] = `
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Test Title Test Title
@@ -136,7 +136,7 @@ exports[`PopoverMessage Component Edge Cases should handle undefined children 1`
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Test Title Test Title
@@ -198,13 +198,13 @@ exports[`PopoverMessage Component Edge Cases should handle very long title 1`] =
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
This is a very long title that might cause layout issues or text wrapping in the popover component This is a very long title that might cause layout issues or text wrapping in the popover component
</span> </span>
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
Test content Test content
@@ -269,13 +269,13 @@ exports[`PopoverMessage Component Rendering should render correctly when visible
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Test Title Test Title
</span> </span>
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
Test content Test content
@@ -338,13 +338,13 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Test Title Test Title
</span> </span>
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
<p> <p>
@@ -412,13 +412,13 @@ exports[`PopoverMessage Component Rendering should render correctly with differe
style="max-width: 450px;" style="max-width: 450px;"
> >
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Custom Title Custom Title
</span> </span>
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
Test content Test content
@@ -485,13 +485,13 @@ exports[`PopoverMessage Component Rendering should render correctly with loading
data-testid="loading-overlay" data-testid="loading-overlay"
/> />
<span <span
class="css-110" class="themeText css-110"
style="font-weight: 600;" style="font-weight: 600;"
> >
Test Title Test Title
</span> </span>
<span <span
class="css-111" class="themeText css-111"
> >
<div> <div>
Test content Test content

View File

@@ -41,7 +41,7 @@ const AddCollectionPanelWrapper: React.FunctionComponent<AddCollectionPanelWrapp
return ( return (
<Stack className="addCollectionPanelWrapper"> <Stack className="addCollectionPanelWrapper">
<Stack.Item className="addCollectionPanelHeader"> <Stack.Item className="addCollectionPanelHeader">
<Text>{ContainerCopyMessages.createNewContainerSubHeading}</Text> <Text className="themeText">{ContainerCopyMessages.createNewContainerSubHeading}</Text>
</Stack.Item> </Stack.Item>
<Stack.Item className="addCollectionPanelBody"> <Stack.Item className="addCollectionPanelBody">
<AddCollectionPanel explorer={explorer} isCopyJobFlow={true} onSubmitSuccess={handleAddCollectionSuccess} /> <AddCollectionPanel explorer={explorer} isCopyJobFlow={true} onSubmitSuccess={handleAddCollectionSuccess} />

View File

@@ -9,7 +9,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot 1`]
class="ms-StackItem addCollectionPanelHeader css-110" class="ms-StackItem addCollectionPanelHeader css-110"
> >
<span <span
class="css-111" class="themeText css-111"
> >
Select the properties for your container. Select the properties for your container.
</span> </span>
@@ -50,7 +50,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
class="ms-StackItem addCollectionPanelHeader css-110" class="ms-StackItem addCollectionPanelHeader css-110"
> >
<span <span
class="css-111" class="themeText css-111"
> >
Select the properties for your container. Select the properties for your container.
</span> </span>
@@ -91,7 +91,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
class="ms-StackItem addCollectionPanelHeader css-110" class="ms-StackItem addCollectionPanelHeader css-110"
> >
<span <span
class="css-111" class="themeText css-111"
> >
Select the properties for your container. Select the properties for your container.
</span> </span>
@@ -132,7 +132,7 @@ exports[`AddCollectionPanelWrapper Component Rendering should match snapshot wit
class="ms-StackItem addCollectionPanelHeader css-110" class="ms-StackItem addCollectionPanelHeader css-110"
> >
<span <span
class="css-111" class="themeText css-111"
> >
Select the properties for your container. Select the properties for your container.
</span> </span>

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
import { Checkbox, Stack } from "@fluentui/react"; import { Checkbox, ICheckboxStyles, Stack } from "@fluentui/react";
import React from "react"; import React from "react";
import ContainerCopyMessages from "../../../../ContainerCopyMessages"; import ContainerCopyMessages from "../../../../ContainerCopyMessages";
@@ -9,8 +9,25 @@ interface MigrationTypeCheckboxProps {
onChange: (_ev?: React.FormEvent, checked?: boolean) => void; onChange: (_ev?: React.FormEvent, checked?: boolean) => void;
} }
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => ( const checkboxStyles: ICheckboxStyles = {
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow"> text: { color: "var(--colorNeutralForeground1)" },
<Checkbox label={ContainerCopyMessages.migrationTypeCheckboxLabel} checked={checked} onChange={onChange} /> checkbox: { borderColor: "var(--colorNeutralStroke1)" },
</Stack> root: {
)); selectors: {
":hover .ms-Checkbox-text": { color: "var(--colorNeutralForeground1)" },
},
},
};
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(({ checked, onChange }) => {
return (
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
<Checkbox
label={ContainerCopyMessages.migrationTypeCheckboxLabel}
checked={checked}
onChange={onChange}
styles={checkboxStyles}
/>
</Stack>
);
});

View File

@@ -21,7 +21,7 @@ const SelectAccount = React.memo(() => {
return ( return (
<Stack data-test="Panel:SelectAccountContainer" className="selectAccountContainer" tokens={{ childrenGap: 15 }}> <Stack data-test="Panel:SelectAccountContainer" className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
<Text>{ContainerCopyMessages.selectAccountDescription}</Text> <Text className="themeText">{ContainerCopyMessages.selectAccountDescription}</Text>
<SubscriptionDropdown /> <SubscriptionDropdown />

View File

@@ -6,7 +6,7 @@ exports[`SelectAccount Component Rendering should render correctly with snapshot
data-test="Panel:SelectAccountContainer" data-test="Panel:SelectAccountContainer"
> >
<span <span
class="css-110" class="themeText css-110"
> >
Please select a source account from which to copy. Please select a source account from which to copy.
</span> </span>

View File

@@ -48,7 +48,7 @@ const SelectSourceAndTargetContainers = ({ showAddCollectionPanel }: SelectSourc
return ( return (
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}> <Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>
<span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span> <span className="themeText">{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
<DatabaseContainerSection <DatabaseContainerSection
heading={ContainerCopyMessages.sourceContainerSubHeading} heading={ContainerCopyMessages.sourceContainerSubHeading}
databaseOptions={sourceDatabaseOptions} databaseOptions={sourceDatabaseOptions}

View File

@@ -41,7 +41,11 @@ export const DatabaseContainerSection = ({
onChange={containerOnChange} onChange={containerOnChange}
/> />
{handleOnDemandCreateContainer && ( {handleOnDemandCreateContainer && (
<ActionButton className="create-container-link-btn" onClick={() => handleOnDemandCreateContainer()}> <ActionButton
className="create-container-link-btn"
style={{ color: "var(--colorBrandForeground1)" }}
onClick={() => handleOnDemandCreateContainer()}
>
{ContainerCopyMessages.createContainerButtonLabel} {ContainerCopyMessages.createContainerButtonLabel}
</ActionButton> </ActionButton>
)} )}

View File

@@ -1,5 +1,6 @@
import { DetailsList, DetailsListLayoutMode, IColumn, Stack, Text } from "@fluentui/react"; import { DetailsList, DetailsListLayoutMode, IColumn, Stack, Text } from "@fluentui/react";
import React, { memo } from "react"; import React, { memo } from "react";
import { useThemeStore } from "../../../../hooks/useTheme";
import ContainerCopyMessages from "../../ContainerCopyMessages"; import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobStatusType } from "../../Enums/CopyJobEnums"; import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
import { CopyJobType } from "../../Types/CopyJobTypes"; import { CopyJobType } from "../../Types/CopyJobTypes";
@@ -63,6 +64,19 @@ const getCopyJobDetailsListColumns = (): IColumn[] => {
}; };
const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => { const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
const isDarkMode = useThemeStore((state) => state.isDarkMode);
const errorMessageStyle: React.CSSProperties = {
whiteSpace: "pre-wrap",
...(isDarkMode && {
whiteSpace: "pre-wrap",
backgroundColor: "var(--colorNeutralBackground2)",
color: "var(--colorNeutralForeground1)",
padding: "10px",
borderRadius: "4px",
}),
};
const selectedContainers = [ const selectedContainers = [
{ {
sourceContainerName: job?.Source?.containerName || "N/A", sourceContainerName: job?.Source?.containerName || "N/A",
@@ -77,10 +91,10 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
<Stack className="copyJobDetailsContainer" tokens={{ childrenGap: 15 }} data-testid="copy-job-details"> <Stack className="copyJobDetailsContainer" tokens={{ childrenGap: 15 }} data-testid="copy-job-details">
{job.Error ? ( {job.Error ? (
<Stack.Item data-testid="error-stack" style={sectionCss.verticalAlign}> <Stack.Item data-testid="error-stack" style={sectionCss.verticalAlign}>
<Text className="bold" style={sectionCss.headingText}> <Text className="bold themeText" style={sectionCss.headingText}>
{ContainerCopyMessages.errorTitle} {ContainerCopyMessages.errorTitle}
</Text> </Text>
<Text as="pre" style={{ whiteSpace: "pre-wrap" }}> <Text as="pre" style={errorMessageStyle}>
{job.Error.message} {job.Error.message}
</Text> </Text>
</Stack.Item> </Stack.Item>
@@ -88,16 +102,16 @@ const CopyJobDetails: React.FC<CopyJobDetailsProps> = ({ job }) => {
<Stack.Item data-testid="selectedcollection-stack"> <Stack.Item data-testid="selectedcollection-stack">
<Stack tokens={{ childrenGap: 15 }}> <Stack tokens={{ childrenGap: 15 }}>
<Stack.Item style={sectionCss.verticalAlign}> <Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold">{ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime}</Text> <Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.lastUpdatedTime}</Text>
<Text>{job.LastUpdatedTime}</Text> <Text className="themeText">{job.LastUpdatedTime}</Text>
</Stack.Item> </Stack.Item>
<Stack.Item style={sectionCss.verticalAlign}> <Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold">{ContainerCopyMessages.sourceAccountLabel}</Text> <Text className="bold themeText">{ContainerCopyMessages.sourceAccountLabel}</Text>
<Text>{job.Source?.remoteAccountName}</Text> <Text className="themeText">{job.Source?.remoteAccountName}</Text>
</Stack.Item> </Stack.Item>
<Stack.Item style={sectionCss.verticalAlign}> <Stack.Item style={sectionCss.verticalAlign}>
<Text className="bold">{ContainerCopyMessages.MonitorJobs.Columns.mode}</Text> <Text className="bold themeText">{ContainerCopyMessages.MonitorJobs.Columns.mode}</Text>
<Text>{job.Mode}</Text> <Text className="themeText">{job.Mode}</Text>
</Stack.Item> </Stack.Item>
</Stack> </Stack>
</Stack.Item> </Stack.Item>

View File

@@ -1,30 +1,14 @@
import { FontIcon, getTheme, mergeStyles, mergeStyleSets, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react"; import { FontIcon, mergeStyles, Spinner, SpinnerSize, Stack, Text } from "@fluentui/react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import React from "react"; import React from "react";
import ContainerCopyMessages from "../../ContainerCopyMessages"; import ContainerCopyMessages from "../../ContainerCopyMessages";
import { CopyJobStatusType } from "../../Enums/CopyJobEnums"; import { CopyJobStatusType } from "../../Enums/CopyJobEnums";
const theme = getTheme();
const iconClass = mergeStyles({ const iconClass = mergeStyles({
fontSize: "16px", fontSize: "16px",
marginRight: "8px", marginRight: "8px",
}); });
const classNames = mergeStyleSets({
[CopyJobStatusType.Pending]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
[CopyJobStatusType.InProgress]: [{ color: theme.palette.themePrimary }, iconClass],
[CopyJobStatusType.Running]: [{ color: theme.palette.themePrimary }, iconClass],
[CopyJobStatusType.Partitioning]: [{ color: theme.palette.themePrimary }, iconClass],
[CopyJobStatusType.Paused]: [{ color: theme.palette.themePrimary }, iconClass],
[CopyJobStatusType.Skipped]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
[CopyJobStatusType.Cancelled]: [{ color: theme.semanticColors.bodySubtext }, iconClass],
[CopyJobStatusType.Failed]: [{ color: theme.semanticColors.errorIcon }, iconClass],
[CopyJobStatusType.Faulted]: [{ color: theme.semanticColors.errorIcon }, iconClass],
[CopyJobStatusType.Completed]: [{ color: theme.semanticColors.successIcon }, iconClass],
unknown: [{ color: theme.semanticColors.bodySubtext }, iconClass],
});
const iconMap: Partial<Record<CopyJobStatusType, string>> = { const iconMap: Partial<Record<CopyJobStatusType, string>> = {
[CopyJobStatusType.Pending]: "Clock", [CopyJobStatusType.Pending]: "Clock",
[CopyJobStatusType.Paused]: "CirclePause", [CopyJobStatusType.Paused]: "CirclePause",
@@ -35,6 +19,17 @@ const iconMap: Partial<Record<CopyJobStatusType, string>> = {
[CopyJobStatusType.Completed]: "CompletedSolid", [CopyJobStatusType.Completed]: "CompletedSolid",
}; };
// Icon colors for different statuses
const statusIconColors: Partial<Record<CopyJobStatusType, string>> = {
[CopyJobStatusType.Failed]: "var(--colorPaletteRedForeground1)",
[CopyJobStatusType.Faulted]: "var(--colorPaletteRedForeground1)",
[CopyJobStatusType.Completed]: "#107c10", // Green color for success
[CopyJobStatusType.InProgress]: "var(--colorBrandForeground1)",
[CopyJobStatusType.Running]: "var(--colorBrandForeground1)",
[CopyJobStatusType.Partitioning]: "var(--colorBrandForeground1)",
[CopyJobStatusType.Paused]: "var(--colorBrandForeground1)",
};
export interface CopyJobStatusWithIconProps { export interface CopyJobStatusWithIconProps {
status: CopyJobStatusType; status: CopyJobStatusType;
} }
@@ -47,19 +42,17 @@ const CopyJobStatusWithIcon: React.FC<CopyJobStatusWithIconProps> = React.memo((
CopyJobStatusType.InProgress, CopyJobStatusType.InProgress,
CopyJobStatusType.Partitioning, CopyJobStatusType.Partitioning,
].includes(status); ].includes(status);
const iconColor = statusIconColors[status] || "var(--colorNeutralForeground2)";
const iconStyle = mergeStyles(iconClass, { color: iconColor });
return ( return (
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
{isSpinnerStatus ? ( {isSpinnerStatus ? (
<Spinner size={SpinnerSize.small} style={{ marginRight: "8px" }} /> <Spinner size={SpinnerSize.small} style={{ marginRight: "8px" }} />
) : ( ) : (
<FontIcon <FontIcon aria-label={status} iconName={iconMap[status] || "UnknownSolid"} className={iconStyle} />
aria-label={status}
iconName={iconMap[status] || "UnknownSolid"}
className={classNames[status] || classNames.unknown}
/>
)} )}
<Text>{statusText}</Text> <Text className="themeText">{statusText}</Text>
</Stack> </Stack>
); );
}); });

View File

@@ -15,6 +15,8 @@ import {
} from "@fluentui/react"; } from "@fluentui/react";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import Pager from "../../../../Common/Pager"; import Pager from "../../../../Common/Pager";
import { useThemeStore } from "../../../../hooks/useTheme";
import { getThemeTokens } from "../../../Theme/ThemeUtil";
import { openCopyJobDetailsPanel } from "../../Actions/CopyJobActions"; import { openCopyJobDetailsPanel } from "../../Actions/CopyJobActions";
import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes"; import { CopyJobType, HandleJobActionClickType } from "../../Types/CopyJobTypes";
import { getColumns } from "./CopyJobColumns"; import { getColumns } from "./CopyJobColumns";
@@ -26,13 +28,15 @@ interface CopyJobsListProps {
} }
const styles = { const styles = {
container: { height: "calc(100vh - 25em)" } as React.CSSProperties, container: { height: "100%" } as React.CSSProperties,
stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties, stackItem: { position: "relative", marginBottom: "20px" } as React.CSSProperties,
}; };
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => { const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pageSize = PAGE_SIZE }) => {
const isDarkMode = useThemeStore((state) => state.isDarkMode);
const themeTokens = getThemeTokens(isDarkMode);
const [startIndex, setStartIndex] = React.useState(0); const [startIndex, setStartIndex] = React.useState(0);
const [sortedJobs, setSortedJobs] = React.useState<CopyJobType[]>(jobs); const [sortedJobs, setSortedJobs] = React.useState<CopyJobType[]>(jobs);
const [sortedColumnKey, setSortedColumnKey] = React.useState<string | undefined>(undefined); const [sortedColumnKey, setSortedColumnKey] = React.useState<string | undefined>(undefined);
@@ -87,11 +91,28 @@ const CopyJobsList: React.FC<CopyJobsListProps> = ({ jobs, handleActionClick, pa
enableShimmer={false} enableShimmer={false}
constrainMode={ConstrainMode.unconstrained} constrainMode={ConstrainMode.unconstrained}
layoutMode={DetailsListLayoutMode.justified} layoutMode={DetailsListLayoutMode.justified}
onRenderDetailsHeader={(props, defaultRender) => ( onRenderDetailsHeader={(props, defaultRender) => {
<Sticky stickyPosition={StickyPositionType.Header} isScrollSynced> const bgColor = themeTokens.colorNeutralBackground3;
{defaultRender({ ...props })} const textColor = themeTokens.colorNeutralForeground1;
</Sticky> return (
)} <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced stickyBackgroundColor={bgColor}>
<div style={{ backgroundColor: bgColor }}>
{defaultRender({
...props,
styles: {
root: {
backgroundColor: bgColor,
selectors: {
".ms-DetailsHeader-cellTitle": { color: textColor },
".ms-DetailsHeader-cellName": { color: textColor },
},
},
},
})}
</div>
</Sticky>
);
}}
/> />
</ScrollablePane> </ScrollablePane>
</Stack.Item> </Stack.Item>

View File

@@ -13,7 +13,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders InProgress with spin
/> />
</div> </div>
<span <span
class="css-112" class="themeText css-112"
> >
Running Running
</span> </span>
@@ -33,7 +33,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Partitioning with sp
/> />
</div> </div>
<span <span
class="css-112" class="themeText css-112"
> >
Running Running
</span> </span>
@@ -53,7 +53,7 @@ exports[`CopyJobStatusWithIcon Spinner Status Types renders Running with spinner
/> />
</div> </div>
<span <span
class="css-112" class="themeText css-112"
> >
Running Running
</span> </span>
@@ -66,7 +66,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Cancelled" aria-label="Cancelled"
class="ms-Icon root-105 css-118 mocked-style-Cancelled" class="ms-Icon root-105 css-118 mocked-styles"
data-icon-name="StatusErrorFull" data-icon-name="StatusErrorFull"
role="img" role="img"
style="font-family: "FabricMDL2Icons-4";" style="font-family: "FabricMDL2Icons-4";"
@@ -74,7 +74,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Cancelled Cancelled
</span> </span>
@@ -87,7 +87,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Completed" aria-label="Completed"
class="ms-Icon root-105 css-120 mocked-style-Completed" class="ms-Icon root-105 css-120 mocked-styles"
data-icon-name="CompletedSolid" data-icon-name="CompletedSolid"
role="img" role="img"
style="font-family: "FabricMDL2Icons-5";" style="font-family: "FabricMDL2Icons-5";"
@@ -95,7 +95,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Completed Completed
</span> </span>
@@ -108,7 +108,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Failed" aria-label="Failed"
class="ms-Icon root-105 css-118 mocked-style-Failed" class="ms-Icon root-105 css-118 mocked-styles"
data-icon-name="StatusErrorFull" data-icon-name="StatusErrorFull"
role="img" role="img"
style="font-family: "FabricMDL2Icons-4";" style="font-family: "FabricMDL2Icons-4";"
@@ -116,7 +116,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Failed Failed
</span> </span>
@@ -129,7 +129,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Faulted" aria-label="Faulted"
class="ms-Icon root-105 css-118 mocked-style-Faulted" class="ms-Icon root-105 css-118 mocked-styles"
data-icon-name="StatusErrorFull" data-icon-name="StatusErrorFull"
role="img" role="img"
style="font-family: "FabricMDL2Icons-4";" style="font-family: "FabricMDL2Icons-4";"
@@ -137,7 +137,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Failed Failed
</span> </span>
@@ -150,7 +150,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Paused" aria-label="Paused"
class="ms-Icon root-105 css-114 mocked-style-Paused" class="ms-Icon root-105 css-114 mocked-styles"
data-icon-name="CirclePause" data-icon-name="CirclePause"
role="img" role="img"
style="font-family: "FabricMDL2Icons-11";" style="font-family: "FabricMDL2Icons-11";"
@@ -158,7 +158,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Paused Paused
</span> </span>
@@ -171,7 +171,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Pending" aria-label="Pending"
class="ms-Icon root-105 css-111 mocked-style-Pending" class="ms-Icon root-105 css-111 mocked-styles"
data-icon-name="Clock" data-icon-name="Clock"
role="img" role="img"
style="font-family: "FabricMDL2Icons-2";" style="font-family: "FabricMDL2Icons-2";"
@@ -179,7 +179,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Queued Queued
</span> </span>
@@ -192,7 +192,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
> >
<i <i
aria-label="Skipped" aria-label="Skipped"
class="ms-Icon root-105 css-116 mocked-style-Skipped" class="ms-Icon root-105 css-116 mocked-styles"
data-icon-name="StatusCircleBlock2" data-icon-name="StatusCircleBlock2"
role="img" role="img"
style="font-family: "FabricMDL2Icons-9";" style="font-family: "FabricMDL2Icons-9";"
@@ -200,7 +200,7 @@ exports[`CopyJobStatusWithIcon Static Icon Status Types - Snapshot Tests renders
</i> </i>
<span <span
class="css-112" class="themeText css-112"
> >
Cancelled Cancelled
</span> </span>

View File

@@ -1,6 +1,30 @@
@import "../../../less/Common/Constants.less"; @import "../../../less/Common/Constants.less";
// Common theme-aware classes
.themeText {
color: var(--colorNeutralForeground1);
}
.themeTextSecondary {
color: var(--colorNeutralForeground2);
}
.themeLinkText {
color: var(--colorBrandForeground1);
}
.themeBackground {
background-color: var(--colorNeutralBackground1);
}
.themeBackgroundSecondary {
background-color: var(--colorNeutralBackground2);
}
#containerCopyWrapper { #containerCopyWrapper {
background-color: var(--colorNeutralBackground1);
color: var(--colorNeutralForeground1);
.centerContent { .centerContent {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -9,20 +33,30 @@
.noCopyJobsMessage { .noCopyJobsMessage {
font-weight: 600; font-weight: 600;
margin: 0 auto; margin: 0 auto;
color: @FocusColor; color: var(--colorNeutralForeground2);
} }
button.createCopyJobButton { button.createCopyJobButton {
color: @LinkColor; color: var(--colorBrandForeground1);
} }
} }
} }
.createCopyJobScreensContainer { .createCopyJobScreensContainer {
height: 100%; height: 100%;
padding: 1em 1.5em; padding: 1em 1.5em;
background-color: var(--colorNeutralBackground1);
color: var(--colorNeutralForeground1);
.pointInTimeRestoreContainer, .onlineCopyContainer { .pointInTimeRestoreContainer, .onlineCopyContainer {
position: relative; position: relative;
} }
.toggle-label {
color: var(--colorNeutralForeground1);
}
.accordionHeaderText {
color: var(--colorNeutralForeground1);
}
label { label {
padding: 0; padding: 0;
@@ -71,7 +105,7 @@
} }
.foreground { .foreground {
z-index: 10; z-index: 10;
background-color: #f9f9f9; background-color: var(--colorNeutralBackground2);
padding: 20px; padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: translate(0%, -9%); transform: translate(0%, -9%);
@@ -83,11 +117,12 @@
.create-container-link-btn { .create-container-link-btn {
padding: 0; padding: 0;
height: 25px; height: 25px;
color: @LinkColor; color: var(--colorBrandForeground1);
&:focus { &:focus {
outline: none; outline: none;
} }
} }
/* Create collection panel */ /* Create collection panel */
@@ -105,7 +140,6 @@
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
.ms-DetailsList { .ms-DetailsList {
width: 100%; width: 100%;
@@ -114,33 +148,33 @@
padding: @DefaultSpace 20px; padding: @DefaultSpace 20px;
font-weight: 600; font-weight: 600;
font-size: @DefaultFontSize; font-size: @DefaultFontSize;
color: @BaseHigh; color: var(--colorNeutralForeground1);
background-color: @BaseLow; background-color: var(--colorNeutralBackground2);
border-bottom: @ButtonBorderWidth solid @BaseMedium; border-bottom: @ButtonBorderWidth solid var(--colorNeutralStroke1);
&:hover { &:hover {
background-color: @BaseMediumLow; background-color: var(--colorNeutralBackground3);
} }
} }
} }
.ms-DetailsRow { .ms-DetailsRow {
border-bottom: @ButtonBorderWidth solid @BaseMedium; border-bottom: @ButtonBorderWidth solid var(--colorNeutralStroke1);
&:hover { &:hover {
background-color: @BaseMediumLow; background-color: var(--colorNeutralBackground2);
} }
.ms-DetailsRow-cell { .ms-DetailsRow-cell {
padding: @MediumSpace 20px; padding: @MediumSpace 20px;
font-size: @DefaultFontSize; font-size: @DefaultFontSize;
color: @BaseHigh; color: var(--colorNeutralForeground1);
min-height: 48px; min-height: 48px;
display: flex; display: flex;
align-items: center; align-items: center;
.jobNameLink { .jobNameLink {
color: @LinkColor; color: var(--colorBrandForeground1);
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -168,7 +202,7 @@
} }
.ms-DetailsRow-cell { .ms-DetailsRow-cell {
font-size: @DefaultFontSize; font-size: @DefaultFontSize;
color: @BaseHigh; color: var(--colorNeutralForeground1);
} }
} }
} }

View File

@@ -187,7 +187,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text> <Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
<Text styles={textSubHeadingStyle}>Partitioning</Text> <Text styles={textSubHeadingStyle}>Partitioning</Text>
</Stack> </Stack>
<Stack tokens={{ childrenGap: 5 }}> <Stack tokens={{ childrenGap: 5 }} data-test="partition-key-values">
<Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text> <Text styles={textSubHeadingStyle1}>{partitionKeyValue}</Text>
<Text styles={textSubHeadingStyle1}> <Text styles={textSubHeadingStyle1}>
{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"} {isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"}
@@ -199,6 +199,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
{!isReadOnly && ( {!isReadOnly && (
<> <>
<MessageBar <MessageBar
data-test="partition-key-warning"
messageBarType={MessageBarType.warning} messageBarType={MessageBarType.warning}
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }} messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
styles={darkThemeMessageBarStyles} styles={darkThemeMessageBarStyles}
@@ -220,6 +221,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
</Text> </Text>
{configContext.platform !== Platform.Emulator && ( {configContext.platform !== Platform.Emulator && (
<PrimaryButton <PrimaryButton
data-test="change-partition-key-button"
styles={{ root: { width: "fit-content" } }} styles={{ root: { width: "fit-content" } }}
text="Change" text="Change"
onClick={startPartitionkeyChangeWorkflow} onClick={startPartitionkeyChangeWorkflow}

View File

@@ -103,7 +103,10 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
offText="Inactive" offText="Inactive"
checked={bucket.maxThroughputPercentage !== 100} checked={bucket.maxThroughputPercentage !== 100}
onChange={(event, checked) => onToggle(bucket.id, checked)} onChange={(event, checked) => onToggle(bucket.id, checked)}
styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }} styles={{
root: { marginBottom: 0 },
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
}}
></Toggle> ></Toggle>
</Stack> </Stack>
))} ))}

View File

@@ -78,6 +78,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
</Text> </Text>
</Stack> </Stack>
<Stack <Stack
data-test="partition-key-values"
tokens={ tokens={
{ {
"childrenGap": 5, "childrenGap": 5,
@@ -108,6 +109,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
</Stack> </Stack>
</Stack> </Stack>
<StyledMessageBar <StyledMessageBar
data-test="partition-key-warning"
messageBarIconProps={ messageBarIconProps={
{ {
"className": "messageBarWarningIcon", "className": "messageBarWarningIcon",
@@ -160,6 +162,7 @@ exports[`PartitionKeyComponent renders default component and matches snapshot 1`
To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container. To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.
</Text> </Text>
<CustomizedPrimaryButton <CustomizedPrimaryButton
data-test="change-partition-key-button"
onClick={[Function]} onClick={[Function]}
styles={ styles={
{ {
@@ -237,6 +240,7 @@ exports[`PartitionKeyComponent renders read-only component and matches snapshot
</Text> </Text>
</Stack> </Stack>
<Stack <Stack
data-test="partition-key-values"
tokens={ tokens={
{ {
"childrenGap": 5, "childrenGap": 5,

View File

@@ -53,6 +53,7 @@ type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexTyp
const labelStyles = { const labelStyles = {
root: { root: {
fontSize: 12, fontSize: 12,
color: "var(--colorNeutralForeground1)",
}, },
}; };
@@ -63,6 +64,8 @@ const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldSt
field: { field: {
fontSize: 12, fontSize: 12,
padding: "0 8px", padding: "0 8px",
backgroundColor: "var(--colorNeutralBackground1)",
color: "var(--colorNeutralForeground1)",
}, },
}; };

View File

@@ -853,7 +853,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{!isSynapseLinkEnabled() && ( {!isSynapseLinkEnabled() && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Text variant="small"> <Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
Azure Synapse Link is required for creating an analytical store{" "} Azure Synapse Link is required for creating an analytical store{" "}
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account. <br /> {getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account. <br />
<Link <Link

View File

@@ -475,6 +475,11 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
className="panelGroupSpacing" className="panelGroupSpacing"
> >
<Text <Text
style={
{
"color": "var(--colorNeutralForeground1)",
}
}
variant="small" variant="small"
> >
Azure Synapse Link is required for creating an analytical store Azure Synapse Link is required for creating an analytical store

View File

@@ -208,7 +208,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
</div> </div>
</Stack> </Stack>
{createNewContainer ? ( {createNewContainer ? (
<Stack> <Stack data-test="create-new-container-form">
<MessageBar>All configurations except for unique keys will be copied from the source container</MessageBar> <MessageBar>All configurations except for unique keys will be copied from the source container</MessageBar>
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Stack horizontal> <Stack horizontal>
@@ -230,6 +230,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
</TooltipHost> </TooltipHost>
</Stack> </Stack>
<input <input
data-test="new-container-id-input"
name="collectionId" name="collectionId"
id="collectionId" id="collectionId"
type="text" type="text"
@@ -271,6 +272,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
<input <input
type="text" type="text"
data-test="new-container-partition-key-input"
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
aria-required aria-required
required required
@@ -304,6 +306,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
type="text" type="text"
id="addCollection-partitionKeyValue" id="addCollection-partitionKeyValue"
key={`addCollection-partitionKeyValue_${index}`} key={`addCollection-partitionKeyValue_${index}`}
data-test={`new-container-sub-partition-key-input-${index}`}
aria-required aria-required
required required
size={40} size={40}
@@ -327,6 +330,8 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
}} }}
/> />
<IconButton <IconButton
data-test={`remove-sub-partition-key-button-${index}`}
ariaLabel="Remove hierarchical partition key"
iconProps={{ iconName: "Delete" }} iconProps={{ iconName: "Delete" }}
style={{ height: 27 }} style={{ height: 27 }}
onClick={() => { onClick={() => {
@@ -339,6 +344,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
})} })}
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<DefaultButton <DefaultButton
data-test="add-sub-partition-key-button"
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }} styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
disabled={subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition} disabled={subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])} onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])}
@@ -346,7 +352,11 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
Add hierarchical partition key Add hierarchical partition key
</DefaultButton> </DefaultButton>
{subPartitionKeys.length > 0 && ( {subPartitionKeys.length > 0 && (
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}> <Text
data-test="hierarchical-partitioning-info-text"
variant="small"
style={{ color: "var(--colorNeutralForeground1)" }}
>
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to <Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
partition your data with up to three levels of keys for better data distribution. Requires .NET V3, partition your data with up to three levels of keys for better data distribution. Requires .NET V3,
Java V4 SDK, or preview JavaScript V3 SDK.{" "} Java V4 SDK, or preview JavaScript V3 SDK.{" "}
@@ -359,7 +369,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
</Stack> </Stack>
</Stack> </Stack>
) : ( ) : (
<Stack> <Stack data-test="use-existing-container-form">
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
@@ -390,6 +400,7 @@ export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
}} }}
defaultSelectedKey={targetCollectionId} defaultSelectedKey={targetCollectionId}
responsiveMode={999} responsiveMode={999}
ariaLabel="Existing Containers"
/> />
</Stack> </Stack>
)} )}

View File

@@ -2,7 +2,7 @@ import React from "react";
import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicator_3Squares from "../../../images/LoadingIndicator_3Squares.gif";
export const PanelLoadingScreen: React.FunctionComponent = () => ( export const PanelLoadingScreen: React.FunctionComponent = () => (
<div id="loadingScreen" className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer"> <div id="loadingScreen" className="dataExplorerLoaderContainer dataExplorerLoaderforcopyJobs">
<img className="dataExplorerLoader" src={LoadingIndicator_3Squares} /> <img className="dataExplorerLoader" src={LoadingIndicator_3Squares} />
</div> </div>
); );

View File

@@ -61,7 +61,8 @@ const useStyles = makeStyles({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
minHeight: "100vh", height: "100%",
overflowY: "auto",
backgroundColor: "var(--colorNeutralBackground1)", backgroundColor: "var(--colorNeutralBackground1)",
color: "var(--colorNeutralForeground1)", color: "var(--colorNeutralForeground1)",
}, },
@@ -73,20 +74,19 @@ const useStyles = makeStyles({
}, },
title: { title: {
fontSize: "48px", fontSize: "48px",
fontWeight: "500", fontWeight: "400",
margin: "16px auto", margin: "16px auto",
color: "var(--colorNeutralForeground1)", color: "var(--colorNeutralForeground1)",
}, },
subtitle: { subtitle: {
fontSize: "18px", fontSize: "18px",
marginBottom: "40px",
color: "var(--colorNeutralForeground2)", color: "var(--colorNeutralForeground2)",
}, },
cardContainer: { cardContainer: {
display: "grid", display: "grid",
gridTemplateColumns: "repeat(2, 1fr)", gridTemplateColumns: "repeat(2, 1fr)",
gap: "16px", gap: "16px",
width: "66%", width: "60%",
margin: "0 auto", margin: "0 auto",
backgroundColor: "var(--colorNeutralBackground1)", backgroundColor: "var(--colorNeutralBackground1)",
color: "var(--colorNeutralForeground1)", color: "var(--colorNeutralForeground1)",
@@ -100,7 +100,7 @@ const useStyles = makeStyles({
color: "var(--colorNeutralForeground1)", color: "var(--colorNeutralForeground1)",
border: "1px solid var(--colorNeutralStroke1)", border: "1px solid var(--colorNeutralStroke1)",
borderRadius: "4px", borderRadius: "4px",
boxShadow: "var(--shadow4)", boxShadow: "rgba(0, 0, 0, 0.25) 0px 4px 4px",
cursor: "pointer", cursor: "pointer",
minHeight: "150px", minHeight: "150px",
"&:hover": { "&:hover": {
@@ -128,11 +128,10 @@ const useStyles = makeStyles({
textAlign: "left", textAlign: "left",
}, },
moreStuffContainer: { moreStuffContainer: {
display: "grid", display: "flex",
gridTemplateColumns: "repeat(3, 1fr)", justifyContent: "space-between",
gap: "32px", gap: "32px",
width: "66%", width: "90%",
margin: "40px auto",
}, },
moreStuffColumn: { moreStuffColumn: {
display: "flex", display: "flex",
@@ -227,7 +226,7 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({ explorer }) => {
return ( return (
<Stack <Stack
className="splashStackContainer" className="splashStackContainer"
style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} style={{ width: "60%", cursor: "pointer", margin: "40px auto" }}
tokens={{ childrenGap: 16 }} tokens={{ childrenGap: 16 }}
> >
<Stack className="splashStackRow" horizontal> <Stack className="splashStackRow" horizontal>
@@ -903,9 +902,9 @@ export const SplashScreen: React.FC<SplashScreenProps> = ({ explorer }) => {
return ( return (
<div className={styles.splashScreenContainer}> <div className={styles.splashScreenContainer}>
<div className={styles.splashScreen}> <div className={styles.splashScreen}>
<h1 className={styles.title} role="heading" aria-label="Welcome to Azure Cosmos DB"> <h2 className={styles.title} role="heading" aria-label="Welcome to Azure Cosmos DB">
Welcome to Azure Cosmos DB<span className="activePatch"></span> Welcome to Azure Cosmos DB<span className="activePatch"></span>
</h1> </h2>
<div className={styles.subtitle}>Globally distributed, multi-model database service for any scale</div> <div className={styles.subtitle}>Globally distributed, multi-model database service for any scale</div>
{getSplashScreenButtons()} {getSplashScreenButtons()}
{useCarousel.getState().showCoachMark && ( {useCarousel.getState().showCoachMark && (

View File

@@ -15,7 +15,7 @@ const useStyles = makeStyles({
button: { button: {
border: "1px solid var(--colorNeutralStroke1)", border: "1px solid var(--colorNeutralStroke1)",
boxSizing: "border-box", boxSizing: "border-box",
boxShadow: "var(--shadow4)", boxShadow: "rgba(0, 0, 0, 0.25) 0px 4px 4px",
borderRadius: "4px", borderRadius: "4px",
padding: "32px 16px", padding: "32px 16px",
backgroundColor: "var(--colorNeutralBackground1)", backgroundColor: "var(--colorNeutralBackground1)",

View File

@@ -125,7 +125,10 @@ const App = (): JSX.Element => {
<KeyboardShortcutRoot> <KeyboardShortcutRoot>
<div className="flexContainer" aria-hidden="false"> <div className="flexContainer" aria-hidden="false">
{userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? ( {userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
<ContainerCopyPanel explorer={explorer} /> <>
<ContainerCopyPanel explorer={explorer} />
<SidePanel />
</>
) : ( ) : (
<DivExplorer explorer={explorer} /> <DivExplorer explorer={explorer} />
)} )}

View File

@@ -438,7 +438,6 @@ body.isDarkMode {
button { button {
&:not(.ms-Button):not(.ms-IconButton) { &:not(.ms-Button):not(.ms-IconButton) {
background-color: var(--colorNeutralBackground1);
color: var(--colorNeutralForeground1); color: var(--colorNeutralForeground1);
&:hover { &:hover {

View File

@@ -470,6 +470,15 @@ export class DataExplorer {
return this.frame.getByTestId("notification-console/header-status"); return this.frame.getByTestId("notification-console/header-status");
} }
async getDropdownItemByName(name: string, ariaLabel?: string): Promise<Locator> {
const dropdownItemsWrapper = this.frame.locator("div.ms-Dropdown-items");
if (ariaLabel) {
expect(await dropdownItemsWrapper.getAttribute("aria-label")).toEqual(ariaLabel);
}
const containerDropdownItems = dropdownItemsWrapper.locator("button.ms-Dropdown-item[role='option']");
return containerDropdownItems.filter({ hasText: name });
}
/** Waits for the Data Explorer app to load */ /** Waits for the Data Explorer app to load */
static async waitForExplorer(page: Page) { static async waitForExplorer(page: Page) {
const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle(); const iframeElement = await page.getByTestId("DataExplorerFrame").elementHandle();

View File

@@ -9,7 +9,7 @@ let queryTab: QueryTab = null!;
let queryEditor: Editor = null!; let queryEditor: Editor = null!;
test.beforeAll("Create Test Database", async () => { test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(true); context = await createTestSQLContainer({ includeTestData: true });
}); });
test.beforeEach("Open new query tab", async ({ page }) => { test.beforeEach("Open new query tab", async ({ page }) => {

View File

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

View File

@@ -14,7 +14,7 @@ test.describe("Autoscale and Manual throughput", () => {
let explorer: DataExplorer = null!; let explorer: DataExplorer = null!;
test.beforeAll("Create Test Database", async () => { test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(true); context = await createTestSQLContainer({ includeTestData: true });
}); });
test.beforeEach("Open container settings", async ({ page }) => { test.beforeEach("Open container settings", async ({ page }) => {

View File

@@ -7,7 +7,7 @@ test.describe("Settings under Scale & Settings", () => {
let explorer: DataExplorer = null!; let explorer: DataExplorer = null!;
test.beforeAll("Create Test Database", async () => { test.beforeAll("Create Test Database", async () => {
context = await createTestSQLContainer(true); context = await createTestSQLContainer({ includeTestData: true });
}); });
test.beforeEach("Open Settings tab under Scale & Settings", async ({ page }) => { test.beforeEach("Open Settings tab under Scale & Settings", async ({ page }) => {

View File

@@ -74,8 +74,18 @@ export class TestContainerContext {
} }
} }
export async function createTestSQLContainer(includeTestData?: boolean) { type createTestSqlContainerConfig = {
const databaseId = generateUniqueName("db"); includeTestData?: boolean;
partitionKey?: string;
databaseName?: string;
};
export async function createTestSQLContainer({
includeTestData = false,
partitionKey = "/partitionKey",
databaseName = "",
}: createTestSqlContainerConfig = {}) {
const databaseId = databaseName ? databaseName : generateUniqueName("db");
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
const credentials = getAzureCLICredentials(); const credentials = getAzureCLICredentials();
const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials); const adaptedCredentials = new AzureIdentityCredentialAdapter(credentials);
@@ -104,7 +114,7 @@ export async function createTestSQLContainer(includeTestData?: boolean) {
try { try {
const { container } = await database.containers.createIfNotExists({ const { container } = await database.containers.createIfNotExists({
id: containerId, id: containerId,
partitionKey: "/partitionKey", partitionKey,
}); });
if (includeTestData) { if (includeTestData) {
const batchCount = TestData.length / 100; const batchCount = TestData.length / 100;