mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-29 00:47:01 +00:00
Remove DocumentsTab and MongoDocumentsTab. Fix build issues.
This commit is contained in:
parent
1f4d0f21f0
commit
b8cbd77069
@ -89,10 +89,7 @@ src/Explorer/Tables/TableEntityProcessor.ts
|
|||||||
src/Explorer/Tables/Utilities.ts
|
src/Explorer/Tables/Utilities.ts
|
||||||
src/Explorer/Tabs/ConflictsTab.ts
|
src/Explorer/Tabs/ConflictsTab.ts
|
||||||
src/Explorer/Tabs/DatabaseSettingsTab.ts
|
src/Explorer/Tabs/DatabaseSettingsTab.ts
|
||||||
src/Explorer/Tabs/DocumentsTab.test.ts
|
|
||||||
src/Explorer/Tabs/DocumentsTab.ts
|
|
||||||
src/Explorer/Tabs/GraphTab.ts
|
src/Explorer/Tabs/GraphTab.ts
|
||||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
|
||||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||||
src/Explorer/Tabs/ScriptTabBase.ts
|
src/Explorer/Tabs/ScriptTabBase.ts
|
||||||
src/Explorer/Tabs/TabComponents.ts
|
src/Explorer/Tabs/TabComponents.ts
|
||||||
|
@ -3,8 +3,7 @@ import * as _ from "underscore";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
|
||||||
import { useDatabases } from "../Explorer/useDatabases";
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
@ -162,10 +161,10 @@ export class QueriesClient {
|
|||||||
{
|
{
|
||||||
partitionKey: QueriesClient.PartitionKey,
|
partitionKey: QueriesClient.PartitionKey,
|
||||||
partitionKeyProperties: ["id"],
|
partitionKeyProperties: ["id"],
|
||||||
} as DocumentsTab,
|
} as IDocumentIdContainer,
|
||||||
query,
|
query,
|
||||||
[query.queryName],
|
[query.queryName],
|
||||||
); // TODO: Remove DocumentId's dependency on DocumentsTab
|
);
|
||||||
const options: any = { partitionKey: query.resourceId };
|
const options: any = { partitionKey: query.resourceId };
|
||||||
return deleteDocument(queriesCollection, documentId)
|
return deleteDocument(queriesCollection, documentId)
|
||||||
.then(
|
.then(
|
||||||
|
@ -1,239 +0,0 @@
|
|||||||
<div
|
|
||||||
class="tab-pane active tabdocuments flexContainer"
|
|
||||||
data-bind="
|
|
||||||
setTemplateReady: true,
|
|
||||||
attr:{
|
|
||||||
id: tabId
|
|
||||||
},
|
|
||||||
visible: isActive"
|
|
||||||
role="tabpanel"
|
|
||||||
>
|
|
||||||
<!-- ko if: false -->
|
|
||||||
<!-- Messagebox Ok Cancel- Start -->
|
|
||||||
<div class="messagebox-background">
|
|
||||||
<div class="messagebox">
|
|
||||||
<h2 class="messagebox-title">Title</h2>
|
|
||||||
<div class="messagebox-text" tabindex="0">Text</div>
|
|
||||||
<div class="messagebox-buttons">
|
|
||||||
<div class="messagebox-buttons-container">
|
|
||||||
<button value="ok" class="messagebox-button-primary">Ok</button>
|
|
||||||
<button value="cancel" class="messagebox-button-default">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Messagebox OK Cancel - End -->
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<!-- Filter - Start -->
|
|
||||||
<div class="filterdivs" data-bind="visible: isFilterCreated">
|
|
||||||
<!-- Read-only Filter - Start -->
|
|
||||||
<div class="filterDocCollapsed" data-bind="visible: !isFilterExpanded() && !isPreferredApiMongoDB">
|
|
||||||
<span class="selectQuery">SELECT * FROM c</span>
|
|
||||||
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
|
|
||||||
<button class="filterbtnstyle queryButton" data-bind="click: onShowFilterClick">Edit Filter</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="filterDocCollapsed"
|
|
||||||
data-bind="
|
|
||||||
visible: !isFilterExpanded() && isPreferredApiMongoDB"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="selectQuery"
|
|
||||||
data-bind="
|
|
||||||
visible: appliedFilter().length > 0"
|
|
||||||
>Filter :
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="noFilterApplied"
|
|
||||||
data-bind="
|
|
||||||
visible: !appliedFilter().length > 0"
|
|
||||||
>No filter applied</span
|
|
||||||
>
|
|
||||||
<span class="appliedQuery" data-bind="text: appliedFilter"></span>
|
|
||||||
<button
|
|
||||||
class="filterbtnstyle queryButton"
|
|
||||||
data-bind="
|
|
||||||
click: onShowFilterClick"
|
|
||||||
>
|
|
||||||
Edit Filter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Read-only Filter - End -->
|
|
||||||
|
|
||||||
<!-- Editable Filter - start -->
|
|
||||||
<div
|
|
||||||
class="filterDocExpanded"
|
|
||||||
data-bind="
|
|
||||||
visible: isFilterExpanded"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div class="editFilterContainer">
|
|
||||||
<span class="filterspan" data-bind="visible: !isPreferredApiMongoDB"> SELECT * FROM c </span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
list="filtersList"
|
|
||||||
class="querydropdown"
|
|
||||||
title="Type a query predicate or choose one from the list."
|
|
||||||
data-bind="
|
|
||||||
attr:{
|
|
||||||
placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.'
|
|
||||||
},
|
|
||||||
css: { placeholderVisible: filterContent().length === 0 },
|
|
||||||
textInput: filterContent,
|
|
||||||
event: { keydown: onFilterKeyDown }"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<datalist
|
|
||||||
id="filtersList"
|
|
||||||
data-bind="
|
|
||||||
foreach: lastFilterContents"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
data-bind="
|
|
||||||
value: $data"
|
|
||||||
></option>
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<span class="filterbuttonpad">
|
|
||||||
<button
|
|
||||||
class="filterbtnstyle queryButton"
|
|
||||||
data-bind="
|
|
||||||
click: refreshDocumentsGrid.bind($data, true),
|
|
||||||
enable: applyFilterButton.enabled"
|
|
||||||
aria-label="Apply filter"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
Apply Filter
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<span class="filterbuttonpad">
|
|
||||||
<button
|
|
||||||
class="filterbtnstyle queryButton"
|
|
||||||
data-bind="
|
|
||||||
visible: !isPreferredApiMongoDB && isExecuting,
|
|
||||||
click: onAbortQueryClick"
|
|
||||||
aria-label="Cancel Query"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
Cancel Query
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="filterclose"
|
|
||||||
role="button"
|
|
||||||
aria-label="close filter"
|
|
||||||
tabindex="0"
|
|
||||||
data-bind="
|
|
||||||
click: onHideFilterClick, event: { keydown: onCloseButtonKeyDown }"
|
|
||||||
>
|
|
||||||
<img src="/close-black.svg" style="height: 14px; width: 14px" alt="Hide filter" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Editable Filter - End -->
|
|
||||||
</div>
|
|
||||||
<!-- Filter - End -->
|
|
||||||
|
|
||||||
<!-- Ids and Editor - Start -->
|
|
||||||
<div class="documentsTabGridAndEditor">
|
|
||||||
<div class="documentsContainerWithSplitter" , data-bind="attr: { id: documentContentsContainerId }">
|
|
||||||
<div class="flexContainer">
|
|
||||||
<!-- Document Ids - Start -->
|
|
||||||
<div
|
|
||||||
class="documentsGridHeaderContainer tabdocuments scrollable"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
id: documentContentsGridId,
|
|
||||||
tabindex: documentIds().length <= 0 ? -1 : 0
|
|
||||||
},
|
|
||||||
style: { height: dataContentsGridScrollHeight },
|
|
||||||
event: { keydown: accessibleDocumentList.onKeyDown }"
|
|
||||||
>
|
|
||||||
<table id="tabsTable" class="table table-hover can-select dataTable">
|
|
||||||
<thead id="theadcontent">
|
|
||||||
<tr>
|
|
||||||
<th class="documentsGridHeader" data-bind="text: idHeader" tabindex="0"></th>
|
|
||||||
<!-- ko if: showPartitionKey -->
|
|
||||||
<!-- ko foreach: partitionKeyPropertyHeaders -->
|
|
||||||
<th
|
|
||||||
class="documentsGridHeader documentsGridPartition evenlySpacedHeader"
|
|
||||||
data-bind="
|
|
||||||
attr: {
|
|
||||||
title: $data
|
|
||||||
},
|
|
||||||
text: $data"
|
|
||||||
tabindex="0"
|
|
||||||
></th>
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- /ko -->
|
|
||||||
<th
|
|
||||||
class="refreshColHeader"
|
|
||||||
role="button"
|
|
||||||
aria-label="Refresh documents"
|
|
||||||
data-bind="event: { keydown: onRefreshButtonKeyDown }"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="refreshcol"
|
|
||||||
src="/refresh-cosmos.svg"
|
|
||||||
data-bind="click: refreshDocumentsGrid.bind($data, false)"
|
|
||||||
alt="Refresh documents"
|
|
||||||
tabindex="0"
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tbodycontent">
|
|
||||||
<!-- ko foreach: documentIds -->
|
|
||||||
<tr
|
|
||||||
class="pointer accessibleListElement"
|
|
||||||
data-bind="
|
|
||||||
click: $data.click,
|
|
||||||
css: {
|
|
||||||
gridRowSelected: $parent.selectedDocumentId && $parent.selectedDocumentId() && $parent.selectedDocumentId().rid === $data.rid,
|
|
||||||
gridRowHighlighted: $parent.accessibleDocumentList.currentItem() && $parent.accessibleDocumentList.currentItem().rid === $data.rid
|
|
||||||
}"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<td class="tabdocumentsGridElement"><a data-bind="text: $data.id, attr: { title: $data.id }"></a></td>
|
|
||||||
<!-- ko if: $data.partitionKeyProperties -->
|
|
||||||
<!-- ko foreach: $data.stringPartitionKeyValues -->
|
|
||||||
<td class="tabdocumentsGridElement" data-bind="colspan: $parent.stringPartitionKeyValues.length + 1">
|
|
||||||
<a data-bind="text: $data, attr: { title: $data }"></a>
|
|
||||||
</td>
|
|
||||||
<!-- /ko -->
|
|
||||||
<!-- /ko -->
|
|
||||||
</tr>
|
|
||||||
<!-- /ko -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="loadMore">
|
|
||||||
<a
|
|
||||||
role="button"
|
|
||||||
data-bind="click: loadNextPage.bind($data, false), event: { keypress: onLoadMoreKeyInput }"
|
|
||||||
tabindex="0"
|
|
||||||
>Load more</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<!-- Document Ids - End -->
|
|
||||||
<!-- Splitter -->
|
|
||||||
</div>
|
|
||||||
<div class="splitter ui-resizable-handle ui-resizable-e colResizePointer" id="h_splitter2"></div>
|
|
||||||
</div>
|
|
||||||
<div class="documentWaterMark" data-bind="visible: !shouldShowEditor()">
|
|
||||||
<p><img src="/DocumentWaterMark.svg" alt="Document WaterMark" /></p>
|
|
||||||
<p class="documentWaterMarkText">Create new or work with existing document(s).</p>
|
|
||||||
</div>
|
|
||||||
<!-- Editor - Start -->
|
|
||||||
<json-editor
|
|
||||||
class="editorDivContent"
|
|
||||||
data-bind="visible: shouldShowEditor, css: { mongoDocumentEditor: isPreferredApiMongoDB }"
|
|
||||||
params="{content: initialDocumentContent, isReadOnly: false,lineNumbers: 'on',ariaLabel: 'Document editor',
|
|
||||||
updatedContent: selectedDocumentContent}"
|
|
||||||
></json-editor>
|
|
||||||
<!-- Editor - End -->
|
|
||||||
</div>
|
|
||||||
<!-- Ids and Editor - End -->
|
|
||||||
</div>
|
|
@ -1,152 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import { updateUserContext } from "../../UserContext";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import DocumentId from "../Tree/DocumentId";
|
|
||||||
import DocumentsTab from "./DocumentsTab";
|
|
||||||
|
|
||||||
describe("Documents tab", () => {
|
|
||||||
describe("buildQuery", () => {
|
|
||||||
it("should generate the right select query for SQL API", () => {
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.buildQuery("")).toContain("select");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("showPartitionKey", () => {
|
|
||||||
const explorer = new Explorer();
|
|
||||||
const mongoExplorer = new Explorer();
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableGremlin" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionWithoutPartitionKey = <ViewModels.Collection>(<unknown>{
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
database: {
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
},
|
|
||||||
container: explorer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
database: {
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
},
|
|
||||||
partitionKey: {
|
|
||||||
paths: ["/foo"],
|
|
||||||
kind: "Hash",
|
|
||||||
version: 2,
|
|
||||||
systemKey: true,
|
|
||||||
},
|
|
||||||
container: explorer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionWithNonSystemPartitionKey = <ViewModels.Collection>(<unknown>{
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
database: {
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
},
|
|
||||||
partitionKey: {
|
|
||||||
paths: ["/foo"],
|
|
||||||
kind: "Hash",
|
|
||||||
version: 2,
|
|
||||||
systemKey: false,
|
|
||||||
},
|
|
||||||
container: explorer,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mongoCollectionWithSystemPartitionKey = <ViewModels.Collection>(<unknown>{
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
database: {
|
|
||||||
id: ko.observable<string>("foo"),
|
|
||||||
},
|
|
||||||
partitionKey: {
|
|
||||||
paths: ["/foo"],
|
|
||||||
kind: "Hash",
|
|
||||||
version: 2,
|
|
||||||
systemKey: true,
|
|
||||||
},
|
|
||||||
container: mongoExplorer,
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false for null or undefined collection", () => {
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false for null or undefined partitionKey", () => {
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
collection: collectionWithoutPartitionKey,
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true for non-Mongo accounts with system partitionKey", () => {
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
collection: collectionWithSystemPartitionKey,
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false for Mongo accounts with system partitionKey", () => {
|
|
||||||
updateUserContext({
|
|
||||||
apiType: "Mongo",
|
|
||||||
});
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
collection: mongoCollectionWithSystemPartitionKey,
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true for non-system partitionKey", () => {
|
|
||||||
const documentsTab = new DocumentsTab({
|
|
||||||
collection: collectionWithNonSystemPartitionKey,
|
|
||||||
partitionKey: null,
|
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
||||||
title: "",
|
|
||||||
tabPath: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(documentsTab.showPartitionKey).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
File diff suppressed because it is too large
Load Diff
@ -15,9 +15,9 @@ import { Platform, configContext } from "ConfigContext";
|
|||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||||
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
|
||||||
import DocumentsTab from "Explorer/Tabs/DocumentsTab";
|
|
||||||
import { getPlatformTheme } from "Explorer/Theme/ThemeUtil";
|
import { getPlatformTheme } from "Explorer/Theme/ThemeUtil";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
@ -31,6 +31,7 @@ import { format } from "react-string-format";
|
|||||||
import { CSSProperties } from "styled-components";
|
import { CSSProperties } from "styled-components";
|
||||||
import DeleteDocumentIcon from "../../../../images/DeleteDocument.svg";
|
import DeleteDocumentIcon from "../../../../images/DeleteDocument.svg";
|
||||||
import NewDocumentIcon from "../../../../images/NewDocument.svg";
|
import NewDocumentIcon from "../../../../images/NewDocument.svg";
|
||||||
|
import UploadIcon from "../../../../images/Upload_16x16.svg";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
import SaveIcon from "../../../../images/save-cosmos.svg";
|
import SaveIcon from "../../../../images/save-cosmos.svg";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
@ -228,6 +229,25 @@ type ButtonsDependencies = {
|
|||||||
onDeleteExistingDocumentsClick: UiKeyboardEvent;
|
onDeleteExistingDocumentsClick: UiKeyboardEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createUploadButton = (container: Explorer): CommandButtonComponentProps => {
|
||||||
|
const label = "Upload Item";
|
||||||
|
return {
|
||||||
|
id: "uploadItemBtn",
|
||||||
|
iconSrc: UploadIcon,
|
||||||
|
iconAlt: label,
|
||||||
|
onCommandClick: () => {
|
||||||
|
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
|
||||||
|
selectedCollection && container.openUploadItemsPanePane();
|
||||||
|
},
|
||||||
|
commandButtonLabel: label,
|
||||||
|
ariaLabel: label,
|
||||||
|
hasPopup: true,
|
||||||
|
disabled:
|
||||||
|
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
|
||||||
|
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const getTabsButtons = ({
|
const getTabsButtons = ({
|
||||||
_collection,
|
_collection,
|
||||||
selectedRows,
|
selectedRows,
|
||||||
@ -344,7 +364,7 @@ const getTabsButtons = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isPreferredApiMongoDB) {
|
if (!isPreferredApiMongoDB) {
|
||||||
buttons.push(DocumentsTab._createUploadButton(_collection.container));
|
buttons.push(createUploadButton(_collection.container));
|
||||||
}
|
}
|
||||||
|
|
||||||
return buttons;
|
return buttons;
|
||||||
@ -1671,12 +1691,12 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
<div
|
<div
|
||||||
className="tab-pane active"
|
className="tab-pane active"
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
setTemplateReady: true,
|
setTemplateReady: true,
|
||||||
attr:{
|
attr:{
|
||||||
id: tabId
|
id: tabId
|
||||||
},
|
},
|
||||||
visible: isActive"
|
visible: isActive"
|
||||||
*/
|
*/
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
style={{ display: "flex" }}
|
style={{ display: "flex" }}
|
||||||
>
|
>
|
||||||
@ -1769,9 +1789,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
onClick={() => refreshDocumentsGrid(true)}
|
onClick={() => refreshDocumentsGrid(true)}
|
||||||
disabled={!applyFilterButton.enabled}
|
disabled={!applyFilterButton.enabled}
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
click: refreshDocumentsGrid.bind($data, true),
|
click: refreshDocumentsGrid.bind($data, true),
|
||||||
enable: applyFilterButton.enabled"
|
enable: applyFilterButton.enabled"
|
||||||
*/
|
*/
|
||||||
aria-label="Apply filter"
|
aria-label="Apply filter"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
@ -1784,9 +1804,9 @@ const DocumentsTabComponent: React.FunctionComponent<{
|
|||||||
style={filterButtonStyle}
|
style={filterButtonStyle}
|
||||||
appearance="primary"
|
appearance="primary"
|
||||||
/* data-bind="
|
/* data-bind="
|
||||||
visible: !isPreferredApiMongoDB && isExecuting,
|
visible: !isPreferredApiMongoDB && isExecuting,
|
||||||
click: onAbortQueryClick"
|
click: onAbortQueryClick"
|
||||||
*/
|
*/
|
||||||
aria-label="Cancel Query"
|
aria-label="Cancel Query"
|
||||||
onClick={() => queryAbortController.abort()}
|
onClick={() => queryAbortController.abort()}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -1,320 +0,0 @@
|
|||||||
import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos";
|
|
||||||
import { extractPartitionKeyValues } from "Utils/QueryUtils";
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import Q from "q";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
||||||
import * as Logger from "../../Common/Logger";
|
|
||||||
import {
|
|
||||||
createDocument,
|
|
||||||
deleteDocument,
|
|
||||||
queryDocuments,
|
|
||||||
readDocument,
|
|
||||||
updateDocument,
|
|
||||||
} from "../../Common/MongoProxyClient";
|
|
||||||
import MongoUtility from "../../Common/MongoUtility";
|
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { useDialog } from "../Controls/Dialog";
|
|
||||||
import DocumentId from "../Tree/DocumentId";
|
|
||||||
import ObjectId from "../Tree/ObjectId";
|
|
||||||
import DocumentsTab from "./DocumentsTab";
|
|
||||||
|
|
||||||
export default class MongoDocumentsTab extends DocumentsTab {
|
|
||||||
public collection: ViewModels.Collection;
|
|
||||||
private continuationToken: string;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.DocumentsTabOptions) {
|
|
||||||
super(options);
|
|
||||||
this.lastFilterContents = ko.observableArray<string>(['{"id":"foo"}', "{ qty: { $gte: 20 } }"]);
|
|
||||||
|
|
||||||
this.partitionKeyProperties = this.partitionKeyProperties?.map((partitionKeyProperty, i) => {
|
|
||||||
if (partitionKeyProperty && ~partitionKeyProperty.indexOf(`"`)) {
|
|
||||||
partitionKeyProperty = partitionKeyProperty.replace(/["]+/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (partitionKeyProperty && partitionKeyProperty.indexOf("$v") > -1) {
|
|
||||||
// From $v.shard.$v.key.$v > shard.key
|
|
||||||
partitionKeyProperty = partitionKeyProperty.replace(/.\$v/g, "").replace(/\$v./g, "");
|
|
||||||
this.partitionKeyPropertyHeaders[i] = "/" + partitionKeyProperty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return partitionKeyProperty;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isFilterExpanded = ko.observable<boolean>(true);
|
|
||||||
super.buildCommandBarOptions.bind(this);
|
|
||||||
super.buildCommandBarOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSaveNewDocumentClick = (): Promise<any> => {
|
|
||||||
const documentContent = JSON.parse(this.selectedDocumentContent());
|
|
||||||
this.displayedError("");
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDocument, {
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const partitionKeyProperty = this.partitionKeyProperties?.[0];
|
|
||||||
if (partitionKeyProperty !== "_id" && !this._hasShardKeySpecified(documentContent)) {
|
|
||||||
const message = `The document is lacking the shard property: ${partitionKeyProperty}`;
|
|
||||||
this.displayedError(message);
|
|
||||||
let that = this;
|
|
||||||
setTimeout(() => {
|
|
||||||
that.displayedError("");
|
|
||||||
}, Constants.ClientDefaults.errorNotificationTimeoutMs);
|
|
||||||
this.isExecutionError(true);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.CreateDocument,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: message,
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
Logger.logError("Failed to save new document: Document shard key not defined", "MongoDocumentsTab");
|
|
||||||
throw new Error("Document without shard key");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isExecutionError(false);
|
|
||||||
this.isExecuting(true);
|
|
||||||
return createDocument(
|
|
||||||
this.collection.databaseId,
|
|
||||||
this.collection,
|
|
||||||
this.partitionKeyProperties?.[0],
|
|
||||||
documentContent,
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
(savedDocument: any) => {
|
|
||||||
const partitionKeyArray: PartitionKey[] = extractPartitionKeyValues(
|
|
||||||
savedDocument,
|
|
||||||
this._getPartitionKeyDefinition() as PartitionKeyDefinition,
|
|
||||||
);
|
|
||||||
|
|
||||||
let id = new ObjectId(this, savedDocument, partitionKeyArray);
|
|
||||||
let ids = this.documentIds();
|
|
||||||
ids.push(id);
|
|
||||||
delete savedDocument._self;
|
|
||||||
|
|
||||||
let value: string = this.renderObjectForEditor(savedDocument || {}, null, 4);
|
|
||||||
this.selectedDocumentContent.setBaseline(value);
|
|
||||||
|
|
||||||
this.selectedDocumentId(id);
|
|
||||||
this.documentIds(ids);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.CreateDocument,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.isExecutionError(true);
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
useDialog.getState().showOkModalDialog("Create document failed", errorMessage);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.CreateDocument,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.finally(() => this.isExecuting(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
public onSaveExistingDocumentClick = (): Promise<any> => {
|
|
||||||
const selectedDocumentId = this.selectedDocumentId();
|
|
||||||
const documentContent = this.selectedDocumentContent();
|
|
||||||
this.isExecutionError(false);
|
|
||||||
this.isExecuting(true);
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.UpdateDocument, {
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return updateDocument(this.collection.databaseId, this.collection, selectedDocumentId, documentContent)
|
|
||||||
.then(
|
|
||||||
(updatedDocument: any) => {
|
|
||||||
let value: string = this.renderObjectForEditor(updatedDocument || {}, null, 4);
|
|
||||||
this.selectedDocumentContent.setBaseline(value);
|
|
||||||
|
|
||||||
this.documentIds().forEach((documentId: DocumentId) => {
|
|
||||||
if (documentId.rid === updatedDocument._rid) {
|
|
||||||
const partitionKeyArray: PartitionKey[] = extractPartitionKeyValues(
|
|
||||||
updatedDocument,
|
|
||||||
this._getPartitionKeyDefinition() as PartitionKeyDefinition,
|
|
||||||
);
|
|
||||||
|
|
||||||
const id = new ObjectId(this, updatedDocument, partitionKeyArray);
|
|
||||||
documentId.id(id.id());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.exisitingDocumentNoEdits);
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.UpdateDocument,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
this.isExecutionError(true);
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.UpdateDocument,
|
|
||||||
{
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.finally(() => this.isExecuting(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
public buildQuery(filter: string): string {
|
|
||||||
return filter || "{}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public async selectDocument(documentId: DocumentId): Promise<void> {
|
|
||||||
this.selectedDocumentId(documentId);
|
|
||||||
const content = await readDocument(this.collection.databaseId, this.collection, documentId);
|
|
||||||
this.initDocumentEditor(documentId, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadNextPage(): Q.Promise<any> {
|
|
||||||
this.isExecuting(true);
|
|
||||||
this.isExecutionError(false);
|
|
||||||
const filter: string = this.filterContent().trim();
|
|
||||||
const query: string = this.buildQuery(filter);
|
|
||||||
|
|
||||||
return Q(queryDocuments(this.collection.databaseId, this.collection, true, query, this.continuationToken))
|
|
||||||
.then(
|
|
||||||
({ continuationToken, documents }) => {
|
|
||||||
this.continuationToken = continuationToken;
|
|
||||||
let currentDocuments = this.documentIds();
|
|
||||||
const currentDocumentsRids = currentDocuments.map((currentDocument) => currentDocument.rid);
|
|
||||||
const nextDocumentIds = documents
|
|
||||||
.filter((d: any) => {
|
|
||||||
return currentDocumentsRids.indexOf(d._rid) < 0;
|
|
||||||
})
|
|
||||||
.map((rawDocument: any) => {
|
|
||||||
const partitionKeyValue = rawDocument._partitionKeyValue;
|
|
||||||
return new DocumentId(this, rawDocument, [partitionKeyValue]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const merged = currentDocuments.concat(nextDocumentIds);
|
|
||||||
|
|
||||||
this.documentIds(merged);
|
|
||||||
currentDocuments = this.documentIds();
|
|
||||||
if (this.filterContent().length > 0 && currentDocuments.length > 0) {
|
|
||||||
currentDocuments[0].click();
|
|
||||||
} else {
|
|
||||||
this.selectedDocumentContent("");
|
|
||||||
this.selectedDocumentId(null);
|
|
||||||
this.editorState(ViewModels.DocumentExplorerState.noDocumentSelected);
|
|
||||||
}
|
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseName: this.collection.databaseId,
|
|
||||||
collectionName: this.collection.id(),
|
|
||||||
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
},
|
|
||||||
this.onLoadStartKey,
|
|
||||||
);
|
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.Tab,
|
|
||||||
{
|
|
||||||
databaseName: this.collection.databaseId,
|
|
||||||
collectionName: this.collection.id(),
|
|
||||||
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.tabTitle(),
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
this.onLoadStartKey,
|
|
||||||
);
|
|
||||||
this.onLoadStartKey = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.finally(() => this.isExecuting(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _onEditorContentChange(newContent: string) {
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentValid ||
|
|
||||||
this.editorState() === ViewModels.DocumentExplorerState.newDocumentInvalid
|
|
||||||
) {
|
|
||||||
let parsed: any = JSON.parse(newContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mongo uses BSON format for _id, trying to parse it as JSON blocks normal flow in an edit
|
|
||||||
this.onValidDocumentEdit();
|
|
||||||
} catch (e) {
|
|
||||||
this.onInvalidDocumentEdit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Renders a Javascript object to be displayed inside Monaco Editor */
|
|
||||||
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
|
|
||||||
return MongoUtility.tojson(value, null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _hasShardKeySpecified(document: any): boolean {
|
|
||||||
return Boolean(extractPartitionKeyValues(document, this._getPartitionKeyDefinition() as PartitionKeyDefinition));
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getPartitionKeyDefinition(): DataModels.PartitionKey {
|
|
||||||
let partitionKey: DataModels.PartitionKey = this.partitionKey;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.partitionKey &&
|
|
||||||
this.partitionKey.paths &&
|
|
||||||
this.partitionKey.paths.length &&
|
|
||||||
this.partitionKey.paths.length > 0 &&
|
|
||||||
this.partitionKey.paths[0].indexOf("$v") > -1
|
|
||||||
) {
|
|
||||||
// Convert BsonSchema2 to /path format
|
|
||||||
partitionKey = {
|
|
||||||
kind: partitionKey.kind,
|
|
||||||
paths: ["/" + this.partitionKeyProperties?.[0].replace(/\./g, "/")],
|
|
||||||
version: partitionKey.version,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return partitionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected __deleteDocument(documentId: DocumentId): Promise<void> {
|
|
||||||
return deleteDocument(this.collection.databaseId, this.collection, documentId);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +1,17 @@
|
|||||||
|
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { useTabs } from "../../hooks/useTabs";
|
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
import { useTabs } from "../../hooks/useTabs";
|
||||||
import { container } from "../Controls/Settings/TestUtils";
|
import { container } from "../Controls/Settings/TestUtils";
|
||||||
import DocumentId from "../Tree/DocumentId";
|
import DocumentId from "../Tree/DocumentId";
|
||||||
import DocumentsTab from "./DocumentsTab";
|
|
||||||
import { NewQueryTab } from "./QueryTab/QueryTab";
|
import { NewQueryTab } from "./QueryTab/QueryTab";
|
||||||
|
|
||||||
describe("useTabs tests", () => {
|
describe("useTabs tests", () => {
|
||||||
let database: ViewModels.Database;
|
let database: ViewModels.Database;
|
||||||
let collection: ViewModels.Collection;
|
let collection: ViewModels.Collection;
|
||||||
let queryTab: NewQueryTab;
|
let queryTab: NewQueryTab;
|
||||||
let documentsTab: DocumentsTab;
|
let documentsTab: DocumentsTabV2;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@ -56,7 +56,7 @@ describe("useTabs tests", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
documentsTab = new DocumentsTab({
|
documentsTab = new DocumentsTabV2({
|
||||||
partitionKey: undefined,
|
partitionKey: undefined,
|
||||||
documentIds: ko.observableArray<DocumentId>(),
|
documentIds: ko.observableArray<DocumentId>(),
|
||||||
tabKind: ViewModels.CollectionTabKind.Documents,
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId, { IDocumentIdContainer } from "./DocumentId";
|
||||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
|
||||||
|
|
||||||
export default class ObjectId extends DocumentId {
|
export default class ObjectId extends DocumentId {
|
||||||
constructor(container: DocumentsTab, data: any, partitionKeyValue: any) {
|
constructor(container: IDocumentIdContainer, data: any, partitionKeyValue: any) {
|
||||||
super(container, data, partitionKeyValue);
|
super(container, data, partitionKeyValue);
|
||||||
if (typeof data._id === "object") {
|
if (typeof data._id === "object") {
|
||||||
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
|
this.id = ko.observable(data._id[Object.keys(data._id)[0]]);
|
||||||
|
Loading…
Reference in New Issue
Block a user