Fabric native improvements: Settings pane, Partition Key settings tab, sample data and message contract (#2119)

* Hide entire Accordion of options in Settings Pane

* In PartitionKeyComponent hide "Change partition key" label when read-only.

* Create sample data container with correct pkey

* Add unit tests to PartitionKeyComponent

* Fix format

* fix unit test snapshot

* Add Fabric message to open Settings to given tab id

* Improve syntax on message contract

* Remove "(preview)" in partition key tab title in Settings Tab
This commit is contained in:
Laurent Nguyen 2025-04-29 17:50:20 +02:00 committed by GitHub
parent 274c85d2de
commit 2f858ecf9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 677 additions and 421 deletions

View File

@ -18,10 +18,13 @@ export type DataExploreMessageV3 =
| { | {
type: FabricMessageTypes.GetAllResourceTokens; type: FabricMessageTypes.GetAllResourceTokens;
id: string; id: string;
}
| {
type: FabricMessageTypes.OpenSettings;
settingsId: string;
}; };
export interface GetCosmosTokenMessageOptions {
export type GetCosmosTokenMessageOptions = {
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges"; resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
resourceId: string; resourceId: string;
}; }

View File

@ -6,6 +6,7 @@ export enum FabricMessageTypes {
GetAllResourceTokens = "GetAllResourceTokens", GetAllResourceTokens = "GetAllResourceTokens",
GetAccessToken = "GetAccessToken", GetAccessToken = "GetAccessToken",
Ready = "Ready", Ready = "Ready",
OpenSettings = "OpenSettings",
} }
export interface AuthorizationToken { export interface AuthorizationToken {

View File

@ -0,0 +1,41 @@
import { shallow } from "enzyme";
import {
PartitionKeyComponent,
PartitionKeyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import Explorer from "Explorer/Explorer";
import React from "react";
describe("PartitionKeyComponent", () => {
// Create a test setup function to get fresh instances for each test
const setupTest = () => {
// Create an instance of the mocked Explorer
const explorer = new Explorer();
// Create minimal mock objects for database and collection
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockDatabase = {} as any as import("../../../../Contracts/ViewModels").Database;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mockCollection = {} as any as import("../../../../Contracts/ViewModels").Collection;
// Create props with the mocked Explorer instance
const props: PartitionKeyComponentProps = {
database: mockDatabase,
collection: mockCollection,
explorer,
};
return { explorer, props };
};
it("renders default component and matches snapshot", () => {
const { props } = setupTest();
const wrapper = shallow(<PartitionKeyComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders read-only component and matches snapshot", () => {
const { props } = setupTest();
const wrapper = shallow(<PartitionKeyComponent {...props} isReadOnly={true} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -161,7 +161,7 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
return ( return (
<Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}> <Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}>
<Stack tokens={{ childrenGap: 10 }}> <Stack tokens={{ childrenGap: 10 }}>
<Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text> {!isReadOnly && <Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text>}
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20 }}>
<Stack tokens={{ childrenGap: 5 }}> <Stack tokens={{ childrenGap: 5 }}>
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text> <Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>

View File

@ -0,0 +1,196 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PartitionKeyComponent renders default component and matches snapshot 1`] = `
<Stack
styles={
{
"root": {
"maxWidth": 600,
},
}
}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Text
styles={
{
"root": {
"fontSize": 16,
"fontWeight": 600,
},
}
}
>
Change
partition key
</Text>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
>
Current
partition key
</Text>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
>
Partitioning
</Text>
</Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text />
<Text>
Non-hierarchical
</Text>
</Stack>
</Stack>
</Stack>
<StyledMessageBar
messageBarType={5}
>
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the source container for the entire duration of the partition key change process.
<StyledLinkBase
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
target="_blank"
underline={true}
>
Learn more
</StyledLinkBase>
</StyledMessageBar>
<Text>
To change the partition key, a new destination container must be created or an existing destination container selected. Data will then be copied to the destination container.
</Text>
<CustomizedPrimaryButton
onClick={[Function]}
styles={
{
"root": {
"width": "fit-content",
},
}
}
text="Change"
/>
</Stack>
`;
exports[`PartitionKeyComponent renders read-only component and matches snapshot 1`] = `
<Stack
styles={
{
"root": {
"maxWidth": 600,
},
}
}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 10,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 20,
}
}
>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
>
Current
partition key
</Text>
<Text
styles={
{
"root": {
"fontWeight": 600,
},
}
}
>
Partitioning
</Text>
</Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text />
<Text>
Non-hierarchical
</Text>
</Stack>
</Stack>
</Stack>
</Stack>
`;

View File

@ -1,6 +1,7 @@
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
const zeroValue = 0; const zeroValue = 0;
@ -165,7 +166,7 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
case SettingsV2TabTypes.IndexingPolicyTab: case SettingsV2TabTypes.IndexingPolicyTab:
return "Indexing Policy"; return "Indexing Policy";
case SettingsV2TabTypes.PartitionKeyTab: case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys (preview)"; return isFabricNative() ? "Partition Keys" : "Partition Keys (preview)";
case SettingsV2TabTypes.ComputedPropertiesTab: case SettingsV2TabTypes.ComputedPropertiesTab:
return "Computed Properties"; return "Computed Properties";
case SettingsV2TabTypes.ContainerVectorPolicyTab: case SettingsV2TabTypes.ContainerVectorPolicyTab:

View File

@ -23,7 +23,7 @@ import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { useDialog } from "Explorer/Controls/Dialog"; import { useDialog } from "Explorer/Controls/Dialog";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabric } from "Platform/Fabric/FabricUtil"; import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
import { import {
AppStateComponentNames, AppStateComponentNames,
deleteAllStates, deleteAllStates,
@ -607,441 +607,447 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
return ( return (
<RightPaneForm {...genericPaneProps}> <RightPaneForm {...genericPaneProps}>
<div className={`paneMainContent ${styles.container}`}> <div className={`paneMainContent ${styles.container}`}>
<Accordion className={`customAccordion ${styles.firstItem}`}> {!isFabricNative() && (
{shouldShowQueryPageOptions && ( <Accordion className={`customAccordion ${styles.firstItem}`}>
<AccordionItem value="1"> {shouldShowQueryPageOptions && (
<AccordionHeader> <AccordionItem value="1">
<div className={styles.header}>Page Options</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
many query results per page.
</div>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
</div>
<div className={`tabs ${styles.settingsSectionContainer}`}>
{isCustomPageOptionSelected() && (
<div className="tabcontent">
<div className={styles.settingsSectionDescription}>
Query results per page{" "}
<InfoTooltip className={styles.headerIcon}>
Enter the number of query results that should be shown per page.
</InfoTooltip>
</div>
<SpinButton
ariaLabel="Custom query items per page"
value={"" + customItemPerPage}
onIncrement={(newValue) => {
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
}}
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
min={1}
step={1}
className="textfontclr"
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
/>
</div>
)}
</div>
</AccordionPanel>
</AccordionItem>
)}
{showEnableEntraIdRbac && (
<AccordionItem value="2">
<AccordionHeader>
<div className={styles.header}>Enable Entra ID RBAC</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID
RBAC.
<a
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
target="_blank"
rel="noopener noreferrer"
>
{" "}
Learn more{" "}
</a>
</div>
<ChoiceGroup
ariaLabelledBy="enableDataPlaneRBACOptions"
options={dataPlaneRBACOptionsList}
styles={choiceButtonStyles}
selectedKey={enableDataPlaneRBACOption}
onChange={handleOnDataPlaneRBACOptionChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
<AccordionItem value="3">
<AccordionHeader>
<div className={styles.header}>Region Selection</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Changes region the Cosmos Client uses to access account.
</div>
<div>
<span className={styles.subHeader}>Select Region</span>
<InfoTooltip className={styles.headerIcon}>
Changes the account endpoint used to perform client operations.
</InfoTooltip>
</div>
<Dropdown
placeholder={
selectedRegionalEndpoint
? regionOptions.find((option) => option.key === selectedRegionalEndpoint)?.text
: regionOptions[0]?.text
}
onChange={handleOnSelectedRegionOptionChange}
options={regionOptions}
styles={{ root: { marginBottom: "10px" } }}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && !isEmulator && (
<>
<AccordionItem value="4">
<AccordionHeader> <AccordionHeader>
<div className={styles.header}>Query Timeout</div> <div className={styles.header}>Page Options</div>
</AccordionHeader> </AccordionHeader>
<AccordionPanel> <AccordionPanel>
<div className={styles.settingsSectionContainer}> <div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}> <div className={styles.settingsSectionDescription}>
When a query reaches a specified time limit, a popup with an option to cancel the query will show Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as
unless automatic cancellation has been enabled. many query results per page.
</div>
<Toggle
styles={toggleStyles}
label="Enable query timeout"
onChange={handleOnQueryTimeoutToggleChange}
defaultChecked={queryTimeoutEnabled}
/>
</div>
{queryTimeoutEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="Query timeout (ms)"
labelPosition={Position.top}
defaultValue={(queryTimeout || 5000).toString()}
min={100}
step={1000}
onChange={handleOnQueryTimeoutSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
<Toggle
label="Automatically cancel query after timeout"
styles={toggleStyles}
onChange={handleOnAutomaticallyCancelQueryToggleChange}
defaultChecked={automaticallyCancelQueryAfterTimeout}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="5">
<AccordionHeader>
<div className={styles.header}>RU Limit</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
If a query exceeds a configured RU limit, the query will be aborted.
</div>
<Toggle
styles={toggleStyles}
label="Enable RU limit"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
{ruThresholdEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="RU Limit (RU)"
labelPosition={Position.top}
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
min={1}
step={1000}
onChange={handleOnRUThresholdSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="6">
<AccordionHeader>
<div className={styles.header}>Default Query Results View</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Select the default view to use when displaying query results.
</div> </div>
<ChoiceGroup <ChoiceGroup
ariaLabelledBy="defaultQueryResultsView" ariaLabelledBy="pageOptions"
selectedKey={defaultQueryResultsView} selectedKey={pageOption}
options={defaultQueryResultsViewOptionList} options={pageOptionList}
styles={choiceButtonStyles} styles={choiceButtonStyles}
onChange={handleOnDefaultQueryResultsViewChange} onChange={handleOnPageOptionChange}
/>
</div>
<div className={`tabs ${styles.settingsSectionContainer}`}>
{isCustomPageOptionSelected() && (
<div className="tabcontent">
<div className={styles.settingsSectionDescription}>
Query results per page{" "}
<InfoTooltip className={styles.headerIcon}>
Enter the number of query results that should be shown per page.
</InfoTooltip>
</div>
<SpinButton
ariaLabel="Custom query items per page"
value={"" + customItemPerPage}
onIncrement={(newValue) => {
setCustomItemPerPage(parseInt(newValue) + 1 || customItemPerPage);
}}
onDecrement={(newValue) => setCustomItemPerPage(parseInt(newValue) - 1 || customItemPerPage)}
onValidate={(newValue) => setCustomItemPerPage(parseInt(newValue) || customItemPerPage)}
min={1}
step={1}
className="textfontclr"
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
/>
</div>
)}
</div>
</AccordionPanel>
</AccordionItem>
)}
{showEnableEntraIdRbac && (
<AccordionItem value="2">
<AccordionHeader>
<div className={styles.header}>Enable Entra ID RBAC</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
ID RBAC.
<a
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
target="_blank"
rel="noopener noreferrer"
>
{" "}
Learn more{" "}
</a>
</div>
<ChoiceGroup
ariaLabelledBy="enableDataPlaneRBACOptions"
options={dataPlaneRBACOptionsList}
styles={choiceButtonStyles}
selectedKey={enableDataPlaneRBACOption}
onChange={handleOnDataPlaneRBACOptionChange}
/> />
</div> </div>
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
</> )}
)} {userContext.apiType === "SQL" && userContext.authType === AuthType.AAD && !isFabric() && (
<AccordionItem value="3">
<AccordionHeader>
<div className={styles.header}>Region Selection</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Changes region the Cosmos Client uses to access account.
</div>
<div>
<span className={styles.subHeader}>Select Region</span>
<InfoTooltip className={styles.headerIcon}>
Changes the account endpoint used to perform client operations.
</InfoTooltip>
</div>
<Dropdown
placeholder={
selectedRegionalEndpoint
? regionOptions.find((option) => option.key === selectedRegionalEndpoint)?.text
: regionOptions[0]?.text
}
onChange={handleOnSelectedRegionOptionChange}
options={regionOptions}
styles={{ root: { marginBottom: "10px" } }}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && !isEmulator && (
<>
<AccordionItem value="4">
<AccordionHeader>
<div className={styles.header}>Query Timeout</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
When a query reaches a specified time limit, a popup with an option to cancel the query will
show unless automatic cancellation has been enabled.
</div>
<Toggle
styles={toggleStyles}
label="Enable query timeout"
onChange={handleOnQueryTimeoutToggleChange}
defaultChecked={queryTimeoutEnabled}
/>
</div>
{queryTimeoutEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="Query timeout (ms)"
labelPosition={Position.top}
defaultValue={(queryTimeout || 5000).toString()}
min={100}
step={1000}
onChange={handleOnQueryTimeoutSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
<Toggle
label="Automatically cancel query after timeout"
styles={toggleStyles}
onChange={handleOnAutomaticallyCancelQueryToggleChange}
defaultChecked={automaticallyCancelQueryAfterTimeout}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
{showRetrySettings && ( <AccordionItem value="5">
<AccordionItem value="7"> <AccordionHeader>
<AccordionHeader> <div className={styles.header}>RU Limit</div>
<div className={styles.header}>Retry Settings</div> </AccordionHeader>
</AccordionHeader> <AccordionPanel>
<AccordionPanel> <div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionContainer}> <div className={styles.settingsSectionDescription}>
<div className={styles.settingsSectionDescription}> If a query exceeds a configured RU limit, the query will be aborted.
Retry policy associated with throttled requests during CosmosDB queries. </div>
<Toggle
styles={toggleStyles}
label="Enable RU limit"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
{ruThresholdEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="RU Limit (RU)"
labelPosition={Position.top}
defaultValue={(ruThreshold || DefaultRUThreshold).toString()}
min={1}
step={1000}
onChange={handleOnRUThresholdSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
styles={spinButtonStyles}
/>
</div>
)}
</AccordionPanel>
</AccordionItem>
<AccordionItem value="6">
<AccordionHeader>
<div className={styles.header}>Default Query Results View</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Select the default view to use when displaying query results.
</div>
<ChoiceGroup
ariaLabelledBy="defaultQueryResultsView"
selectedKey={defaultQueryResultsView}
options={defaultQueryResultsViewOptionList}
styles={choiceButtonStyles}
onChange={handleOnDefaultQueryResultsViewChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
</>
)}
{showRetrySettings && (
<AccordionItem value="7">
<AccordionHeader>
<div className={styles.header}>Retry Settings</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Retry policy associated with throttled requests during CosmosDB queries.
</div>
<div>
<span className={styles.subHeader}>Max retry attempts</span>
<InfoTooltip className={styles.headerIcon}>
Max number of retries to be performed for a request. Default value 9.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + retryAttempts}
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
<InfoTooltip className={styles.headerIcon}>
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned
as part of the response. Default value is 0 milliseconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1000}
step={1000}
value={"" + retryInterval}
onChange={handleOnRetryIntervalSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Max wait time (s)</span>
<InfoTooltip className={styles.headerIcon}>
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
seconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + MaxWaitTimeInSeconds}
onChange={handleOnMaxWaitTimeSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) =>
setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)
}
onDecrement={(newValue) =>
setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)
}
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
styles={spinButtonStyles}
/>
</div> </div>
<div> </AccordionPanel>
<span className={styles.subHeader}>Max retry attempts</span> </AccordionItem>
<InfoTooltip className={styles.headerIcon}> )}
Max number of retries to be performed for a request. Default value 9. {!isEmulator && (
</InfoTooltip> <AccordionItem value="8">
<AccordionHeader>
<div className={styles.header}>Enable container pagination</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable container pagination"
checked={containerPaginationEnabled}
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
label="Enable container pagination"
/>
</div> </div>
<SpinButton </AccordionPanel>
labelPosition={Position.top} </AccordionItem>
min={1} )}
step={1} {shouldShowCrossPartitionOption && (
value={"" + retryAttempts} <AccordionItem value="9">
onChange={handleOnQueryRetryAttemptsSpinButtonChange} <AccordionHeader>
incrementButtonAriaLabel="Increase value by 1" <div className={styles.header}>Enable cross-partition query</div>
decrementButtonAriaLabel="Decrease value by 1" </AccordionHeader>
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)} <AccordionPanel>
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)} <div className={styles.settingsSectionContainer}>
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)} <div className={styles.settingsSectionDescription}>
styles={spinButtonStyles} Send more than one request while executing a query. More than one request is necessary if the
/> query is not scoped to single partition key value.
<div> </div>
<span className={styles.subHeader}>Fixed retry interval (ms)</span> <Checkbox
<InfoTooltip className={styles.headerIcon}> styles={{
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned label: { padding: 0 },
as part of the response. Default value is 0 milliseconds. }}
</InfoTooltip> className="padding"
ariaLabel="Enable cross partition query"
checked={crossPartitionQueryEnabled}
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
label="Enable cross-partition query"
/>
</div> </div>
<SpinButton </AccordionPanel>
labelPosition={Position.top} </AccordionItem>
min={1000} )}
step={1000} {shouldShowParallelismOption && (
value={"" + retryInterval} <AccordionItem value="10">
onChange={handleOnRetryIntervalSpinButtonChange} <AccordionHeader>
incrementButtonAriaLabel="Increase value by 1000" <div className={styles.header}>Max degree of parallelism</div>
decrementButtonAriaLabel="Decrease value by 1000" </AccordionHeader>
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)} <AccordionPanel>
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)} <div className={styles.settingsSectionContainer}>
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)} <div className={styles.settingsSectionDescription}>
styles={spinButtonStyles} Gets or sets the number of concurrent operations run client side during parallel query execution.
/> A positive property value limits the number of concurrent operations to the set value. If it is
<div> set to less than 0, the system automatically decides the number of concurrent operations to run.
<span className={styles.subHeader}>Max wait time (s)</span> </div>
<InfoTooltip className={styles.headerIcon}> <SpinButton
Max wait time in seconds to wait for a request while the retries are happening. Default value 30 min={-1}
seconds. step={1}
</InfoTooltip> className="textfontclr"
role="textbox"
id="max-degree"
value={"" + maxDegreeOfParallelism}
onIncrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)
}
onDecrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
}
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
ariaLabel="Max degree of parallelism"
label="Max degree of parallelism"
/>
</div> </div>
<SpinButton </AccordionPanel>
labelPosition={Position.top} </AccordionItem>
min={1} )}
step={1} {shouldShowPriorityLevelOption && (
value={"" + MaxWaitTimeInSeconds} <AccordionItem value="11">
onChange={handleOnMaxWaitTimeSpinButtonChange} <AccordionHeader>
incrementButtonAriaLabel="Increase value by 1" <div className={styles.header}>Priority Level</div>
decrementButtonAriaLabel="Decrease value by 1" </AccordionHeader>
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)} <AccordionPanel>
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)} <div className={styles.settingsSectionContainer}>
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)} <div className={styles.settingsSectionDescription}>
styles={spinButtonStyles} Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
/> Execution. If &quot;None&quot; is selected, Data Explorer will not specify priority level, and the
</div> server-side default priority level will be used.
</AccordionPanel> </div>
</AccordionItem> <ChoiceGroup
)} ariaLabelledBy="priorityLevel"
{!isEmulator && ( selectedKey={priorityLevel}
<AccordionItem value="8"> options={priorityLevelOptionList}
<AccordionHeader> styles={choiceButtonStyles}
<div className={styles.header}>Enable container pagination</div> onChange={handleOnPriorityLevelOptionChange}
</AccordionHeader> />
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</div> </div>
<Checkbox </AccordionPanel>
styles={{ </AccordionItem>
label: { padding: 0 }, )}
}} {shouldShowGraphAutoVizOption && (
className="padding" <AccordionItem value="12">
ariaLabel="Enable container pagination" <AccordionHeader>
checked={containerPaginationEnabled} <div className={styles.header}>Display Gremlin query results as:&nbsp;</div>
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)} </AccordionHeader>
label="Enable container pagination" <AccordionPanel>
/> <div className={styles.settingsSectionContainer}>
</div> <div className={styles.settingsSectionDescription}>
</AccordionPanel> Select Graph to automatically visualize the query results as a Graph or JSON to display the
</AccordionItem> results as JSON.
)} </div>
{shouldShowCrossPartitionOption && ( <ChoiceGroup
<AccordionItem value="9"> selectedKey={graphAutoVizDisabled}
<AccordionHeader> options={graphAutoOptionList}
<div className={styles.header}>Enable cross-partition query</div> onChange={handleOnGremlinChange}
</AccordionHeader> aria-label="Graph Auto-visualization"
<AccordionPanel> />
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Send more than one request while executing a query. More than one request is necessary if the query
is not scoped to single partition key value.
</div> </div>
<Checkbox </AccordionPanel>
styles={{ </AccordionItem>
label: { padding: 0 }, )}
}} {shouldShowCopilotSampleDBOption && (
className="padding" <AccordionItem value="13">
ariaLabel="Enable cross partition query" <AccordionHeader>
checked={crossPartitionQueryEnabled} <div className={styles.header}>Enable sample database</div>
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)} </AccordionHeader>
label="Enable cross-partition query" <AccordionPanel>
/> <div className={styles.settingsSectionContainer}>
</div> <div className={styles.settingsSectionDescription}>
</AccordionPanel> This is a sample database and collection with synthetic product data you can use to explore using
</AccordionItem> NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
)} is created by, and maintained by Microsoft at no cost to you.
{shouldShowParallelismOption && ( </div>
<AccordionItem value="10"> <Checkbox
<AccordionHeader> styles={{
<div className={styles.header}>Max degree of parallelism</div> label: { padding: 0 },
</AccordionHeader> }}
<AccordionPanel> className="padding"
<div className={styles.settingsSectionContainer}> ariaLabel="Enable sample db for Query Advisor"
<div className={styles.settingsSectionDescription}> checked={copilotSampleDBEnabled}
Gets or sets the number of concurrent operations run client side during parallel query execution. A onChange={handleSampleDatabaseChange}
positive property value limits the number of concurrent operations to the set value. If it is set to label="Enable sample database"
less than 0, the system automatically decides the number of concurrent operations to run. />
</div> </div>
<SpinButton </AccordionPanel>
min={-1} </AccordionItem>
step={1} )}
className="textfontclr" </Accordion>
role="textbox" )}
id="max-degree"
value={"" + maxDegreeOfParallelism}
onIncrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) + 1 || maxDegreeOfParallelism)
}
onDecrement={(newValue) =>
setMaxDegreeOfParallelism(parseInt(newValue) - 1 || maxDegreeOfParallelism)
}
onValidate={(newValue) => setMaxDegreeOfParallelism(parseInt(newValue) || maxDegreeOfParallelism)}
ariaLabel="Max degree of parallelism"
label="Max degree of parallelism"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowPriorityLevelOption && (
<AccordionItem value="11">
<AccordionHeader>
<div className={styles.header}>Priority Level</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Sets the priority level for data-plane requests from Data Explorer when using Priority-Based
Execution. If &quot;None&quot; is selected, Data Explorer will not specify priority level, and the
server-side default priority level will be used.
</div>
<ChoiceGroup
ariaLabelledBy="priorityLevel"
selectedKey={priorityLevel}
options={priorityLevelOptionList}
styles={choiceButtonStyles}
onChange={handleOnPriorityLevelOptionChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowGraphAutoVizOption && (
<AccordionItem value="12">
<AccordionHeader>
<div className={styles.header}>Display Gremlin query results as:&nbsp;</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Select Graph to automatically visualize the query results as a Graph or JSON to display the results
as JSON.
</div>
<ChoiceGroup
selectedKey={graphAutoVizDisabled}
options={graphAutoOptionList}
onChange={handleOnGremlinChange}
aria-label="Graph Auto-visualization"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowCopilotSampleDBOption && (
<AccordionItem value="13">
<AccordionHeader>
<div className={styles.header}>Enable sample database</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
This is a sample database and collection with synthetic product data you can use to explore using
NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and
is created by, and maintained by Microsoft at no cost to you.
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable sample db for Query Advisor"
checked={copilotSampleDBEnabled}
onChange={handleSampleDatabaseChange}
label="Enable sample database"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
</Accordion>
<div className="settingsSection"> <div className="settingsSection">
<div className="settingsSectionPart"> <div className="settingsSectionPart">

View File

@ -1,3 +1,4 @@
import { BackendDefaults } from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection"; import { createCollection } from "Common/dataAccess/createCollection";
import Explorer from "Explorer/Explorer"; import Explorer from "Explorer/Explorer";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
@ -35,6 +36,11 @@ export const createContainer = async (
collectionId: containerName, collectionId: containerName,
databaseId: databaseName, databaseId: databaseName,
databaseLevelThroughput: false, databaseLevelThroughput: false,
partitionKey: {
paths: [`/${SAMPLE_DATA_PARTITION_KEY}`],
kind: "Hash",
version: BackendDefaults.partitionKeyVersion,
},
}; };
await createCollection(createRequest); await createCollection(createRequest);
await explorer.refreshAllDatabases(); await explorer.refreshAllDatabases();
@ -47,6 +53,8 @@ export const createContainer = async (
return newCollection; return newCollection;
}; };
const SAMPLE_DATA_PARTITION_KEY = "category"; // This pkey is specifically set for queryCopilotSampleData.json below
export const importData = async (collection: ViewModels.Collection): Promise<void> => { export const importData = async (collection: ViewModels.Collection): Promise<void> => {
// TODO: keep same chunk as ContainerSampleGenerator // TODO: keep same chunk as ContainerSampleGenerator
const dataFileContent = await import( const dataFileContent = await import(