Compare commits

...

12 Commits

Author SHA1 Message Date
Satyapriya Bai
5659e74754 Resolved Comments and enhanced code. 2024-07-01 13:20:41 +05:30
Satyapriya Bai
199ce64bdb Merge branch 'master' of https://github.com/Azure/cosmos-explorer into 2819223 2024-07-01 13:18:45 +05:30
Satyapriya Bai
e984719991 reset. 2024-07-01 13:17:27 +05:30
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
MokireddySampath
f6ebb20ff1 Update QueryCopilotPromptbar.tsx 2024-01-30 00:24:05 +05:30
MokireddySampath
8599f1d98c Merge branch 'master' into 2819223 2024-01-04 22:09:47 +05:30
Sampath
f6e8fb012d lint changes done 2023-12-11 23:54:28 +05:30
Sampath
13fafb624e keyboard navigation has been added to the suggestions under the querycopilot search bar 2023-12-11 23:44:54 +05:30
9 changed files with 118 additions and 59 deletions

View File

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

View File

@@ -20,7 +20,7 @@
outline-offset: 2px;
}
.outlineNone{
.outlineNone {
outline: none !important;
}
@@ -28,3 +28,8 @@
.deleteQuery:focus::after {
outline: none !important;
}
.highlightedButtonStyles {
outline: 1px dashed back;
background-color: #ebebeb;
}

View File

@@ -34,12 +34,13 @@ import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { userContext } from "UserContext";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React, { useRef, useState } from "react";
import React, { useCallback, useRef, useState } from "react";
import HintIcon from "../../../images/Hint.svg";
import RecentIcon from "../../../images/Recent.svg";
import errorIcon from "../../../images/close-black.svg";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { useTabs } from "../../hooks/useTabs";
import "../Controls/ThroughputInput/ThroughputInput.less";
import { useCopilotStore } from "../QueryCopilot/QueryCopilotContext";
import { useSelectedNode } from "../useSelectedNode";
@@ -108,7 +109,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
setErrorMessage,
errorMessage,
} = useCopilotStore();
const [focusedIndex, setFocusedIndex] = useState(-1);
const sampleProps: SamplePromptsProps = {
isSamplePromptsOpen: isSamplePromptsOpen,
setIsSamplePromptsOpen: setIsSamplePromptsOpen,
@@ -301,6 +302,41 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
return "Content is updated";
}
};
const openSamplePrompts = () => {
inputEdited.current = true;
setShowSamplePrompts(true);
};
const suggestionHistory = filteredHistories.concat(filteredSuggestedPrompts.map((item) => item.text));
const handleKeyDown: React.KeyboardEventHandler = (e) => {
const { key } = e;
if (key === "ArrowDown") {
setFocusedIndex((focusedIndex + 1) % suggestionHistory.length);
}
if (key === "ArrowUp") {
setFocusedIndex((focusedIndex + suggestionHistory.length - 1) % suggestionHistory.length);
}
if (key === "Enter") {
e.preventDefault();
handleSelection(focusedIndex);
}
};
const handleSelection = (selectedIndex: number) => {
const selectedItem = suggestionHistory[selectedIndex];
if (!selectedItem) {
return resetSearchComplete();
} else {
handlePromptSet(selectedItem);
}
};
const resetSearchComplete = useCallback(() => {
setFocusedIndex(-1);
setShowSamplePrompts(false);
}, []);
const handlePromptSet = (promptText: string) => {
inputEdited.current = true;
setUserPrompt(promptText);
};
React.useEffect(() => {
useTabs.getState().setIsQueryErrorThrown(false);
@@ -331,23 +367,13 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
id="naturalLanguageInput"
value={userPrompt}
onChange={handleUserPromptChange}
onClick={() => {
inputEdited.current = true;
setShowSamplePrompts(true);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && userPrompt) {
inputEdited.current = true;
startGenerateQueryProcess();
}
}}
onClick={openSamplePrompts}
onFocus={() => setShowSamplePrompts(true)}
onKeyDown={handleKeyDown}
style={{ lineHeight: 30 }}
styles={{
root: { width: "100%" },
suffix: {
background: "none",
padding: 0,
},
suffix: { background: "none", padding: 0 },
fieldGroup: {
borderRadius: 4,
borderColor: "#D1D1D1",
@@ -360,7 +386,8 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
},
}}
disabled={isGeneratingQuery}
autoComplete="off"
autoComplete="list"
aria-expanded={showSamplePrompts}
placeholder="Ask a question in natural language and well generate the query for you."
aria-labelledby="copilot-textfield-label"
onRenderSuffix={() => {
@@ -369,7 +396,10 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
iconProps={{ iconName: "Send" }}
disabled={isGeneratingQuery || !userPrompt.trim()}
style={{ background: "none" }}
onClick={() => startGenerateQueryProcess()}
onClick={() => {
startGenerateQueryProcess();
setShowSamplePrompts(false);
}}
aria-label="Send"
/>
);
@@ -434,6 +464,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
}}
onRenderIcon={() => <Image src={RecentIcon} styles={{ root: { overflow: "unset" } }} />}
styles={promptStyles}
className={focusedIndex === i ? "highlightedButtonStyles" : "buttonstyles"}
>
{history}
</DefaultButton>
@@ -454,7 +485,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
>
Suggested Prompts
</Text>
{filteredSuggestedPrompts.map((prompt) => (
{filteredSuggestedPrompts.map((prompt, index) => (
<DefaultButton
key={prompt.id}
onClick={() => {
@@ -464,6 +495,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
}}
onRenderIcon={() => <Image src={HintIcon} />}
styles={promptStyles}
className={focusedIndex === filteredHistories.length + index ? "highlightedButtonStyles" : ""}
>
{prompt.text}
</DefaultButton>

View File

@@ -363,18 +363,24 @@ export class CassandraAPIDataClient extends TableDataClient {
entitiesToDelete: Entities.ITableEntity[],
): Promise<any> {
const query = `DELETE FROM ${collection.databaseId}.${collection.id()} WHERE `;
const partitionKeyProperty = this.getCassandraPartitionKeyProperty(collection);
const partitionKeys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys;
await Promise.all(
entitiesToDelete.map(async (currEntityToDelete: Entities.ITableEntity) => {
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 {
await this.queryDocuments(collection, currQuery);
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted row ${currEntityToDelete.RowKey._}`);

View File

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

View File

@@ -7,7 +7,10 @@ import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import MongoUtility from "Common/MongoUtility";
import { StyleConstants } from "Common/StyleConstants";
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 { readDocument } from "Common/dataAccess/readDocument";
import { updateDocument } from "Common/dataAccess/updateDocument";
@@ -824,7 +827,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}, [initialDocumentContent, selectedDocumentContentBaseline, setSelectedDocumentContent]);
/**
* Implementation using bulk delete
* Implementation using bulk delete NoSQL API
*/
let _deleteDocuments = useCallback(
async (toDeleteDocumentIds: DocumentId[]): Promise<DocumentId[]> => {
@@ -834,7 +837,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
tabTitle,
});
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(
(deletedIds) => {
TelemetryProcessor.traceSuccess(
@@ -1800,7 +1810,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
size={tableContainerSizePx}
columnHeaders={columnHeaders}
isSelectionDisabled={
configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly
(partitionKey.systemKey && !isPreferredApiMongoDB) ||
(configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly)
}
/>
{tableItems.length > 0 && (

View File

@@ -1,6 +1,5 @@
import { useMongoProxyEndpoint } from "Common/MongoProxyClient";
import React, { Component } from "react";
import * as Constants from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
@@ -113,12 +112,6 @@ export default class MongoShellTabComponent extends Component<
const resourceId = databaseAccount?.id;
const accountName = databaseAccount?.name;
const documentEndpoint = databaseAccount?.properties.mongoEndpoint || databaseAccount?.properties.documentEndpoint;
const mongoEndpoint =
documentEndpoint.substr(
Constants.MongoDBAccounts.protocol.length + 3,
documentEndpoint.length -
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length),
) + Constants.MongoDBAccounts.defaultPort.toString();
const databaseId = this.props.collection.databaseId;
const collectionId = this.props.collection.id();
const apiEndpoint = this._useMongoProxyEndpoint
@@ -132,7 +125,7 @@ export default class MongoShellTabComponent extends Component<
data: {
resourceId: resourceId,
accountName: accountName,
mongoEndpoint: mongoEndpoint,
mongoEndpoint: documentEndpoint,
authorization: authorization,
databaseId: databaseId,
collectionId: collectionId,

View File

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

View File

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