mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-02 15:51:16 +00:00
Compare commits
7 Commits
users/sind
...
sung_playw
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ed0c79e1 | ||
|
|
c2785ace83 | ||
|
|
bb5d3bfc42 | ||
|
|
98a21f2fb1 | ||
|
|
945c457bd5 | ||
|
|
5b7d1a74af | ||
|
|
8c0e6da377 |
@@ -19,6 +19,6 @@
|
|||||||
</frameworkAssemblies>
|
</frameworkAssemblies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
<file src="**\*" target="content"/>
|
<file src="**\*" exclude="obj\**\*" target="content"/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -116,6 +116,7 @@
|
|||||||
"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",
|
||||||
|
"web-vitals": "4.2.4",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
@@ -35930,6 +35931,11 @@
|
|||||||
"defaults": "^1.0.3"
|
"defaults": "^1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-vitals": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
|
|||||||
@@ -111,6 +111,7 @@
|
|||||||
"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",
|
||||||
|
"web-vitals": "4.2.4",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"zustand": "3.5.0"
|
"zustand": "3.5.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
|||||||
trace: "off",
|
trace: "off",
|
||||||
video: "off",
|
video: "off",
|
||||||
screenshot: "on",
|
screenshot: "on",
|
||||||
testIdAttribute: "data-test",
|
testIdAttribute: "data-testid",
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,12 +76,10 @@ const mockedRbacUtils = RbacUtils as jest.Mocked<typeof RbacUtils>;
|
|||||||
const mockedCopyJobPrerequisitesCache = CopyJobPrerequisitesCacheModule as jest.Mocked<
|
const mockedCopyJobPrerequisitesCache = CopyJobPrerequisitesCacheModule as jest.Mocked<
|
||||||
typeof CopyJobPrerequisitesCacheModule
|
typeof CopyJobPrerequisitesCacheModule
|
||||||
>;
|
>;
|
||||||
|
|
||||||
interface TestWrapperProps {
|
interface TestWrapperProps {
|
||||||
state: CopyJobContextState;
|
state: CopyJobContextState;
|
||||||
onResult?: (result: PermissionGroupConfig[]) => void;
|
onResult?: (result: PermissionGroupConfig[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TestWrapper: React.FC<TestWrapperProps> = ({ state, onResult }) => {
|
const TestWrapper: React.FC<TestWrapperProps> = ({ state, onResult }) => {
|
||||||
const result = usePermissionSections(state);
|
const result = usePermissionSections(state);
|
||||||
|
|
||||||
|
|||||||
@@ -214,9 +214,9 @@ export const Dialog: FC = () => {
|
|||||||
{contentHtml}
|
{contentHtml}
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<PrimaryButton {...primaryButtonProps} data-test={`DialogButton:${primaryButtonText}`} />
|
<PrimaryButton {...primaryButtonProps} data-testid={`DialogButton:${primaryButtonText}`} />
|
||||||
{secondaryButtonProps && (
|
{secondaryButtonProps && (
|
||||||
<DefaultButton {...secondaryButtonProps} data-test={`DialogButton:${secondaryButtonText}`} />
|
<DefaultButton {...secondaryButtonProps} data-testid={`DialogButton:${secondaryButtonText}`} />
|
||||||
)}
|
)}
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</FluentDialog>
|
</FluentDialog>
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
|
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
data-test="EditorReact/Host/Unloaded"
|
data-testid="EditorReact/Host/Unloaded"
|
||||||
className={this.props.className || "jsonEditor"}
|
className={this.props.className || "jsonEditor"}
|
||||||
style={this.props.monacoContainerStyles}
|
style={this.props.monacoContainerStyles}
|
||||||
ref={(elt: HTMLElement) => this.setRef(elt)}
|
ref={(elt: HTMLElement) => this.setRef(elt)}
|
||||||
@@ -148,7 +148,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
|
|
||||||
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
this.rootNode.dataset["test"] = "EditorReact/Host/Loaded";
|
this.rootNode.dataset["testid"] = "EditorReact/Host/Loaded";
|
||||||
|
|
||||||
// In development, we want to be able to access the editor instance from the console
|
// In development, we want to be able to access the editor instance from the console
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export const InputDataList: FC<InputDataListProps> = ({
|
|||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
id="filterInput"
|
id="filterInput"
|
||||||
data-test={"DocumentsTab/FilterInput"}
|
data-testid={"DocumentsTab/FilterInput"}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
checked={isAutoscaleSelected}
|
checked={isAutoscaleSelected}
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
|
data-testid="ThroughputInput/ThroughputMode:Autoscale"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
onChange={(e) => handleOnChangeMode(e, "Autoscale")}
|
||||||
/>
|
/>
|
||||||
@@ -224,6 +225,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
type="radio"
|
type="radio"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
role="radio"
|
role="radio"
|
||||||
|
data-testid="ThroughputInput/ThroughputMode:Manual"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
onChange={(e) => handleOnChangeMode(e, "Manual")}
|
||||||
/>
|
/>
|
||||||
@@ -286,7 +288,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
<TextField
|
<TextField
|
||||||
id="autoscaleRUValueField"
|
id="autoscaleRUValueField"
|
||||||
data-test="autoscaleRUInput"
|
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||||
type="number"
|
type="number"
|
||||||
styles={{
|
styles={{
|
||||||
fieldGroup: { width: 100, height: 27, flexShrink: 0 },
|
fieldGroup: { width: 100, height: 27, flexShrink: 0 },
|
||||||
@@ -352,6 +354,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-testid="ThroughputInput/ManualThroughputInput"
|
||||||
type="number"
|
type="number"
|
||||||
styles={{
|
styles={{
|
||||||
fieldGroup: { width: 300, height: 27 },
|
fieldGroup: { width: 300, height: 27 },
|
||||||
|
|||||||
@@ -682,6 +682,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={true}
|
checked={true}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
|
data-testid="ThroughputInput/ThroughputMode:Autoscale"
|
||||||
id="Autoscale-input"
|
id="Autoscale-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -699,6 +700,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
aria-required={true}
|
aria-required={true}
|
||||||
checked={false}
|
checked={false}
|
||||||
className="throughputInputRadioBtn"
|
className="throughputInputRadioBtn"
|
||||||
|
data-testid="ThroughputInput/ThroughputMode:Manual"
|
||||||
id="Manual-input"
|
id="Manual-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -2144,7 +2146,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
</Stack>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Container max RU/s"
|
ariaLabel="Container max RU/s"
|
||||||
data-test="autoscaleRUInput"
|
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||||
errorMessage=""
|
errorMessage=""
|
||||||
id="autoscaleRUValueField"
|
id="autoscaleRUValueField"
|
||||||
key=".0:$.$.1"
|
key=".0:$.$.1"
|
||||||
@@ -2171,7 +2173,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Container max RU/s"
|
ariaLabel="Container max RU/s"
|
||||||
data-test="autoscaleRUInput"
|
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
errorMessage=""
|
errorMessage=""
|
||||||
id="autoscaleRUValueField"
|
id="autoscaleRUValueField"
|
||||||
@@ -2472,7 +2474,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
|
|||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
aria-label="Container max RU/s"
|
aria-label="Container max RU/s"
|
||||||
className="ms-TextField-field field-124"
|
className="ms-TextField-field field-124"
|
||||||
data-test="autoscaleRUInput"
|
data-testid="ThroughputInput/AutoscaleRUInput"
|
||||||
id="autoscaleRUValueField"
|
id="autoscaleRUValueField"
|
||||||
max="9007199254740991"
|
max="9007199254740991"
|
||||||
min={1000}
|
min={1000}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||||||
|
|
||||||
const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => (
|
const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
data-testid={`TreeNode/ContextMenuItem:${menuItem.label}`}
|
||||||
disabled={menuItem.isDisabled}
|
disabled={menuItem.isDisabled}
|
||||||
key={menuItem.label}
|
key={menuItem.label}
|
||||||
onClick={() => menuItem.onClick(contextMenuRef)}
|
onClick={() => menuItem.onClick(contextMenuRef)}
|
||||||
@@ -160,14 +160,14 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||||||
const expandIcon = isLoading ? (
|
const expandIcon = isLoading ? (
|
||||||
<Spinner size="extra-tiny" />
|
<Spinner size="extra-tiny" />
|
||||||
) : !isBranch ? undefined : openItems.includes(treeNodeId) ? (
|
) : !isBranch ? undefined : openItems.includes(treeNodeId) ? (
|
||||||
<ChevronDown20Regular data-test="TreeNode/CollapseIcon" />
|
<ChevronDown20Regular data-testid="TreeNode/CollapseIcon" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight20Regular data-text="TreeNode/ExpandIcon" />
|
<ChevronRight20Regular data-testid="TreeNode/ExpandIcon" />
|
||||||
);
|
);
|
||||||
|
|
||||||
const treeItem = (
|
const treeItem = (
|
||||||
<TreeItem
|
<TreeItem
|
||||||
data-test={`TreeNodeContainer:${treeNodeId}`}
|
data-testid={`TreeNodeContainer:${treeNodeId}`}
|
||||||
value={treeNodeId}
|
value={treeNodeId}
|
||||||
itemType={isBranch ? "branch" : "leaf"}
|
itemType={isBranch ? "branch" : "leaf"}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
@@ -179,7 +179,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||||||
shouldShowAsSelected && treeStyles.selectedItem,
|
shouldShowAsSelected && treeStyles.selectedItem,
|
||||||
node.className && treeStyles[node.className],
|
node.className && treeStyles[node.className],
|
||||||
)}
|
)}
|
||||||
data-test={`TreeNode:${treeNodeId}`}
|
data-testid={`TreeNode:${treeNodeId}`}
|
||||||
actions={
|
actions={
|
||||||
contextMenuItems.length > 0 && {
|
contextMenuItems.length > 0 && {
|
||||||
className: treeStyles.actionsButtonContainer,
|
className: treeStyles.actionsButtonContainer,
|
||||||
@@ -189,13 +189,13 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
aria-label="More options"
|
aria-label="More options"
|
||||||
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
|
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
|
||||||
data-test="TreeNode/ContextMenuTrigger"
|
data-testid="TreeNode/ContextMenuTrigger"
|
||||||
appearance="subtle"
|
appearance="subtle"
|
||||||
ref={contextMenuRef}
|
ref={contextMenuRef}
|
||||||
icon={<MoreHorizontal20Regular />}
|
icon={<MoreHorizontal20Regular />}
|
||||||
/>
|
/>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
|
<MenuPopover data-testid={`TreeNode/ContextMenu:${treeNodeId}`}>
|
||||||
<MenuList>{contextMenuItems}</MenuList>
|
<MenuList>{contextMenuItems}</MenuList>
|
||||||
</MenuPopover>
|
</MenuPopover>
|
||||||
</Menu>
|
</Menu>
|
||||||
@@ -208,7 +208,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
|
|||||||
<span className={treeStyles.nodeLabel}>{node.label}</span>
|
<span className={treeStyles.nodeLabel}>{node.label}</span>
|
||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
{!node.isLoading && node.children?.length > 0 && (
|
{!node.isLoading && node.children?.length > 0 && (
|
||||||
<Tree data-test={`Tree:${treeNodeId}`} className={treeStyles.tree}>
|
<Tree data-testid={`Tree:${treeNodeId}`} className={treeStyles.tree}>
|
||||||
{getSortedChildren(node).map((childNode: TreeNode) => (
|
{getSortedChildren(node).map((childNode: TreeNode) => (
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
openItems={openItems}
|
openItems={openItems}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
exports[`TreeNodeComponent does not render children if the node is loading 1`] = `
|
exports[`TreeNodeComponent does not render children if the node is loading 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -11,10 +11,10 @@ exports[`TreeNodeComponent does not render children if the node is loading 1`] =
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -112,7 +112,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -122,7 +122,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level={0}
|
aria-level={0}
|
||||||
className="fui-TreeItem r15xhw3a"
|
className="fui-TreeItem r15xhw3a"
|
||||||
data-fui-tree-item-value="root"
|
data-fui-tree-item-value="root"
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -144,7 +144,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -164,7 +164,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
"layoutRef": {
|
"layoutRef": {
|
||||||
"current": <div
|
"current": <div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -173,7 +173,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -220,13 +220,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="0"
|
aria-level="0"
|
||||||
class="fui-TreeItem r15xhw3a"
|
class="fui-TreeItem r15xhw3a"
|
||||||
data-fui-tree-item-value="root"
|
data-fui-tree-item-value="root"
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -235,7 +235,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -270,7 +270,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
class="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root"
|
data-testid="Tree:root"
|
||||||
role="tree"
|
role="tree"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -278,13 +278,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child1Label"
|
data-fui-tree-item-value="root/child1Label"
|
||||||
data-test="TreeNodeContainer:root/child1Label"
|
data-testid="TreeNodeContainer:root/child1Label"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child1Label"
|
data-testid="TreeNode:root/child1Label"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -293,7 +293,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -332,13 +332,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child2LoadingLabel"
|
data-testid="TreeNode:root/child2LoadingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -347,7 +347,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -385,13 +385,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child3ExpandingLabel"
|
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -441,10 +441,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -457,19 +457,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||||
>
|
>
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -506,7 +506,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
<Tree
|
<Tree
|
||||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root"
|
data-testid="Tree:root"
|
||||||
>
|
>
|
||||||
<TreeProvider
|
<TreeProvider
|
||||||
value={
|
value={
|
||||||
@@ -574,7 +574,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
className="fui-Tree rnv2ez3 ___17a32do_7zrvj80 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root"
|
data-testid="Tree:root"
|
||||||
role="tree"
|
role="tree"
|
||||||
>
|
>
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
@@ -610,7 +610,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root/child1Label"
|
data-testid="TreeNodeContainer:root/child1Label"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root/child1Label"
|
value="root/child1Label"
|
||||||
@@ -620,7 +620,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level={1}
|
aria-level={1}
|
||||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child1Label"
|
data-fui-tree-item-value="root/child1Label"
|
||||||
data-test="TreeNodeContainer:root/child1Label"
|
data-testid="TreeNodeContainer:root/child1Label"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -642,7 +642,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -662,7 +662,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
"layoutRef": {
|
"layoutRef": {
|
||||||
"current": <div
|
"current": <div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child1Label"
|
data-testid="TreeNode:root/child1Label"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -671,7 +671,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -718,13 +718,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child1Label"
|
data-fui-tree-item-value="root/child1Label"
|
||||||
data-test="TreeNodeContainer:root/child1Label"
|
data-testid="TreeNodeContainer:root/child1Label"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child1Label"
|
data-testid="TreeNode:root/child1Label"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -733,7 +733,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -775,10 +775,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child1Label"
|
data-testid="TreeNode:root/child1Label"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -791,19 +791,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child1Label"
|
data-testid="TreeNode:root/child1Label"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||||
>
|
>
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -840,7 +840,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
<Tree
|
<Tree
|
||||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root/child1Label"
|
data-testid="Tree:root/child1Label"
|
||||||
>
|
>
|
||||||
<TreeProvider
|
<TreeProvider
|
||||||
value={
|
value={
|
||||||
@@ -881,7 +881,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root/child2LoadingLabel"
|
value="root/child2LoadingLabel"
|
||||||
@@ -891,7 +891,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level={1}
|
aria-level={1}
|
||||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -913,7 +913,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -933,7 +933,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
"layoutRef": {
|
"layoutRef": {
|
||||||
"current": <div
|
"current": <div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child2LoadingLabel"
|
data-testid="TreeNode:root/child2LoadingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -942,7 +942,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -989,13 +989,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child2LoadingLabel"
|
data-fui-tree-item-value="root/child2LoadingLabel"
|
||||||
data-test="TreeNodeContainer:root/child2LoadingLabel"
|
data-testid="TreeNodeContainer:root/child2LoadingLabel"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child2LoadingLabel"
|
data-testid="TreeNode:root/child2LoadingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -1004,7 +1004,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
class="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -1046,10 +1046,10 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child2LoadingLabel"
|
data-testid="TreeNode:root/child2LoadingLabel"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -1062,19 +1062,19 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="fui-TreeItemLayout r1bx0xiv ___9uolwu0_9b0r4g0 fk6fouc fkhj508 figsok6 f1i3iumi fo100m9 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child2LoadingLabel"
|
data-testid="TreeNode:root/child2LoadingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
className="fui-TreeItemLayout__expandIcon rh4pu5o"
|
||||||
>
|
>
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
className="___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0"
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
height="20"
|
height="20"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -1140,7 +1140,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root/child3ExpandingLabel"
|
value="root/child3ExpandingLabel"
|
||||||
@@ -1149,7 +1149,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level={1}
|
aria-level={1}
|
||||||
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
className="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
@@ -1188,7 +1188,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
"layoutRef": {
|
"layoutRef": {
|
||||||
"current": <div
|
"current": <div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child3ExpandingLabel"
|
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -1240,13 +1240,13 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
aria-level="1"
|
aria-level="1"
|
||||||
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
class="fui-TreeItem r15xhw3a ___jer8zr0_6no3ah0 f10bgyvd"
|
||||||
data-fui-tree-item-value="root/child3ExpandingLabel"
|
data-fui-tree-item-value="root/child3ExpandingLabel"
|
||||||
data-test="TreeNodeContainer:root/child3ExpandingLabel"
|
data-testid="TreeNodeContainer:root/child3ExpandingLabel"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
class="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child3ExpandingLabel"
|
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -1294,7 +1294,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child3ExpandingLabel"
|
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -1305,7 +1305,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="fui-TreeItemLayout r1bx0xiv ___dxcrnh0_vz3p260 fk6fouc fkhj508 figsok6 f1i3iumi f1k1erfc fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root/child3ExpandingLabel"
|
data-testid="TreeNode:root/child3ExpandingLabel"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden={true}
|
aria-hidden={true}
|
||||||
@@ -1345,7 +1345,7 @@ exports[`TreeNodeComponent fully renders a tree 1`] = `
|
|||||||
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loaded 1`] = `
|
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loaded 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1353,7 +1353,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -1374,7 +1374,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
|||||||
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loading 1`] = `
|
exports[`TreeNodeComponent renders a loading spinner if the node is loading: loading 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1382,7 +1382,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<Spinner
|
<Spinner
|
||||||
size="extra-tiny"
|
size="extra-tiny"
|
||||||
@@ -1408,7 +1408,7 @@ exports[`TreeNodeComponent renders a loading spinner if the node is loading: loa
|
|||||||
exports[`TreeNodeComponent renders a node as expandable if it has empty, but defined, children array 1`] = `
|
exports[`TreeNodeComponent renders a node as expandable if it has empty, but defined, children array 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1416,10 +1416,10 @@ exports[`TreeNodeComponent renders a node as expandable if it has empty, but def
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -1450,7 +1450,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||||||
>
|
>
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1468,22 +1468,22 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||||||
appearance="subtle"
|
appearance="subtle"
|
||||||
aria-label="More options"
|
aria-label="More options"
|
||||||
className="___1pg0eu5_pgl3ex0 f1twygmj"
|
className="___1pg0eu5_pgl3ex0 f1twygmj"
|
||||||
data-test="TreeNode/ContextMenuTrigger"
|
data-testid="TreeNode/ContextMenuTrigger"
|
||||||
icon={<MoreHorizontal20Regular />}
|
icon={<MoreHorizontal20Regular />}
|
||||||
/>
|
/>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
<MenuPopover
|
<MenuPopover
|
||||||
data-test="TreeNode/ContextMenu:root"
|
data-testid="TreeNode/ContextMenu:root"
|
||||||
>
|
>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
data-testid="TreeNode/ContextMenuItem:enabledItem"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
enabledItem
|
enabledItem
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
data-testid="TreeNode/ContextMenuItem:disabledItem"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
@@ -1496,7 +1496,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -1516,14 +1516,14 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||||||
<MenuPopover>
|
<MenuPopover>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test="TreeNode/ContextMenuItem:enabledItem"
|
data-testid="TreeNode/ContextMenuItem:enabledItem"
|
||||||
key="enabledItem"
|
key="enabledItem"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
>
|
>
|
||||||
enabledItem
|
enabledItem
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
data-test="TreeNode/ContextMenuItem:disabledItem"
|
data-testid="TreeNode/ContextMenuItem:disabledItem"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
key="disabledItem"
|
key="disabledItem"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
@@ -1538,7 +1538,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
|
|||||||
exports[`TreeNodeComponent renders a single node 1`] = `
|
exports[`TreeNodeComponent renders a single node 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1546,7 +1546,7 @@ exports[`TreeNodeComponent renders a single node 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -1567,7 +1567,7 @@ exports[`TreeNodeComponent renders a single node 1`] = `
|
|||||||
exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1575,7 +1575,7 @@ exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
@@ -1596,7 +1596,7 @@ exports[`TreeNodeComponent renders an icon if the node has one 1`] = `
|
|||||||
exports[`TreeNodeComponent renders selected parent node as selected if no descendant nodes are selected 1`] = `
|
exports[`TreeNodeComponent renders selected parent node as selected if no descendant nodes are selected 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1604,10 +1604,10 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -1626,7 +1626,7 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
|||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
<Tree
|
<Tree
|
||||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root"
|
data-testid="Tree:root"
|
||||||
>
|
>
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
key="child1Label"
|
key="child1Label"
|
||||||
@@ -1679,7 +1679,7 @@ exports[`TreeNodeComponent renders selected parent node as selected if no descen
|
|||||||
exports[`TreeNodeComponent renders selected parent node as unselected if any descendant node is selected 1`] = `
|
exports[`TreeNodeComponent renders selected parent node as unselected if any descendant node is selected 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="branch"
|
itemType="branch"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1687,10 +1687,10 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
className="___z7owk70_14ep1pe fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
expandIcon={
|
expandIcon={
|
||||||
<ChevronRight20Regular
|
<ChevronRight20Regular
|
||||||
data-text="TreeNode/ExpandIcon"
|
data-testid="TreeNode/ExpandIcon"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBefore={
|
iconBefore={
|
||||||
@@ -1709,7 +1709,7 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
|||||||
</TreeItemLayout>
|
</TreeItemLayout>
|
||||||
<Tree
|
<Tree
|
||||||
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
className="___17a32do_0000000 f1acs6jw f11qra4b fepn2xe f1nbblvp f19d5ny4 fzz4f4n"
|
||||||
data-test="Tree:root"
|
data-testid="Tree:root"
|
||||||
>
|
>
|
||||||
<TreeNodeComponent
|
<TreeNodeComponent
|
||||||
key="child1Label"
|
key="child1Label"
|
||||||
@@ -1763,7 +1763,7 @@ exports[`TreeNodeComponent renders selected parent node as unselected if any des
|
|||||||
exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = `
|
exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = `
|
||||||
<TreeItem
|
<TreeItem
|
||||||
className=""
|
className=""
|
||||||
data-test="TreeNodeContainer:root"
|
data-testid="TreeNodeContainer:root"
|
||||||
itemType="leaf"
|
itemType="leaf"
|
||||||
onOpenChange={[Function]}
|
onOpenChange={[Function]}
|
||||||
value="root"
|
value="root"
|
||||||
@@ -1771,7 +1771,7 @@ exports[`TreeNodeComponent renders single selected leaf node as selected 1`] = `
|
|||||||
<TreeItemLayout
|
<TreeItemLayout
|
||||||
actions={false}
|
actions={false}
|
||||||
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
className="___rq9vxg0_1ykn2d2 fkhj508 fbv8p0b f1f09k3d fg706s2 frpde29 f1n8cmsf f1ktbui8 f1nfm20t f1do9gdl"
|
||||||
data-test="TreeNode:root"
|
data-testid="TreeNode:root"
|
||||||
iconBefore={
|
iconBefore={
|
||||||
<img
|
<img
|
||||||
alt=""
|
alt=""
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse
|
|||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import { UploadDetailsRecord } from "../Contracts/ViewModels";
|
import { UploadDetailsRecord } from "../Contracts/ViewModels";
|
||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
|
import MetricScenario from "../Metrics/MetricEvents";
|
||||||
|
import { ApplicationMetricPhase } from "../Metrics/ScenarioConfig";
|
||||||
|
import { scenarioMonitor } from "../Metrics/ScenarioMonitor";
|
||||||
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -402,7 +405,9 @@ export default class Explorer {
|
|||||||
updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) =>
|
updatedDatabases = [...updatedDatabases, ...deltaDatabases.toAdd].sort((db1, db2) =>
|
||||||
db1.id().localeCompare(db2.id()),
|
db1.id().localeCompare(db2.id()),
|
||||||
);
|
);
|
||||||
useDatabases.setState({ databases: updatedDatabases });
|
useDatabases.setState({ databases: updatedDatabases, databasesFetchedSuccessfully: true });
|
||||||
|
scenarioMonitor.completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
||||||
|
|
||||||
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
|
await this.refreshAndExpandNewDatabases(deltaDatabases.toAdd, updatedDatabases);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
@@ -416,6 +421,8 @@ export default class Explorer {
|
|||||||
startKey,
|
startKey,
|
||||||
);
|
);
|
||||||
logConsoleError(`Error while refreshing databases: ${errorMessage}`);
|
logConsoleError(`Error while refreshing databases: ${errorMessage}`);
|
||||||
|
useDatabases.setState({ databasesFetchedSuccessfully: false });
|
||||||
|
scenarioMonitor.failPhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabasesFetched);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1183,6 +1190,11 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refreshExplorer(): Promise<void> {
|
public async refreshExplorer(): Promise<void> {
|
||||||
|
// Start DatabaseLoad scenario before fetching databases
|
||||||
|
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
||||||
|
scenarioMonitor.start(MetricScenario.DatabaseLoad);
|
||||||
|
}
|
||||||
|
|
||||||
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
|
||||||
userContext.authType === AuthType.ResourceToken
|
userContext.authType === AuthType.ResourceToken
|
||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
|
|||||||
name: label,
|
name: label,
|
||||||
disabled: btn.disabled,
|
disabled: btn.disabled,
|
||||||
ariaLabel: btn.ariaLabel,
|
ariaLabel: btn.ariaLabel,
|
||||||
"data-test": `CommandBar/Button:${label}`,
|
"data-testid": `CommandBar/Button:${label}`,
|
||||||
buttonStyles: {
|
buttonStyles: {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="expandCollapseButton" data-test="NotificationConsole/ExpandCollapseButton">
|
<div className="expandCollapseButton" data-testid="NotificationConsole/ExpandCollapseButton">
|
||||||
<img
|
<img
|
||||||
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
||||||
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
||||||
@@ -145,7 +145,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
height={this.props.isConsoleExpanded ? "auto" : 0}
|
height={this.props.isConsoleExpanded ? "auto" : 0}
|
||||||
onAnimationEnd={this.onConsoleWasExpanded}
|
onAnimationEnd={this.onConsoleWasExpanded}
|
||||||
>
|
>
|
||||||
<div data-test="NotificationConsole/Contents" className="notificationConsoleContents">
|
<div data-testid="NotificationConsole/Contents" className="notificationConsoleContents">
|
||||||
<div className="notificationConsoleControls">
|
<div className="notificationConsoleControls">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label="Filter:"
|
label="Filter:"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-testid="NotificationConsole/ExpandCollapseButton"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Expand icon"
|
alt="Expand icon"
|
||||||
@@ -122,7 +122,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="notificationConsoleContents"
|
className="notificationConsoleContents"
|
||||||
data-test="NotificationConsole/Contents"
|
data-testid="NotificationConsole/Contents"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="notificationConsoleControls"
|
className="notificationConsoleControls"
|
||||||
@@ -273,7 +273,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-testid="NotificationConsole/ExpandCollapseButton"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Expand icon"
|
alt="Expand icon"
|
||||||
@@ -307,7 +307,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="notificationConsoleContents"
|
className="notificationConsoleContents"
|
||||||
data-test="NotificationConsole/Contents"
|
data-testid="NotificationConsole/Contents"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="notificationConsoleControls"
|
className="notificationConsoleControls"
|
||||||
|
|||||||
@@ -56,16 +56,16 @@ export class StatusBar extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BarContainer>
|
<BarContainer>
|
||||||
<Bar data-test="notebookStatusBar">
|
<Bar data-testid="notebookStatusBar">
|
||||||
<RightStatus>
|
<RightStatus>
|
||||||
{this.props.lastSaved ? (
|
{this.props.lastSaved ? (
|
||||||
<p data-test="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
<p data-testid="saveStatus"> Last saved {distanceInWordsToNow(this.props.lastSaved)} </p>
|
||||||
) : (
|
) : (
|
||||||
<p> Not saved yet </p>
|
<p> Not saved yet </p>
|
||||||
)}
|
)}
|
||||||
</RightStatus>
|
</RightStatus>
|
||||||
<LeftStatus>
|
<LeftStatus>
|
||||||
<p data-test="kernelStatus">
|
<p data-testid="kernelStatus">
|
||||||
{name} | {this.props.kernelStatus}
|
{name} | {this.props.kernelStatus}
|
||||||
</p>
|
</p>
|
||||||
</LeftStatus>
|
</LeftStatus>
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
id="databaseCreateNew"
|
id="databaseCreateNew"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseRadio:CreateNew"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -314,6 +315,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
name="databaseType"
|
name="databaseType"
|
||||||
type="radio"
|
type="radio"
|
||||||
role="radio"
|
role="radio"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseRadio:UseExisting"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||||
/>
|
/>
|
||||||
@@ -337,6 +339,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
size={40}
|
size={40}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
aria-label="New database id, Type a new database id"
|
aria-label="New database id, Type a new database id"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseId"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
value={this.state.newDatabaseId}
|
value={this.state.newDatabaseId}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
@@ -346,18 +349,20 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
{!isServerlessAccount() && (
|
{!isServerlessAccount() && (
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Checkbox
|
<div data-testid="AddCollectionPanel/SharedThroughputCheckbox">
|
||||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
<Checkbox
|
||||||
checked={this.state.isSharedThroughputChecked}
|
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||||
styles={{
|
checked={this.state.isSharedThroughputChecked}
|
||||||
text: { fontSize: 12 },
|
styles={{
|
||||||
checkbox: { width: 12, height: 12 },
|
text: { fontSize: 12 },
|
||||||
label: { padding: 0, alignItems: "center" },
|
checkbox: { width: 12, height: 12 },
|
||||||
}}
|
label: { padding: 0, alignItems: "center" },
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
}}
|
||||||
this.setState({ isSharedThroughputChecked: isChecked })
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||||
}
|
this.setState({ isSharedThroughputChecked: isChecked })
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<TooltipHost
|
<TooltipHost
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||||
@@ -396,6 +401,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
{!this.state.createNewDatabase && (
|
{!this.state.createNewDatabase && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
ariaLabel="Choose an existing database"
|
ariaLabel="Choose an existing database"
|
||||||
|
data-testid="AddCollectionPanel/ExistingDatabaseDropdown"
|
||||||
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
||||||
style={{ width: 300, fontSize: 12 }}
|
style={{ width: 300, fontSize: 12 }}
|
||||||
placeholder="Choose an existing database"
|
placeholder="Choose an existing database"
|
||||||
@@ -443,6 +449,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
placeholder={`e.g., ${getCollectionName()}1`}
|
placeholder={`e.g., ${getCollectionName()}1`}
|
||||||
size={40}
|
size={40}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
|
data-testid="AddCollectionPanel/CollectionId"
|
||||||
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
|
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
|
||||||
value={this.state.collectionId}
|
value={this.state.collectionId}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
@@ -576,6 +583,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="addCollection-partitionKeyValue"
|
id="addCollection-partitionKeyValue"
|
||||||
|
data-testid="AddCollectionPanel/PartitionKey"
|
||||||
aria-required
|
aria-required
|
||||||
required
|
required
|
||||||
size={40}
|
size={40}
|
||||||
@@ -612,6 +620,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="addCollection-partitionKeyValue"
|
id="addCollection-partitionKeyValue"
|
||||||
|
data-testid="AddCollectionPanel/PartitionKey"
|
||||||
key={`addCollection-partitionKeyValue_${index}`}
|
key={`addCollection-partitionKeyValue_${index}`}
|
||||||
aria-required
|
aria-required
|
||||||
required
|
required
|
||||||
@@ -729,7 +738,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!isFabricNative() && userContext.apiType === "SQL" && (
|
{!isFabricNative() && userContext.apiType === "SQL" && (
|
||||||
<Stack style={{ marginTop: -2, marginBottom: -4 }}>
|
<Stack style={{ marginTop: -2, marginBottom: -4 }} data-testid="AddCollectionPanel/UniqueKeysSection">
|
||||||
{UniqueKeysHeader()}
|
{UniqueKeysHeader()}
|
||||||
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
|
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
@@ -743,6 +752,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
: "Comma separated paths e.g. /firstName,/address/zipCode"
|
||||||
}
|
}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
|
data-testid="AddCollectionPanel/UniqueKey"
|
||||||
value={uniqueKey}
|
value={uniqueKey}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
|
||||||
@@ -769,6 +779,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
iconProps={{ iconName: "Add" }}
|
iconProps={{ iconName: "Add" }}
|
||||||
|
data-testid="AddCollectionPanel/AddUniqueKeyButton"
|
||||||
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
|
styles={{ root: { padding: 0 }, label: { fontSize: 12 } }}
|
||||||
onClick={() => this.setState({ uniqueKeys: [...this.state.uniqueKeys, ""] })}
|
onClick={() => this.setState({ uniqueKeys: [...this.state.uniqueKeys, ""] })}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
aria-label="Create new database"
|
aria-label="Create new database"
|
||||||
checked={true}
|
checked={true}
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseRadio:CreateNew"
|
||||||
id="databaseCreateNew"
|
id="databaseCreateNew"
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -73,6 +74,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
aria-label="Use existing database"
|
aria-label="Use existing database"
|
||||||
checked={false}
|
checked={false}
|
||||||
className="panelRadioBtn"
|
className="panelRadioBtn"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseRadio:UseExisting"
|
||||||
name="databaseType"
|
name="databaseType"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
role="radio"
|
role="radio"
|
||||||
@@ -94,6 +96,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
aria-required={true}
|
aria-required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
|
data-testid="AddCollectionPanel/DatabaseId"
|
||||||
id="newDatabaseId"
|
id="newDatabaseId"
|
||||||
name="newDatabaseId"
|
name="newDatabaseId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -109,26 +112,30 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
<Stack
|
<Stack
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
<div
|
||||||
checked={false}
|
data-testid="AddCollectionPanel/SharedThroughputCheckbox"
|
||||||
label="Share throughput across containers"
|
>
|
||||||
onChange={[Function]}
|
<StyledCheckboxBase
|
||||||
styles={
|
checked={false}
|
||||||
{
|
label="Share throughput across containers"
|
||||||
"checkbox": {
|
onChange={[Function]}
|
||||||
"height": 12,
|
styles={
|
||||||
"width": 12,
|
{
|
||||||
},
|
"checkbox": {
|
||||||
"label": {
|
"height": 12,
|
||||||
"alignItems": "center",
|
"width": 12,
|
||||||
"padding": 0,
|
},
|
||||||
},
|
"label": {
|
||||||
"text": {
|
"alignItems": "center",
|
||||||
"fontSize": 12,
|
"padding": 0,
|
||||||
},
|
},
|
||||||
|
"text": {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
/>
|
||||||
/>
|
</div>
|
||||||
<StyledTooltipHostBase
|
<StyledTooltipHostBase
|
||||||
content="Throughput configured at the database level will be shared across all containers within the database."
|
content="Throughput configured at the database level will be shared across all containers within the database."
|
||||||
directionalHint={4}
|
directionalHint={4}
|
||||||
@@ -191,6 +198,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
aria-required={true}
|
aria-required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
|
data-testid="AddCollectionPanel/CollectionId"
|
||||||
id="collectionId"
|
id="collectionId"
|
||||||
name="collectionId"
|
name="collectionId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -252,6 +260,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
aria-label="Partition key"
|
aria-label="Partition key"
|
||||||
aria-required={true}
|
aria-required={true}
|
||||||
className="panelTextField"
|
className="panelTextField"
|
||||||
|
data-testid="AddCollectionPanel/PartitionKey"
|
||||||
id="addCollection-partitionKeyValue"
|
id="addCollection-partitionKeyValue"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
pattern=".*"
|
pattern=".*"
|
||||||
@@ -304,6 +313,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
/>
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
|
data-testid="AddCollectionPanel/UniqueKeysSection"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"marginBottom": -4,
|
"marginBottom": -4,
|
||||||
@@ -338,6 +348,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
</StyledTooltipHostBase>
|
</StyledTooltipHostBase>
|
||||||
</Stack>
|
</Stack>
|
||||||
<CustomizedActionButton
|
<CustomizedActionButton
|
||||||
|
data-testid="AddCollectionPanel/AddUniqueKeyButton"
|
||||||
iconProps={
|
iconProps={
|
||||||
{
|
{
|
||||||
"iconName": "Add",
|
"iconName": "Add",
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
{keyspaceCreateNew && (
|
{keyspaceCreateNew && (
|
||||||
<Stack className="panelGroupSpacing">
|
<Stack className="panelGroupSpacing">
|
||||||
<TextField
|
<TextField
|
||||||
|
data-testid="AddCollectionPanel/DatabaseId"
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
required={true}
|
required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -215,16 +216,20 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
|
|
||||||
{!isServerlessAccount() && (
|
{!isServerlessAccount() && (
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Checkbox
|
<div data-testid="AddCollectionPanel/SharedThroughputCheckbox">
|
||||||
label="Provision shared throughput"
|
<Checkbox
|
||||||
checked={isKeyspaceShared}
|
label="Provision shared throughput"
|
||||||
styles={{
|
checked={isKeyspaceShared}
|
||||||
text: { fontSize: 12 },
|
styles={{
|
||||||
checkbox: { width: 12, height: 12 },
|
text: { fontSize: 12 },
|
||||||
label: { padding: 0, alignItems: "center" },
|
checkbox: { width: 12, height: 12 },
|
||||||
}}
|
label: { padding: 0, alignItems: "center" },
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
|
}}
|
||||||
/>
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||||
|
setIsKeyspaceShared(isChecked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
||||||
the keyspace
|
the keyspace
|
||||||
@@ -287,6 +292,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
||||||
</Text>
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
|
data-testid="AddCollectionPanel/CollectionId"
|
||||||
underlined
|
underlined
|
||||||
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
|||||||
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
<Text variant="small">Confirm by typing the {collectionName.toLowerCase()} id</Text>
|
||||||
<TextField
|
<TextField
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
|
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||||
autoFocus
|
autoFocus
|
||||||
value={inputCollectionName}
|
value={inputCollectionName}
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Confirm by typing the container id"
|
ariaLabel="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -57,6 +58,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Confirm by typing the container id"
|
ariaLabel="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -353,6 +355,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
aria-label="Confirm by typing the container id"
|
aria-label="Confirm by typing the container id"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-113"
|
className="ms-TextField-field field-113"
|
||||||
|
data-testid="DeleteCollectionConfirmationPane/ConfirmInput"
|
||||||
id="confirmCollectionId"
|
id="confirmCollectionId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -379,7 +382,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
@@ -390,7 +393,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -681,7 +684,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -977,7 +980,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1271,7 +1274,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2157,7 +2160,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
|||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-122"
|
className="ms-Button ms-Button--primary root-122"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
|||||||
<Text variant="small">{confirmDatabase}</Text>
|
<Text variant="small">{confirmDatabase}</Text>
|
||||||
<TextField
|
<TextField
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
data-test="Input:confirmDatabaseId"
|
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||||
autoFocus
|
autoFocus
|
||||||
styles={{ fieldGroup: { width: 300 } }}
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
onChange={(event, newInput?: string) => {
|
onChange={(event, newInput?: string) => {
|
||||||
|
|||||||
@@ -5312,7 +5312,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Execute"
|
ariaLabel="Execute"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Execute"
|
text="Execute"
|
||||||
@@ -5323,7 +5323,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Execute"
|
ariaLabel="Execute"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -5614,7 +5614,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Execute"
|
ariaLabel="Execute"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -5910,7 +5910,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Execute"
|
ariaLabel="Execute"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -6204,7 +6204,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Execute"
|
ariaLabel="Execute"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -7090,7 +7090,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
|
|||||||
aria-label="Execute"
|
aria-label="Execute"
|
||||||
className="ms-Button ms-Button--primary root-148"
|
className="ms-Button ms-Button--primary root-148"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel
|
<Panel
|
||||||
data-test={`Panel:${this.props.headerText}`}
|
data-testid={`Panel:${this.props.headerText}`}
|
||||||
headerText={this.props.headerText}
|
headerText={this.props.headerText}
|
||||||
isOpen={this.props.isOpen}
|
isOpen={this.props.isOpen}
|
||||||
onDismiss={this.onDissmiss}
|
onDismiss={this.onDissmiss}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const PanelFooterComponent: React.FunctionComponent<PanelFooterProps> = (
|
|||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
type="submit"
|
type="submit"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
text={buttonLabel}
|
text={buttonLabel}
|
||||||
ariaLabel={buttonLabel}
|
ariaLabel={buttonLabel}
|
||||||
disabled={!!isButtonDisabled}
|
disabled={!!isButtonDisabled}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Load"
|
text="Load"
|
||||||
@@ -32,7 +32,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -323,7 +323,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -619,7 +619,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -913,7 +913,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Load"
|
ariaLabel="Load"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1799,7 +1799,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
|
|||||||
aria-label="Load"
|
aria-label="Load"
|
||||||
className="ms-Button ms-Button--primary root-109"
|
className="ms-Button ms-Button--primary root-109"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Create"
|
ariaLabel="Create"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Create"
|
text="Create"
|
||||||
@@ -699,7 +699,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Create"
|
ariaLabel="Create"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -990,7 +990,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Create"
|
ariaLabel="Create"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1286,7 +1286,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Create"
|
ariaLabel="Create"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1580,7 +1580,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Create"
|
ariaLabel="Create"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2466,7 +2466,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
|||||||
aria-label="Create"
|
aria-label="Create"
|
||||||
className="ms-Button ms-Button--primary root-128"
|
className="ms-Button ms-Button--primary root-128"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -1258,7 +1258,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
@@ -1269,7 +1269,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -1560,7 +1560,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1856,7 +1856,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2150,7 +2150,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -3036,7 +3036,7 @@ exports[`Table query select Panel should render Default properly 1`] = `
|
|||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-125"
|
className="ms-Button ms-Button--primary root-125"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Add Entity"
|
text="Add Entity"
|
||||||
@@ -380,7 +380,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -671,7 +671,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -967,7 +967,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1261,7 +1261,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Add Entity"
|
ariaLabel="Add Entity"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2147,7 +2147,7 @@ exports[`Excute Add Table Entity Pane should render Default properly 1`] = `
|
|||||||
aria-label="Add Entity"
|
aria-label="Add Entity"
|
||||||
className="ms-Button ms-Button--primary root-113"
|
className="ms-Button ms-Button--primary root-113"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="Update"
|
text="Update"
|
||||||
@@ -386,7 +386,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -677,7 +677,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -973,7 +973,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1267,7 +1267,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="Update"
|
ariaLabel="Update"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2153,7 +2153,7 @@ exports[`Excute Edit Table Entity Pane should render Default properly 1`] = `
|
|||||||
aria-label="Update"
|
aria-label="Update"
|
||||||
className="ms-Button ms-Button--primary root-113"
|
className="ms-Button ms-Button--primary root-113"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
ariaLabel="Confirm by typing the Database id (name)"
|
ariaLabel="Confirm by typing the Database id (name)"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
data-test="Input:confirmDatabaseId"
|
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -382,7 +382,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
<TextFieldBase
|
<TextFieldBase
|
||||||
ariaLabel="Confirm by typing the Database id (name)"
|
ariaLabel="Confirm by typing the Database id (name)"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
data-test="Input:confirmDatabaseId"
|
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||||
deferredValidationTime={200}
|
deferredValidationTime={200}
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -678,7 +678,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
aria-label="Confirm by typing the Database id (name)"
|
aria-label="Confirm by typing the Database id (name)"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="ms-TextField-field field-117"
|
className="ms-TextField-field field-117"
|
||||||
data-test="Input:confirmDatabaseId"
|
data-testid="DeleteDatabaseConfirmationPanel/ConfirmInput"
|
||||||
id="confirmDatabaseId"
|
id="confirmDatabaseId"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
@@ -1054,7 +1054,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedPrimaryButton
|
<CustomizedPrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
text="OK"
|
text="OK"
|
||||||
@@ -1065,7 +1065,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
styles={
|
styles={
|
||||||
@@ -1356,7 +1356,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<CustomizedDefaultButton
|
<CustomizedDefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1652,7 +1652,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
>
|
>
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -1946,7 +1946,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
<BaseButton
|
<BaseButton
|
||||||
ariaLabel="OK"
|
ariaLabel="OK"
|
||||||
baseClassName="ms-Button"
|
baseClassName="ms-Button"
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onRenderDescription={[Function]}
|
onRenderDescription={[Function]}
|
||||||
@@ -2832,7 +2832,7 @@ exports[`Delete Database Confirmation Pane Should call delete database 1`] = `
|
|||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-130"
|
className="ms-Button ms-Button--primary root-130"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
data-test="Panel/OkButton"
|
data-testid="Panel/OkButton"
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ exports[`PaneContainerComponent test should be resize if notification console is
|
|||||||
<StyledPanelBase
|
<StyledPanelBase
|
||||||
closeButtonAriaLabel="Close test"
|
closeButtonAriaLabel="Close test"
|
||||||
customWidth="440px"
|
customWidth="440px"
|
||||||
data-test="Panel:test"
|
data-testid="Panel:test"
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
headerText="test"
|
headerText="test"
|
||||||
isFooterAtBottom={true}
|
isFooterAtBottom={true}
|
||||||
@@ -43,7 +43,7 @@ exports[`PaneContainerComponent test should not render console with panel 1`] =
|
|||||||
<StyledPanelBase
|
<StyledPanelBase
|
||||||
closeButtonAriaLabel="Close test"
|
closeButtonAriaLabel="Close test"
|
||||||
customWidth="440px"
|
customWidth="440px"
|
||||||
data-test="Panel:test"
|
data-testid="Panel:test"
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
headerText="test"
|
headerText="test"
|
||||||
isFooterAtBottom={true}
|
isFooterAtBottom={true}
|
||||||
@@ -84,7 +84,7 @@ exports[`PaneContainerComponent test should render with panel content and header
|
|||||||
<StyledPanelBase
|
<StyledPanelBase
|
||||||
closeButtonAriaLabel="Close test"
|
closeButtonAriaLabel="Close test"
|
||||||
customWidth="440px"
|
customWidth="440px"
|
||||||
data-test="Panel:test"
|
data-testid="Panel:test"
|
||||||
headerClassName="panelHeader"
|
headerClassName="panelHeader"
|
||||||
headerText="test"
|
headerText="test"
|
||||||
isFooterAtBottom={true}
|
isFooterAtBottom={true}
|
||||||
|
|||||||
@@ -242,9 +242,14 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.globalCommandsContainer} data-test="GlobalCommands">
|
<div className={styles.globalCommandsContainer} data-testid="GlobalCommands">
|
||||||
{actions.length === 1 ? (
|
{actions.length === 1 ? (
|
||||||
<Button icon={primaryAction.icon} onClick={onPrimaryActionClick} ref={primaryFocusableRef}>
|
<Button
|
||||||
|
data-testid={`GlobalCommands/Button:${primaryAction.label}`}
|
||||||
|
icon={primaryAction.icon}
|
||||||
|
onClick={onPrimaryActionClick}
|
||||||
|
ref={primaryFocusableRef}
|
||||||
|
>
|
||||||
{primaryAction.label}
|
{primaryAction.label}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
@@ -253,8 +258,12 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
{(triggerProps: MenuButtonProps) => (
|
{(triggerProps: MenuButtonProps) => (
|
||||||
<div ref={setGlobalCommandButton}>
|
<div ref={setGlobalCommandButton}>
|
||||||
<SplitButton
|
<SplitButton
|
||||||
|
data-testid={`GlobalCommands/Button:${primaryAction.label}`}
|
||||||
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
|
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
|
||||||
primaryActionButton={{ onClick: onPrimaryActionClick, ref: primaryFocusableRef }}
|
primaryActionButton={{
|
||||||
|
onClick: onPrimaryActionClick,
|
||||||
|
ref: primaryFocusableRef,
|
||||||
|
}}
|
||||||
className={styles.globalCommandsSplitButton}
|
className={styles.globalCommandsSplitButton}
|
||||||
icon={primaryAction.icon}
|
icon={primaryAction.icon}
|
||||||
>
|
>
|
||||||
@@ -376,7 +385,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
{!isFabricNative() && (
|
{!isFabricNative() && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-test="Sidebar/RefreshButton"
|
data-testid="Sidebar/RefreshButton"
|
||||||
className={styles.floatingControlButton}
|
className={styles.floatingControlButton}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
title="Refresh"
|
title="Refresh"
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
jest.mock("rx-jupyter", () => ({
|
||||||
|
sessions: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
JupyterContentProvider: jest.fn().mockImplementation(() => ({})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock("Common/dataAccess/queryDocuments", () => ({
|
jest.mock("Common/dataAccess/queryDocuments", () => ({
|
||||||
queryDocuments: jest.fn(() => ({
|
queryDocuments: jest.fn(() => ({
|
||||||
// Omit headers, because we can't mock a private field and we don't need to test it
|
// Omit headers, because we can't mock a private field and we don't need to test it
|
||||||
|
|||||||
@@ -2146,8 +2146,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CosmosFluentProvider className={styles.container}>
|
<CosmosFluentProvider className={styles.container}>
|
||||||
<div data-test={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
|
<div data-testid={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
|
||||||
<div data-test={"DocumentsTab/Filter"} className={`${styles.filterRow} ${styles.smallScreenContent}`}>
|
<div data-testid={"DocumentsTab/Filter"} className={`${styles.filterRow} ${styles.smallScreenContent}`}>
|
||||||
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
|
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
|
||||||
<InputDataList
|
<InputDataList
|
||||||
dropdownOptions={getFilterChoices()}
|
dropdownOptions={getFilterChoices()}
|
||||||
@@ -2164,7 +2164,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
appearance="primary"
|
appearance="primary"
|
||||||
data-test={"DocumentsTab/ApplyFilter"}
|
data-testid={"DocumentsTab/ApplyFilter"}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isExecuting) {
|
if (isExecuting) {
|
||||||
@@ -2191,7 +2191,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
>
|
>
|
||||||
<Allotment.Pane preferredSize={`${tabStateData.leftPaneWidthPercent}%`} minSize={55}>
|
<Allotment.Pane preferredSize={`${tabStateData.leftPaneWidthPercent}%`} minSize={55}>
|
||||||
<div
|
<div
|
||||||
data-test={"DocumentsTab/DocumentsPane"}
|
data-testid={"DocumentsTab/DocumentsPane"}
|
||||||
style={{ height: "100%", width: "100%", overflow: "hidden" }}
|
style={{ height: "100%", width: "100%", overflow: "hidden" }}
|
||||||
ref={tableContainerRef}
|
ref={tableContainerRef}
|
||||||
>
|
>
|
||||||
@@ -2237,7 +2237,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
{tableItems.length > 0 && (
|
{tableItems.length > 0 && (
|
||||||
<a
|
<a
|
||||||
className={styles.loadMore}
|
className={styles.loadMore}
|
||||||
data-test={"DocumentsTab/LoadMore"}
|
data-testid={"DocumentsTab/LoadMore"}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => loadNextPage(documentsIterator.iterator, false)}
|
onClick={() => loadNextPage(documentsIterator.iterator, false)}
|
||||||
@@ -2249,7 +2249,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
</div>
|
</div>
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
<Allotment.Pane minSize={30}>
|
<Allotment.Pane minSize={30}>
|
||||||
<div data-test={"DocumentsTab/ResultsPane"} style={{ height: "100%", width: "100%" }}>
|
<div data-testid={"DocumentsTab/ResultsPane"} style={{ height: "100%", width: "100%" }}>
|
||||||
{isTabActive && selectedDocumentContent && selectedRows.size <= 1 && (
|
{isTabActive && selectedDocumentContent && selectedRows.size <= 1 && (
|
||||||
<EditorReact
|
<EditorReact
|
||||||
language={"json"}
|
language={"json"}
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ import { act } from "react-dom/test-utils";
|
|||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
|
||||||
|
jest.mock("rx-jupyter", () => ({
|
||||||
|
sessions: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
JupyterContentProvider: jest.fn().mockImplementation(() => ({})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
jest.requireActual("Explorer/Controls/Editor/EditorReact");
|
jest.requireActual("Explorer/Controls/Editor/EditorReact");
|
||||||
|
|
||||||
const PROPERTY_VALUE = "__SOME_PROPERTY_VALUE__";
|
const PROPERTY_VALUE = "__SOME_PROPERTY_VALUE__";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="tab-pane active"
|
className="tab-pane active"
|
||||||
data-test="DocumentsTab"
|
data-testid="DocumentsTab"
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29 ___1ngl8o6_0000000 fz7mnu6 fl3egqs flhmrkm"
|
className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29 ___1ngl8o6_0000000 fz7mnu6 fl3egqs flhmrkm"
|
||||||
data-test="DocumentsTab/Filter"
|
data-testid="DocumentsTab/Filter"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
SELECT * FROM c
|
SELECT * FROM c
|
||||||
@@ -51,7 +51,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
<Button
|
<Button
|
||||||
appearance="primary"
|
appearance="primary"
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
data-test="DocumentsTab/ApplyFilter"
|
data-testid="DocumentsTab/ApplyFilter"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -68,7 +68,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
preferredSize="35%"
|
preferredSize="35%"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-test="DocumentsTab/DocumentsPane"
|
data-testid="DocumentsTab/DocumentsPane"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
@@ -130,7 +130,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
minSize={30}
|
minSize={30}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-test="DocumentsTab/ResultsPane"
|
data-testid="DocumentsTab/ResultsPane"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const ErrorList: React.FC<{ errors: QueryError[] }> = ({ errors }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataGrid
|
<DataGrid
|
||||||
data-test="QueryTab/ResultsPane/ErrorList"
|
data-testid="QueryTab/ResultsPane/ErrorList"
|
||||||
items={errors}
|
items={errors}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sortable
|
sortable
|
||||||
@@ -131,9 +131,9 @@ export const ErrorList: React.FC<{ errors: QueryError[] }> = ({ errors }) => {
|
|||||||
</DataGridHeader>
|
</DataGridHeader>
|
||||||
<DataGridBody<QueryError>>
|
<DataGridBody<QueryError>>
|
||||||
{({ item, rowId }) => (
|
{({ item, rowId }) => (
|
||||||
<DataGridRow<QueryError> key={rowId} data-test={`Row:${rowId}`}>
|
<DataGridRow<QueryError> key={rowId} data-testid={`Row:${rowId}`}>
|
||||||
{({ columnId, renderCell }) => (
|
{({ columnId, renderCell }) => (
|
||||||
<DataGridCell data-test={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
<DataGridCell data-testid={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||||
)}
|
)}
|
||||||
</DataGridRow>
|
</DataGridRow>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import QueryError from "Common/QueryError";
|
|||||||
import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar";
|
import { IndeterminateProgressBar } from "Explorer/Controls/IndeterminateProgressBar";
|
||||||
import { MessageBanner } from "Explorer/Controls/MessageBanner";
|
import { MessageBanner } from "Explorer/Controls/MessageBanner";
|
||||||
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
import { useQueryTabStyles } from "Explorer/Tabs/QueryTab/Styles";
|
||||||
|
import useZoomLevel from "hooks/useZoomLevel";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { conditionalClass } from "Utils/StyleUtils";
|
||||||
import RunQuery from "../../../../images/RunQuery.png";
|
import RunQuery from "../../../../images/RunQuery.png";
|
||||||
import { QueryResults } from "../../../Contracts/ViewModels";
|
import { QueryResults } from "../../../Contracts/ViewModels";
|
||||||
import { ErrorList } from "./ErrorList";
|
import { ErrorList } from "./ErrorList";
|
||||||
import { ResultsView } from "./ResultsView";
|
import { ResultsView } from "./ResultsView";
|
||||||
import useZoomLevel from "hooks/useZoomLevel";
|
|
||||||
import { conditionalClass } from "Utils/StyleUtils";
|
|
||||||
|
|
||||||
export interface ResultsViewProps {
|
export interface ResultsViewProps {
|
||||||
isMongoDB: boolean;
|
isMongoDB: boolean;
|
||||||
@@ -27,7 +27,7 @@ const ExecuteQueryCallToAction: React.FC = () => {
|
|||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
const isZoomed = useZoomLevel();
|
const isZoomed = useZoomLevel();
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
<div data-testid="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<img
|
<img
|
||||||
@@ -54,7 +54,7 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
|||||||
const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent);
|
const maybeSubQuery = queryEditorContent && /.*\(.*SELECT.*\)/i.test(queryEditorContent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab/ResultsPane" className={styles.queryResultsPanel}>
|
<div data-testid="QueryTab/ResultsPane" className={styles.queryResultsPanel}>
|
||||||
{isExecuting && <IndeterminateProgressBar />}
|
{isExecuting && <IndeterminateProgressBar />}
|
||||||
<MessageBanner
|
<MessageBanner
|
||||||
messageId="QueryEditor.EmptyMongoQuery"
|
messageId="QueryEditor.EmptyMongoQuery"
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ describe("QueryTabComponent", () => {
|
|||||||
|
|
||||||
const { container } = render(<QueryTabComponent {...propsMock} />);
|
const { container } = render(<QueryTabComponent {...propsMock} />);
|
||||||
|
|
||||||
const launchCopilotButton = container.querySelector('[data-test="QueryTab/ResultsPane/ExecuteCTA"]');
|
const launchCopilotButton = container.querySelector('[data-testid="QueryTab/ResultsPane/ExecuteCTA"]');
|
||||||
fireEvent.keyDown(launchCopilotButton, { key: "c", altKey: true });
|
fireEvent.keyDown(launchCopilotButton, { key: "c", altKey: true });
|
||||||
|
|
||||||
expect(mockStore.setShowCopilotSidebar).toHaveBeenCalledWith(true);
|
expect(mockStore.setShowCopilotSidebar).toHaveBeenCalledWith(true);
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Allotment.Pane
|
<Allotment.Pane
|
||||||
data-test="QueryTab/EditorPane"
|
data-testid="QueryTab/EditorPane"
|
||||||
preferredSize={
|
preferredSize={
|
||||||
this.state.queryViewSizePercent !== undefined ? `${this.state.queryViewSizePercent}%` : undefined
|
this.state.queryViewSizePercent !== undefined ? `${this.state.queryViewSizePercent}%` : undefined
|
||||||
}
|
}
|
||||||
@@ -813,7 +813,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
const shouldScaleElements = this.state.showCopilotSidebar && this.isCopilotTabActive;
|
const shouldScaleElements = this.state.showCopilotSidebar && this.isCopilotTabActive;
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab" style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
<div data-testid="QueryTab" style={{ display: "flex", flexDirection: "row", height: "100%" }}>
|
||||||
<div style={{ width: shouldScaleElements ? "70%" : "100%", height: "100%" }}>
|
<div style={{ width: shouldScaleElements ? "70%" : "100%", height: "100%" }}>
|
||||||
{this.getEditorAndQueryResult()}
|
{this.getEditorAndQueryResult()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
|
|||||||
return (
|
return (
|
||||||
<div className={styles.metricsGridContainer}>
|
<div className={styles.metricsGridContainer}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
data-test="QueryTab/ResultsPane/ResultsView/QueryStatsList"
|
data-testid="QueryTab/ResultsPane/ResultsView/QueryStatsList"
|
||||||
className={styles.queryStatsGrid}
|
className={styles.queryStatsGrid}
|
||||||
items={generateQueryStatsItems()}
|
items={generateQueryStatsItems()}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -504,9 +504,9 @@ const QueryStatsTab: React.FC<Pick<ResultsViewProps, "queryResults">> = ({ query
|
|||||||
</DataGridHeader>
|
</DataGridHeader>
|
||||||
<DataGridBody<IDocument>>
|
<DataGridBody<IDocument>>
|
||||||
{({ item, rowId }) => (
|
{({ item, rowId }) => (
|
||||||
<DataGridRow<IDocument> key={rowId} data-test={`Row:${rowId}`}>
|
<DataGridRow<IDocument> key={rowId} data-testid={`Row:${rowId}`}>
|
||||||
{({ columnId, renderCell }) => (
|
{({ columnId, renderCell }) => (
|
||||||
<DataGridCell data-test={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
<DataGridCell data-testid={`Row:${rowId}/Column:${columnId}`}>{renderCell(item)}</DataGridCell>
|
||||||
)}
|
)}
|
||||||
</DataGridRow>
|
</DataGridRow>
|
||||||
)}
|
)}
|
||||||
@@ -532,17 +532,17 @@ export const ResultsView: React.FC<ResultsViewProps> = ({ isMongoDB, queryResult
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
<div data-testid="QueryTab/ResultsPane/ResultsView" className={styles.queryResultsTabPanel}>
|
||||||
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
|
<TabList selectedValue={activeTab} onTabSelect={onTabSelect}>
|
||||||
<Tab
|
<Tab
|
||||||
data-test="QueryTab/ResultsPane/ResultsView/ResultsTab"
|
data-testid="QueryTab/ResultsPane/ResultsView/ResultsTab"
|
||||||
id={ResultsTabs.Results}
|
id={ResultsTabs.Results}
|
||||||
value={ResultsTabs.Results}
|
value={ResultsTabs.Results}
|
||||||
>
|
>
|
||||||
Results
|
Results
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
data-test="QueryTab/ResultsPane/ResultsView/QueryStatsTab"
|
data-testid="QueryTab/ResultsPane/ResultsView/QueryStatsTab"
|
||||||
id={ResultsTabs.QueryStats}
|
id={ResultsTabs.QueryStats}
|
||||||
value={ResultsTabs.QueryStats}
|
value={ResultsTabs.QueryStats}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -237,14 +237,14 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
|
|||||||
if (tab) {
|
if (tab) {
|
||||||
if ("render" in tab) {
|
if ("render" in tab) {
|
||||||
return (
|
return (
|
||||||
<div data-test={`Tab:${tab.tabId}`} {...attrs}>
|
<div data-testid={`Tab:${tab.tabId}`} {...attrs}>
|
||||||
{tab.render()}
|
{tab.render()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div data-test={`Tab:${tab.tabId}`} {...attrs} ref={ref} data-bind="html:html" />;
|
return <div data-testid={`Tab:${tab.tabId}`} {...attrs} ref={ref} data-bind="html:html" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
|
const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ import { updateUserContext, userContext } from "../../UserContext";
|
|||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import Database from "./Database";
|
import Database from "./Database";
|
||||||
|
|
||||||
|
jest.mock("rx-jupyter", () => ({
|
||||||
|
sessions: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
JupyterContentProvider: jest.fn().mockImplementation(() => ({})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
const createMockContainer = (): Explorer => {
|
const createMockContainer = (): Explorer => {
|
||||||
const mockContainer = new Explorer();
|
const mockContainer = new Explorer();
|
||||||
return mockContainer;
|
return mockContainer;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { ReactTabKind, useTabs } from "hooks/useTabs";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import shallow from "zustand/shallow";
|
import shallow from "zustand/shallow";
|
||||||
|
import { useDatabaseLoadScenario } from "../../Metrics/useMetricPhases";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { useNotebook } from "../Notebook/useNotebook";
|
import { useNotebook } from "../Notebook/useNotebook";
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
resourceTokenCollection: state.resourceTokenCollection,
|
resourceTokenCollection: state.resourceTokenCollection,
|
||||||
sampleDataResourceTokenCollection: state.sampleDataResourceTokenCollection,
|
sampleDataResourceTokenCollection: state.sampleDataResourceTokenCollection,
|
||||||
}));
|
}));
|
||||||
|
const databasesFetchedSuccessfully = useDatabases((state) => state.databasesFetchedSuccessfully);
|
||||||
const { isCopilotEnabled, isCopilotSampleDBEnabled } = useQueryCopilot((state) => ({
|
const { isCopilotEnabled, isCopilotSampleDBEnabled } = useQueryCopilot((state) => ({
|
||||||
isCopilotEnabled: state.copilotEnabled,
|
isCopilotEnabled: state.copilotEnabled,
|
||||||
isCopilotSampleDBEnabled: state.copilotSampleDBEnabled,
|
isCopilotSampleDBEnabled: state.copilotSampleDBEnabled,
|
||||||
@@ -114,6 +116,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
}
|
}
|
||||||
}, [databaseTreeNodes, sampleDataNodes]);
|
}, [databaseTreeNodes, sampleDataNodes]);
|
||||||
|
|
||||||
|
// Track complete DatabaseLoad scenario (start, tree rendered, interactive)
|
||||||
|
useDatabaseLoadScenario(databaseTreeNodes, databasesFetchedSuccessfully);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Compute open items based on node.isExpanded
|
// Compute open items based on node.isExpanded
|
||||||
const updateOpenItems = (node: TreeNode, parentNodeId: string): void => {
|
const updateOpenItems = (node: TreeNode, parentNodeId: string): void => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ interface DatabasesState {
|
|||||||
databases: ViewModels.Database[];
|
databases: ViewModels.Database[];
|
||||||
resourceTokenCollection: ViewModels.CollectionBase;
|
resourceTokenCollection: ViewModels.CollectionBase;
|
||||||
sampleDataResourceTokenCollection: ViewModels.CollectionBase;
|
sampleDataResourceTokenCollection: ViewModels.CollectionBase;
|
||||||
|
databasesFetchedSuccessfully: boolean; // Track if last database fetch was successful
|
||||||
updateDatabase: (database: ViewModels.Database) => void;
|
updateDatabase: (database: ViewModels.Database) => void;
|
||||||
addDatabases: (databases: ViewModels.Database[]) => void;
|
addDatabases: (databases: ViewModels.Database[]) => void;
|
||||||
deleteDatabase: (database: ViewModels.Database) => void;
|
deleteDatabase: (database: ViewModels.Database) => void;
|
||||||
@@ -30,6 +31,7 @@ export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
|||||||
databases: [],
|
databases: [],
|
||||||
resourceTokenCollection: undefined,
|
resourceTokenCollection: undefined,
|
||||||
sampleDataResourceTokenCollection: undefined,
|
sampleDataResourceTokenCollection: undefined,
|
||||||
|
databasesFetchedSuccessfully: false,
|
||||||
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ const App: React.FunctionComponent = () => {
|
|||||||
// Setting key is needed so React will re-render this element on any account change
|
// Setting key is needed so React will re-render this element on any account change
|
||||||
key={databaseAccount?.id || encryptedTokenMetadata?.accountName || authType}
|
key={databaseAccount?.id || encryptedTokenMetadata?.accountName || authType}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
data-test="DataExplorerFrame"
|
data-testid="DataExplorerFrame"
|
||||||
id="explorerMenu"
|
id="explorerMenu"
|
||||||
name="explorer"
|
name="explorer"
|
||||||
className="iframe"
|
className="iframe"
|
||||||
|
|||||||
29
src/Main.tsx
29
src/Main.tsx
@@ -60,6 +60,10 @@ import "./Explorer/Panes/PanelComponent.less";
|
|||||||
import { SidePanel } from "./Explorer/Panes/PanelContainerComponent";
|
import { SidePanel } from "./Explorer/Panes/PanelContainerComponent";
|
||||||
import "./Explorer/SplashScreen/SplashScreen.less";
|
import "./Explorer/SplashScreen/SplashScreen.less";
|
||||||
import "./Libs/jquery";
|
import "./Libs/jquery";
|
||||||
|
import MetricScenario from "./Metrics/MetricEvents";
|
||||||
|
import { MetricScenarioProvider, useMetricScenario } from "./Metrics/MetricScenarioProvider";
|
||||||
|
import { ApplicationMetricPhase } from "./Metrics/ScenarioConfig";
|
||||||
|
import { useInteractive } from "./Metrics/useMetricPhases";
|
||||||
import { appThemeFabric } from "./Platform/Fabric/FabricTheme";
|
import { appThemeFabric } from "./Platform/Fabric/FabricTheme";
|
||||||
import "./Shared/appInsights";
|
import "./Shared/appInsights";
|
||||||
import { useConfig } from "./hooks/useConfig";
|
import { useConfig } from "./hooks/useConfig";
|
||||||
@@ -79,13 +83,27 @@ const App: React.FunctionComponent = () => {
|
|||||||
StyleConstants.updateStyles();
|
StyleConstants.updateStyles();
|
||||||
const explorer = useKnockoutExplorer(config?.platform);
|
const explorer = useKnockoutExplorer(config?.platform);
|
||||||
|
|
||||||
|
// Scenario-based health tracking: start ApplicationLoad and complete phases.
|
||||||
|
const { startScenario, completePhase } = useMetricScenario();
|
||||||
|
React.useEffect(() => {
|
||||||
|
startScenario(MetricScenario.ApplicationLoad);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (explorer) {
|
||||||
|
completePhase(MetricScenario.ApplicationLoad, ApplicationMetricPhase.ExplorerInitialized);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [explorer]);
|
||||||
|
|
||||||
if (!explorer) {
|
if (!explorer) {
|
||||||
return <LoadingExplorer />;
|
return <LoadingExplorer />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardShortcutRoot>
|
<KeyboardShortcutRoot>
|
||||||
<div className="flexContainer" aria-hidden="false" data-test="DataExplorerRoot">
|
<div className="flexContainer" aria-hidden="false" data-testid="DataExplorerRoot">
|
||||||
{userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
|
{userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
|
||||||
<ContainerCopyPanel explorer={explorer} />
|
<ContainerCopyPanel explorer={explorer} />
|
||||||
) : (
|
) : (
|
||||||
@@ -104,9 +122,16 @@ const App: React.FunctionComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mainElement = document.getElementById("Main");
|
const mainElement = document.getElementById("Main");
|
||||||
ReactDOM.render(<App />, mainElement);
|
ReactDOM.render(
|
||||||
|
<MetricScenarioProvider>
|
||||||
|
<App />
|
||||||
|
</MetricScenarioProvider>,
|
||||||
|
mainElement,
|
||||||
|
);
|
||||||
|
|
||||||
function DivExplorer({ explorer }: { explorer: Explorer }): JSX.Element {
|
function DivExplorer({ explorer }: { explorer: Explorer }): JSX.Element {
|
||||||
|
useInteractive(MetricScenario.ApplicationLoad);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="divExplorer" className="flexContainer hideOverflows">
|
<div id="divExplorer" className="flexContainer hideOverflows">
|
||||||
<div id="freeTierTeachingBubble"> </div>
|
<div id="freeTierTeachingBubble"> </div>
|
||||||
|
|||||||
16
src/Metrics/Constants.ts
Normal file
16
src/Metrics/Constants.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { ApiType } from "Common/Constants";
|
||||||
|
import { Platform } from "ConfigContext";
|
||||||
|
|
||||||
|
// Metric scenarios represent lifecycle checkpoints we measure.
|
||||||
|
export enum MetricScenario {
|
||||||
|
ApplicationLoad = "ApplicationLoad",
|
||||||
|
DatabaseLoad = "DatabaseLoad",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic metric emission event describing scenario outcome.
|
||||||
|
export interface MetricEvent {
|
||||||
|
readonly platform: Platform;
|
||||||
|
readonly api: ApiType;
|
||||||
|
readonly scenario: MetricScenario;
|
||||||
|
readonly healthy: boolean;
|
||||||
|
}
|
||||||
104
src/Metrics/MetricEvents.test.ts
Normal file
104
src/Metrics/MetricEvents.test.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
import { fetchWithTimeout } from "../Utils/FetchWithTimeout";
|
||||||
|
import MetricScenario, { reportHealthy, reportUnhealthy } from "./MetricEvents";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { Response } = require("node-fetch");
|
||||||
|
|
||||||
|
jest.mock("../Utils/AuthorizationUtils", () => ({
|
||||||
|
getAuthorizationHeader: jest.fn().mockReturnValue({ header: "authorization", token: "Bearer test-token" }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("../Utils/FetchWithTimeout", () => ({
|
||||||
|
fetchWithTimeout: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("MetricEvents", () => {
|
||||||
|
const mockFetchWithTimeout = fetchWithTimeout as jest.MockedFunction<typeof fetchWithTimeout>;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reportHealthy success includes auth header", async () => {
|
||||||
|
const mockResponse = new Response(null, { status: 200 });
|
||||||
|
mockFetchWithTimeout.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await reportHealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL");
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(Response);
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
expect(result.status).toBe(200);
|
||||||
|
expect(mockFetchWithTimeout).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const callArgs = mockFetchWithTimeout.mock.calls[0];
|
||||||
|
expect(callArgs[0]).toContain("/api/dataexplorer/metrics/health");
|
||||||
|
expect(callArgs[1]?.headers).toEqual({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
authorization: "Bearer test-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = JSON.parse(callArgs[1]?.body as string);
|
||||||
|
expect(body.scenario).toBe(MetricScenario.ApplicationLoad);
|
||||||
|
expect(body.platform).toBe(Platform.Portal);
|
||||||
|
expect(body.api).toBe("SQL");
|
||||||
|
expect(body.healthy).toBe(true);
|
||||||
|
expect(getAuthorizationHeader).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("reportUnhealthy failure status", async () => {
|
||||||
|
const mockResponse = new Response("Failure", { status: 500 });
|
||||||
|
mockFetchWithTimeout.mockResolvedValue(mockResponse);
|
||||||
|
|
||||||
|
const result = await reportUnhealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL");
|
||||||
|
|
||||||
|
expect(result).toBeInstanceOf(Response);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
expect(result.status).toBe(500);
|
||||||
|
|
||||||
|
const callArgs = mockFetchWithTimeout.mock.calls[0];
|
||||||
|
const body = JSON.parse(callArgs[1]?.body as string);
|
||||||
|
expect(body.healthy).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("helpers healthy/unhealthy", async () => {
|
||||||
|
mockFetchWithTimeout.mockResolvedValue(new Response(null, { status: 201 }));
|
||||||
|
|
||||||
|
const healthyResult = await reportHealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL");
|
||||||
|
const unhealthyResult = await reportUnhealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL");
|
||||||
|
|
||||||
|
expect(healthyResult.status).toBe(201);
|
||||||
|
expect(unhealthyResult.status).toBe(201);
|
||||||
|
expect(mockFetchWithTimeout).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("throws when backend endpoint missing", async () => {
|
||||||
|
const original = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||||
|
(configContext as { PORTAL_BACKEND_ENDPOINT: string }).PORTAL_BACKEND_ENDPOINT = "";
|
||||||
|
|
||||||
|
await expect(reportHealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL")).rejects.toThrow(
|
||||||
|
"baseUri is null or empty",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockFetchWithTimeout).not.toHaveBeenCalled();
|
||||||
|
(configContext as { PORTAL_BACKEND_ENDPOINT: string }).PORTAL_BACKEND_ENDPOINT = original;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("propagates fetch errors", async () => {
|
||||||
|
mockFetchWithTimeout.mockRejectedValue(new Error("Network error"));
|
||||||
|
|
||||||
|
await expect(reportHealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL")).rejects.toThrow(
|
||||||
|
"Network error",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("propagates timeout errors", async () => {
|
||||||
|
const abortError = new DOMException("The operation was aborted", "AbortError");
|
||||||
|
mockFetchWithTimeout.mockRejectedValue(abortError);
|
||||||
|
|
||||||
|
await expect(reportUnhealthy(MetricScenario.ApplicationLoad, Platform.Portal, "SQL")).rejects.toThrow(
|
||||||
|
"The operation was aborted",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
28
src/Metrics/MetricEvents.ts
Normal file
28
src/Metrics/MetricEvents.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Metrics module: scenario metric emission logic.
|
||||||
|
import { MetricEvent, MetricScenario } from "Metrics/Constants";
|
||||||
|
import { createUri } from "../Common/UrlUtility";
|
||||||
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
import { ApiType } from "../UserContext";
|
||||||
|
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
|
import { fetchWithTimeout } from "../Utils/FetchWithTimeout";
|
||||||
|
|
||||||
|
const RELATIVE_PATH = "/api/dataexplorer/metrics/health"; // Endpoint retains 'health' for backend compatibility.
|
||||||
|
|
||||||
|
export const reportHealthy = (scenario: MetricScenario, platform: Platform, api: ApiType): Promise<Response> =>
|
||||||
|
send({ platform, api, scenario, healthy: true });
|
||||||
|
|
||||||
|
export const reportUnhealthy = (scenario: MetricScenario, platform: Platform, api: ApiType): Promise<Response> =>
|
||||||
|
send({ platform, api, scenario, healthy: false });
|
||||||
|
|
||||||
|
const send = async (event: MetricEvent): Promise<Response> => {
|
||||||
|
const url = createUri(configContext?.PORTAL_BACKEND_ENDPOINT, RELATIVE_PATH);
|
||||||
|
const authHeader = getAuthorizationHeader();
|
||||||
|
|
||||||
|
return await fetchWithTimeout(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json", [authHeader.header]: authHeader.token },
|
||||||
|
body: JSON.stringify(event),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricScenario;
|
||||||
17
src/Metrics/MetricScenarioConfigs.ts
Normal file
17
src/Metrics/MetricScenarioConfigs.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import MetricScenario from "./MetricEvents";
|
||||||
|
import { ApplicationMetricPhase, CommonMetricPhase, ScenarioConfig } from "./ScenarioConfig";
|
||||||
|
|
||||||
|
export const scenarioConfigs: Record<MetricScenario, ScenarioConfig> = {
|
||||||
|
[MetricScenario.ApplicationLoad]: {
|
||||||
|
requiredPhases: [ApplicationMetricPhase.ExplorerInitialized, CommonMetricPhase.Interactive],
|
||||||
|
timeoutMs: 10000,
|
||||||
|
},
|
||||||
|
[MetricScenario.DatabaseLoad]: {
|
||||||
|
requiredPhases: [
|
||||||
|
ApplicationMetricPhase.DatabasesFetched,
|
||||||
|
ApplicationMetricPhase.DatabaseTreeRendered,
|
||||||
|
CommonMetricPhase.Interactive,
|
||||||
|
],
|
||||||
|
timeoutMs: 10000,
|
||||||
|
},
|
||||||
|
};
|
||||||
29
src/Metrics/MetricScenarioProvider.tsx
Normal file
29
src/Metrics/MetricScenarioProvider.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React, { useContext } from "react";
|
||||||
|
import MetricScenario from "./MetricEvents";
|
||||||
|
import { MetricPhase } from "./ScenarioConfig";
|
||||||
|
import { scenarioMonitor } from "./ScenarioMonitor";
|
||||||
|
|
||||||
|
interface MetricScenarioContextValue {
|
||||||
|
startScenario: (scenario: MetricScenario) => void;
|
||||||
|
startPhase: (scenario: MetricScenario, phase: MetricPhase) => void;
|
||||||
|
completePhase: (scenario: MetricScenario, phase: MetricPhase) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MetricScenarioContext = React.createContext<MetricScenarioContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
export const MetricScenarioProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const value: MetricScenarioContextValue = {
|
||||||
|
startScenario: (s: MetricScenario) => scenarioMonitor.start(s),
|
||||||
|
startPhase: (s: MetricScenario, p: MetricPhase) => scenarioMonitor.startPhase(s, p),
|
||||||
|
completePhase: (s: MetricScenario, p: MetricPhase) => scenarioMonitor.completePhase(s, p),
|
||||||
|
};
|
||||||
|
return <MetricScenarioContext.Provider value={value}>{children}</MetricScenarioContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useMetricScenario(): MetricScenarioContextValue {
|
||||||
|
const ctx = useContext(MetricScenarioContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("useMetricScenario must be used within MetricScenarioProvider");
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
47
src/Metrics/ScenarioConfig.ts
Normal file
47
src/Metrics/ScenarioConfig.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import MetricScenario from "./MetricEvents";
|
||||||
|
|
||||||
|
// Common phases shared across all scenarios
|
||||||
|
export enum CommonMetricPhase {
|
||||||
|
Interactive = "Interactive",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application-specific phases
|
||||||
|
export enum ApplicationMetricPhase {
|
||||||
|
ExplorerInitialized = "ExplorerInitialized",
|
||||||
|
DatabasesFetched = "DatabasesFetched",
|
||||||
|
DatabaseTreeRendered = "DatabaseTreeRendered",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combined type for all metric phases
|
||||||
|
export type MetricPhase = CommonMetricPhase | ApplicationMetricPhase;
|
||||||
|
|
||||||
|
export interface WebVitals {
|
||||||
|
lcp?: number; // Largest Contentful Paint
|
||||||
|
inp?: number; // Interaction to Next Paint
|
||||||
|
cls?: number; // Cumulative Layout Shift
|
||||||
|
fcp?: number; // First Contentful Paint
|
||||||
|
ttfb?: number; // Time to First Byte
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScenarioConfig<TPhase extends string = MetricPhase> {
|
||||||
|
requiredPhases: TPhase[];
|
||||||
|
timeoutMs: number;
|
||||||
|
validate?: (ctx: ScenarioContextSnapshot<TPhase>) => boolean; // Optional custom validation
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PhaseTimings {
|
||||||
|
endTimeISO: string; // When the phase completed
|
||||||
|
durationMs: number; // Duration from scenario start to phase completion
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScenarioContextSnapshot<TPhase extends string = MetricPhase> {
|
||||||
|
scenario: MetricScenario;
|
||||||
|
startTimeISO: string; // Human-readable ISO timestamp
|
||||||
|
endTimeISO: string; // Human-readable end timestamp
|
||||||
|
durationMs: number; // Total scenario duration from start to end
|
||||||
|
completed: TPhase[]; // Array for JSON serialization
|
||||||
|
failedPhases?: TPhase[]; // Phases that failed
|
||||||
|
timedOut: boolean;
|
||||||
|
vitals?: WebVitals;
|
||||||
|
phaseTimings?: Record<string, PhaseTimings>; // Start/end times for each phase
|
||||||
|
}
|
||||||
267
src/Metrics/ScenarioMonitor.ts
Normal file
267
src/Metrics/ScenarioMonitor.ts
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
import { Metric, onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
|
import { trackEvent } from "../Shared/appInsights";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import MetricScenario, { reportHealthy, reportUnhealthy } from "./MetricEvents";
|
||||||
|
import { scenarioConfigs } from "./MetricScenarioConfigs";
|
||||||
|
import { MetricPhase, PhaseTimings, ScenarioConfig, ScenarioContextSnapshot, WebVitals } from "./ScenarioConfig";
|
||||||
|
|
||||||
|
interface PhaseContext {
|
||||||
|
startMarkName: string; // Performance mark name for phase start
|
||||||
|
endMarkName?: string; // Performance mark name for phase end
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InternalScenarioContext {
|
||||||
|
scenario: MetricScenario;
|
||||||
|
config: ScenarioConfig;
|
||||||
|
startMarkName: string;
|
||||||
|
completed: Set<MetricPhase>;
|
||||||
|
failed: Set<MetricPhase>;
|
||||||
|
phases: Map<MetricPhase, PhaseContext>; // Track start/end for each phase
|
||||||
|
timeoutId?: number;
|
||||||
|
emitted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScenarioMonitor {
|
||||||
|
private contexts = new Map<MetricScenario, InternalScenarioContext>();
|
||||||
|
private vitals: WebVitals = {};
|
||||||
|
private vitalsInitialized = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initializeVitals();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeVitals() {
|
||||||
|
if (this.vitalsInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.vitalsInitialized = true;
|
||||||
|
|
||||||
|
onLCP((metric: Metric) => {
|
||||||
|
this.vitals.lcp = metric.value;
|
||||||
|
});
|
||||||
|
onINP((metric: Metric) => {
|
||||||
|
this.vitals.inp = metric.value;
|
||||||
|
});
|
||||||
|
onCLS((metric: Metric) => {
|
||||||
|
this.vitals.cls = metric.value;
|
||||||
|
});
|
||||||
|
onFCP((metric: Metric) => {
|
||||||
|
this.vitals.fcp = metric.value;
|
||||||
|
});
|
||||||
|
onTTFB((metric: Metric) => {
|
||||||
|
this.vitals.ttfb = metric.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start(scenario: MetricScenario) {
|
||||||
|
if (this.contexts.has(scenario)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const config = scenarioConfigs[scenario];
|
||||||
|
if (!config) {
|
||||||
|
throw new Error(`Missing scenario config for ${scenario}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startMarkName = `scenario_${scenario}_start`;
|
||||||
|
performance.mark(startMarkName);
|
||||||
|
|
||||||
|
const ctx: InternalScenarioContext = {
|
||||||
|
scenario,
|
||||||
|
config,
|
||||||
|
startMarkName,
|
||||||
|
completed: new Set<MetricPhase>(),
|
||||||
|
failed: new Set<MetricPhase>(),
|
||||||
|
phases: new Map<MetricPhase, PhaseContext>(),
|
||||||
|
emitted: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start all required phases at scenario start time
|
||||||
|
config.requiredPhases.forEach((phase) => {
|
||||||
|
const phaseStartMarkName = `scenario_${scenario}_${phase}_start`;
|
||||||
|
performance.mark(phaseStartMarkName);
|
||||||
|
ctx.phases.set(phase, { startMarkName: phaseStartMarkName });
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.timeoutId = window.setTimeout(() => this.emit(ctx, false, true), config.timeoutMs);
|
||||||
|
this.contexts.set(scenario, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
startPhase(scenario: MetricScenario, phase: MetricPhase) {
|
||||||
|
const ctx = this.contexts.get(scenario);
|
||||||
|
if (!ctx || ctx.emitted || !ctx.config.requiredPhases.includes(phase) || ctx.phases.has(phase)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startMarkName = `scenario_${scenario}_${phase}_start`;
|
||||||
|
performance.mark(startMarkName);
|
||||||
|
ctx.phases.set(phase, { startMarkName });
|
||||||
|
}
|
||||||
|
|
||||||
|
completePhase(scenario: MetricScenario, phase: MetricPhase) {
|
||||||
|
const ctx = this.contexts.get(scenario);
|
||||||
|
const phaseCtx = ctx?.phases.get(phase);
|
||||||
|
if (!ctx || ctx.emitted || !ctx.config.requiredPhases.includes(phase) || !phaseCtx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endMarkName = `scenario_${scenario}_${phase}_end`;
|
||||||
|
performance.mark(endMarkName);
|
||||||
|
phaseCtx.endMarkName = endMarkName;
|
||||||
|
ctx.completed.add(phase);
|
||||||
|
|
||||||
|
this.tryEmitIfReady(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
failPhase(scenario: MetricScenario, phase: MetricPhase) {
|
||||||
|
const ctx = this.contexts.get(scenario);
|
||||||
|
if (!ctx || ctx.emitted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the explicitly failed phase
|
||||||
|
performance.mark(`scenario_${scenario}_${phase}_failed`);
|
||||||
|
ctx.failed.add(phase);
|
||||||
|
|
||||||
|
// Mark all remaining incomplete required phases as failed
|
||||||
|
ctx.config.requiredPhases.forEach((requiredPhase) => {
|
||||||
|
if (!ctx.completed.has(requiredPhase) && !ctx.failed.has(requiredPhase)) {
|
||||||
|
ctx.failed.add(requiredPhase);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build a snapshot with failure info
|
||||||
|
const failureSnapshot = this.buildSnapshot(ctx, { final: false, timedOut: false });
|
||||||
|
|
||||||
|
// Emit unhealthy immediately
|
||||||
|
this.emit(ctx, false, false, failureSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryEmitIfReady(ctx: InternalScenarioContext) {
|
||||||
|
const allDone = ctx.config.requiredPhases.every((p) => ctx.completed.has(p));
|
||||||
|
if (!allDone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const finalSnapshot = this.buildSnapshot(ctx, { final: true, timedOut: false });
|
||||||
|
const healthy = ctx.config.validate ? ctx.config.validate(finalSnapshot) : true;
|
||||||
|
this.emit(ctx, healthy, false, finalSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPhaseTimings(ctx: InternalScenarioContext): Record<string, PhaseTimings> {
|
||||||
|
const result: Record<string, PhaseTimings> = {};
|
||||||
|
const navigationStart = performance.timeOrigin;
|
||||||
|
|
||||||
|
ctx.phases.forEach((phaseCtx, phase) => {
|
||||||
|
// Only include completed phases (those with endMarkName)
|
||||||
|
if (phaseCtx.endMarkName) {
|
||||||
|
const endEntry = performance.getEntriesByName(phaseCtx.endMarkName)[0];
|
||||||
|
if (endEntry) {
|
||||||
|
const endTimeISO = new Date(navigationStart + endEntry.startTime).toISOString();
|
||||||
|
|
||||||
|
// Use Performance API measure to calculate duration
|
||||||
|
const measureName = `scenario_${ctx.scenario}_${phase}_duration`;
|
||||||
|
performance.measure(measureName, phaseCtx.startMarkName, phaseCtx.endMarkName);
|
||||||
|
const measure = performance.getEntriesByName(measureName)[0];
|
||||||
|
if (measure) {
|
||||||
|
result[phase] = {
|
||||||
|
endTimeISO,
|
||||||
|
durationMs: measure.duration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private emit(ctx: InternalScenarioContext, healthy: boolean, timedOut: boolean, snapshot?: ScenarioContextSnapshot) {
|
||||||
|
if (ctx.emitted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.emitted = true;
|
||||||
|
if (ctx.timeoutId) {
|
||||||
|
clearTimeout(ctx.timeoutId);
|
||||||
|
ctx.timeoutId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = configContext.platform;
|
||||||
|
const api = userContext.apiType;
|
||||||
|
|
||||||
|
// Build snapshot if not provided
|
||||||
|
const finalSnapshot = snapshot || this.buildSnapshot(ctx, { final: false, timedOut });
|
||||||
|
|
||||||
|
// Emit enriched telemetry with performance data
|
||||||
|
// TODO: Call portal backend metrics endpoint
|
||||||
|
trackEvent(
|
||||||
|
{ name: "MetricScenarioComplete" },
|
||||||
|
{
|
||||||
|
scenario: ctx.scenario,
|
||||||
|
healthy: healthy.toString(),
|
||||||
|
timedOut: timedOut.toString(),
|
||||||
|
platform,
|
||||||
|
api,
|
||||||
|
durationMs: finalSnapshot.durationMs.toString(),
|
||||||
|
completedPhases: finalSnapshot.completed.join(","),
|
||||||
|
failedPhases: finalSnapshot.failedPhases?.join(","),
|
||||||
|
lcp: finalSnapshot.vitals?.lcp?.toString(),
|
||||||
|
inp: finalSnapshot.vitals?.inp?.toString(),
|
||||||
|
cls: finalSnapshot.vitals?.cls?.toString(),
|
||||||
|
fcp: finalSnapshot.vitals?.fcp?.toString(),
|
||||||
|
ttfb: finalSnapshot.vitals?.ttfb?.toString(),
|
||||||
|
phaseTimings: JSON.stringify(finalSnapshot.phaseTimings),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call portal backend health metrics endpoint
|
||||||
|
if (healthy && !timedOut) {
|
||||||
|
reportHealthy(ctx.scenario, platform, api);
|
||||||
|
} else {
|
||||||
|
reportUnhealthy(ctx.scenario, platform, api);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup performance entries
|
||||||
|
this.cleanupPerformanceEntries(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanupPerformanceEntries(ctx: InternalScenarioContext) {
|
||||||
|
performance.clearMarks(ctx.startMarkName);
|
||||||
|
ctx.config.requiredPhases.forEach((phase) => {
|
||||||
|
performance.clearMarks(`scenario_${ctx.scenario}_${phase}`);
|
||||||
|
});
|
||||||
|
performance.clearMeasures(`scenario_${ctx.scenario}_total`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildSnapshot(
|
||||||
|
ctx: InternalScenarioContext,
|
||||||
|
opts: { final: boolean; timedOut: boolean },
|
||||||
|
): ScenarioContextSnapshot {
|
||||||
|
const phaseTimings = this.getPhaseTimings(ctx);
|
||||||
|
|
||||||
|
// Capture current time once for consistency
|
||||||
|
const currentTime = performance.now();
|
||||||
|
|
||||||
|
// Convert performance timestamps (relative to navigationStart) to absolute timestamps
|
||||||
|
const navigationStart = performance.timeOrigin;
|
||||||
|
const startEntry = performance.getEntriesByName(ctx.startMarkName)[0];
|
||||||
|
const startTimeISO = new Date(navigationStart + (startEntry?.startTime || 0)).toISOString();
|
||||||
|
const endTimeISO = new Date(navigationStart + currentTime).toISOString();
|
||||||
|
|
||||||
|
// Calculate overall scenario duration directly from the timestamps
|
||||||
|
const durationMs = currentTime - (startEntry?.startTime || 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
scenario: ctx.scenario,
|
||||||
|
startTimeISO,
|
||||||
|
endTimeISO,
|
||||||
|
durationMs,
|
||||||
|
completed: Array.from(ctx.completed),
|
||||||
|
failedPhases: ctx.failed.size > 0 ? Array.from(ctx.failed) : undefined,
|
||||||
|
timedOut: opts.timedOut,
|
||||||
|
vitals: { ...this.vitals },
|
||||||
|
phaseTimings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const scenarioMonitor = new ScenarioMonitor();
|
||||||
40
src/Metrics/useMetricPhases.ts
Normal file
40
src/Metrics/useMetricPhases.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import React from "react";
|
||||||
|
import MetricScenario from "./MetricEvents";
|
||||||
|
import { useMetricScenario } from "./MetricScenarioProvider";
|
||||||
|
import { ApplicationMetricPhase, CommonMetricPhase } from "./ScenarioConfig";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to automatically complete the Interactive phase when the component becomes interactive.
|
||||||
|
* Uses requestAnimationFrame to complete after the browser has painted.
|
||||||
|
*/
|
||||||
|
export function useInteractive(scenario: MetricScenario) {
|
||||||
|
const { completePhase } = useMetricScenario();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
completePhase(scenario, CommonMetricPhase.Interactive);
|
||||||
|
});
|
||||||
|
}, [scenario, completePhase]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to manage DatabaseLoad scenario phase completions.
|
||||||
|
* Tracks tree rendering and completes Interactive phase.
|
||||||
|
* Only completes DatabaseTreeRendered if the database fetch was successful.
|
||||||
|
* Note: Scenario must be started before databases are fetched (in refreshExplorer).
|
||||||
|
*/
|
||||||
|
export function useDatabaseLoadScenario(databaseTreeNodes: unknown[], fetchSucceeded: boolean) {
|
||||||
|
const { completePhase } = useMetricScenario();
|
||||||
|
const hasCompletedTreeRenderRef = React.useRef(false);
|
||||||
|
|
||||||
|
// Track DatabaseTreeRendered phase (only if fetch succeeded)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!hasCompletedTreeRenderRef.current && fetchSucceeded) {
|
||||||
|
hasCompletedTreeRenderRef.current = true;
|
||||||
|
completePhase(MetricScenario.DatabaseLoad, ApplicationMetricPhase.DatabaseTreeRendered);
|
||||||
|
}
|
||||||
|
}, [databaseTreeNodes, fetchSucceeded, completePhase]);
|
||||||
|
|
||||||
|
// Track Interactive phase
|
||||||
|
useInteractive(MetricScenario.DatabaseLoad);
|
||||||
|
}
|
||||||
@@ -114,7 +114,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
|||||||
<div id="connectWithAad">
|
<div id="connectWithAad">
|
||||||
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
||||||
{enableConnectionStringLogin && (
|
{enableConnectionStringLogin && (
|
||||||
<p className="switchConnectTypeText" data-test="Link:SwitchConnectionType" onClick={showForm}>
|
<p className="switchConnectTypeText" data-testid="Link:SwitchConnectionType" onClick={showForm}>
|
||||||
Connect to your account with connection string
|
Connect to your account with connection string
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div style={{ overflowX: "auto" }} data-test="DataExplorerRoot">
|
<div style={{ overflowX: "auto" }} data-testid="DataExplorerRoot">
|
||||||
<Stack tokens={containerStackTokens}>
|
<Stack tokens={containerStackTokens}>
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
||||||
<div
|
<div
|
||||||
data-test="DataExplorerRoot"
|
data-testid="DataExplorerRoot"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"overflowX": "auto",
|
"overflowX": "auto",
|
||||||
@@ -339,7 +339,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 1`] = `
|
|||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
||||||
<div
|
<div
|
||||||
data-test="DataExplorerRoot"
|
data-testid="DataExplorerRoot"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"overflowX": "auto",
|
"overflowX": "auto",
|
||||||
@@ -734,7 +734,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 2`] = `
|
|||||||
|
|
||||||
exports[`SelfServeComponent message bar and spinner snapshots 3`] = `
|
exports[`SelfServeComponent message bar and spinner snapshots 3`] = `
|
||||||
<div
|
<div
|
||||||
data-test="DataExplorerRoot"
|
data-testid="DataExplorerRoot"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"overflowX": "auto",
|
"overflowX": "auto",
|
||||||
@@ -835,7 +835,7 @@ exports[`SelfServeComponent message bar and spinner snapshots 4`] = `
|
|||||||
|
|
||||||
exports[`SelfServeComponent should render and honor save, discard, refresh actions 1`] = `
|
exports[`SelfServeComponent should render and honor save, discard, refresh actions 1`] = `
|
||||||
<div
|
<div
|
||||||
data-test="DataExplorerRoot"
|
data-testid="DataExplorerRoot"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"overflowX": "auto",
|
"overflowX": "auto",
|
||||||
|
|||||||
37
src/Utils/FetchWithTimeout.ts
Normal file
37
src/Utils/FetchWithTimeout.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Perform a fetch with an AbortController-based timeout. Returns the Response or throws (including AbortError on timeout).
|
||||||
|
*
|
||||||
|
* Usage: await fetchWithTimeout(url, { method: 'GET', headers: {...} }, 10000);
|
||||||
|
*
|
||||||
|
* A shared helper to remove duplicated inline implementations across the codebase.
|
||||||
|
*/
|
||||||
|
export async function fetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
init: RequestInit = {},
|
||||||
|
timeoutMs: number = 5000,
|
||||||
|
): Promise<Response> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const id = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { ...init, signal: controller.signal });
|
||||||
|
return response;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience wrapper that returns null instead of throwing on timeout / network error.
|
||||||
|
* Useful for feature probing scenarios where failure should be treated as absence.
|
||||||
|
*/
|
||||||
|
export async function tryFetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
init: RequestInit = {},
|
||||||
|
timeoutMs: number = 5000,
|
||||||
|
): Promise<Response | null> {
|
||||||
|
try {
|
||||||
|
return await fetchWithTimeout(url, init, timeoutMs);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,3 +50,39 @@ require("jquery-ui-dist/jquery-ui");
|
|||||||
|
|
||||||
// The test environment Data Explorer uses does not have crypto.subtle implementation
|
// The test environment Data Explorer uses does not have crypto.subtle implementation
|
||||||
(<any>global).crypto.subtle = {};
|
(<any>global).crypto.subtle = {};
|
||||||
|
|
||||||
|
// Mock Performance API for scenario monitoring
|
||||||
|
const performanceMock = {
|
||||||
|
...(typeof performance !== "undefined" ? performance : {}),
|
||||||
|
mark: jest.fn(),
|
||||||
|
measure: jest.fn(),
|
||||||
|
clearMarks: jest.fn(),
|
||||||
|
clearMeasures: jest.fn(),
|
||||||
|
getEntriesByName: jest.fn().mockReturnValue([]),
|
||||||
|
getEntriesByType: jest.fn().mockReturnValue([]),
|
||||||
|
now: jest.fn(() => Date.now()),
|
||||||
|
timeOrigin: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign to both global and window
|
||||||
|
Object.defineProperty(global, "performance", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: performanceMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(window, "performance", {
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: performanceMock,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock fetch API - minimal mock to prevent errors
|
||||||
|
(<any>global).fetch = jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: () => Promise.resolve({}),
|
||||||
|
text: () => Promise.resolve(""),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,50 +1,114 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||||
|
import {
|
||||||
|
deleteContainer,
|
||||||
|
deleteKeyspace,
|
||||||
|
openAndFillCreateCassandraTablePanel,
|
||||||
|
} from "../helpers/containerCreationHelpers";
|
||||||
|
|
||||||
test("Cassandra keyspace and table CRUD", async ({ page }) => {
|
test("Cassandra: Keyspace and table CRUD", async ({ page }) => {
|
||||||
const keyspaceId = generateUniqueName("db");
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
const tableId = "testtable"; // A unique table name isn't needed because the keyspace is unique
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||||
|
|
||||||
await explorer.globalCommandButton("New Table").click();
|
// Create
|
||||||
await explorer.whilePanelOpen(
|
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||||
"Add Table",
|
keyspaceId,
|
||||||
async (panel, okButton) => {
|
tableId,
|
||||||
await panel.getByPlaceholder("Type a new keyspace id").fill(keyspaceId);
|
isAutoscale: true,
|
||||||
await panel.getByPlaceholder("Enter table Id").fill(tableId);
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
});
|
||||||
await okButton.click();
|
|
||||||
},
|
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||||
);
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Delete table
|
||||||
|
await deleteContainer(explorer, keyspaceId, tableId, "Delete Table");
|
||||||
|
await expect(tableNode.element).not.toBeAttached();
|
||||||
|
|
||||||
|
// Delete keyspace
|
||||||
|
await deleteKeyspace(explorer, keyspaceId);
|
||||||
|
await expect(keyspaceNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Cassandra: New keyspace shared throughput", async ({ page }) => {
|
||||||
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||||
|
|
||||||
|
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||||
|
keyspaceId,
|
||||||
|
tableId,
|
||||||
|
useSharedThroughput: true,
|
||||||
|
});
|
||||||
|
|
||||||
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||||
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||||
|
|
||||||
await tableNode.openContextMenu();
|
await expect(tableNode.element).toBeAttached();
|
||||||
await tableNode.contextMenuItem("Delete Table").click();
|
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Table",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
await expect(tableNode.element).not.toBeAttached();
|
|
||||||
|
|
||||||
await keyspaceNode.openContextMenu();
|
|
||||||
await keyspaceNode.contextMenuItem("Delete Keyspace").click();
|
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Keyspace",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the Keyspace id" }).fill(keyspaceId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteKeyspace(explorer, keyspaceId);
|
||||||
|
await expect(keyspaceNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Cassandra: Manual throughput", async ({ page }) => {
|
||||||
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
|
const tableId = generateUniqueName("table");
|
||||||
|
const manualThroughput = 400;
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||||
|
|
||||||
|
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||||
|
keyspaceId,
|
||||||
|
tableId,
|
||||||
|
isAutoscale: false,
|
||||||
|
throughputValue: manualThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||||
|
const tableNode = await explorer.waitForContainerNode(keyspaceId, tableId);
|
||||||
|
|
||||||
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteKeyspace(explorer, keyspaceId);
|
||||||
|
await expect(keyspaceNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Cassandra: Multiple tables in keyspace", async ({ page }) => {
|
||||||
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
|
const table1Id = generateUniqueName("table");
|
||||||
|
const table2Id = generateUniqueName("table");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Cassandra);
|
||||||
|
|
||||||
|
// Create first table
|
||||||
|
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||||
|
keyspaceId,
|
||||||
|
tableId: table1Id,
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||||
|
await explorer.waitForContainerNode(keyspaceId, table1Id);
|
||||||
|
|
||||||
|
// Create second table in same keyspace
|
||||||
|
await openAndFillCreateCassandraTablePanel(explorer, {
|
||||||
|
keyspaceId,
|
||||||
|
tableId: table2Id,
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
await explorer.waitForContainerNode(keyspaceId, table2Id);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteKeyspace(explorer, keyspaceId);
|
||||||
await expect(keyspaceNode.element).not.toBeAttached();
|
await expect(keyspaceNode.element).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ 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.
|
||||||
*/
|
*/
|
||||||
globalCommandButton(label: string): Locator {
|
globalCommandButton(label: string): Locator {
|
||||||
return this.frame.getByTestId("GlobalCommands").getByText(label);
|
return this.frame.getByTestId(`GlobalCommands/Button:${label}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Select the command bar button with the specified label */
|
/** Select the command bar button with the specified label */
|
||||||
|
|||||||
@@ -1,22 +1,108 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||||
|
import {
|
||||||
|
GREMLIN_CONFIG,
|
||||||
|
deleteContainer,
|
||||||
|
deleteDatabase,
|
||||||
|
openAndFillCreateContainerPanel,
|
||||||
|
} from "../helpers/containerCreationHelpers";
|
||||||
|
|
||||||
test("Gremlin graph CRUD", async ({ page }) => {
|
test("Gremlin: Database and graph CRUD", async ({ page }) => {
|
||||||
const databaseId = generateUniqueName("db");
|
const databaseId = generateUniqueName("db");
|
||||||
const graphId = "testgraph"; // A unique graph name isn't needed because the database is unique
|
const graphId = generateUniqueName("graph");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||||
|
|
||||||
|
// Create
|
||||||
|
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: graphId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||||
|
await expect(graphNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Delete graph
|
||||||
|
await deleteContainer(explorer, databaseId, graphId, "Delete Graph");
|
||||||
|
await expect(graphNode.element).not.toBeAttached();
|
||||||
|
|
||||||
|
// Delete database
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Gremlin: New database shared throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const graphId = generateUniqueName("graph");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: graphId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
useSharedThroughput: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||||
|
|
||||||
|
await expect(graphNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Gremlin: Manual throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const graphId = generateUniqueName("graph");
|
||||||
|
const manualThroughput = 400;
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, GREMLIN_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: graphId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
isAutoscale: false,
|
||||||
|
throughputValue: manualThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||||
|
|
||||||
|
await expect(graphNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Gremlin: No unique keys support", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const graphId = generateUniqueName("graph");
|
||||||
|
|
||||||
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
const explorer = await DataExplorer.open(page, TestAccount.Gremlin);
|
||||||
|
|
||||||
// Create new database and graph
|
|
||||||
await explorer.globalCommandButton("New Graph").click();
|
await explorer.globalCommandButton("New Graph").click();
|
||||||
await explorer.whilePanelOpen(
|
await explorer.whilePanelOpen(
|
||||||
"New Graph",
|
"New Graph",
|
||||||
async (panel, okButton) => {
|
async (panel, okButton) => {
|
||||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
|
||||||
await panel.getByRole("textbox", { name: "Graph id, Example Graph1" }).fill(graphId);
|
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(graphId);
|
||||||
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
||||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
||||||
|
|
||||||
|
// Verify unique key button is not present (Gremlin-specific API limitation)
|
||||||
|
const uniqueKeyButton = panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton");
|
||||||
|
await expect(uniqueKeyButton).not.toBeVisible();
|
||||||
|
|
||||||
await okButton.click();
|
await okButton.click();
|
||||||
},
|
},
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
@@ -24,29 +110,9 @@ test("Gremlin graph CRUD", async ({ page }) => {
|
|||||||
|
|
||||||
const databaseNode = await explorer.waitForNode(databaseId);
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
const graphNode = await explorer.waitForContainerNode(databaseId, graphId);
|
||||||
|
await expect(graphNode.element).toBeAttached();
|
||||||
|
|
||||||
await graphNode.openContextMenu();
|
// Cleanup
|
||||||
await graphNode.contextMenuItem("Delete Graph").click();
|
await deleteDatabase(explorer, databaseId);
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Graph",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the graph id" }).fill(graphId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
await expect(graphNode.element).not.toBeAttached();
|
|
||||||
|
|
||||||
await databaseNode.openContextMenu();
|
|
||||||
await databaseNode.contextMenuItem("Delete Database").click();
|
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Database",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the Database id" }).fill(databaseId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(databaseNode.element).not.toBeAttached();
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|||||||
323
test/helpers/containerCreationHelpers.ts
Normal file
323
test/helpers/containerCreationHelpers.ts
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
import { Locator } from "@playwright/test";
|
||||||
|
import { DataExplorer, TestAccount } from "../fx";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container creation test API configuration
|
||||||
|
* Defines labels and selectors specific to each Cosmos DB API
|
||||||
|
*/
|
||||||
|
export interface ApiConfig {
|
||||||
|
account: TestAccount;
|
||||||
|
commandLabel: string; // "New Container", "New Collection", "New Graph", "New Table"
|
||||||
|
containerIdLabel: string; // "Container id", "Collection id", "Graph id", "Table id"
|
||||||
|
panelTitle: string; // "New Container", "New Collection", "New Graph", "Add Table"
|
||||||
|
databaseIdPlaceholder: string; // "Type a new keyspace id" for Cassandra, etc.
|
||||||
|
containerIdPlaceholder: string;
|
||||||
|
partitionKeyLabel?: string; // "Partition key", "Shard key", or undefined for Tables
|
||||||
|
partitionKeyPlaceholder?: string;
|
||||||
|
confirmDeleteLabel: string; // "Confirm by typing the [container/collection/table/graph] id"
|
||||||
|
databaseName?: string; // "TablesDB" for Tables, undefined for others
|
||||||
|
supportsUniqueKeys: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SQL_CONFIG: ApiConfig = {
|
||||||
|
account: TestAccount.SQL,
|
||||||
|
commandLabel: "New Container",
|
||||||
|
containerIdLabel: "Container id, Example Container1",
|
||||||
|
panelTitle: "New Container",
|
||||||
|
databaseIdPlaceholder: "Type a new database id",
|
||||||
|
containerIdPlaceholder: "e.g., Container1",
|
||||||
|
partitionKeyLabel: "Partition key",
|
||||||
|
partitionKeyPlaceholder: "/pk",
|
||||||
|
confirmDeleteLabel: "Confirm by typing the container id",
|
||||||
|
supportsUniqueKeys: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MONGO_CONFIG: ApiConfig = {
|
||||||
|
account: TestAccount.Mongo,
|
||||||
|
commandLabel: "New Collection",
|
||||||
|
containerIdLabel: "Collection id, Example Collection1",
|
||||||
|
panelTitle: "New Collection",
|
||||||
|
databaseIdPlaceholder: "Type a new database id",
|
||||||
|
containerIdPlaceholder: "e.g., Collection1",
|
||||||
|
partitionKeyLabel: "Shard key",
|
||||||
|
partitionKeyPlaceholder: "pk",
|
||||||
|
confirmDeleteLabel: "Confirm by typing the collection id",
|
||||||
|
supportsUniqueKeys: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MONGO32_CONFIG: ApiConfig = {
|
||||||
|
...MONGO_CONFIG,
|
||||||
|
account: TestAccount.Mongo32,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GREMLIN_CONFIG: ApiConfig = {
|
||||||
|
account: TestAccount.Gremlin,
|
||||||
|
commandLabel: "New Graph",
|
||||||
|
containerIdLabel: "Graph id, Example Graph1",
|
||||||
|
panelTitle: "New Graph",
|
||||||
|
databaseIdPlaceholder: "Type a new database id",
|
||||||
|
containerIdPlaceholder: "e.g., Graph1",
|
||||||
|
partitionKeyLabel: "Partition key",
|
||||||
|
partitionKeyPlaceholder: "/pk",
|
||||||
|
confirmDeleteLabel: "Confirm by typing the graph id",
|
||||||
|
supportsUniqueKeys: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TABLES_CONFIG: ApiConfig = {
|
||||||
|
account: TestAccount.Tables,
|
||||||
|
commandLabel: "New Table",
|
||||||
|
containerIdLabel: "Table id, Example Table1",
|
||||||
|
panelTitle: "New Table",
|
||||||
|
databaseIdPlaceholder: "", // Not used
|
||||||
|
containerIdPlaceholder: "e.g., Table1",
|
||||||
|
confirmDeleteLabel: "Confirm by typing the table id",
|
||||||
|
databaseName: "TablesDB",
|
||||||
|
supportsUniqueKeys: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CASSANDRA_CONFIG: ApiConfig = {
|
||||||
|
account: TestAccount.Cassandra,
|
||||||
|
commandLabel: "New Table",
|
||||||
|
containerIdLabel: "Enter table Id",
|
||||||
|
panelTitle: "Add Table",
|
||||||
|
databaseIdPlaceholder: "Type a new keyspace id",
|
||||||
|
containerIdPlaceholder: "Enter table Id",
|
||||||
|
confirmDeleteLabel: "Confirm by typing the table id",
|
||||||
|
supportsUniqueKeys: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills database selection in the panel
|
||||||
|
* Automatically selects "Create new" and fills the database ID
|
||||||
|
*/
|
||||||
|
export async function fillDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
|
||||||
|
// Wait for the radio button to be visible and click it (more reliable than check for custom styled radios)
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").waitFor({ state: "visible" });
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:CreateNew").click();
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(databaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills existing database selection
|
||||||
|
* Selects "Use existing" and clicks the dropdown to select the database
|
||||||
|
*/
|
||||||
|
export async function fillExistingDatabaseSelection(panel: Locator, databaseId: string): Promise<void> {
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").waitFor({ state: "visible" });
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseRadio:UseExisting").click();
|
||||||
|
await panel.getByTestId("AddCollectionPanel/ExistingDatabaseDropdown").click();
|
||||||
|
await panel.locator(`text=${databaseId}`).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills container/collection/graph/table details
|
||||||
|
*/
|
||||||
|
export async function fillContainerDetails(
|
||||||
|
panel: Locator,
|
||||||
|
containerId: string,
|
||||||
|
partitionKey: string | undefined,
|
||||||
|
): Promise<void> {
|
||||||
|
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(containerId);
|
||||||
|
|
||||||
|
if (partitionKey) {
|
||||||
|
await panel.getByTestId("AddCollectionPanel/PartitionKey").first().fill(partitionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills Cassandra-specific table details
|
||||||
|
* (keyspace and table IDs are separate for Cassandra)
|
||||||
|
*/
|
||||||
|
export async function fillCassandraTableDetails(panel: Locator, keyspaceId: string, tableId: string): Promise<void> {
|
||||||
|
await panel.getByTestId("AddCollectionPanel/DatabaseId").fill(keyspaceId);
|
||||||
|
await panel.getByTestId("AddCollectionPanel/CollectionId").fill(tableId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets throughput mode and value
|
||||||
|
* @param isAutoscale - if true, sets autoscale mode; if false, sets manual mode
|
||||||
|
*/
|
||||||
|
export async function setThroughput(panel: Locator, isAutoscale: boolean, throughputValue: number): Promise<void> {
|
||||||
|
const testId = isAutoscale ? "ThroughputInput/ThroughputMode:Autoscale" : "ThroughputInput/ThroughputMode:Manual";
|
||||||
|
await panel.getByTestId(testId).check();
|
||||||
|
|
||||||
|
if (isAutoscale) {
|
||||||
|
await panel.getByTestId("ThroughputInput/AutoscaleRUInput").fill(throughputValue.toString());
|
||||||
|
} else {
|
||||||
|
await panel.getByTestId("ThroughputInput/ManualThroughputInput").fill(throughputValue.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a unique key to the container (SQL/Mongo only)
|
||||||
|
*/
|
||||||
|
export async function addUniqueKey(panel: Locator, uniqueKeyValue: string): Promise<void> {
|
||||||
|
// Scroll to find the unique key section
|
||||||
|
await panel.getByTestId("AddCollectionPanel/UniqueKeysSection").scrollIntoViewIfNeeded();
|
||||||
|
|
||||||
|
// Click the "Add unique key" button
|
||||||
|
await panel.getByTestId("AddCollectionPanel/AddUniqueKeyButton").click();
|
||||||
|
|
||||||
|
// Fill in the unique key value
|
||||||
|
const uniqueKeyInput = panel.getByTestId("AddCollectionPanel/UniqueKey").first();
|
||||||
|
await uniqueKeyInput.fill(uniqueKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a database and waits for it to disappear from the tree
|
||||||
|
*/
|
||||||
|
export async function deleteDatabase(
|
||||||
|
explorer: DataExplorer,
|
||||||
|
databaseId: string,
|
||||||
|
databaseNodeName: string = databaseId,
|
||||||
|
): Promise<void> {
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseNodeName);
|
||||||
|
await databaseNode.openContextMenu();
|
||||||
|
await databaseNode.contextMenuItem("Delete Database").click();
|
||||||
|
await explorer.whilePanelOpen(
|
||||||
|
"Delete Database",
|
||||||
|
async (panel: Locator, okButton: Locator) => {
|
||||||
|
await panel.getByTestId("DeleteDatabaseConfirmationPanel/ConfirmInput").fill(databaseId);
|
||||||
|
await okButton.click();
|
||||||
|
},
|
||||||
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a keyspace (Cassandra only)
|
||||||
|
*/
|
||||||
|
export async function deleteKeyspace(explorer: DataExplorer, keyspaceId: string): Promise<void> {
|
||||||
|
const keyspaceNode = await explorer.waitForNode(keyspaceId);
|
||||||
|
await keyspaceNode.openContextMenu();
|
||||||
|
await keyspaceNode.contextMenuItem("Delete Keyspace").click();
|
||||||
|
await explorer.whilePanelOpen(
|
||||||
|
"Delete Keyspace",
|
||||||
|
async (panel: Locator, okButton: Locator) => {
|
||||||
|
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(keyspaceId);
|
||||||
|
await okButton.click();
|
||||||
|
},
|
||||||
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a container/collection/graph/table
|
||||||
|
*/
|
||||||
|
export async function deleteContainer(
|
||||||
|
explorer: DataExplorer,
|
||||||
|
databaseId: string,
|
||||||
|
containerId: string,
|
||||||
|
deleteLabel: string, // "Delete Container", "Delete Collection", etc.
|
||||||
|
): Promise<void> {
|
||||||
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||||
|
await containerNode.openContextMenu();
|
||||||
|
await containerNode.contextMenuItem(deleteLabel).click();
|
||||||
|
await explorer.whilePanelOpen(
|
||||||
|
deleteLabel,
|
||||||
|
async (panel: Locator, okButton: Locator) => {
|
||||||
|
// All container/collection/graph/table deletes use same panel with test ID
|
||||||
|
await panel.getByTestId("DeleteCollectionConfirmationPane/ConfirmInput").fill(containerId);
|
||||||
|
await okButton.click();
|
||||||
|
},
|
||||||
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the create container dialog and fills in the form based on scenario
|
||||||
|
*/
|
||||||
|
export async function openAndFillCreateContainerPanel(
|
||||||
|
explorer: DataExplorer,
|
||||||
|
config: ApiConfig,
|
||||||
|
options: {
|
||||||
|
databaseId: string;
|
||||||
|
containerId: string;
|
||||||
|
partitionKey?: string;
|
||||||
|
useExistingDatabase?: boolean;
|
||||||
|
isAutoscale?: boolean;
|
||||||
|
throughputValue?: number;
|
||||||
|
uniqueKey?: string;
|
||||||
|
useSharedThroughput?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
await explorer.globalCommandButton(config.commandLabel).click();
|
||||||
|
await explorer.whilePanelOpen(
|
||||||
|
config.panelTitle,
|
||||||
|
async (panel, okButton) => {
|
||||||
|
// Database selection
|
||||||
|
if (options.useExistingDatabase) {
|
||||||
|
await fillExistingDatabaseSelection(panel, options.databaseId);
|
||||||
|
} else {
|
||||||
|
await fillDatabaseSelection(panel, options.databaseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared throughput checkbox (if applicable)
|
||||||
|
if (options.useSharedThroughput) {
|
||||||
|
await panel
|
||||||
|
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
|
||||||
|
.getByRole("checkbox")
|
||||||
|
.check({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container details
|
||||||
|
await fillContainerDetails(panel, options.containerId, options.partitionKey);
|
||||||
|
|
||||||
|
// Throughput (only if not using shared throughput)
|
||||||
|
if (!options.useSharedThroughput) {
|
||||||
|
const isAutoscale = options.isAutoscale !== false;
|
||||||
|
const throughputValue = options.throughputValue || 1000;
|
||||||
|
await setThroughput(panel, isAutoscale, throughputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique keys (if applicable)
|
||||||
|
if (options.uniqueKey && config.supportsUniqueKeys) {
|
||||||
|
await addUniqueKey(panel, options.uniqueKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
await okButton.click();
|
||||||
|
},
|
||||||
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the create table dialog for Cassandra and fills in the form
|
||||||
|
* Cassandra has a different UI pattern than other APIs
|
||||||
|
*/
|
||||||
|
export async function openAndFillCreateCassandraTablePanel(
|
||||||
|
explorer: DataExplorer,
|
||||||
|
options: {
|
||||||
|
keyspaceId: string;
|
||||||
|
tableId: string;
|
||||||
|
isAutoscale?: boolean;
|
||||||
|
throughputValue?: number;
|
||||||
|
useSharedThroughput?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
await explorer.globalCommandButton("New Table").click();
|
||||||
|
await explorer.whilePanelOpen(
|
||||||
|
"Add Table",
|
||||||
|
async (panel, okButton) => {
|
||||||
|
// Fill Cassandra-specific table details
|
||||||
|
await fillCassandraTableDetails(panel, options.keyspaceId, options.tableId);
|
||||||
|
|
||||||
|
// Shared throughput checkbox (if applicable)
|
||||||
|
if (options.useSharedThroughput) {
|
||||||
|
await panel
|
||||||
|
.getByTestId("AddCollectionPanel/SharedThroughputCheckbox")
|
||||||
|
.getByRole("checkbox")
|
||||||
|
.check({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throughput (only if not using shared throughput)
|
||||||
|
if (!options.useSharedThroughput) {
|
||||||
|
const isAutoscale = options.isAutoscale !== false;
|
||||||
|
const throughputValue = options.throughputValue || 1000;
|
||||||
|
await setThroughput(panel, isAutoscale, throughputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
await okButton.click();
|
||||||
|
},
|
||||||
|
{ closeTimeout: 5 * 60 * 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,58 +1,118 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||||
|
import {
|
||||||
|
MONGO32_CONFIG,
|
||||||
|
MONGO_CONFIG,
|
||||||
|
deleteContainer,
|
||||||
|
deleteDatabase,
|
||||||
|
openAndFillCreateContainerPanel,
|
||||||
|
} from "../helpers/containerCreationHelpers";
|
||||||
|
|
||||||
(
|
(
|
||||||
[
|
[
|
||||||
["latest API version", TestAccount.Mongo],
|
["latest API version", MONGO_CONFIG],
|
||||||
["3.2 API", TestAccount.Mongo32],
|
["3.2 API", MONGO32_CONFIG],
|
||||||
] as [string, TestAccount][]
|
] as [string, typeof MONGO_CONFIG][]
|
||||||
).forEach(([apiVersionDescription, accountType]) => {
|
).forEach(([apiVersionDescription, config]) => {
|
||||||
test(`Mongo CRUD using ${apiVersionDescription}`, async ({ page }) => {
|
test(`Mongo: Database and collection CRUD using ${apiVersionDescription}`, async ({ page }) => {
|
||||||
const databaseId = generateUniqueName("db");
|
const databaseId = generateUniqueName("db");
|
||||||
const collectionId = "testcollection"; // A unique collection name isn't needed because the database is unique
|
const collectionId = generateUniqueName("collection");
|
||||||
|
|
||||||
const explorer = await DataExplorer.open(page, accountType);
|
const explorer = await DataExplorer.open(page, config.account);
|
||||||
|
|
||||||
await explorer.globalCommandButton("New Collection").click();
|
// Create
|
||||||
await explorer.whilePanelOpen(
|
await openAndFillCreateContainerPanel(explorer, config, {
|
||||||
"New Collection",
|
databaseId,
|
||||||
async (panel, okButton) => {
|
containerId: collectionId,
|
||||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
partitionKey: "pk",
|
||||||
await panel.getByRole("textbox", { name: "Collection id, Example Collection1" }).fill(collectionId);
|
isAutoscale: true,
|
||||||
await panel.getByRole("textbox", { name: "Shard key" }).fill("pk");
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
});
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
const databaseNode = await explorer.waitForNode(databaseId);
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||||
|
await expect(collectionNode.element).toBeAttached();
|
||||||
|
|
||||||
await collectionNode.openContextMenu();
|
// Delete collection
|
||||||
await collectionNode.contextMenuItem("Delete Collection").click();
|
await deleteContainer(explorer, databaseId, collectionId, "Delete Collection");
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Collection",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the collection id" }).fill(collectionId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
await expect(collectionNode.element).not.toBeAttached();
|
await expect(collectionNode.element).not.toBeAttached();
|
||||||
|
|
||||||
await databaseNode.openContextMenu();
|
// Delete database
|
||||||
await databaseNode.contextMenuItem("Delete Database").click();
|
await deleteDatabase(explorer, databaseId);
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Database",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the Database id" }).fill(databaseId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(databaseNode.element).not.toBeAttached();
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Mongo: New database shared throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const collectionId = generateUniqueName("collection");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: collectionId,
|
||||||
|
partitionKey: "pk",
|
||||||
|
useSharedThroughput: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||||
|
|
||||||
|
await expect(collectionNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Mongo: Unique keys", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const collectionId = generateUniqueName("collection");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: collectionId,
|
||||||
|
partitionKey: "pk",
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
uniqueKey: "email",
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||||
|
|
||||||
|
await expect(collectionNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Mongo: Manual throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const collectionId = generateUniqueName("collection");
|
||||||
|
const manualThroughput = 400;
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Mongo);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, MONGO_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId: collectionId,
|
||||||
|
partitionKey: "pk",
|
||||||
|
isAutoscale: false,
|
||||||
|
throughputValue: manualThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const collectionNode = await explorer.waitForContainerNode(databaseId, collectionId);
|
||||||
|
|
||||||
|
await expect(collectionNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,51 +1,110 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||||
|
import {
|
||||||
|
SQL_CONFIG,
|
||||||
|
deleteContainer,
|
||||||
|
deleteDatabase,
|
||||||
|
openAndFillCreateContainerPanel,
|
||||||
|
} from "../helpers/containerCreationHelpers";
|
||||||
|
|
||||||
test("SQL database and container CRUD", async ({ page }) => {
|
test("SQL: Database and container CRUD", async ({ page }) => {
|
||||||
const databaseId = generateUniqueName("db");
|
const databaseId = generateUniqueName("db");
|
||||||
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||||
|
|
||||||
await explorer.globalCommandButton("New Container").click();
|
// Create
|
||||||
await explorer.whilePanelOpen(
|
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||||
"New Container",
|
databaseId,
|
||||||
async (panel, okButton) => {
|
containerId,
|
||||||
await panel.getByPlaceholder("Type a new database id").fill(databaseId);
|
partitionKey: "/pk",
|
||||||
await panel.getByRole("textbox", { name: "Container id, Example Container1" }).fill(containerId);
|
isAutoscale: true,
|
||||||
await panel.getByRole("textbox", { name: "Partition key" }).fill("/pk");
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
});
|
||||||
await okButton.click();
|
|
||||||
},
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||||
);
|
await expect(containerNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Delete container
|
||||||
|
await deleteContainer(explorer, databaseId, containerId, "Delete Container");
|
||||||
|
await expect(containerNode.element).not.toBeAttached();
|
||||||
|
|
||||||
|
// Delete database
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SQL: New database shared throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
useSharedThroughput: true,
|
||||||
|
});
|
||||||
|
|
||||||
const databaseNode = await explorer.waitForNode(databaseId);
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||||
|
|
||||||
await containerNode.openContextMenu();
|
await expect(containerNode.element).toBeAttached();
|
||||||
await containerNode.contextMenuItem("Delete Container").click();
|
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Container",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the container id" }).fill(containerId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
await expect(containerNode.element).not.toBeAttached();
|
|
||||||
|
|
||||||
await databaseNode.openContextMenu();
|
|
||||||
await databaseNode.contextMenuItem("Delete Database").click();
|
|
||||||
await explorer.whilePanelOpen(
|
|
||||||
"Delete Database",
|
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the database id" }).fill(databaseId);
|
|
||||||
await okButton.click();
|
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SQL: Unique keys", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
uniqueKey: "/email,/username",
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||||
|
|
||||||
|
await expect(containerNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SQL: Manual throughput", async ({ page }) => {
|
||||||
|
const databaseId = generateUniqueName("db");
|
||||||
|
const containerId = generateUniqueName("container");
|
||||||
|
const manualThroughput = 400;
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.SQL);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, SQL_CONFIG, {
|
||||||
|
databaseId,
|
||||||
|
containerId,
|
||||||
|
partitionKey: "/pk",
|
||||||
|
isAutoscale: false,
|
||||||
|
throughputValue: manualThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const databaseNode = await explorer.waitForNode(databaseId);
|
||||||
|
const containerNode = await explorer.waitForContainerNode(databaseId, containerId);
|
||||||
|
|
||||||
|
await expect(containerNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteDatabase(explorer, databaseId);
|
||||||
await expect(databaseNode.element).not.toBeAttached();
|
await expect(databaseNode.element).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,35 +1,116 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
import { DataExplorer, TEST_AUTOSCALE_THROUGHPUT_RU, TestAccount, generateUniqueName } from "../fx";
|
||||||
|
import { TABLES_CONFIG, deleteContainer, openAndFillCreateContainerPanel } from "../helpers/containerCreationHelpers";
|
||||||
|
|
||||||
test("Tables CRUD", async ({ page }) => {
|
test("Tables: CRUD", async ({ page }) => {
|
||||||
const tableId = generateUniqueName("table"); // A unique table name IS needed because the database is shared when using Table Storage.
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||||
|
|
||||||
await explorer.globalCommandButton("New Table").click();
|
// Create
|
||||||
await explorer.whilePanelOpen(
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
"New Table",
|
databaseId: "TablesDB",
|
||||||
async (panel, okButton) => {
|
containerId: tableId,
|
||||||
await panel.getByRole("textbox", { name: "Table id, Example Table1" }).fill(tableId);
|
isAutoscale: true,
|
||||||
await panel.getByTestId("autoscaleRUInput").fill(TEST_AUTOSCALE_THROUGHPUT_RU.toString());
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
await okButton.click();
|
});
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||||
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
await tableNode.openContextMenu();
|
// Delete table
|
||||||
await tableNode.contextMenuItem("Delete Table").click();
|
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||||
await explorer.whilePanelOpen(
|
await expect(tableNode.element).not.toBeAttached();
|
||||||
"Delete Table",
|
});
|
||||||
async (panel, okButton) => {
|
|
||||||
await panel.getByRole("textbox", { name: "Confirm by typing the table id" }).fill(tableId);
|
test("Tables: New database shared throughput", async ({ page }) => {
|
||||||
await okButton.click();
|
const tableId = generateUniqueName("table");
|
||||||
},
|
|
||||||
{ closeTimeout: 5 * 60 * 1000 },
|
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||||
);
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
|
databaseId: "TablesDB",
|
||||||
|
containerId: tableId,
|
||||||
|
useSharedThroughput: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||||
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||||
|
await expect(tableNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Tables: Manual throughput", async ({ page }) => {
|
||||||
|
const tableId = generateUniqueName("table");
|
||||||
|
const manualThroughput = 400;
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
|
databaseId: "TablesDB",
|
||||||
|
containerId: tableId,
|
||||||
|
isAutoscale: false,
|
||||||
|
throughputValue: manualThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||||
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||||
|
await expect(tableNode.element).not.toBeAttached();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Tables: Multiple tables in TablesDB", async ({ page }) => {
|
||||||
|
const table1Id = generateUniqueName("table");
|
||||||
|
const table2Id = generateUniqueName("table");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||||
|
|
||||||
|
// Create first table
|
||||||
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
|
databaseId: "TablesDB",
|
||||||
|
containerId: table1Id,
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
await explorer.waitForContainerNode("TablesDB", table1Id);
|
||||||
|
|
||||||
|
// Create second table
|
||||||
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
|
databaseId: "TablesDB",
|
||||||
|
containerId: table2Id,
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
await explorer.waitForContainerNode("TablesDB", table2Id);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteContainer(explorer, "TablesDB", table1Id, "Delete Table");
|
||||||
|
await deleteContainer(explorer, "TablesDB", table2Id, "Delete Table");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Tables: No partition key support", async ({ page }) => {
|
||||||
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
|
const explorer = await DataExplorer.open(page, TestAccount.Tables);
|
||||||
|
|
||||||
|
await openAndFillCreateContainerPanel(explorer, TABLES_CONFIG, {
|
||||||
|
databaseId: "TablesDB",
|
||||||
|
containerId: tableId,
|
||||||
|
isAutoscale: true,
|
||||||
|
throughputValue: TEST_AUTOSCALE_THROUGHPUT_RU,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableNode = await explorer.waitForContainerNode("TablesDB", tableId);
|
||||||
|
await expect(tableNode.element).toBeAttached();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await deleteContainer(explorer, "TablesDB", tableId, "Delete Table");
|
||||||
await expect(tableNode.element).not.toBeAttached();
|
await expect(tableNode.element).not.toBeAttached();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ const initTestExplorer = async (): Promise<void> => {
|
|||||||
);
|
);
|
||||||
iframe.id = "explorerMenu";
|
iframe.id = "explorerMenu";
|
||||||
iframe.name = "explorer";
|
iframe.name = "explorer";
|
||||||
iframe.setAttribute("data-test", "DataExplorerFrame");
|
iframe.setAttribute("data-testid", "DataExplorerFrame");
|
||||||
iframe.classList.add("iframe");
|
iframe.classList.add("iframe");
|
||||||
iframe.title = "explorer";
|
iframe.title = "explorer";
|
||||||
iframe.src = iframeSrc; // CodeQL [SM03712] Not used in production, only for testing purposes
|
iframe.src = iframeSrc; // CodeQL [SM03712] Not used in production, only for testing purposes
|
||||||
|
|||||||
Reference in New Issue
Block a user