mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-06-11 06:57:40 +01:00
Merge branch 'master' of https://github.com/Azure/cosmos-explorer
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { AuthType } from "AuthType";
|
import { AuthType } from "AuthType";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { Features } from "Platform/Hosted/extractFeatures";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
@@ -253,7 +252,7 @@ describe("SettingsComponent", () => {
|
|||||||
it("should save throughput bucket changes when Save button is clicked", async () => {
|
it("should save throughput bucket changes when Save button is clicked", async () => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
apiType: "SQL",
|
apiType: "SQL",
|
||||||
features: { enableThroughputBuckets: true } as Features,
|
throughputBucketsEnabled: true,
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
this.throughputBucketsEnabled =
|
this.throughputBucketsEnabled = userContext.throughputBucketsEnabled;
|
||||||
userContext.apiType === "SQL" &&
|
|
||||||
userContext.features.enableThroughputBuckets &&
|
|
||||||
userContext.authType === AuthType.AAD;
|
|
||||||
|
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
@@ -1074,11 +1071,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
databaseId: this.collection.databaseId,
|
databaseId: this.collection.databaseId,
|
||||||
collectionId: this.collection.id(),
|
collectionId: this.collection.id(),
|
||||||
currentOffer: this.collection.offer(),
|
currentOffer: this.collection.offer(),
|
||||||
autopilotThroughput: this.collection.offer().autoscaleMaxThroughput
|
autopilotThroughput: this.collection.offer?.()?.autoscaleMaxThroughput
|
||||||
? this.collection.offer().autoscaleMaxThroughput
|
? this.collection.offer?.()?.autoscaleMaxThroughput
|
||||||
: undefined,
|
: undefined,
|
||||||
manualThroughput: this.collection.offer().manualThroughput
|
manualThroughput: this.collection.offer?.()?.manualThroughput
|
||||||
? this.collection.offer().manualThroughput
|
? this.collection.offer?.()?.manualThroughput
|
||||||
: undefined,
|
: undefined,
|
||||||
throughputBuckets: this.state.throughputBuckets,
|
throughputBuckets: this.state.throughputBuckets,
|
||||||
});
|
});
|
||||||
@@ -1344,7 +1341,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.throughputBucketsEnabled) {
|
if (this.throughputBucketsEnabled && !hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ThroughputBucketsTab,
|
tab: SettingsV2TabTypes.ThroughputBucketsTab,
|
||||||
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
|
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
|
||||||
|
|||||||
+11
-5
@@ -26,7 +26,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
it("renders the correct number of buckets", () => {
|
it("renders the correct number of buckets", () => {
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} />);
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders buckets in the correct order even if input is unordered", () => {
|
it("renders buckets in the correct order even if input is unordered", () => {
|
||||||
@@ -36,8 +36,14 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
];
|
];
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
|
||||||
|
|
||||||
const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent);
|
const bucketLabels = screen.getAllByText(/Bucket \d+/).map((el) => el.textContent);
|
||||||
expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]);
|
expect(bucketLabels).toEqual([
|
||||||
|
"Bucket 1 (Data Explorer Query Bucket)",
|
||||||
|
"Bucket 2",
|
||||||
|
"Bucket 3",
|
||||||
|
"Bucket 4",
|
||||||
|
"Bucket 5",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders all provided buckets even if they exceed the max default bucket count", () => {
|
it("renders all provided buckets even if they exceed the max default bucket count", () => {
|
||||||
@@ -53,7 +59,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
|
||||||
|
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(7);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(7);
|
||||||
|
|
||||||
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
||||||
expect(screen.getByDisplayValue("60")).toBeInTheDocument();
|
expect(screen.getByDisplayValue("60")).toBeInTheDocument();
|
||||||
@@ -171,7 +177,7 @@ describe("ThroughputBucketsComponent", () => {
|
|||||||
|
|
||||||
it("ensures default buckets are used when no buckets are provided", () => {
|
it("ensures default buckets are used when no buckets are provided", () => {
|
||||||
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
|
||||||
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5);
|
||||||
expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
|
expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
|
|||||||
value={bucket.maxThroughputPercentage}
|
value={bucket.maxThroughputPercentage}
|
||||||
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
||||||
showValue={false}
|
showValue={false}
|
||||||
label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
||||||
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
||||||
disabled={bucket.maxThroughputPercentage === 100}
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } fro
|
|||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
|
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
|
||||||
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
@@ -1196,6 +1197,11 @@ export default class Explorer {
|
|||||||
await this.initNotebooks(userContext.databaseAccount);
|
await this.initNotebooks(userContext.databaseAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") {
|
||||||
|
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
|
||||||
|
updateUserContext({ throughputBucketsEnabled });
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshSampleData();
|
this.refreshSampleData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboard
|
|||||||
import { isFabric, isFabricMirrored, isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
import { isFabric, isFabricMirrored, isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
||||||
|
import { conditionalClass } from "Utils/StyleUtils";
|
||||||
import { Allotment, AllotmentHandle } from "allotment";
|
import { Allotment, AllotmentHandle } from "allotment";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
|
import useZoomLevel from "hooks/useZoomLevel";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
@@ -104,6 +106,23 @@ const useSidebarStyles = makeStyles({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
accessibleContent: {
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
overflow: "scroll",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
minHeightResponsive: {
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
minHeight: "400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accessibleContentZoom: {
|
||||||
|
overflow: "scroll",
|
||||||
|
},
|
||||||
|
|
||||||
|
minHeightZoom: {
|
||||||
|
minHeight: "400px",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface GlobalCommandsProps {
|
interface GlobalCommandsProps {
|
||||||
@@ -275,6 +294,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
const [expandedSize, setExpandedSize] = React.useState(300);
|
const [expandedSize, setExpandedSize] = React.useState(300);
|
||||||
const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo";
|
const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo";
|
||||||
const allotment = useRef<AllotmentHandle>(null);
|
const allotment = useRef<AllotmentHandle>(null);
|
||||||
|
const isZoomed = useZoomLevel();
|
||||||
|
|
||||||
const expand = useCallback(() => {
|
const expand = useCallback(() => {
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
@@ -325,11 +345,23 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebarContainer">
|
<div className="sidebarContainer">
|
||||||
<Allotment ref={allotment} onChange={onChange} onDragEnd={onDragEnd} className="resourceTreeAndTabs">
|
<Allotment
|
||||||
|
ref={allotment}
|
||||||
|
onChange={onChange}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
className={`resourceTreeAndTabs ${styles.accessibleContent} ${conditionalClass(
|
||||||
|
isZoomed,
|
||||||
|
styles.accessibleContentZoom,
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
{/* Collections Tree - Start */}
|
{/* Collections Tree - Start */}
|
||||||
{hasSidebar && (
|
{hasSidebar && (
|
||||||
// When collapsed, we force the pane to 24 pixels wide and make it non-resizable.
|
// When collapsed, we force the pane to 24 pixels wide and make it non-resizable.
|
||||||
<Allotment.Pane minSize={24} preferredSize={250}>
|
<Allotment.Pane
|
||||||
|
className={`${styles.minHeightResponsive} ${conditionalClass(isZoomed, styles.minHeightZoom)}`}
|
||||||
|
minSize={24}
|
||||||
|
preferredSize={250}
|
||||||
|
>
|
||||||
<CosmosFluentProvider className={mergeClasses(styles.sidebar)}>
|
<CosmosFluentProvider className={mergeClasses(styles.sidebar)}>
|
||||||
<div className={styles.sidebarContainer}>
|
<div className={styles.sidebarContainer}>
|
||||||
{loading && (
|
{loading && (
|
||||||
@@ -385,7 +417,10 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
)}
|
)}
|
||||||
<Allotment.Pane minSize={200}>
|
<Allotment.Pane
|
||||||
|
className={`${styles.minHeightResponsive} ${conditionalClass(isZoomed, styles.minHeightZoom)}`}
|
||||||
|
minSize={200}
|
||||||
|
>
|
||||||
<Tabs explorer={explorer} />
|
<Tabs explorer={explorer} />
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
</Allotment>
|
</Allotment>
|
||||||
|
|||||||
@@ -290,10 +290,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
|||||||
<form className="connectExplorerFormContainer">
|
<form className="connectExplorerFormContainer">
|
||||||
<div className="splashScreenContainer">
|
<div className="splashScreenContainer">
|
||||||
<div className="splashScreen">
|
<div className="splashScreen">
|
||||||
<h1 className="title" role="heading" aria-label={title}>
|
<h2 className="title" role="heading" aria-label={title}>
|
||||||
{title}
|
{title}
|
||||||
<FeaturePanelLauncher />
|
<FeaturePanelLauncher />
|
||||||
</h1>
|
</h2>
|
||||||
<div className="subtitle">{subtitle}</div>
|
<div className="subtitle">{subtitle}</div>
|
||||||
{this.getSplashScreenButtons()}
|
{this.getSplashScreenButtons()}
|
||||||
{useCarousel.getState().showCoachMark && (
|
{useCarousel.getState().showCoachMark && (
|
||||||
|
|||||||
@@ -144,6 +144,13 @@ export const useDocumentsTabStyles = makeStyles({
|
|||||||
deleteProgressContent: {
|
deleteProgressContent: {
|
||||||
paddingTop: tokens.spacingVerticalL,
|
paddingTop: tokens.spacingVerticalL,
|
||||||
},
|
},
|
||||||
|
smallScreenContent: {
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
flexWrap: "wrap",
|
||||||
|
minHeight: "max-content",
|
||||||
|
padding: "4px",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export class DocumentsTabV2 extends TabsBase {
|
export class DocumentsTabV2 extends TabsBase {
|
||||||
@@ -2102,7 +2109,7 @@ 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-test={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
|
||||||
<div data-test={"DocumentsTab/Filter"} className={styles.filterRow}>
|
<div data-test={"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()}
|
||||||
|
|||||||
@@ -15,7 +15,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"
|
className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29 ___1ngl8o6_0000000 fz7mnu6 fl3egqs flhmrkm"
|
||||||
data-test="DocumentsTab/Filter"
|
data-test="DocumentsTab/Filter"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ 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;
|
||||||
@@ -23,11 +25,16 @@ interface QueryResultProps extends ResultsViewProps {
|
|||||||
|
|
||||||
const ExecuteQueryCallToAction: React.FC = () => {
|
const ExecuteQueryCallToAction: React.FC = () => {
|
||||||
const styles = useQueryTabStyles();
|
const styles = useQueryTabStyles();
|
||||||
|
const isZoomed = useZoomLevel();
|
||||||
return (
|
return (
|
||||||
<div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
<div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<img src={RunQuery} aria-hidden="true" />
|
<img
|
||||||
|
className={`${styles.responsiveImg} ${conditionalClass(isZoomed, styles.zoomedImageSize)}`}
|
||||||
|
src={RunQuery}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>Execute a query to see the results</p>
|
<p>Execute a query to see the results</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export const useQueryTabStyles = makeStyles({
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
overflow: "scroll",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
queryResultsMessage: {
|
queryResultsMessage: {
|
||||||
...shorthands.margin("5px"),
|
...shorthands.margin("5px"),
|
||||||
@@ -38,6 +41,9 @@ export const useQueryTabStyles = makeStyles({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
rowGap: "12px",
|
rowGap: "12px",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
height: "auto",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
queryResultsTabContentContainer: {
|
queryResultsTabContentContainer: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -93,4 +99,12 @@ export const useQueryTabStyles = makeStyles({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
},
|
},
|
||||||
|
responsiveImg: {
|
||||||
|
"@media (max-width: 420px)": {
|
||||||
|
width: "50px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
zoomedImageSize: {
|
||||||
|
width: "60px",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ export interface UserContext {
|
|||||||
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
||||||
readonly dataPlaneRbacEnabled?: boolean;
|
readonly dataPlaneRbacEnabled?: boolean;
|
||||||
readonly refreshCosmosClient?: boolean;
|
readonly refreshCosmosClient?: boolean;
|
||||||
|
throughputBucketsEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { configContext } from "ConfigContext";
|
||||||
|
import { FeatureRegistration } from "Contracts/DataModels";
|
||||||
|
import { AuthorizationTokenHeaderMetadata } from "Contracts/ViewModels";
|
||||||
|
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||||
|
|
||||||
|
export const featureRegistered = async (subscriptionId: string, feature: string) => {
|
||||||
|
const api_version = "2021-07-01";
|
||||||
|
const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/${feature}?api-version=${api_version}`;
|
||||||
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
let response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await _fetchWithTimeout(url, headers);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response?.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const featureRegistration = (await response?.json()) as FeatureRegistration;
|
||||||
|
return featureRegistration?.properties?.state === "Registered";
|
||||||
|
};
|
||||||
|
|
||||||
|
async function _fetchWithTimeout(
|
||||||
|
url: string,
|
||||||
|
headers: {
|
||||||
|
[x: string]: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const timeout = 10000;
|
||||||
|
const options = { timeout };
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const id = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
|
const response = await window.fetch(url, {
|
||||||
|
headers,
|
||||||
|
...options,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(id);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -21,3 +21,11 @@ export function copyStyles(sourceDoc: Document, targetDoc: Document): void {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditionally returns a class name based on a boolean condition.
|
||||||
|
* If the condition is true, returns the `trueValue` class; otherwise, returns `falseValue` (or an empty string if not provided).
|
||||||
|
*/
|
||||||
|
export function conditionalClass(condition: boolean, trueValue: string, falseValue?: string): string {
|
||||||
|
return condition ? trueValue : falseValue || "";
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const useZoomLevel = (threshold: number = 2): boolean => {
|
||||||
|
const [isZoomed, setIsZoomed] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkZoom = () => {
|
||||||
|
const zoomLevel = window.devicePixelRatio;
|
||||||
|
setIsZoomed(zoomLevel >= threshold);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkZoom();
|
||||||
|
window.addEventListener("resize", checkZoom);
|
||||||
|
return () => window.removeEventListener("resize", checkZoom);
|
||||||
|
}, [threshold]);
|
||||||
|
|
||||||
|
return isZoomed;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useZoomLevel;
|
||||||
Reference in New Issue
Block a user