Migrate Query Tab to React (#852)

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
vaidankarswapnil 2021-06-15 00:45:13 +05:30 committed by GitHub
parent baa3252ba8
commit 999fad3bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1496 additions and 62 deletions

38
package-lock.json generated
View File

@ -5650,6 +5650,15 @@
"redux": "^4.0.0"
}
},
"@types/react-splitter-layout": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/react-splitter-layout/-/react-splitter-layout-3.0.1.tgz",
"integrity": "sha512-NsKq32LdG11G/Uj+xo2QmC9S8YSe8JRtxkBhsBE7ODFs0zcnzNEqFAQirP0H7rPe2WFGiu+d/44xbHsew7QAJw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-table": {
"version": "6.8.7",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
@ -17690,12 +17699,6 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -18499,9 +18502,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash-es": {
"version": "4.17.20",
@ -18728,9 +18731,9 @@
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
},
"marked": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.3.tgz",
"integrity": "sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA==",
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.0.6.tgz",
"integrity": "sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A==",
"dev": true
},
"martinez-polygon-clipping": {
@ -21635,6 +21638,11 @@
"react-is": "^16.9.0"
}
},
"react-splitter-layout": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-splitter-layout/-/react-splitter-layout-4.0.0.tgz",
"integrity": "sha512-SLqOjBOxRuizWUa83w6q5/u9cDWa9/yj9Iko9V9JFN8x+cqIXiDlUFWSx+icz3IIgvsN/oRIw3za5/32RjIwrA=="
},
"react-syntax-highlighter": {
"version": "12.2.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
@ -24367,12 +24375,6 @@
"universalify": "^2.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",

View File

@ -89,6 +89,7 @@
"react-i18next": "11.8.5",
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"redux": "4.0.4",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
@ -123,6 +124,7 @@
"@types/react-dom": "17.0.3",
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1",
"@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",

View File

@ -12,7 +12,7 @@ import {
QueriesGridComponentProps,
} from "../../Controls/QueriesGridReactComponent/QueriesGridComponent";
import Explorer from "../../Explorer";
import QueryTab from "../../Tabs/QueryTab";
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
interface BrowseQueriesPaneProps {
explorer: Explorer;
@ -31,13 +31,13 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
} else if (userContext.apiType === "Mongo") {
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
} else {
selectedCollection.onNewQueryClick(selectedCollection, undefined);
selectedCollection.onNewQueryClick(selectedCollection, undefined, savedQuery.query);
}
const queryTab = explorer.tabsManager.activeTab() as QueryTab;
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
queryTab.tabTitle(savedQuery.queryName);
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
queryTab.initialEditorContent(savedQuery.query);
queryTab.sqlQueryEditorContent(savedQuery.query);
trace(Action.LoadSavedQuery, ActionModifiers.Mark, {
dataExplorerArea: Areas.ContextualPane,
queryName: savedQuery.queryName,

View File

@ -8,7 +8,6 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
import { userContext } from "../../../UserContext";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import QueryTab from "../../Tabs/QueryTab";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
interface LoadQueryPaneProps {
@ -60,20 +59,19 @@ export const LoadQueryPane: FunctionComponent<LoadQueryPaneProps> = ({ explorer
const loadQueryFromFile = async (file: File): Promise<void> => {
const selectedCollection: Collection = explorer?.findSelectedCollection();
if (!selectedCollection) {
logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
} else if (userContext.apiType === "Mongo") {
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
} else {
selectedCollection.onNewQueryClick(selectedCollection, undefined);
}
const reader = new FileReader();
let fileData: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reader.onload = (evt: any): void => {
const fileData: string = evt.target.result;
const queryTab = explorer.tabsManager.activeTab() as QueryTab;
queryTab.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData);
fileData = evt.target.result;
if (!selectedCollection) {
logError("No collection was selected", "LoadQueryPane.loadQueryFromFile");
} else if (userContext.apiType === "Mongo") {
selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
} else {
selectedCollection.onNewQueryClick(selectedCollection, undefined, fileData);
}
};
reader.onerror = (): void => {

View File

@ -9,7 +9,7 @@ import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetry/TelemetryProcessor";
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import QueryTab from "../../Tabs/QueryTab";
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
interface SaveQueryPaneProps {
@ -33,8 +33,9 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
logConsoleError("Failed to save query: account not setup to save queries");
}
const queryTab = explorer && (explorer.tabsManager.activeTab() as QueryTab);
const query: string = queryTab && queryTab.sqlQueryEditorContent();
const queryTab = explorer && (explorer.tabsManager.activeTab() as NewQueryTab);
const query: string = queryTab && queryTab.iTabAccessor.onSaveClickEvent();
if (!queryName || queryName.length === 0) {
setFormError("No query name specified");
logConsoleError("Could not save query -- No query name specified. Please specify a query name.");

View File

@ -281,7 +281,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return MongoUtility.tojson(value, null, false);
}

View File

@ -1,10 +1,10 @@
import * as ViewModels from "../../Contracts/ViewModels";
import Q from "q";
import MongoUtility from "../../Common/MongoUtility";
import QueryTab from "./QueryTab";
import * as HeadersUtility from "../../Common/HeadersUtility";
import { queryIterator } from "../../Common/MongoProxyClient";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import { queryIterator } from "../../Common/MongoProxyClient";
import MongoUtility from "../../Common/MongoUtility";
import * as ViewModels from "../../Contracts/ViewModels";
import QueryTab from "./QueryTab";
export default class MongoQueryTab extends QueryTab {
public collection: ViewModels.Collection;
@ -16,7 +16,7 @@ export default class MongoQueryTab extends QueryTab {
this.monacoSettings = new ViewModels.MonacoEditorSettings("plaintext", false);
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return MongoUtility.tojson(value, null, false);
}

View File

@ -0,0 +1,54 @@
import React from "react";
import * as DataModels from "../../../Contracts/DataModels";
import type { QueryTabOptions } from "../../../Contracts/ViewModels";
import Explorer from "../../Explorer";
import { IQueryTabComponentProps, ITabAccessor } from "../../Tabs/QueryTab/QueryTabComponent";
import TabsBase from "../TabsBase";
import QueryTabComponent from "./QueryTabComponent";
export interface IQueryTabProps {
container: Explorer;
}
export class NewQueryTab extends TabsBase {
public queryText: string;
public currentQuery: string;
public partitionKey: DataModels.PartitionKey;
public iQueryTabComponentProps: IQueryTabComponentProps;
public iTabAccessor: ITabAccessor;
constructor(options: QueryTabOptions, private props: IQueryTabProps) {
super(options);
this.partitionKey = options.partitionKey;
this.iQueryTabComponentProps = {
collection: this.collection,
isExecutionError: this.isExecutionError(),
tabId: this.tabId,
tabsBaseInstance: this,
queryText: options.queryText,
partitionKey: this.partitionKey,
container: this.props.container,
onTabAccessor: (instance: ITabAccessor): void => {
this.iTabAccessor = instance;
},
};
}
public render(): JSX.Element {
return <QueryTabComponent {...this.iQueryTabComponentProps} />;
}
public onTabClick(): void {
this.manager?.activateTab(this);
this.iTabAccessor.onTabClickEvent();
}
public onCloseTabButtonClick(): void {
this.manager?.closeTab(this);
this.iTabAccessor.onCloseClickEvent(true);
}
public getContainer(): Explorer {
return this.props.container;
}
}

View File

@ -0,0 +1,285 @@
@import "../../../../less/Common/Constants.less";
@import "../../../../less/Common/TabCommon.less";
@MongoQueryEditorHeight: 50px;
@ResultsTextFontWeight: 600;
@ToggleHeight: 30px;
@ToggleWidth: 250px;
@QueryEngineExeInfo: 305px;
.tab-pane {
.tabContentContainer();
.tabPaneContentContainer {
position: relative;
.tabContentContainer();
.mongoQueryHelper {
margin: @DefaultSpace 0px 0px 44px;
}
.splitter-layout {
.layout-pane-primary {
overflow: hidden !important;
.queryEditor {
.flex-display();
height: 100%;
width: 100%;
margin-top: @SmallSpace;
.jsonEditor {
border: none;
margin-top: @SmallSpace;
}
}
}
.queryEditor.mongoQueryEditor {
margin-top: 32px;
overflow: hidden;
}
.queryEditorHorizontalSplitter {
margin: auto;
display: block;
}
}
.queryErrorsHeaderContainer {
padding: 24px @LargeSpace 0px @MediumSpace;
.queryErrors {
font-size: @mediumFontSize;
list-style-type: none;
color: @BaseDark;
font-weight: bold;
margin-left: 24px;
}
}
.queryResultErrorContentContainer {
.flex-display();
.flex-direction();
font-size: @DefaultFontSize;
padding: @DefaultSpace;
height: 100%;
width: 100%;
overflow: hidden;
.queryEditorWatermark {
text-align: center;
margin: auto;
height: 25vh; // this is to align the water mark in center of the layout.
p {
margin-bottom: @LargeSpace;
color: @BaseHigh;
}
.queryEditorWatermarkText {
color: @BaseHigh;
font-size: @DefaultFontSize;
font-family: @DataExplorerFont;
}
}
.queryResultsErrorsContent {
height: 100%;
margin-left: @MediumSpace;
.flex-display();
.flex-direction();
div[role="tabpanel"] {
height: 100%;
div:nth-child(1) {
height: 100%;
}
}
.result-metadata {
padding: @LargeSpace @SmallSpace @MediumSpace @MediumSpace;
height: auto !important;
.queryResultDivider {
margin-left: @SmallSpace;
margin-right: @SmallSpace;
}
.queryResultNextEnable {
color: @AccentMediumHigh;
font-size: @mediumFontSize;
cursor: pointer;
img {
height: @ImgHeight;
width: @ImgWidth;
margin-left: @SmallSpace;
}
}
.queryResultNextDisable {
color: @BaseMediumHigh;
opacity: 0.5;
font-size: @mediumFontSize;
img {
height: @ImgHeight;
width: @ImgWidth;
margin-left: @SmallSpace;
}
}
}
.tab-pane.active {
height: 100%;
width: 100%;
}
.errorContent {
.flex-display();
width: 60%;
white-space: nowrap;
font-size: @mediumFontSize;
padding: 0px @MediumSpace 0px @MediumSpace;
.errorMessage {
padding: @SmallSpace;
overflow: hidden;
text-overflow: ellipsis;
}
}
.errorDetailsLink {
cursor: pointer;
padding: @SmallSpace;
}
.queryMetricsSummaryContainer {
.flex-display();
.flex-direction();
overflow: hidden;
position: relative;
height: 100%;
.queryMetricsSummary {
margin: @LargeSpace @LargeSpace 0px @DefaultSpace;
table-layout: fixed;
display: block;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
caption {
width: 100px;
}
.queryMetricsSummaryHead {
.flex-display();
}
.queryMetricsSummaryHeader.queryMetricsSummaryTuple {
font-size: 10px;
}
.queryMetricsSummaryBody {
.flex-display();
.flex-direction();
}
.queryMetricsSummaryTuple {
border-bottom: 1px solid @BaseMedium;
height: 32px;
font-size: 12px;
width: 100%;
.flex-display();
th,
td {
padding: @DefaultSpace;
&:nth-child(1) {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 0 0 50%;
}
&:nth-child(3) {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 0 0 50%;
}
.queryMetricInfoTooltip {
.infoTooltip();
&:hover .queryMetricTooltipText {
.tooltipVisible();
}
&:focus .queryMetricTooltipText {
.tooltipVisible();
}
.queryMetricTooltipText {
top: -50px;
width: auto;
white-space: nowrap;
left: 6px;
visibility: hidden;
background-color: @BaseHigh;
color: @BaseLight;
position: absolute;
z-index: 1;
padding: @MediumSpace;
&::after {
border-width: (2 * @MediumSpace) (2 * @MediumSpace) 0px 0px;
bottom: -14px;
.tooltipTextAfter();
}
}
.queryEngineExeTimeInfo {
width: @QueryEngineExeInfo;
top: -85px;
white-space: pre-wrap;
}
}
}
}
}
.downloadMetricsLinkContainer {
margin: 24px 0px 50px @MediumSpace;
position: sticky;
#downloadMetricsLink {
color: @BaseHigh;
padding: @DefaultSpace;
font-size: @mediumFontSize;
border: @ButtonBorderWidth solid @BaseLight;
cursor: pointer;
&:hover {
.hover();
}
&:active {
border: @ButtonBorderWidth dashed @AccentMedium;
.active();
}
}
}
}
json-editor {
.flex-display();
.flex-direction();
overflow: hidden;
height: 100%;
width: 100%;
padding: @SmallSpace 0px @SmallSpace @MediumSpace;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import { TabsManager } from "./TabsManager";
// TODO: Use specific actions for logging telemetry data
export default class TabsBase extends WaitsForTemplateViewModel {
private static id = 0;
@ -149,7 +148,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}
/** Renders a Javascript object to be displayed inside Monaco Editor */
protected renderObjectForEditor(value: any, replacer: any, space: string | number): string {
public renderObjectForEditor(value: any, replacer: any, space: string | number): string {
return JSON.stringify(value, replacer, space);
}
@ -164,7 +163,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
return [];
}
protected updateNavbarWithTabsButtons = (): void => {
public updateNavbarWithTabsButtons = (): void => {
if (this.isActive()) {
useCommandBar.getState().setContextButtons(this.getTabsButtons());
}

View File

@ -30,7 +30,7 @@ import GraphTab from "../Tabs/GraphTab";
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
import MongoQueryTab from "../Tabs/MongoQueryTab";
import MongoShellTab from "../Tabs/MongoShellTab";
import QueryTab from "../Tabs/QueryTab";
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
import QueryTablesTab from "../Tabs/QueryTablesTab";
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
import ConflictId from "./ConflictId";
@ -617,19 +617,22 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title,
});
const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
title: title,
tabPath: "",
collection: this,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
queryText: queryText,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
});
this.container.tabsManager.activateNewTab(queryTab);
this.container.tabsManager.activateNewTab(
new NewQueryTab(
{
tabKind: ViewModels.CollectionTabKind.Query,
title: title,
tabPath: "",
collection: this,
node: this,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
queryText: queryText,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
},
{ container: this.container }
)
);
}
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {