mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-10-17 09:08:29 +01:00
* Document page now loads list of docs and displays selection * DocumentsTabV2 now properly loads documents, show partition keys and display first doc with proper selection behavior. Move it to its own folder. * Extract table in a separate component * Resizable columns on the document table * Fix selection behavior and some layout issue * Adding table scrolling * Fix NaN height issue * Fix NaN height issue * Fix column sizing + cell selection * Improvement in width size. Add Load More * Add react editor and pass column headers * Dynamic columns for pk * Fix initial columns size * Add nav buttons * Editing content updates buttons state * Discard and save buttons working * Fix save new document. Implement delete. * Remove debug display * Fix unexpand filter and reformat * Fix compil issues * Add refresh button * Update column header placeholder style * Implement delete multiple docs * Fix multi delete * Fix show/hide delete button * Fix selection behavior * Fix UX with buttons behavior and editor display * Fix UX issue with not discarding edit changes * Add some TODO's * Remove debugging info and reformat * Add mongo support * Fix build issues * Fix table header. Remove debug statement * Restore broken nosql * Fix mongo save new document/update document * Fix bugs with clicking on newly created documents * Fix comment * Fix double fetch issue when clicking on an item * Auto-select last document when saving new document * Fix resourceTokenPartitionKey code * Fix format * Fix isQueryCopilotSampleContainer flag * Fix unused code * Call tab when updating error flag * Destructure props to make useEffect dependencies work * Fix loadStartKey * minor update * Fix format * Add title to table * Fix table coming off its container with unwanted horizontal scrollbar * Increase table width. Fix eslint issue. * Move refresh documents button from table back to DocumentsTabV2 * Fix load more text centering * Don't show Load More if nothing to show * Fix columns min width * Add keyboard shortcuts * Add keyboard handlers to load more and refresh button * Add keyboard support to select table row * Disable eslint issue from fluent library * Connect cancel query button * Add Fluent V9 theme for Fabric (#1821) * Clean up dependencies and memoize internal functions and object. Move methods and object that don't depend on state outside of component. * Fix filter disappearing when clicking Apply Filter * Fix typo and format * Implement bulk delete for nosql * Replace filter ui components with fluent ui * Remove jquery calls * Migrate unit test to DocumentsTabV2 * Remove DocumentsTab and MongoDocumentsTab. Fix build issues. * Properly handle activetab * Remove comments and unused code * Port keyboard shortcuts from commitId 1f4d0f2 * Port item editor shortcuts to improved Items tab branch (#1831) * set filter focus on Ctrl+Shift+F * implement filter enter/esc keybinds * remove debugging * Collapse filter when query is executed * Fix monaco editor not happy when parent is null * Fix how bulk delete operation gets called when no partition key * Fix update id list after delete * Fix deleteDocuments * Fix build issue * Fix bug in mongo delete * Fix mongo delete flow * Proper error handling in mongo * Handle >100 bulk delete operations * Add unit tests for DocumentsTableComponent * More improvements to table unit tests * Fix import. Disable selection test for now * Add more DocumentsTab unit react tests * Remove selection test * Add more unit tests. Add lcov coverage report to display in vscode * Move unit tests to correct file * Add unit test on command bar * Fix build issues * Add more unit tests * Remove unneeded call * Add DocumentsTab for Mongo API * Fix linting errors * Update fluent ui v9 dependency. Color columns separation. Fix refresh button placement to not interfere with header cell width. * Revert @fluentui/react-components to a safe version that compiles * Add confirmation window when documents have been deleted * Fix mongo unit tests * Fix format * Update src/Common/dataAccess/deleteDocument.ts Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com> * Update src/Common/dataAccess/deleteDocument.ts Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com> * Update src/Common/dataAccess/deleteDocument.ts Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com> * Fix bug with markup. Simplify code. * Protect against creating React editor without parent node * Replace rendering tests with snapshot match * Add test screenshot to troubleshoot e2e test * Revert "Add test screenshot to troubleshoot e2e test" This reverts commit 1b8138ade00571972c0e9bce77846e8fe4a3446a. * Attempt 2 at troubleshooting failing test * Revert "Attempt 2 at troubleshooting failing test" This reverts commit 3e51a593bf2001d44b6e5316d4865e6e861c21c3. * Delete button now shows if one or more rows are selected --------- Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com> Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>
216 lines
8.2 KiB
TypeScript
216 lines
8.2 KiB
TypeScript
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
|
import * as _ from "underscore";
|
|
import * as DataModels from "../Contracts/DataModels";
|
|
import * as ViewModels from "../Contracts/ViewModels";
|
|
import Explorer from "../Explorer/Explorer";
|
|
import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId";
|
|
import { useDatabases } from "../Explorer/useDatabases";
|
|
import { userContext } from "../UserContext";
|
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
|
import { handleError } from "./ErrorHandlingUtils";
|
|
import { createCollection } from "./dataAccess/createCollection";
|
|
import { createDocument } from "./dataAccess/createDocument";
|
|
import { deleteDocument } from "./dataAccess/deleteDocument";
|
|
import { queryDocuments } from "./dataAccess/queryDocuments";
|
|
|
|
export class QueriesClient {
|
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
|
paths: [`/${SavedQueries.PartitionKeyProperty}`],
|
|
kind: "Hash",
|
|
version: BackendDefaults.partitionKeyVersion,
|
|
};
|
|
private static readonly FetchQuery: string = "SELECT * FROM c";
|
|
private static readonly FetchMongoQuery: string = "{}";
|
|
|
|
public constructor(private container: Explorer) {}
|
|
|
|
public async setupQueriesCollection(): Promise<DataModels.Collection> {
|
|
const queriesCollection: ViewModels.Collection = this.findQueriesCollection();
|
|
if (queriesCollection) {
|
|
return Promise.resolve(queriesCollection.rawDataModel);
|
|
}
|
|
|
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
|
|
|
|
if (isServerlessAccount()) {
|
|
return createCollection({
|
|
collectionId: SavedQueries.CollectionName,
|
|
createNewDatabase: true,
|
|
databaseId: SavedQueries.DatabaseName,
|
|
partitionKey: QueriesClient.PartitionKey,
|
|
databaseLevelThroughput: false,
|
|
});
|
|
} else {
|
|
return createCollection({
|
|
collectionId: SavedQueries.CollectionName,
|
|
createNewDatabase: true,
|
|
databaseId: SavedQueries.DatabaseName,
|
|
partitionKey: QueriesClient.PartitionKey,
|
|
offerThroughput: SavedQueries.OfferThroughput,
|
|
databaseLevelThroughput: false,
|
|
})
|
|
.then(
|
|
(collection: DataModels.Collection) => {
|
|
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
|
|
return Promise.resolve(collection);
|
|
},
|
|
(error: any) => {
|
|
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
|
|
return Promise.reject(error);
|
|
},
|
|
)
|
|
.finally(() => clearMessage());
|
|
}
|
|
}
|
|
|
|
public async saveQuery(query: DataModels.Query): Promise<void> {
|
|
const queriesCollection = this.findQueriesCollection();
|
|
if (!queriesCollection) {
|
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
|
return Promise.reject(errorMessage);
|
|
}
|
|
|
|
try {
|
|
this.validateQuery(query);
|
|
} catch (error) {
|
|
const errorMessage: string = "Invalid query specified";
|
|
NotificationConsoleUtils.logConsoleError(`Failed to save query ${query.queryName}: ${errorMessage}`);
|
|
return Promise.reject(errorMessage);
|
|
}
|
|
|
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Saving query ${query.queryName}`);
|
|
query.id = query.queryName;
|
|
return createDocument(queriesCollection, query)
|
|
.then(
|
|
(savedQuery: DataModels.Query) => {
|
|
NotificationConsoleUtils.logConsoleInfo(`Successfully saved query ${query.queryName}`);
|
|
return Promise.resolve();
|
|
},
|
|
(error: any) => {
|
|
if (error.code === HttpStatusCodes.Conflict.toString()) {
|
|
error = `Query ${query.queryName} already exists`;
|
|
}
|
|
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
|
|
return Promise.reject(error);
|
|
},
|
|
)
|
|
.finally(() => clearMessage());
|
|
}
|
|
|
|
public async getQueries(): Promise<DataModels.Query[]> {
|
|
const queriesCollection = this.findQueriesCollection();
|
|
if (!queriesCollection) {
|
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
|
return Promise.reject(errorMessage);
|
|
}
|
|
|
|
const options: any = { enableCrossPartitionQuery: true };
|
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Fetching saved queries");
|
|
const results = await queryDocuments(
|
|
SavedQueries.DatabaseName,
|
|
SavedQueries.CollectionName,
|
|
this.fetchQueriesQuery(),
|
|
options,
|
|
).fetchAll();
|
|
|
|
let queries: DataModels.Query[] = _.map(results.resources, (document: DataModels.Query) => {
|
|
if (!document) {
|
|
return undefined;
|
|
}
|
|
const { id, resourceId, query, queryName } = document;
|
|
const parsedQuery: DataModels.Query = {
|
|
resourceId: resourceId,
|
|
queryName: queryName,
|
|
query: query,
|
|
id: id,
|
|
};
|
|
try {
|
|
this.validateQuery(parsedQuery);
|
|
return parsedQuery;
|
|
} catch (error) {
|
|
return undefined;
|
|
}
|
|
});
|
|
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
|
|
NotificationConsoleUtils.logConsoleInfo("Successfully fetched saved queries");
|
|
clearMessage();
|
|
return queries;
|
|
}
|
|
|
|
public async deleteQuery(query: DataModels.Query): Promise<void> {
|
|
const queriesCollection = this.findQueriesCollection();
|
|
if (!queriesCollection) {
|
|
const errorMessage: string = "Account not set up to perform saved query operations";
|
|
NotificationConsoleUtils.logConsoleError(`Failed to fetch saved queries: ${errorMessage}`);
|
|
return Promise.reject(errorMessage);
|
|
}
|
|
|
|
try {
|
|
this.validateQuery(query);
|
|
} catch (error) {
|
|
const errorMessage: string = "Invalid query specified";
|
|
NotificationConsoleUtils.logConsoleError(`Failed to delete query ${query.queryName}: ${errorMessage}`);
|
|
}
|
|
|
|
const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Deleting query ${query.queryName}`);
|
|
query.id = query.queryName;
|
|
const documentId = new DocumentId(
|
|
{
|
|
partitionKey: QueriesClient.PartitionKey,
|
|
partitionKeyProperties: ["id"],
|
|
} as IDocumentIdContainer,
|
|
query,
|
|
[query.queryName],
|
|
);
|
|
const options: any = { partitionKey: query.resourceId };
|
|
return deleteDocument(queriesCollection, documentId)
|
|
.then(
|
|
() => {
|
|
NotificationConsoleUtils.logConsoleInfo(`Successfully deleted query ${query.queryName}`);
|
|
return Promise.resolve();
|
|
},
|
|
(error: any) => {
|
|
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
|
|
return Promise.reject(error);
|
|
},
|
|
)
|
|
.finally(() => clearMessage());
|
|
}
|
|
|
|
public getResourceId(): string {
|
|
const { databaseAccount, subscriptionId = "", resourceGroup = "" } = userContext;
|
|
const databaseAccountName = databaseAccount?.name || "";
|
|
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
|
|
}
|
|
|
|
private findQueriesCollection(): ViewModels.Collection {
|
|
const queriesDatabase: ViewModels.Database = _.find(
|
|
useDatabases.getState().databases,
|
|
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName,
|
|
);
|
|
if (!queriesDatabase) {
|
|
return undefined;
|
|
}
|
|
return _.find(
|
|
queriesDatabase.collections(),
|
|
(collection: ViewModels.Collection) => collection.id() === SavedQueries.CollectionName,
|
|
);
|
|
}
|
|
|
|
private validateQuery(query: DataModels.Query): void {
|
|
if (!query || query.queryName == null || query.query == null || query.resourceId == null) {
|
|
throw new Error("Invalid query specified");
|
|
}
|
|
}
|
|
|
|
private fetchQueriesQuery(): string {
|
|
if (userContext.apiType === "Mongo") {
|
|
return QueriesClient.FetchMongoQuery;
|
|
}
|
|
return QueriesClient.FetchQuery;
|
|
}
|
|
}
|