Enable original azure resource tree style for Fabric native and turn on Settings tab (#2103)

* Enable original azure resource tree for Fabric native and turn on Settings page

* Fix unit tests
This commit is contained in:
Laurent Nguyen 2025-04-15 17:49:16 +02:00 committed by GitHub
parent afdbefe36c
commit 94b1e729d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 264 additions and 84 deletions

View File

@ -1,3 +1,4 @@
import { isFabric } from "Platform/Fabric/FabricUtil";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels"; import { Offer, ReadCollectionOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@ -13,6 +14,11 @@ import { readOfferWithSDK } from "./readOfferWithSDK";
export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => { export const readCollectionOffer = async (params: ReadCollectionOfferParams): Promise<Offer> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`); const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
if (isFabric()) {
// Not exposing offers in Fabric
return undefined;
}
try { try {
if ( if (
userContext.authType === AuthType.AAD && userContext.authType === AuthType.AAD &&

View File

@ -12,6 +12,7 @@ import {
ThroughputBucketsComponentProps, ThroughputBucketsComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react"; import * as React from "react";
@ -1277,6 +1278,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId), database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId),
collection: this.collection, collection: this.collection,
explorer: this.props.settingsTab.getContainer(), explorer: this.props.settingsTab.getContainer(),
isReadOnly: isFabricNative(),
}; };
const globalSecondaryIndexComponentProps: GlobalSecondaryIndexComponentProps = { const globalSecondaryIndexComponentProps: GlobalSecondaryIndexComponentProps = {

View File

@ -29,16 +29,26 @@ export interface PartitionKeyComponentProps {
database: ViewModels.Database; database: ViewModels.Database;
collection: ViewModels.Collection; collection: ViewModels.Collection;
explorer: Explorer; explorer: Explorer;
isReadOnly?: boolean; // true: cannot change partition key
} }
export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ database, collection, explorer }) => { export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({
database,
collection,
explorer,
isReadOnly,
}) => {
const { dataTransferJobs } = useDataTransferJobs(); const { dataTransferJobs } = useDataTransferJobs();
const [portalDataTransferJob, setPortalDataTransferJob] = React.useState<DataTransferJobGetResults>(null); const [portalDataTransferJob, setPortalDataTransferJob] = React.useState<DataTransferJobGetResults>(null);
React.useEffect(() => { React.useEffect(() => {
if (isReadOnly) {
return;
}
const loadDataTransferJobs = refreshDataTransferOperations; const loadDataTransferJobs = refreshDataTransferOperations;
loadDataTransferJobs(); loadDataTransferJobs();
}, []); }, [isReadOnly]);
React.useEffect(() => { React.useEffect(() => {
const currentJob = findPortalDataTransferJob(); const currentJob = findPortalDataTransferJob();
@ -163,56 +173,61 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
</Stack> </Stack>
</Stack> </Stack>
</Stack> </Stack>
<MessageBar messageBarType={MessageBarType.warning}>
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the {!isReadOnly && (
source container for the entire duration of the partition key change process. <>
<Link <MessageBar messageBarType={MessageBarType.warning}>
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work" To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to
target="_blank" the source container for the entire duration of the partition key change process.
underline <Link
> href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
Learn more target="_blank"
</Link> underline
</MessageBar> >
<Text> Learn more
To change the partition key, a new destination container must be created or an existing destination container </Link>
selected. Data will then be copied to the destination container. </MessageBar>
</Text> <Text>
{configContext.platform !== Platform.Emulator && ( To change the partition key, a new destination container must be created or an existing destination
<PrimaryButton container selected. Data will then be copied to the destination container.
styles={{ root: { width: "fit-content" } }} </Text>
text="Change" {configContext.platform !== Platform.Emulator && (
onClick={startPartitionkeyChangeWorkflow} <PrimaryButton
disabled={isCurrentJobInProgress(portalDataTransferJob)} styles={{ root: { width: "fit-content" } }}
/> text="Change"
)} onClick={startPartitionkeyChangeWorkflow}
{portalDataTransferJob && ( disabled={isCurrentJobInProgress(portalDataTransferJob)}
<Stack> />
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text> )}
<Stack {portalDataTransferJob && (
horizontal <Stack>
tokens={{ childrenGap: 20 }} <Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>
styles={{ <Stack
root: { horizontal
alignItems: "center", tokens={{ childrenGap: 20 }}
}, styles={{
}} root: {
> alignItems: "center",
<ProgressIndicator },
label={portalDataTransferJob?.properties?.jobName} }}
description={getProgressDescription()} >
percentComplete={getPercentageComplete()} <ProgressIndicator
styles={{ label={portalDataTransferJob?.properties?.jobName}
root: { description={getProgressDescription()}
width: "85%", percentComplete={getPercentageComplete()}
}, styles={{
}} root: {
></ProgressIndicator> width: "85%",
{isCurrentJobInProgress(portalDataTransferJob) && ( },
<DefaultButton text="Cancel" onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)} /> }}
)} ></ProgressIndicator>
</Stack> {isCurrentJobInProgress(portalDataTransferJob) && (
</Stack> <DefaultButton text="Cancel" onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)} />
)}
</Stack>
</Stack>
)}
</>
)} )}
</Stack> </Stack>
); );

View File

@ -306,6 +306,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
} }
} }
isReadOnly={false}
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem

View File

@ -740,12 +740,38 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
] ]
`; `;
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric non read-only 1`] = ` exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric non read-only (native) 1`] = `
[ [
{ {
"children": [ "children": [
{ {
"children": undefined, "children": [
{
"contextMenu": [
{
"iconSrc": {},
"label": "New SQL Query",
"onClick": [Function],
},
{
"iconSrc": {},
"label": "Delete Container",
"onClick": [Function],
"styleClass": "deleteCollectionMenuItem",
},
],
"id": "",
"isSelected": [Function],
"label": "Items",
"onClick": [Function],
},
{
"id": "",
"isSelected": [Function],
"label": "Settings",
"onClick": [Function],
},
],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{ {
@ -772,7 +798,38 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
"onExpanded": [Function], "onExpanded": [Function],
}, },
{ {
"children": undefined, "children": [
{
"contextMenu": [
{
"iconSrc": {},
"label": "New SQL Query",
"onClick": [Function],
},
{
"iconSrc": {},
"label": "Delete Container",
"onClick": [Function],
"styleClass": "deleteCollectionMenuItem",
},
],
"id": "",
"isSelected": [Function],
"label": "Items",
"onClick": [Function],
},
{
"id": "",
"isSelected": [Function],
"label": "Settings",
"onClick": [Function],
},
{
"isSelected": [Function],
"label": "Conflicts",
"onClick": [Function],
},
],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{ {
@ -806,12 +863,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
"label": "New Container", "label": "New Container",
"onClick": [Function], "onClick": [Function],
}, },
{
"iconSrc": {},
"label": "Delete Database",
"onClick": [Function],
"styleClass": "deleteDatabaseMenuItem",
},
], ],
"iconSrc": <DatabaseRegular "iconSrc": <DatabaseRegular
fontSize={16} fontSize={16}
@ -826,7 +877,33 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
{ {
"children": [ "children": [
{ {
"children": undefined, "children": [
{
"contextMenu": [
{
"iconSrc": {},
"label": "New SQL Query",
"onClick": [Function],
},
{
"iconSrc": {},
"label": "Delete Container",
"onClick": [Function],
"styleClass": "deleteCollectionMenuItem",
},
],
"id": "sampleItems",
"isSelected": [Function],
"label": "Items",
"onClick": [Function],
},
{
"id": "sampleSettings",
"isSelected": [Function],
"label": "Settings",
"onClick": [Function],
},
],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{ {
@ -860,12 +937,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
"label": "New Container", "label": "New Container",
"onClick": [Function], "onClick": [Function],
}, },
{
"iconSrc": {},
"label": "Delete Database",
"onClick": [Function],
"styleClass": "deleteDatabaseMenuItem",
},
], ],
"iconSrc": <DatabaseRegular "iconSrc": <DatabaseRegular
fontSize={16} fontSize={16}
@ -880,7 +951,88 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
{ {
"children": [ "children": [
{ {
"children": undefined, "children": [
{
"contextMenu": [
{
"iconSrc": {},
"label": "New SQL Query",
"onClick": [Function],
},
{
"iconSrc": {},
"label": "Delete Container",
"onClick": [Function],
"styleClass": "deleteCollectionMenuItem",
},
],
"id": "",
"isSelected": [Function],
"label": "Items",
"onClick": [Function],
},
{
"id": "",
"isSelected": [Function],
"label": "Settings",
"onClick": [Function],
},
{
"children": [
{
"children": [
{
"children": [
{
"label": "string",
},
{
"label": "HasNulls: false",
},
],
"label": "street",
},
{
"children": [
{
"label": "string",
},
{
"label": "HasNulls: true",
},
],
"label": "line2",
},
{
"children": [
{
"label": "number",
},
{
"label": "HasNulls: false",
},
],
"label": "zip",
},
],
"label": "address",
},
{
"children": [
{
"label": "string",
},
{
"label": "HasNulls: false",
},
],
"label": "orderId",
},
],
"label": "Schema",
"onClick": [Function],
},
],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{ {
@ -919,12 +1071,6 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
"label": "New Container", "label": "New Container",
"onClick": [Function], "onClick": [Function],
}, },
{
"iconSrc": {},
"label": "Delete Database",
"onClick": [Function],
"styleClass": "deleteDatabaseMenuItem",
},
], ],
"iconSrc": <DatabaseRegular "iconSrc": <DatabaseRegular
fontSize={16} fontSize={16}
@ -939,7 +1085,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
] ]
`; `;
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric read-only 1`] = ` exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric read-only (mirrored) 1`] = `
[ [
{ {
"children": [ "children": [

View File

@ -373,18 +373,28 @@ describe("createDatabaseTreeNodes", () => {
it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>, Partial<UserContext>]>([ it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>, Partial<UserContext>]>([
[ [
"the SQL API, on Fabric read-only", "the SQL API, on Fabric read-only (mirrored)",
Platform.Fabric, Platform.Fabric,
false, false,
{ capabilities: [], enableMultipleWriteLocations: true }, { capabilities: [], enableMultipleWriteLocations: true },
{ fabricContext: { isReadOnly: true } as FabricContext<CosmosDbArtifactType> }, {
fabricContext: {
isReadOnly: true,
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
} as FabricContext<CosmosDbArtifactType>,
},
], ],
[ [
"the SQL API, on Fabric non read-only", "the SQL API, on Fabric non read-only (native)",
Platform.Fabric, Platform.Fabric,
false, false,
{ capabilities: [], enableMultipleWriteLocations: true }, { capabilities: [], enableMultipleWriteLocations: true },
{ fabricContext: { isReadOnly: false } as FabricContext<CosmosDbArtifactType> }, {
fabricContext: {
isReadOnly: false,
artifactType: CosmosDbArtifactType.NATIVE,
} as FabricContext<CosmosDbArtifactType>,
},
], ],
[ [
"the SQL API, on Portal", "the SQL API, on Portal",

View File

@ -6,7 +6,7 @@ import StoredProcedure from "Explorer/Tree/StoredProcedure";
import Trigger from "Explorer/Tree/Trigger"; import Trigger from "Explorer/Tree/Trigger";
import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction"; import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabricMirrored } from "Platform/Fabric/FabricUtil"; import { isFabric, isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil";
import { getItemName } from "Utils/APITypeUtils"; import { getItemName } from "Utils/APITypeUtils";
import { isServerlessAccount } from "Utils/CapabilityUtils"; import { isServerlessAccount } from "Utils/CapabilityUtils";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";
@ -23,7 +23,7 @@ import { useNotebook } from "../Notebook/useNotebook";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
export const shouldShowScriptNodes = (): boolean => { export const shouldShowScriptNodes = (): boolean => {
return !isFabricMirrored() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin"); return !isFabric() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
}; };
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />; const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
@ -220,7 +220,7 @@ export const buildCollectionNode = (
): TreeNode => { ): TreeNode => {
let children: TreeNode[]; let children: TreeNode[];
// Flat Tree for Fabric // Flat Tree for Fabric
if (configContext.platform !== Platform.Fabric) { if (!isFabricMirrored()) {
children = buildCollectionNodeChildren(database, collection, isNotebookEnabled, container, refreshActiveTab); children = buildCollectionNodeChildren(database, collection, isNotebookEnabled, container, refreshActiveTab);
} }
@ -318,7 +318,7 @@ const buildCollectionNodeChildren = (
children.push({ children.push({
id, id,
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings", label: database.isDatabaseShared() || isServerlessAccount() || isFabricNative() ? "Settings" : "Scale & Settings",
onClick: collection.onSettingsClick.bind(collection), onClick: collection.onSettingsClick.bind(collection),
isSelected: () => isSelected: () =>
useSelectedNode useSelectedNode