Compare commits

..

42 Commits

Author SHA1 Message Date
Laurent Nguyen
04c01256a6 Cleanup checkbox styles 2024-09-05 11:40:14 +02:00
Laurent Nguyen
1795b8e2e9 Merge branch 'master' into users/languy/save-documentstab-prefs 2024-09-05 09:41:58 +02:00
Laurent Nguyen
e23ba02561 Move column selection and sorting behind feature flag enableDocumentsTableColumnSelection 2024-09-02 12:31:39 +02:00
Laurent Nguyen
85352b74a3 Update unit test snapshot 2024-08-23 18:07:59 +02:00
Laurent Nguyen
26645f8360 Label to indicate which field is a partition key in Column Selection Pane 2024-08-23 17:59:16 +02:00
Laurent Nguyen
777b695051 Add background color to "..." button for column selection 2024-08-23 17:47:02 +02:00
Laurent Nguyen
1f300e32fe Fix table width 2024-08-23 17:32:00 +02:00
Laurent Nguyen
e81408560e Remove unused function 2024-08-23 17:26:22 +02:00
Laurent Nguyen
ed1e2990d0 Update test snapshots 2024-08-23 17:21:40 +02:00
Laurent Nguyen
5e92a0c5d7 Disable column selection for Mongo. Remove extra refresh button 2024-08-23 17:16:43 +02:00
Laurent Nguyen
26b6de4c53 Merge branch 'master' into users/languy/save-documentstab-prefs 2024-08-23 09:57:52 +02:00
Laurent Nguyen
f308cabeaa Revert "Merge branch 'master' into users/languy/save-documentstab-prefs"
This reverts commit e5a82fd356.
2024-08-23 09:53:45 +02:00
Laurent Nguyen
e5a82fd356 Merge branch 'master' into users/languy/save-documentstab-prefs 2024-08-23 09:47:19 +02:00
Laurent Nguyen
4778183e50 Save columns definition (schema) along with selected columns. 2024-08-23 09:23:38 +02:00
Laurent Nguyen
b1d9570a95 Persist column sorting 2024-08-23 08:21:40 +02:00
Laurent Nguyen
2397283649 Persist column selection 2024-08-22 16:19:23 +02:00
Laurent Nguyen
905aa26f27 Fix unit test 2024-08-22 14:30:35 +02:00
Laurent Nguyen
a2556dad06 Fix unit tests 2024-08-22 14:24:44 +02:00
Laurent Nguyen
c9398e303b Merge branch 'master' into users/languy/save-documentstab-prefs 2024-08-22 11:48:38 +02:00
Laurent Nguyen
9d4a9c0601 Add reset button to column selection and fix naming of openUploadItemsPanePane() 2024-08-21 16:39:21 +02:00
Laurent Nguyen
1e10273510 Fix format, update snapshots 2024-08-07 09:36:48 +02:00
Laurent Nguyen
c141e2612b Merge branch 'master' into users/languy/save-documentstab-prefs 2024-08-07 09:27:13 +02:00
Laurent Nguyen
7a179ff34a Fix format 2024-08-06 18:28:52 +02:00
Laurent Nguyen
4e71e340e3 Implement column sorting 2024-07-11 14:33:55 +02:00
Laurent Nguyen
9efbe7d056 Don't allow unselecting last column 2024-07-11 10:04:43 +02:00
Laurent Nguyen
ea2ab19518 Fix heuristic for size update 2024-07-11 10:04:21 +02:00
Laurent Nguyen
5d59c47979 Fix table size issue with some heuristics 2024-07-10 19:14:13 +02:00
Laurent Nguyen
fa460bfba2 Rework column selection UI 2024-07-10 17:20:24 +02:00
Laurent Nguyen
f1dcf1c548 Update choices of column when creating new or updating document 2024-06-25 10:15:41 +02:00
Laurent Nguyen
88f38d6522 Move table values under its own property 2024-06-24 13:38:32 +02:00
Laurent Nguyen
658e2ffe85 Do not allow deselecting all columns 2024-06-21 13:12:50 +02:00
Laurent Nguyen
bea3aa8b55 Accumulate properties rather than replace for column definitions 2024-06-21 12:05:50 +02:00
Laurent Nguyen
ce0cfed128 Only allow data fields that can be rendered (string and numbers) in column selection 2024-06-21 11:42:02 +02:00
Laurent Nguyen
c0a79c1e67 Search uses string includes instead of startsWith 2024-06-20 16:40:04 +02:00
Laurent Nguyen
9945304e18 Switch icons and search compare with lowercase. 2024-06-20 16:36:01 +02:00
Laurent Nguyen
0ce9acdfdf Implement new menu for column selection with search. 2024-06-20 16:23:36 +02:00
Laurent Nguyen
b096fa9bf8 Add column selection from right-click 2024-06-19 13:01:23 +02:00
Laurent Nguyen
55df5cb121 Merge branch 'master' into users/languy/save-documentstab-prefs 2024-06-13 09:14:11 +02:00
Laurent Nguyen
e36853c100 Save column width 2024-06-12 12:26:28 +02:00
Laurent Nguyen
996f785aac Merge branch 'master' into users/languy/save-documentstab-prefs 2024-06-11 17:02:04 +02:00
Laurent Nguyen
6c67f3b2e5 Make table columns generic (no more id and partition keys) 2024-06-11 16:57:17 +02:00
Laurent Nguyen
1ee79881ef Initial implementation of saving split value to local storage 2024-06-10 14:25:58 +02:00
9 changed files with 1047 additions and 1140 deletions

View File

@@ -723,8 +723,16 @@ export function useMongoProxyEndpoint(api: string): boolean {
MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake,
];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
);

View File

@@ -3,12 +3,11 @@ import * as React from "react";
export interface TooltipProps {
children: string;
className?: string;
}
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => {
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children }: TooltipProps) => {
return (
<span className={className}>
<span>
<TooltipHost content={children}>
<Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
</TooltipHost>

View File

@@ -53,8 +53,10 @@ export interface ConfigContext {
NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT?: string;
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string;
JUNO_ENDPOINT: string;
@@ -117,8 +119,10 @@ let configContext: Readonly<ConfigContext> = {
"legacyMongoShell",
"bulkdelete",
],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false,
isPhoenixEnabled: false,
};

View File

@@ -5,13 +5,15 @@ import {
IChoiceGroupOption,
ISpinButtonStyles,
IToggleStyles,
Icon,
MessageBar,
MessageBarType,
Position,
SpinButton,
Toggle,
TooltipHost,
} from "@fluentui/react";
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, makeStyles } from "@fluentui/react-components";
import { makeStyles } from "@fluentui/react-components";
import { AuthType } from "AuthType";
import * as Constants from "Common/Constants";
import { SplitterDirection } from "Common/Splitter";
@@ -57,32 +59,6 @@ const useStyles = makeStyles({
listStyleType: "disc",
paddingLeft: "20px",
},
container: {
display: "flex",
flexDirection: "column",
height: "100%",
},
firstItem: {
flex: "1",
},
header: {
marginRight: "5px",
},
headerIcon: {
paddingTop: "4px",
cursor: "pointer",
},
settingsSectionContainer: {
paddingLeft: "15px",
},
settingsSectionDescription: {
paddingBottom: "10px",
fontSize: "12px",
},
subHeader: {
marginRight: "5px",
fontSize: "12px",
},
});
export const useDataPlaneRbac: DataPlaneRbacStore = create(() => ({
@@ -468,66 +444,82 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
return (
<RightPaneForm {...genericPaneProps}>
<div className={`paneMainContent ${styles.container}`}>
<Accordion className={styles.firstItem}>
{shouldShowQueryPageOptions && (
<AccordionItem value="1">
<AccordionHeader>
<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 className="paneMainContent">
{shouldShowQueryPageOptions && (
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="pageOptions" className="settingsSectionLabel legendLabel">
Page Options
</legend>
<InfoTooltip>
Choose Custom to specify a fixed amount of query results to show, or choose Unlimited to show as many
query results per page.
</InfoTooltip>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
/>
</fieldset>
</div>
<div className="tabs settingsSectionPart">
{isCustomPageOptionSelected() && (
<div className="tabcontent">
<div className="settingsSectionLabel">
Query results per page
<InfoTooltip>Enter the number of query results that should be shown per page.</InfoTooltip>
</div>
<ChoiceGroup
ariaLabelledBy="pageOptions"
selectedKey={pageOption}
options={pageOptionList}
styles={choiceButtonStyles}
onChange={handleOnPageOptionChange}
<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 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>
)}
{userContext.apiType === "SQL" &&
userContext.authType === AuthType.AAD &&
configContext.platform !== Platform.Fabric && (
<AccordionItem value="2">
<AccordionHeader>
<div className={styles.header}>Enable Entra ID RBAC</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
)}
</div>
</div>
)}
{userContext.apiType === "SQL" &&
userContext.authType === AuthType.AAD &&
configContext.platform !== Platform.Fabric && (
<>
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="enableDataPlaneRBACOptions" className="settingsSectionLabel legendLabel">
Enable Entra ID RBAC
</legend>
<TooltipHost
content={
<>
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>
</>
}
>
<Icon iconName="Info" ariaLabel="Info tooltip" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
{showDataPlaneRBACWarning && configContext.platform === Platform.Portal && (
<MessageBar
messageBarType={MessageBarType.warning}
@@ -539,18 +531,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
operations
</MessageBar>
)}
<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}
@@ -558,339 +538,316 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
selectedKey={enableDataPlaneRBACOption}
onChange={handleOnDataPlaneRBACOptionChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{userContext.apiType === "SQL" && (
<>
<AccordionItem value="3">
<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>
<AccordionItem value="4">
<AccordionHeader>
<div className={styles.header}>RU Threshold</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
If a query exceeds a configured RU threshold, the query will be aborted.
</div>
<Toggle
styles={toggleStyles}
label="Enable RU threshold"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
{ruThresholdEnabled && (
<div className={styles.settingsSectionContainer}>
<SpinButton
label="RU Threshold (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="5">
<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>
</fieldset>
</div>
</div>
</>
)}
<AccordionItem value="6">
<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.
{userContext.apiType === "SQL" && (
<>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="ruThresholdLabel" className="settingsSectionLabel legendLabel">
RU Threshold
</legend>
<InfoTooltip>If a query exceeds a configured RU threshold, the query will be aborted.</InfoTooltip>
</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>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="7">
<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>
</AccordionPanel>
</AccordionItem>
{shouldShowCrossPartitionOption && (
<AccordionItem value="8">
<AccordionHeader>
<div className={styles.header}>Enable cross-partition query</div>
</AccordionHeader>
<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>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable cross partition query"
checked={crossPartitionQueryEnabled}
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
label="Enable cross-partition query"
<Toggle
styles={toggleStyles}
label="Enable RU threshold"
onChange={handleOnRUThresholdToggleChange}
defaultChecked={ruThresholdEnabled}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowParallelismOption && (
<AccordionItem value="9">
<AccordionHeader>
<div className={styles.header}>Max degree of parallelism</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
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 set to
less than 0, the system automatically decides the number of concurrent operations to run.
{ruThresholdEnabled && (
<div>
<SpinButton
label="RU Threshold (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>
<SpinButton
min={-1}
step={1}
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>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="queryTimeoutLabel" className="settingsSectionLabel legendLabel">
Query Timeout
</legend>
<InfoTooltip>
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
</InfoTooltip>
</div>
<div>
<Toggle
styles={toggleStyles}
label="Enable query timeout"
onChange={handleOnQueryTimeoutToggleChange}
defaultChecked={queryTimeoutEnabled}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowPriorityLevelOption && (
<AccordionItem value="10">
<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.
{queryTimeoutEnabled && (
<div>
<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>
)}
</div>
</div>
<div className="settingsSection">
<div className="settingsSectionPart">
<div>
<legend id="defaultQueryResultsView" className="settingsSectionLabel legendLabel">
Default Query Results View
</legend>
<InfoTooltip>Select the default view to use when displaying query results.</InfoTooltip>
</div>
<div>
<ChoiceGroup
ariaLabelledBy="priorityLevel"
selectedKey={priorityLevel}
options={priorityLevelOptionList}
ariaLabelledBy="defaultQueryResultsView"
selectedKey={defaultQueryResultsView}
options={defaultQueryResultsViewOptionList}
styles={choiceButtonStyles}
onChange={handleOnPriorityLevelOptionChange}
onChange={handleOnDefaultQueryResultsViewChange}
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
</div>
</div>
</>
)}
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Retry Settings
<InfoTooltip>Retry policy associated with throttled requests during CosmosDB queries.</InfoTooltip>
</div>
<div>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Max retry attempts
</legend>
<InfoTooltip>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>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Fixed retry interval (ms)
</legend>
<InfoTooltip>
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>
<legend id="queryRetryAttemptsLabel" className="settingsSectionLabel legendLabel">
Max wait time (s)
</legend>
<InfoTooltip>
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 className="settingsSection">
<div className="settingsSectionPart settingsSectionInlineCheckbox">
<div className="settingsSectionLabel">
Enable container pagination
<InfoTooltip>
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable container pagination"
checked={containerPaginationEnabled}
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
/>
</div>
</div>
{shouldShowCrossPartitionOption && (
<div className="settingsSection">
<div className="settingsSectionPart settingsSectionInlineCheckbox">
<div className="settingsSectionLabel">
Enable cross-partition query
<InfoTooltip>
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.
</InfoTooltip>
</div>
{shouldShowGraphAutoVizOption && (
<AccordionItem value="11">
<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>
)}
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable cross partition query"
checked={crossPartitionQueryEnabled}
onChange={() => setCrossPartitionQueryEnabled(!crossPartitionQueryEnabled)}
/>
</div>
</div>
)}
{shouldShowParallelismOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Max degree of parallelism
<InfoTooltip>
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 set to
less than 0, the system automatically decides the number of concurrent operations to run.
</InfoTooltip>
</div>
{shouldShowCopilotSampleDBOption && (
<AccordionItem value="12">
<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>
<SpinButton
min={-1}
step={1}
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"
/>
</div>
</div>
)}
{shouldShowPriorityLevelOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<fieldset>
<legend id="priorityLevel" className="settingsSectionLabel legendLabel">
Priority Level
</legend>
<InfoTooltip>
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.
</InfoTooltip>
<ChoiceGroup
ariaLabelledBy="priorityLevel"
selectedKey={priorityLevel}
options={priorityLevelOptionList}
styles={choiceButtonStyles}
onChange={handleOnPriorityLevelOptionChange}
/>
</fieldset>
</div>
</div>
)}
{shouldShowGraphAutoVizOption && (
<div className="settingsSection">
<div className="settingsSectionPart">
<div className="settingsSectionLabel">
Display Gremlin query results as:&nbsp;
<InfoTooltip>
Select Graph to automatically visualize the query results as a Graph or JSON to display the results as
JSON.
</InfoTooltip>
</div>
<ChoiceGroup
selectedKey={graphAutoVizDisabled}
options={graphAutoOptionList}
onChange={handleOnGremlinChange}
aria-label="Graph Auto-visualization"
/>
</div>
</div>
)}
{shouldShowCopilotSampleDBOption && (
<div className="settingsSection">
<div className="settingsSectionPart settingsSectionInlineCheckbox">
<div className="settingsSectionLabel">
Enable sample database
<InfoTooltip>
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.
</InfoTooltip>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="Enable sample db for Query Advisor"
checked={copilotSampleDBEnabled}
onChange={handleSampleDatabaseChange}
/>
</div>
</div>
)}
<div className="settingsSection">
<div className="settingsSectionPart">
<DefaultButton

View File

@@ -26,7 +26,7 @@ import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
import { Allotment, AllotmentHandle } from "allotment";
import { useSidePanel } from "hooks/useSidePanel";
import { debounce } from "lodash";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
const useSidebarStyles = makeStyles({
sidebar: {
@@ -113,12 +113,6 @@ interface GlobalCommand {
const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
const styles = useSidebarStyles();
// Since we have two buttons in the DOM (one for small screens and one for larger screens), we wrap the entire thing in a div.
// However, that messes with the Menu positioning, so we need to get a reference to the 'div' to pass to the Menu.
// We can't use a ref though, because it would be set after the Menu is rendered, so we use a state value to force a re-render.
const [globalCommandButton, setGlobalCommandButton] = useState<HTMLElement | null>(null);
const actions = useMemo<GlobalCommand[]>(() => {
if (
configContext.platform === Platform.Fabric ||
@@ -188,10 +182,10 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
{primaryAction.label}
</Button>
) : (
<Menu positioning={{ target: globalCommandButton, position: "below", align: "end" }}>
<Menu positioning="below-end">
<MenuTrigger disableButtonEnhancement>
{(triggerProps: MenuButtonProps) => (
<div ref={setGlobalCommandButton}>
<>
<SplitButton
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
primaryActionButton={{ onClick: onPrimaryActionClick }}
@@ -203,7 +197,7 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
<MenuButton {...triggerProps} icon={primaryAction.icon} className={styles.globalCommandsMenuButton}>
New...
</MenuButton>
</div>
</>
)}
</MenuTrigger>
<MenuPopover>

View File

@@ -753,11 +753,17 @@ export class CassandraAPIDataClient extends TableDataClient {
CassandraProxyEndpoints.Development,
CassandraProxyEndpoints.Mpac,
CassandraProxyEndpoints.Prod,
CassandraProxyEndpoints.Fairfax,
CassandraProxyEndpoints.Mooncake,
];
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessCassandraProxy &&
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
);

View File

@@ -1,7 +1,7 @@
import { IMessageBarStyles, Link, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext";
import { Platform, configContext, updateConfigContext } from "ConfigContext";
import { IpRule } from "Contracts/DataModels";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { CollectionTabKind } from "Contracts/ViewModels";
@@ -118,7 +118,7 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
setShowMongoAndCassandraProxiesNetworkSettingsWarningState(false);
}}
>
{`We have migrated our middleware to new infrastructure. To avoid issues with Data Explorer access, please
{`We are moving our middleware to new infrastructure. To avoid future issues with Data Explorer access, please
re-enable "Allow access from Azure Portal" on the Networking blade for your account.`}
</MessageBar>
)}
@@ -397,6 +397,12 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
ipAddressesFromIPRules.includes(mongoProxyOutboundIP),
);
if (ipRulesIncludeMongoProxy) {
updateConfigContext({
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
});
}
return !ipRulesIncludeMongoProxy;
} else if (userContext.apiType === "Cassandra") {
const isProdOrMpacCassandraProxyEndpoint: boolean = [
@@ -415,6 +421,12 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
(cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP),
);
if (ipRulesIncludeCassandraProxy) {
updateConfigContext({
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true,
});
}
return !ipRulesIncludeCassandraProxy;
}
}

View File

@@ -12,11 +12,7 @@ import {
subscriptionId,
} from "../fx";
// This test is currently failing because of issues with resource token auth in the backend.
// Marking it as 'fail' means that it will still be run, but Playwright's reporting will be inverted (failing tests will be marked as passing and vice versa).
// This allows us to detect when it starts working again, as the test will fail again.
// At that point, we can remove the 'fail' marker and continue running the test as normal.
test.fail("SQL account using Resource token", async ({ page }) => {
test("SQL account using Resource token", async ({ page }) => {
const credentials = await getAzureCLICredentials();
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const accountName = getAccountName(TestAccount.SQL);
@@ -38,7 +34,9 @@ test.fail("SQL account using Resource token", async ({ page }) => {
});
await expect(containerPermission).toBeDefined();
const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${database.id};CollectionId=${container.id};${containerPermission!._token}`;
const resourceTokenConnectionString = `AccountEndpoint=${account.documentEndpoint};DatabaseId=${
database.id
};CollectionId=${container.id};${containerPermission!._token}`;
await page.goto("https://localhost:1234/hostedExplorer.html");
const switchConnectionLink = page.getByTestId("Link:SwitchConnectionType");