Compare commits

..

6 Commits

Author SHA1 Message Date
Asier Isayas
12442fadfb Mongo Shell and MS Graph changes (no RBAC functionality) 2024-07-11 17:12:28 -04:00
Asier Isayas
17754cba05 Revert to old Mongo Proxy (#1886)
* revert to old mongo proxy

* revert to old mongo proxy

* cleanup

* cleanup

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-27 11:40:05 -07:00
Asier Isayas
b07fa89a23 fix legacy mongo shell regression (#1883)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-26 14:33:57 -04:00
sunghyunkang1111
28db549fa1 pagination loading of subscription and databaseaccounts (#1877) 2024-06-21 14:28:00 -05:00
Laurent Nguyen
fe892dcc62 Temporarily disable bulk Delete for old non-partitioned NoSQL containers (#1880)
* Disable Delete button and document selection when partitionKey.systemKey = true for noSql.

* Update unit tests

* Always show delete button. Use single delete API for systemKey containers
2024-06-21 09:37:34 +02:00
Asier Isayas
380caba5f5 Cassandra: Delete a row for a table that has multiple partition keys (#1879)
* Delete a row with multiple parition keys

* clean up

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-20 12:55:14 -04:00
15 changed files with 78 additions and 91 deletions

View File

@@ -109,14 +109,14 @@ let configContext: Readonly<ConfigContext> = {
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [ NEW_MONGO_APIS: [
"resourcelist", // "resourcelist",
"queryDocuments", // "queryDocuments",
"createDocument", // "createDocument",
"readDocument", // "readDocument",
"updateDocument", // "updateDocument",
"deleteDocument", // "deleteDocument",
"createCollectionWithProxy", // "createCollectionWithProxy",
"legacyMongoShell", // "legacyMongoShell",
], ],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,

View File

@@ -1,7 +1,8 @@
export interface QueryRequestOptions { export interface QueryRequestOptions {
$skipToken?: string; $skipToken?: string;
$top?: number; $top?: number;
subscriptions: string[]; $allowPartialScopes: boolean;
subscriptions?: string[];
} }
export interface QueryResponse { export interface QueryResponse {

View File

@@ -52,15 +52,12 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) { if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
items.push({ items.push({
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => onClick: () =>
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
"Delete " + getDatabaseName(), "Delete " + getDatabaseName(),
<DeleteDatabaseConfirmationPanel <DeleteDatabaseConfirmationPanel refreshDatabases={() => container.refreshAllDatabases()} />,
lastFocusedElement={lastFocusedElement}
refreshDatabases={() => container.refreshAllDatabases()}
/>,
), ),
label: `Delete ${getDatabaseName()}`, label: `Delete ${getDatabaseName()}`,
styleClass: "deleteDatabaseMenuItem", styleClass: "deleteDatabaseMenuItem",
@@ -135,16 +132,13 @@ export const createCollectionContextMenuButton = (
if (configContext.platform !== Platform.Fabric) { if (configContext.platform !== Platform.Fabric) {
items.push({ items.push({
iconSrc: DeleteCollectionIcon, iconSrc: DeleteCollectionIcon,
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => { onClick: () => {
useSelectedNode.getState().setSelectedNode(selectedCollection); useSelectedNode.getState().setSelectedNode(selectedCollection);
useSidePanel useSidePanel
.getState() .getState()
.openSidePanel( .openSidePanel(
"Delete " + getCollectionName(), "Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane <DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />,
lastFocusedElement={lastFocusedElement}
refreshDatabases={() => container.refreshAllDatabases()}
/>,
); );
}, },
label: `Delete ${getCollectionName()}`, label: `Delete ${getCollectionName()}`,

View File

@@ -21,7 +21,7 @@ import { useCallback } from "react";
export interface TreeNodeMenuItem { export interface TreeNodeMenuItem {
label: string; label: string;
onClick: (value?: React.RefObject<HTMLElement>) => void; onClick: () => void;
iconSrc?: string; iconSrc?: string;
isDisabled?: boolean; isDisabled?: boolean;
styleClass?: string; styleClass?: string;
@@ -73,7 +73,6 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
treeNodeId, treeNodeId,
}: TreeNodeComponentProps): JSX.Element => { }: TreeNodeComponentProps): JSX.Element => {
const [isLoading, setIsLoading] = React.useState<boolean>(false); const [isLoading, setIsLoading] = React.useState<boolean>(false);
const contextMenuRef = React.useRef<HTMLButtonElement>(null);
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => { const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
if (!treeNode || !treeNode.children) { if (!treeNode || !treeNode.children) {
@@ -140,7 +139,7 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`} data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
disabled={menuItem.isDisabled} disabled={menuItem.isDisabled}
key={menuItem.label} key={menuItem.label}
onClick={() => menuItem.onClick(contextMenuRef)} onClick={menuItem.onClick}
> >
{menuItem.label} {menuItem.label}
</MenuItem> </MenuItem>
@@ -164,7 +163,6 @@ export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
aria-label="More options" aria-label="More options"
data-test="TreeNode/ContextMenuTrigger" data-test="TreeNode/ContextMenuTrigger"
appearance="subtle" appearance="subtle"
ref={contextMenuRef}
icon={<MoreHorizontal20Regular />} icon={<MoreHorizontal20Regular />}
/> />
</MenuTrigger> </MenuTrigger>

View File

@@ -1560,14 +1560,14 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
<MenuList> <MenuList>
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:enabledItem" data-test="TreeNode/ContextMenuItem:enabledItem"
onClick={[Function]} onClick={[MockFunction enabledItemClick]}
> >
enabledItem enabledItem
</MenuItem> </MenuItem>
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:disabledItem" data-test="TreeNode/ContextMenuItem:disabledItem"
disabled={true} disabled={true}
onClick={[Function]} onClick={[MockFunction disabledItemClick]}
> >
disabledItem disabledItem
</MenuItem> </MenuItem>
@@ -1604,7 +1604,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
<MenuItem <MenuItem
data-test="TreeNode/ContextMenuItem:enabledItem" data-test="TreeNode/ContextMenuItem:enabledItem"
key="enabledItem" key="enabledItem"
onClick={[Function]} onClick={[MockFunction enabledItemClick]}
> >
enabledItem enabledItem
</MenuItem> </MenuItem>
@@ -1612,7 +1612,7 @@ exports[`TreeNodeComponent renders a node with a menu 1`] = `
data-test="TreeNode/ContextMenuItem:disabledItem" data-test="TreeNode/ContextMenuItem:disabledItem"
disabled={true} disabled={true}
key="disabledItem" key="disabledItem"
onClick={[Function]} onClick={[MockFunction disabledItemClick]}
> >
disabledItem disabledItem
</MenuItem> </MenuItem>

View File

@@ -52,9 +52,7 @@ describe("Delete Collection Confirmation Pane", () => {
describe("shouldRecordFeedback()", () => { describe("shouldRecordFeedback()", () => {
it("should return true if last collection and database does not have shared throughput else false", () => { it("should return true if last collection and database does not have shared throughput else false", () => {
const wrapper = shallow( const wrapper = shallow(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false); expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
const database = { id: ko.observable("testDB") } as Database; const database = { id: ko.observable("testDB") } as Database;
@@ -111,9 +109,7 @@ describe("Delete Collection Confirmation Pane", () => {
}); });
it("should call delete collection", () => { it("should call delete collection", () => {
const wrapper = mount( const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmCollectionId")).toBe(true); expect(wrapper.exists("#confirmCollectionId")).toBe(true);
@@ -130,9 +126,7 @@ describe("Delete Collection Confirmation Pane", () => {
}); });
it("should record feedback", async () => { it("should record feedback", async () => {
const wrapper = mount( const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper.exists("#confirmCollectionId")).toBe(true); expect(wrapper.exists("#confirmCollectionId")).toBe(true);
wrapper wrapper
.find("#confirmCollectionId") .find("#confirmCollectionId")

View File

@@ -12,19 +12,17 @@ import { getCollectionName } from "Utils/APITypeUtils";
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { useDatabases } from "../../useDatabases"; import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface DeleteCollectionConfirmationPaneProps { export interface DeleteCollectionConfirmationPaneProps {
refreshDatabases: () => Promise<void>; refreshDatabases: () => Promise<void>;
lastFocusedElement: React.MutableRefObject<HTMLElement>;
} }
export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectionConfirmationPaneProps> = ({
refreshDatabases, refreshDatabases,
lastFocusedElement,
}: DeleteCollectionConfirmationPaneProps) => { }: DeleteCollectionConfirmationPaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>(""); const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
@@ -37,7 +35,7 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const collectionName = getCollectionName().toLocaleLowerCase(); const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = "Delete " + collectionName; const paneTitle = "Delete " + collectionName;
const lastItemElement = lastFocusedElement?.current;
const onSubmit = async (): Promise<void> => { const onSubmit = async (): Promise<void> => {
const collection = useSelectedNode.getState().findSelectedCollection(); const collection = useSelectedNode.getState().findSelectedCollection();
if (!collection || inputCollectionName !== collection.id()) { if (!collection || inputCollectionName !== collection.id()) {
@@ -113,14 +111,6 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
}; };
const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`; const confirmContainer = `Confirm by typing the ${collectionName.toLowerCase()} id`;
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`; const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${collectionName}?`;
useEffect(() => {
return () => {
if (lastItemElement) {
lastItemElement.focus();
}
};
}, [lastItemElement]);
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
<div className="panelFormWrapper"> <div className="panelFormWrapper">

View File

@@ -49,9 +49,7 @@ describe("Delete Database Confirmation Pane", () => {
}); });
it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => { it("shouldRecordFeedback() should return true if last non empty database or is last database that has shared throughput", () => {
const wrapper = shallow( const wrapper = shallow(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true); expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
useDatabases.getState().addDatabases([database]); useDatabases.getState().addDatabases([database]);
@@ -61,9 +59,7 @@ describe("Delete Database Confirmation Pane", () => {
}); });
it("Should call delete database", () => { it("Should call delete database", () => {
const wrapper = mount( const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmDatabaseId")).toBe(true); expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
@@ -78,9 +74,7 @@ describe("Delete Database Confirmation Pane", () => {
}); });
it("should record feedback", async () => { it("should record feedback", async () => {
const wrapper = mount( const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} lastFocusedElement={undefined} />,
);
expect(wrapper.exists("#confirmDatabaseId")).toBe(true); expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
wrapper wrapper
.find("#confirmDatabaseId") .find("#confirmDatabaseId")

View File

@@ -13,7 +13,7 @@ import { getDatabaseName } from "Utils/APITypeUtils";
import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs"; import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import { useDatabases } from "../useDatabases"; import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent"; import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
@@ -21,12 +21,10 @@ import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm
interface DeleteDatabaseConfirmationPanelProps { interface DeleteDatabaseConfirmationPanelProps {
refreshDatabases: () => Promise<void>; refreshDatabases: () => Promise<void>;
lastFocusedElement: React.MutableRefObject<HTMLElement>;
} }
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = ({
refreshDatabases, refreshDatabases,
lastFocusedElement,
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => { }: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase); const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
@@ -36,7 +34,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
const [databaseInput, setDatabaseInput] = useState<string>(""); const [databaseInput, setDatabaseInput] = useState<string>("");
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>(""); const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
const selectedDatabase: Database = useDatabases.getState().findSelectedDatabase(); const selectedDatabase: Database = useDatabases.getState().findSelectedDatabase();
const lastItemElement = lastFocusedElement?.current;
const submit = async (): Promise<void> => { const submit = async (): Promise<void> => {
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) { if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
setFormError( setFormError(
@@ -128,13 +126,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
}; };
const confirmDatabase = `Confirm by typing the ${getDatabaseName()} id`; const confirmDatabase = `Confirm by typing the ${getDatabaseName()} id`;
const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${getDatabaseName()}?`; const reasonInfo = `Help us improve Azure Cosmos DB! What is the reason why you are deleting this ${getDatabaseName()}?`;
useEffect(() => {
return () => {
if (lastItemElement) {
lastItemElement.focus();
}
};
}, [lastItemElement]);
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
{!formError && <PanelInfoErrorComponent {...errorProps} />} {!formError && <PanelInfoErrorComponent {...errorProps} />}

View File

@@ -363,18 +363,24 @@ export class CassandraAPIDataClient extends TableDataClient {
entitiesToDelete: Entities.ITableEntity[], entitiesToDelete: Entities.ITableEntity[],
): Promise<any> { ): Promise<any> {
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `; const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
const partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection); const partitionKeys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys;
await Promise.all( await Promise.all(
entitiesToDelete.map(async (currEntityToDelete: Entities.ITableEntity) => { entitiesToDelete.map(async (currEntityToDelete: Entities.ITableEntity) => {
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting row ${currEntityToDelete.RowKey._}`); const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting row ${currEntityToDelete.RowKey._}`);
const partitionKeyValue = currEntityToDelete[partitionKeyProperty];
const currQuery =
query +
(this.isStringType(partitionKeyValue.$)
? `${partitionKeyProperty} = '${partitionKeyValue._}'`
: `${partitionKeyProperty} = ${partitionKeyValue._}`);
let currQuery = query;
for (let partitionKeyIndex = 0; partitionKeyIndex < partitionKeys.length; partitionKeyIndex++) {
const partitionKey: CassandraTableKey = partitionKeys[partitionKeyIndex];
const partitionKeyValue: Entities.ITableEntityAttribute = currEntityToDelete[partitionKey.property];
currQuery =
currQuery +
(this.isStringType(partitionKeyValue.$)
? `${partitionKey.property} = '${partitionKeyValue._}'`
: `${partitionKey.property} = ${partitionKeyValue._}`);
if (partitionKeyIndex < partitionKeys.length - 1) {
currQuery = `${currQuery} AND `;
}
}
try { try {
await this.queryDocuments(collection, currQuery); await this.queryDocuments(collection, currQuery);
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted row ${currEntityToDelete.RowKey._}`); NotificationConsoleUtils.logConsoleInfo(`Successfully deleted row ${currEntityToDelete.RowKey._}`);

View File

@@ -340,7 +340,7 @@ describe("Documents tab (noSql API)", () => {
isPreferredApiMongoDB: false, isPreferredApiMongoDB: false,
documentIds: [], documentIds: [],
collection: undefined, collection: undefined,
partitionKey: undefined, partitionKey: { kind: "Hash", paths: ["/foo"], version: 2 },
onLoadStartKey: 0, onLoadStartKey: 0,
tabTitle: "", tabTitle: "",
onExecutionErrorChange: (isExecutionError: boolean): void => { onExecutionErrorChange: (isExecutionError: boolean): void => {

View File

@@ -7,7 +7,10 @@ import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import MongoUtility from "Common/MongoUtility"; import MongoUtility from "Common/MongoUtility";
import { StyleConstants } from "Common/StyleConstants"; import { StyleConstants } from "Common/StyleConstants";
import { createDocument } from "Common/dataAccess/createDocument"; import { createDocument } from "Common/dataAccess/createDocument";
import { deleteDocuments as deleteNoSqlDocuments } from "Common/dataAccess/deleteDocument"; import {
deleteDocument as deleteNoSqlDocument,
deleteDocuments as deleteNoSqlDocuments,
} from "Common/dataAccess/deleteDocument";
import { queryDocuments } from "Common/dataAccess/queryDocuments"; import { queryDocuments } from "Common/dataAccess/queryDocuments";
import { readDocument } from "Common/dataAccess/readDocument"; import { readDocument } from "Common/dataAccess/readDocument";
import { updateDocument } from "Common/dataAccess/updateDocument"; import { updateDocument } from "Common/dataAccess/updateDocument";
@@ -824,7 +827,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}, [initialDocumentContent, selectedDocumentContentBaseline, setSelectedDocumentContent]); }, [initialDocumentContent, selectedDocumentContentBaseline, setSelectedDocumentContent]);
/** /**
* Implementation using bulk delete * Implementation using bulk delete NoSQL API
*/ */
let _deleteDocuments = useCallback( let _deleteDocuments = useCallback(
async (toDeleteDocumentIds: DocumentId[]): Promise<DocumentId[]> => { async (toDeleteDocumentIds: DocumentId[]): Promise<DocumentId[]> => {
@@ -834,7 +837,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
tabTitle, tabTitle,
}); });
setIsExecuting(true); setIsExecuting(true);
return deleteNoSqlDocuments(_collection, toDeleteDocumentIds)
// TODO: Once JS SDK Bug fix for bulk deleting legacy containers (whose systemKey==1) is released:
// Remove the check for systemKey, remove call to deleteNoSqlDocument(). deleteNoSqlDocuments() should always be called.
return (
partitionKey.systemKey
? deleteNoSqlDocument(_collection, toDeleteDocumentIds[0]).then(() => [toDeleteDocumentIds[0]])
: deleteNoSqlDocuments(_collection, toDeleteDocumentIds)
)
.then( .then(
(deletedIds) => { (deletedIds) => {
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
@@ -1800,7 +1810,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
size={tableContainerSizePx} size={tableContainerSizePx}
columnHeaders={columnHeaders} columnHeaders={columnHeaders}
isSelectionDisabled={ isSelectionDisabled={
configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly (partitionKey.systemKey && !isPreferredApiMongoDB) ||
(configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly)
} }
/> />
{tableItems.length > 0 && ( {tableItems.length > 0 && (

View File

@@ -132,7 +132,7 @@ export default class MongoShellTabComponent extends Component<
data: { data: {
resourceId: resourceId, resourceId: resourceId,
accountName: accountName, accountName: accountName,
mongoEndpoint: mongoEndpoint, mongoEndpoint: this._useMongoProxyEndpoint ? documentEndpoint : mongoEndpoint,
authorization: authorization, authorization: authorization,
databaseId: databaseId, databaseId: databaseId,
collectionId: collectionId, collectionId: collectionId,

View File

@@ -52,11 +52,17 @@ export async function fetchDatabaseAccountsFromGraph(
const body = { const body = {
query: databaseAccountsQuery, query: databaseAccountsQuery,
subscriptions: [subscriptionId], subscriptions: [subscriptionId],
...(skipToken && { ...(skipToken
options: { ? {
$skipToken: skipToken, options: {
} as QueryRequestOptions, $skipToken: skipToken,
}), } as QueryRequestOptions,
}
: {
options: {
$top: 150,
} as QueryRequestOptions,
}),
}; };
const response = await fetch(managementResourceGraphAPIURL, { const response = await fetch(managementResourceGraphAPIURL, {

View File

@@ -51,11 +51,13 @@ export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<
do { do {
const body = { const body = {
query: subscriptionsQuery, query: subscriptionsQuery,
...(skipToken && { options: {
options: { $allowPartialScopes: true,
$top: 150,
...(skipToken && {
$skipToken: skipToken, $skipToken: skipToken,
} as QueryRequestOptions, }),
}), } as QueryRequestOptions,
}; };
const response = await fetch(managementResourceGraphAPIURL, { const response = await fetch(managementResourceGraphAPIURL, {