Compare commits

..

4 Commits

Author SHA1 Message Date
sunilyadav840
6eedcbd123 fixed eslint of TableDataClient file 2021-10-20 16:31:47 +05:30
victor-meng
55837db65b Revert "Fix keyboard focus does not retain on 'New Database' button a… (#1139)
* Revert "Fix keyboard focus does not retain on 'New Database' button after closing the 'New Database' blade via ESC key (#1109)"

This reverts commit f7e7240010.

* Revert "Fix ally database panel open issue (#1120)"

This reverts commit ed1ffb692f.
2021-10-15 17:36:48 -07:00
victor-meng
9f27cb95b9 Only use the SET keyword once in the update query (#1138) 2021-10-15 12:33:59 -07:00
Hardikkumar Nai
271256bffb resolve_eslint_NodePropertiesComponent (#921)
* resolve_eslint_NodePropertiesComponent

* address commit

* Open new screen: Screen reader does not pass the 'Copied' information after selecting 'Copy' button.

* resolve lint error
2021-10-12 08:43:35 -07:00
22 changed files with 190 additions and 203 deletions

View File

@@ -81,18 +81,14 @@ src/Explorer/Tables/DataTable/DataTableBindingManager.ts
src/Explorer/Tables/DataTable/DataTableBuilder.ts
src/Explorer/Tables/DataTable/DataTableContextMenu.ts
src/Explorer/Tables/DataTable/DataTableOperationManager.ts
# src/Explorer/Tables/DataTable/DataTableOperations.ts
src/Explorer/Tables/DataTable/DataTableViewModel.ts
# src/Explorer/Tables/DataTable/TableCommands.ts
# src/Explorer/Tables/DataTable/TableEntityCache.ts
src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
# src/Explorer/Tables/Entities.ts
src/Explorer/Tables/QueryBuilder/CustomTimestampHelper.ts
src/Explorer/Tables/TableDataClient.ts
src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts
src/Explorer/Tabs/ConflictsTab.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/MongoDocumentsTab.ts
@@ -117,6 +113,8 @@ src/Index.ts
src/Platform/Hosted/Authorization.ts
src/ReactDevTools.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts
src/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts
@@ -131,20 +129,13 @@ src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
; src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphVizComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/LeftPaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/MiddlePaneComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
; src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
src/Explorer/Notebook/NotebookComponent/VirtualCommandBarComponent.tsx
src/Explorer/Notebook/NotebookComponent/contents/index.tsx
; src/Explorer/Notebook/NotebookRenderer/NotebookReadOnlyRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/NotebookRenderer.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/draggable/index.tsx
src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/index.tsx

View File

@@ -58,7 +58,7 @@ export class LeftPaneComponent extends React.Component<LeftPaneComponentProps> {
className={className}
as="tr"
aria-label={node.caption}
onActivated={(e) => this.props.onRootNodeSelected(node.id)}
onActivated={() => this.props.onRootNodeSelected(node.id)}
key={node.id}
>
<td className="resultItem">

View File

@@ -1,8 +1,8 @@
import React from "react";
import { mount, ReactWrapper } from "enzyme";
import * as Q from "q";
import { NodePropertiesComponent, NodePropertiesComponentProps, Mode } from "./NodePropertiesComponent";
import { GraphHighlightedNodeData, EditedProperties, EditedEdges, PossibleVertex } from "./GraphExplorer";
import React from "react";
import { GraphHighlightedNodeData, PossibleVertex } from "./GraphExplorer";
import { Mode, NodePropertiesComponent, NodePropertiesComponentProps } from "./NodePropertiesComponent";
describe("Property pane", () => {
const title = "My Title";
@@ -37,17 +37,18 @@ describe("Property pane", () => {
return {
expandedTitle: title,
isCollapsed: false,
onCollapsedChanged: (newValue: boolean): void => {},
onCollapsedChanged: jest.fn(),
node: highlightedNode,
getPkIdFromNodeData: (v: GraphHighlightedNodeData): string => null,
collectionPartitionKeyProperty: null,
updateVertexProperties: (editedProperties: EditedProperties): Q.Promise<void> => Q.resolve(),
selectNode: (id: string): void => {},
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(null),
possibleEdgeLabels: null,
editGraphEdges: (editedEdges: EditedEdges): Q.Promise<any> => Q.resolve(),
deleteHighlightedNode: (): void => {},
onModeChanged: (newMode: Mode): void => {},
getPkIdFromNodeData: (): string => undefined,
collectionPartitionKeyProperty: undefined,
updateVertexProperties: (): Q.Promise<void> => Q.resolve(),
selectNode: jest.fn(),
updatePossibleVertices: (): Q.Promise<PossibleVertex[]> => Q.resolve(undefined),
possibleEdgeLabels: undefined,
//eslint-disable-next-line
editGraphEdges: (): Q.Promise<any> => Q.resolve(),
deleteHighlightedNode: jest.fn(),
onModeChanged: jest.fn(),
viewMode: Mode.READONLY_PROP,
};
};

View File

@@ -72,7 +72,7 @@ export class NodePropertiesComponent extends React.Component<
super(props);
this.state = {
editedProperties: {
pkId: null,
pkId: undefined,
readOnlyProperties: [],
existingProperties: [],
addedProperties: [],
@@ -98,15 +98,12 @@ export class NodePropertiesComponent extends React.Component<
};
}
public static getDerivedStateFromProps(
props: NodePropertiesComponentProps,
state: NodePropertiesComponentState
): Partial<NodePropertiesComponentState> {
public static getDerivedStateFromProps(props: NodePropertiesComponentProps): Partial<NodePropertiesComponentState> {
if (props.viewMode !== Mode.READONLY_PROP) {
return { isDeleteConfirm: false };
}
return null;
return undefined;
}
public render(): JSX.Element {
@@ -138,10 +135,10 @@ export class NodePropertiesComponent extends React.Component<
* @param value
*/
private static getTypeOption(value: any): ViewModels.InputPropertyValueTypeString {
if (value == null) {
if (value === undefined) {
return "null";
}
let type = typeof value;
const type = typeof value;
switch (type) {
case "number":
case "boolean":
@@ -172,10 +169,9 @@ export class NodePropertiesComponent extends React.Component<
];
const existingProps: ViewModels.InputProperty[] = [];
if (this.props.node.hasOwnProperty("properties")) {
const hProps = this.props.node["properties"];
for (let p in hProps) {
for (const p in hProps) {
const propValues = hProps[p];
(p === partitionKeyProperty ? readOnlyProps : existingProps).push({
key: p,
@@ -437,7 +433,7 @@ export class NodePropertiesComponent extends React.Component<
</div>
);
} else {
return null;
return undefined;
}
}

View File

@@ -307,18 +307,11 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
function createNewDatabase(container: Explorer): CommandButtonComponentProps {
const label = "New " + getDatabaseName();
const newDatabaseButton = document.activeElement as HTMLElement;
return {
iconSrc: AddDatabaseIcon,
iconAlt: label,
onCommandClick: () =>
useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={container} buttonElement={newDatabaseButton} />
),
useSidePanel.getState().openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={container} />),
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,

View File

@@ -4,6 +4,8 @@ import * as React from "react";
import { useFullScreenURLs } from "../hooks/useFullScreenURLs";
export const OpenFullScreen: React.FunctionComponent = () => {
const [isReadUrlCopy, setIsReadUrlCopy] = React.useState<boolean>(false);
const [isReadWriteUrlCopy, setIsReadWriteUrlCopy] = React.useState<boolean>(false);
const result = useFullScreenURLs();
if (!result) {
return <Spinner label="Generating URLs..." ariaLive="assertive" labelPosition="right" />;
@@ -25,8 +27,9 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<DefaultButton
onClick={() => {
copyToClipboard(readWriteUrl);
setIsReadWriteUrlCopy(true);
}}
text="Copy"
text={isReadWriteUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton
@@ -41,9 +44,10 @@ export const OpenFullScreen: React.FunctionComponent = () => {
<Stack horizontal tokens={{ childrenGap: 10 }}>
<DefaultButton
onClick={() => {
setIsReadUrlCopy(true);
copyToClipboard(readUrl);
}}
text="Copy"
text={isReadUrlCopy ? "Copied" : "Copy"}
iconProps={{ iconName: "Copy" }}
/>
<PrimaryButton

View File

@@ -23,12 +23,10 @@ import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneFor
export interface AddDatabasePaneProps {
explorer: Explorer;
buttonElement?: HTMLElement;
}
export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
explorer: container,
buttonElement,
}: AddDatabasePaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
let throughput: number;
@@ -79,7 +77,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
dataExplorerArea: Constants.Areas.ContextualPane,
};
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
buttonElement.focus();
}, []);
const onSubmit = () => {

View File

@@ -7,7 +7,7 @@ import { Collection } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react";
import * as DefaultExperienceUtility from "Shared/DefaultExperienceUtility";
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";

View File

@@ -8,7 +8,7 @@ import { Collection, Database } from "Contracts/ViewModels";
import { useSidePanel } from "hooks/useSidePanel";
import { useTabs } from "hooks/useTabs";
import React, { FunctionComponent, useState } from "react";
import * as DefaultExperienceUtility from "Shared/DefaultExperienceUtility";
import { DefaultExperienceUtility } from "Shared/DefaultExperienceUtility";
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";

View File

@@ -307,23 +307,16 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(),
description: undefined,
onClick: () => this.openAddDatabasePanel(),
onClick: () =>
useSidePanel
.getState()
.openSidePanel("New " + getDatabaseName(), <AddDatabasePanel explorer={this.container} />),
});
}
return items;
}
private openAddDatabasePanel() {
const newDatabaseButton = document.activeElement as HTMLElement;
useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={this.container} buttonElement={newDatabaseButton} />
);
}
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
return {
iconSrc: NotebookIcon,

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { FeedOptions } from "@azure/cosmos";
import * as ko from "knockout";
import Q from "q";
@@ -31,8 +32,6 @@ export interface CassandraTableKey {
}
export abstract class TableDataClient {
constructor() {}
public abstract createDocument(
collection: ViewModels.Collection,
entity: Entities.ITableEntity
@@ -54,7 +53,7 @@ export abstract class TableDataClient {
public abstract deleteDocuments(
collection: ViewModels.Collection,
entitiesToDelete: Entities.ITableEntity[]
): Promise<any>;
): Promise<unknown>;
}
export class TablesAPIDataClient extends TableDataClient {
@@ -67,7 +66,7 @@ export class TablesAPIDataClient extends TableDataClient {
collection,
TableEntityProcessor.convertEntityToNewDocument(<Entities.ITableEntityForTablesAPI>entity)
).then(
(newDocument: any) => {
(newDocument: unknown) => {
const newEntity = TableEntityProcessor.convertDocumentsToEntities([newDocument])[0];
deferred.resolve(newEntity);
},
@@ -146,7 +145,7 @@ export class CassandraAPIDataClient extends TableDataClient {
const clearInProgressMessage = logConsoleProgress(`Adding new row to table ${collection.id()}`);
let properties = "(";
let values = "(";
for (let property in entity) {
for (const property in entity) {
if (entity[property]._ === null) {
continue;
}
@@ -164,7 +163,7 @@ export class CassandraAPIDataClient extends TableDataClient {
const deferred = Q.defer<Entities.ITableEntity>();
this.queryDocuments(collection, query)
.then(
(data: any) => {
() => {
entity[TableConstants.EntityKeyNames.RowKey] = entity[this.getCassandraPartitionKeyProperty(collection)];
entity[TableConstants.EntityKeyNames.RowKey]._ = entity[TableConstants.EntityKeyNames.RowKey]._.toString();
logConsoleInfo(`Successfully added new row to table ${collection.id()}`);
@@ -188,10 +187,10 @@ export class CassandraAPIDataClient extends TableDataClient {
try {
let whereSegment = " WHERE";
let keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
const keys: CassandraTableKey[] = collection.cassandraKeys.partitionKeys.concat(
collection.cassandraKeys.clusteringKeys
);
for (let keyIndex in keys) {
for (const keyIndex in keys) {
const key = keys[keyIndex].property;
const keyType = keys[keyIndex].type;
whereSegment += this.isStringType(keyType)
@@ -202,14 +201,21 @@ export class CassandraAPIDataClient extends TableDataClient {
let updateQuery = `UPDATE ${collection.databaseId}.${collection.id()}`;
let isPropertyUpdated = false;
for (let property in newEntity) {
let isFirstPropertyToUpdate = true;
for (const property in newEntity) {
if (
!originalDocument[property] ||
newEntity[property]._.toString() !== originalDocument[property]._.toString()
) {
updateQuery += this.isStringType(newEntity[property].$)
? ` SET ${property} = '${newEntity[property]._}',`
: ` SET ${property} = ${newEntity[property]._},`;
let propertyQuerySegment = this.isStringType(newEntity[property].$)
? `${property} = '${newEntity[property]._}',`
: `${property} = ${newEntity[property]._},`;
// Only add the "SET" keyword once
if (isFirstPropertyToUpdate) {
propertyQuerySegment = " SET " + propertyQuerySegment;
isFirstPropertyToUpdate = false;
}
updateQuery += propertyQuerySegment;
isPropertyUpdated = true;
}
}
@@ -222,7 +228,7 @@ export class CassandraAPIDataClient extends TableDataClient {
let deleteQuery = `DELETE `;
let isPropertyDeleted = false;
for (let property in originalDocument) {
for (const property in originalDocument) {
if (property !== TableConstants.EntityKeyNames.RowKey && !newEntity[property] && !!originalDocument[property]) {
deleteQuery += ` ${property},`;
isPropertyDeleted = true;
@@ -326,16 +332,16 @@ export class CassandraAPIDataClient extends TableDataClient {
resourceId: string,
explorer: Explorer,
createKeyspaceQuery: string
): Q.Promise<any> {
): Q.Promise<unknown> {
if (!createKeyspaceQuery) {
return Q.reject("No query specified");
}
const deferred: Q.Deferred<any> = Q.defer();
const deferred: Q.Deferred<unknown> = Q.defer();
const clearInProgressMessage = logConsoleProgress(`Creating a new keyspace with query ${createKeyspaceQuery}`);
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createKeyspaceQuery)
.then(
(data: any) => {
() => {
logConsoleInfo(`Successfully created a keyspace with query ${createKeyspaceQuery}`);
deferred.resolve();
},
@@ -359,8 +365,8 @@ export class CassandraAPIDataClient extends TableDataClient {
explorer: Explorer,
createTableQuery: string,
createKeyspaceQuery?: string
): Q.Promise<any> {
let createKeyspacePromise: Q.Promise<any>;
): Q.Promise<unknown> {
let createKeyspacePromise: Q.Promise<unknown>;
if (createKeyspaceQuery) {
createKeyspacePromise = this.createKeyspace(cassandraEndpoint, resourceId, explorer, createKeyspaceQuery);
} else {
@@ -373,7 +379,7 @@ export class CassandraAPIDataClient extends TableDataClient {
const clearInProgressMessage = logConsoleProgress(`Creating a new table with query ${createTableQuery}`);
this.createOrDeleteQuery(cassandraEndpoint, resourceId, createTableQuery)
.then(
(data: any) => {
() => {
logConsoleInfo(`Successfully created a table with query ${createTableQuery}`);
deferred.resolve();
},
@@ -392,7 +398,7 @@ export class CassandraAPIDataClient extends TableDataClient {
}
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!!collection.cassandraKeys) {
if (collection.cassandraKeys) {
return Q.resolve(collection.cassandraKeys);
}
const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`);
@@ -401,7 +407,7 @@ export class CassandraAPIDataClient extends TableDataClient {
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestKeysApi
: Constants.CassandraBackend.keysApi;
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKeys>();
$.ajax(endpoint, {
@@ -422,7 +428,7 @@ export class CassandraAPIDataClient extends TableDataClient {
logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`);
deferred.resolve(data);
},
(error: any) => {
(error: Error) => {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(error);
}
@@ -432,7 +438,7 @@ export class CassandraAPIDataClient extends TableDataClient {
}
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
if (!!collection.cassandraSchema) {
if (collection.cassandraSchema) {
return Q.resolve(collection.cassandraSchema);
}
const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`);
@@ -441,7 +447,7 @@ export class CassandraAPIDataClient extends TableDataClient {
authType === AuthType.EncryptedToken
? Constants.CassandraBackend.guestSchemaApi
: Constants.CassandraBackend.schemaApi;
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKey[]>();
$.ajax(endpoint, {
@@ -462,7 +468,7 @@ export class CassandraAPIDataClient extends TableDataClient {
logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`);
deferred.resolve(data.columns);
},
(error: any) => {
(error: Error) => {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(error);
}
@@ -489,7 +495,7 @@ export class CassandraAPIDataClient extends TableDataClient {
beforeSend: this.setAuthorizationHeader,
cache: false,
}).then(
(data: any) => {
() => {
deferred.resolve();
},
(reason) => {

View File

@@ -10,7 +10,7 @@ describe("Documents tab", () => {
describe("buildQuery", () => {
it("should generate the right select query for SQL API", () => {
const documentsTab = new DocumentsTab({
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
@@ -82,9 +82,9 @@ describe("Documents tab", () => {
container: mongoExplorer,
});
it("should be false for undefined or undefined collection", () => {
it("should be false for null or undefined collection", () => {
const documentsTab = new DocumentsTab({
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
@@ -94,10 +94,10 @@ describe("Documents tab", () => {
expect(documentsTab.showPartitionKey).toBe(false);
});
it("should be false for undefined or undefined partitionKey", () => {
it("should be false for null or undefined partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithoutPartitionKey,
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
@@ -110,7 +110,7 @@ describe("Documents tab", () => {
it("should be true for non-Mongo accounts with system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithSystemPartitionKey,
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
@@ -126,7 +126,7 @@ describe("Documents tab", () => {
});
const documentsTab = new DocumentsTab({
collection: mongoCollectionWithSystemPartitionKey,
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",
@@ -139,7 +139,7 @@ describe("Documents tab", () => {
it("should be true for non-system partitionKey", () => {
const documentsTab = new DocumentsTab({
collection: collectionWithNonSystemPartitionKey,
partitionKey: undefined,
partitionKey: null,
documentIds: ko.observableArray<DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
title: "",

View File

@@ -118,7 +118,7 @@ export default class NotebookTabV2 extends NotebookTabBase {
const saveButtonChildren = [];
if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
saveButtonChildren.push({
iconName: "Copy",
iconName: copyToLabel,
onCommandClick: () => this.copyNotebook(),
commandButtonLabel: copyToLabel,
hasPopup: false,

View File

@@ -1,6 +1,6 @@
import * as ko from "knockout";
import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import * as Constants from "../Common/Constants";
export abstract class WaitsForTemplateViewModel implements ViewModels.WaitsForTemplate {
public isTemplateReady: ko.Observable<boolean>;
@@ -14,11 +14,11 @@ export abstract class WaitsForTemplateViewModel implements ViewModels.WaitsForTe
callback(value);
});
document.addEventListener("keydown", (e: KeyboardEvent) => {
document.addEventListener("keydown", function (e: KeyboardEvent) {
// To trap keyboard focus in AddCollection pane
const firstFocusableElement = document.getElementById("closeBtnAddCollection");
const lastFocusableElement = document.getElementById("submitBtnAddCollection");
const isTabPressed = e.keyCode === Constants.KeyCodes.Tab;
let firstFocusableElement = document.getElementById("closeBtnAddCollection");
let lastFocusableElement = document.getElementById("submitBtnAddCollection");
var isTabPressed = e.keyCode === Constants.KeyCodes.Tab;
if (isTabPressed) {
if (e.shiftKey) {
/* shift + tab */ if (document.activeElement === firstFocusableElement) {

View File

@@ -1,29 +1,31 @@
import * as Constants from "../../Common/Constants";
import { configContext } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import * as DefaultExperienceUtility from "../../Shared/DefaultExperienceUtility";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { userContext } from "../../UserContext";
export const _generateResourceUrl = (): string => {
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const apiKind: DataModels.ApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType);
const accountEndpoint = databaseAccount?.properties?.documentEndpoint || "";
const sid = subscriptionId || "";
const rg = resourceGroup || "";
const dba = databaseAccount?.name || "";
const resourceUrl = encodeURIComponent(accountEndpoint);
const rid = "";
const rtype = "";
return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`;
};
export default class AuthHeadersUtil {
public static async generateEncryptedToken(readOnly: boolean = false): Promise<DataModels.GenerateTokenResponse> {
const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + AuthHeadersUtil._generateResourceUrl();
const headers: any = { authorization: userContext.authorizationToken };
headers[Constants.HttpHeaders.getReadOnlyKey] = readOnly;
export const generateEncryptedToken = async (readOnly = false): Promise<DataModels.GenerateTokenResponse> => {
const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + _generateResourceUrl();
const headers: any = { authorization: userContext.authorizationToken };
headers[Constants.HttpHeaders.getReadOnlyKey] = readOnly;
const response = await fetch(url, { method: "POST", headers });
const result = await response.json();
// This API has a quirk where the response must be parsed to JSON twice
return JSON.parse(result);
}
const response = await fetch(url, { method: "POST", headers });
const result = await response.json();
// This API has a quirk where the response must be parsed to JSON twice
return JSON.parse(result);
};
private static _generateResourceUrl(): string {
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
const apiKind: DataModels.ApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.apiType);
const accountEndpoint = databaseAccount?.properties?.documentEndpoint || "";
const sid = subscriptionId || "";
const rg = resourceGroup || "";
const dba = databaseAccount?.name || "";
const resourceUrl = encodeURIComponent(accountEndpoint);
const rid = "";
const rtype = "";
return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`;
}
}

View File

@@ -1,6 +1,6 @@
import { AccountKind, CapabilityNames } from "../../Common/Constants";
import { AccessInputMetadata, ApiKind } from "../../Contracts/DataModels";
import * as DefaultExperienceUtility from "../../Shared/DefaultExperienceUtility";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { userContext } from "../../UserContext";
export function getDatabaseAccountPropertiesFromMetadata(metadata: AccessInputMetadata): unknown {

View File

@@ -1,13 +1,13 @@
import * as DataModels from "../Contracts/DataModels";
import { userContext } from "../UserContext";
import * as DefaultExperienceUtility from "./DefaultExperienceUtility";
import { DefaultExperienceUtility } from "./DefaultExperienceUtility";
describe("Default Experience Utility", () => {
describe("getDefaultExperienceFromApiKind()", () => {
const runScenario = (apiKind: number, expectedExperience: typeof userContext.apiType): void => {
function runScenario(apiKind: number, expectedExperience: typeof userContext.apiType): void {
const resolvedExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(apiKind);
expect(resolvedExperience).toEqual(expectedExperience);
};
}
describe("On SQL", () => {
it("should return SQL", () => runScenario(DataModels.ApiKind.SQL, "SQL"));
@@ -35,10 +35,10 @@ describe("Default Experience Utility", () => {
});
describe("getApiKindFromDefaultExperience()", () => {
const runScenario = (defaultExperience: typeof userContext.apiType | null, expectedApiKind: number): void => {
function runScenario(defaultExperience: typeof userContext.apiType | null, expectedApiKind: number): void {
const resolvedApiKind = DefaultExperienceUtility.getApiKindFromDefaultExperience(defaultExperience);
expect(resolvedApiKind).toEqual(expectedApiKind);
};
}
describe("On SQL", () => {
it("should return SQL", () => runScenario("SQL", DataModels.ApiKind.SQL));
@@ -60,8 +60,8 @@ describe("Default Experience Utility", () => {
it("should return Graph", () => runScenario("Gremlin", DataModels.ApiKind.Graph));
});
describe("On undefined", () => {
it("should return SQL", () => runScenario(undefined, DataModels.ApiKind.SQL));
describe("On null", () => {
it("should return SQL", () => runScenario(null, DataModels.ApiKind.SQL));
});
});
});

View File

@@ -1,45 +1,47 @@
import * as DataModels from "../Contracts/DataModels";
import { userContext } from "../UserContext";
export const getApiKindFromDefaultExperience = (defaultExperience: typeof userContext.apiType): DataModels.ApiKind => {
if (!defaultExperience) {
return DataModels.ApiKind.SQL;
}
switch (defaultExperience) {
case "SQL":
export class DefaultExperienceUtility {
public static getApiKindFromDefaultExperience(defaultExperience: typeof userContext.apiType): DataModels.ApiKind {
if (!defaultExperience) {
return DataModels.ApiKind.SQL;
case "Mongo":
return DataModels.ApiKind.MongoDB;
case "Tables":
return DataModels.ApiKind.Table;
case "Cassandra":
return DataModels.ApiKind.Cassandra;
case "Gremlin":
return DataModels.ApiKind.Graph;
default:
return DataModels.ApiKind.SQL;
}
};
}
export const getDefaultExperienceFromApiKind = (apiKind: DataModels.ApiKind): typeof userContext.apiType => {
if (apiKind === undefined) {
return "SQL";
switch (defaultExperience) {
case "SQL":
return DataModels.ApiKind.SQL;
case "Mongo":
return DataModels.ApiKind.MongoDB;
case "Tables":
return DataModels.ApiKind.Table;
case "Cassandra":
return DataModels.ApiKind.Cassandra;
case "Gremlin":
return DataModels.ApiKind.Graph;
default:
return DataModels.ApiKind.SQL;
}
}
switch (apiKind) {
case DataModels.ApiKind.SQL:
return "SQL";
case DataModels.ApiKind.MongoDB:
case DataModels.ApiKind.MongoDBCompute:
return "Mongo";
case DataModels.ApiKind.Table:
return "Tables";
case DataModels.ApiKind.Cassandra:
return "Cassandra";
case DataModels.ApiKind.Graph:
return "Gremlin";
default:
public static getDefaultExperienceFromApiKind(apiKind: DataModels.ApiKind): typeof userContext.apiType {
if (apiKind == null) {
return "SQL";
}
switch (apiKind) {
case DataModels.ApiKind.SQL:
return "SQL";
case DataModels.ApiKind.MongoDB:
case DataModels.ApiKind.MongoDBCompute:
return "Mongo";
case DataModels.ApiKind.Table:
return "Tables";
case DataModels.ApiKind.Cassandra:
return "Cassandra";
case DataModels.ApiKind.Graph:
return "Gremlin";
default:
return "SQL";
}
}
};
}

View File

@@ -5,35 +5,37 @@ import { ServerConnection, TerminalManager } from "@jupyterlab/services";
import { Terminal } from "@jupyterlab/terminal";
import { Panel, Widget } from "@phosphor/widgets";
export const createTerminalApp = async (serverSettings: ServerConnection.ISettings) => {
const manager = new TerminalManager({
serverSettings: serverSettings,
});
const session = await manager.startNew();
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
export class JupyterLabAppFactory {
public static async createTerminalApp(serverSettings: ServerConnection.ISettings) {
const manager = new TerminalManager({
serverSettings: serverSettings,
});
const session = await manager.startNew();
const term = new Terminal(session, { theme: "dark", shutdownOnClose: true });
if (!term) {
console.error("Failed starting terminal");
return;
if (!term) {
console.error("Failed starting terminal");
return;
}
term.title.closable = false;
term.addClass("terminalWidget");
let panel = new Panel();
panel.addWidget(term as any);
panel.id = "main";
// Attach the widget to the dom.
Widget.attach(panel, document.body);
// Handle resize events.
window.addEventListener("resize", () => {
panel.update();
});
// Dispose terminal when unloading.
window.addEventListener("unload", () => {
panel.dispose();
});
}
term.title.closable = false;
term.addClass("terminalWidget");
const panel = new Panel();
panel.addWidget(term as any);
panel.id = "main";
// Attach the widget to the dom.
Widget.attach(panel, document.body);
// Handle resize events.
window.addEventListener("resize", () => {
panel.update();
});
// Dispose terminal when unloading.
window.addEventListener("unload", () => {
panel.dispose();
});
};
}

View File

@@ -6,7 +6,7 @@ import { Action } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { updateUserContext } from "../UserContext";
import "./index.css";
import { createTerminalApp } from "./JupyterLabAppFactory";
import { JupyterLabAppFactory } from "./JupyterLabAppFactory";
import { TerminalProps } from "./TerminalProps";
const createServerSettings = (props: TerminalProps): ServerConnection.ISettings => {
@@ -54,7 +54,7 @@ const initTerminal = async (props: TerminalProps) => {
const startTime = TelemetryProcessor.traceStart(Action.OpenTerminal, data);
try {
await createTerminalApp(serverSettings);
await JupyterLabAppFactory.createTerminalApp(serverSettings);
TelemetryProcessor.traceSuccess(Action.OpenTerminal, data, startTime);
} catch (error) {
TelemetryProcessor.traceFailure(Action.OpenTerminal, data, startTime);

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { GenerateTokenResponse } from "../Contracts/DataModels";
import * as AuthHeadersUtil from "../Platform/Hosted/Authorization";
import AuthHeadersUtil from "../Platform/Hosted/Authorization";
export function useFullScreenURLs(): GenerateTokenResponse | undefined {
const [state, setState] = useState<GenerateTokenResponse>();

View File

@@ -26,7 +26,7 @@ import {
getDatabaseAccountPropertiesFromMetadata,
} from "../Platform/Hosted/HostedUtils";
import { CollectionCreation } from "../Shared/Constants";
import * as DefaultExperienceUtility from "../Shared/DefaultExperienceUtility";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { PortalEnv, updateUserContext, userContext } from "../UserContext";
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";