Compare commits

..

29 Commits

Author SHA1 Message Date
tomasvaron
04d6a17b70 Merge branch 'user/t-tomasvaron/chatbot' of https://github.com/Azure/cosmos-explorer into user/t-tomasvaron/chatbot 2022-07-27 21:30:39 -04:00
tomasvaron
905413bf17 remove testing values 2022-07-27 21:29:52 -04:00
tomasvaron
f519aa7be3 Remove newline 2022-07-27 21:27:11 -04:00
tomasvaron
053e346fa4 revert changes 2022-07-27 21:26:21 -04:00
tomasvaron
b4909d9e13 merge 2022-07-18 11:59:08 -04:00
tomasvaron
b268b525d8 merge with master 2022-07-18 11:23:29 -04:00
tomasvaron
02d0353215 Added space between the chat symbol and button text 2022-07-14 10:30:28 -04:00
victor-meng
c5ef0e608e Fix aggregated query metrics show incorrect number (#1302) 2022-07-12 10:39:40 -07:00
victor-meng
d66e85f431 Allow users to delete TablesDB database when enableSDKOperations feature flag is set (#1300) 2022-07-06 10:54:49 -07:00
victor-meng
0996489897 Improve quickstart teaching bubble telemetries and make the home page a tab (#1299) 2022-07-06 10:54:35 -07:00
victor-meng
f83634c50d Upgrade js sdk version (#1297) 2022-06-28 17:52:46 -07:00
tomasvaron
4fd153d676 chatbot sticky button 2022-06-20 15:16:07 -04:00
tomasvaron
7b1f3bb493 Merge branch 'user/balalakshmin/chatbot' into user/t-tomasvaron/chatbot 2022-05-11 11:30:04 -04:00
Bala Lakshmi Narayanasami
95b43d83d1 Compile errors fixed 2022-05-11 15:12:55 +05:30
Bala Lakshmi Narayanasami
f9494030ac Format fix 2022-05-10 18:08:25 +05:30
Bala Lakshmi Narayanasami
a1087b2626 Updating package-lock 2022-05-10 16:35:19 +05:30
Bala Lakshmi Narayanasami
5aeca52234 Reverting local change 2022-05-10 15:42:00 +05:30
Bala Lakshmi Narayanasami
6c77430775 Fixed lint errors 2022-05-10 15:34:58 +05:30
Bala Lakshmi Narayanasami
7bc78f126b Reverting unwanted changes 2022-05-09 19:08:26 +05:30
Bala Lakshmi Narayanasami
e3ef515d8b Reverting vscode settings changes 2022-05-09 19:05:42 +05:30
Bala Lakshmi Narayanasami
7ffc1bc35a Code cleanup 2022-05-09 19:04:17 +05:30
Bala Lakshmi Narayanasami
6add6d2e86 Changes for getting conversation token 2022-05-09 18:50:04 +05:30
Bala Lakshmi Narayanasami
a089bac80e Adding chatbot behind feature flag 2022-05-06 16:59:47 +05:30
Bala Lakshmi Narayanasami
c0b5e185aa Merge branch 'master' of https://github.com/Azure/cosmos-explorer into user/balalakshmin/chatbot 2022-05-04 16:55:03 +05:30
Bala Lakshmi Narayanasami
986fe39d07 Minor fix 2022-04-27 16:13:37 +05:30
Bala Lakshmi Narayanasami
fd511f2706 icon alignment fix 2022-02-17 21:02:42 +05:30
Bala Lakshmi Narayanasami
c35e23eb47 updating botframework version 2022-01-12 19:45:34 +05:30
Bala Lakshmi Narayanasami
80be52685f Merge branch 'master' of https://github.com/Azure/cosmos-explorer into user/balalakshmin/chatbot 2022-01-06 19:48:55 +05:30
Bala Lakshmi Narayanasami
d29fd6e957 Merge latest master changes to chatbot 2021-08-03 17:11:00 +05:30
44 changed files with 17784 additions and 1357 deletions

View File

@@ -1,30 +0,0 @@
{
"version": "1.0",
"tasks": [
{
"taskType": "trigger",
"capabilityId": "AutoMerge",
"subCapability": "AutoMerge",
"version": "1.0",
"id": "LUEPwPETV",
"config": {
"taskName": "Auto Merge",
"label": "automerge",
"minMinutesOpen": "5",
"mergeType": "squash",
"deleteBranches": true,
"requireAllStatuses": true,
"requireSpecificCheckRuns": false,
"usePrDescriptionAsCommitMessage": true,
"requireAllStatuses_exemptList": [
"Azure Pipelines",
"Dependabot",
"GitHub Pages",
"Check Enforcer"
],
"silentMode": true
}
}
],
"userGroups": []
}

23
less/chat.less Normal file
View File

@@ -0,0 +1,23 @@
@import "./Common/Constants";
.chat {
display: grid;
justify-content: right;
padding: 10px;
.chatButton {
margin: 0 45px 15px 0;
border: 10px;
min-height: 44px;
&:focus {
outline: 0px;
}
>span {
font-size: 17px;
font-family: @DataExplorerFont;
color: @AccentLow;
}
}
}

18467
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.16.1",
"@azure/cosmos": "3.16.2",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
@@ -47,6 +47,7 @@
"@types/node-fetch": "2.5.7",
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"botframework-webchat": "4.14.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "3.0.0",
"clipboard-copy": "4.0.1",
@@ -127,6 +128,7 @@
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1",
"@types/react-youtube": "7.10.0",
"@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1",

View File

@@ -24,7 +24,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
);
try {
let collection: DataModels.Collection;
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = {
autoPilotMaxThroughput: params.autoPilotMaxThroughput,

View File

@@ -25,7 +25,8 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
if (userContext.apiType === "Tables") {
throw new Error("Creating database resources is not allowed for tables accounts");
}
const database: DataModels.Database = await (userContext.authType === AuthType.AAD && !userContext.useSDKOperations
const database: DataModels.Database = await (userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations
? createDatabaseWithARM(params)
: createDatabaseWithSDK(params));

View File

@@ -20,7 +20,11 @@ export async function createStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,

View File

@@ -14,7 +14,11 @@ export async function createTrigger(
): Promise<TriggerDefinition | SqlTriggerResource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,

View File

@@ -20,7 +20,11 @@ export async function createUserDefinedFunction(
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,

View File

@@ -12,7 +12,7 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteCollectionWithARM(databaseId, collectionId);
} else {
await client().database(databaseId).container(collectionId).delete();

View File

@@ -12,10 +12,7 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (userContext.apiType === "Tables") {
throw new Error("Deleting database resources is not allowed for tables accounts");
}
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
await deleteDatabaseWithARM(databaseId);
} else {
await client().database(databaseId).delete();

View File

@@ -12,7 +12,11 @@ export async function deleteStoredProcedure(
): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -8,7 +8,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -8,7 +8,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -14,7 +14,11 @@ export const readCollectionOffer = async (params: ReadCollectionOfferParams): Pr
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readCollectionOfferWithARM(params.databaseId, params.collectionId);
}

View File

@@ -13,7 +13,11 @@ import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readCollectionsWithARM(databaseId);
}

View File

@@ -13,7 +13,11 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
return await readDatabaseOfferWithARM(params.databaseId);
}

View File

@@ -13,7 +13,11 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client().databases.readAll().fetchAll();

View File

@@ -12,7 +12,11 @@ export async function readStoredProcedures(
): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -13,7 +13,11 @@ export async function readTriggers(
): Promise<SqlTriggerResource[] | TriggerDefinition[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType === "SQL") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType === "SQL"
) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,

View File

@@ -11,9 +11,9 @@ export async function readUserDefinedFunctions(
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const rpResponse = await listSqlUserDefinedFunctions(
subscriptionId,
resourceGroup,

View File

@@ -33,7 +33,11 @@ export async function updateCollection(
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations && userContext.apiType !== "Tables") {
if (
userContext.authType === AuthType.AAD &&
!userContext.features.enableSDKoperations &&
userContext.apiType !== "Tables"
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()

View File

@@ -56,7 +56,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try {
if (userContext.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.apiType === "Tables") {

View File

@@ -20,9 +20,9 @@ export async function updateStoredProcedure(
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlStoredProcedure(
subscriptionId,
resourceGroup,

View File

@@ -13,9 +13,9 @@ export async function updateTrigger(
trigger: SqlTriggerResource
): Promise<SqlTriggerResource | TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlTrigger(
subscriptionId,
resourceGroup,

View File

@@ -19,9 +19,9 @@ export async function updateUserDefinedFunction(
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
const { authType, useSDKOperations, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
const { authType, apiType, subscriptionId, resourceGroup, databaseAccount } = userContext;
try {
if (authType === AuthType.AAD && !useSDKOperations && apiType === "SQL") {
if (authType === AuthType.AAD && !userContext.features.enableSDKoperations && apiType === "SQL") {
const getResponse = await getSqlUserDefinedFunction(
subscriptionId,
resourceGroup,

View File

@@ -9,7 +9,7 @@ import {
allowedJunoOrigins,
allowedMongoBackendEndpoints,
allowedMsalRedirectEndpoints,
validateEndpoint,
validateEndpoint
} from "Utils/EndpointValidation";
export enum Platform {
@@ -190,3 +190,4 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
}
export { configContext };

View File

@@ -44,7 +44,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
},
];
if (userContext.apiType !== "Tables") {
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () =>

View File

@@ -0,0 +1,42 @@
import { IIconProps } from '@fluentui/react';
import { PrimaryButton } from '@fluentui/react/lib/Button';
import { AuthType } from 'AuthType';
import { SupportPaneComponent } from 'Explorer/Controls/SupportPaneComponent/SupportPaneComponent';
import Explorer from 'Explorer/Explorer';
import { useSidePanel } from 'hooks/useSidePanel';
import * as React from 'react';
import { userContext } from 'UserContext';
export interface ChatButtonProps {
container: Explorer;
}
const chatIcon: IIconProps = { iconName: 'ChatSolid', style: { marginRight: 10 } };
export const ChatButtonAction: React.FunctionComponent<ChatButtonProps> = props => {
const { container } = props;
if (userContext.authType === AuthType.AAD && userContext.features.enableChatbot) {
return (
<PrimaryButton className={"chatButton"} iconProps={chatIcon} onClick={() => {
useSidePanel
.getState()
.openSidePanel(
"Chat Assistant (Beta)",
<SupportPaneComponent
directLineToken={container.conversationToken()}
userToken={userContext.authorizationToken}
subId={userContext.subscriptionId}
rg={userContext.resourceGroup}
accName={userContext.databaseAccount.name}
/>
);
}}>
<span> Help? </span>
</PrimaryButton>
);
}
return <div></div>
};

View File

@@ -1,13 +1,13 @@
/**
* React component for Command button component.
*/
import { Icon, IIconStyles } from "@fluentui/react";
import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as StringUtils from "../../../Utils/StringUtils";
/**
* Options for this component
*/
@@ -243,6 +243,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
if (this.props.children && this.props.children.length > 0) {
contentClassName += " hasHiddenItems";
}
const iconButtonStyles: Partial<IIconStyles> = { root: { marginBottom: -3 } };
return (
<div className="commandButtonReact">
@@ -259,7 +260,18 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
onClick={(e: React.MouseEvent<HTMLSpanElement>) => this.commandClickCallback(e)}
>
<div className={contentClassName}>
<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />
if (this.props.iconName){" "}
{
<div>
<Icon
styles={iconButtonStyles}
className="panelInfoIcon"
iconName={this.props.iconName}
ariaLabel="ChatBot"
/>
</div>
}{" "}
else {<img className="commandIcon" src={this.props.iconSrc} alt={this.props.iconAlt} />}
{CommandButtonComponent.renderLabel(this.props)}
</div>
</span>

View File

@@ -0,0 +1,46 @@
import { Activity } from "botframework-directlinejs";
import ReactWebChat from "botframework-webchat";
import React from "react";
import * as _ from "underscore";
const BotFramework = require('botframework-webchat');
export interface SupportPaneComponentProps {
directLineToken: string;
userToken: string;
subId: string;
rg: string;
accName: string;
}
export class SupportPaneComponent extends React.Component<SupportPaneComponentProps> {
private readonly userId: string = _.uniqueId();
constructor(props: SupportPaneComponentProps) {
super(props);
}
public render(): JSX.Element {
const styleOptions = {
bubbleBackground: "rgba(0, 0, 255, .1)",
bubbleFromUserBackground: "rgba(0, 255, 0, .1)",
suggestedActionLayout: 'flow',
markdownRespectCRLF: true,
};
const directLine = BotFramework.createDirectLine({ token: this.props.directLineToken });
const dl = {
...directLine,
postActivity: (activity: Activity) => {
activity.channelData.token = this.props.userToken;
activity.channelData.subId = this.props.subId;
activity.channelData.rg = this.props.rg;
activity.channelData.accName = this.props.accName;
return directLine.postActivity(activity);
},
};
return <ReactWebChat directLine={dl} userID={this.userId} styleOptions={styleOptions} />;
}
}

View File

@@ -1,5 +1,6 @@
import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { configContext } from "ConfigContext";
import { IGalleryItem } from "Juno/JunoClient";
import * as ko from "knockout";
import React from "react";
@@ -34,6 +35,7 @@ import { userContext } from "../UserContext";
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
@@ -85,6 +87,12 @@ export default class Explorer {
// Notebooks
public notebookManager?: NotebookManager;
public conversationToken: ko.Observable<string>;
public userToken: ko.Observable<string>;
public subId: ko.Observable<string>;
public rg: ko.Observable<string>;
public accName: ko.Observable<string>;
private _isInitializingNotebooks: boolean;
private notebookToImport: {
name: string;
@@ -106,6 +114,10 @@ export default class Explorer {
this.queriesClient = new QueriesClient(this);
this.conversationToken = ko.observable<string>();
this.generateConversationToken();
useSelectedNode.subscribe(() => {
// Make sure switching tabs restores tabs display
this.isTabsContentExpanded(false);
@@ -455,6 +467,30 @@ export default class Explorer {
useDialog.getState().openDialog(resetConfirmationDialogProps);
}
private async generateConversationToken() {
const url = `${configContext.JUNO_ENDPOINT}/api/chatbot/bot${userContext.databaseAccount.id}/conversationToken`;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(url, {
method: "GET",
headers: {
[Constants.HttpHeaders.authorization]: authorizationHeader.token,
Accept: "application/json",
[Constants.HttpHeaders.contentType]: "application/json",
},
});
if (!response.ok) {
throw new Error(await response.json());
}
const tokenResponse: { conversationId: string; token: string; expires_in: number } = await response.json();
this.conversationToken(tokenResponse?.token);
if (tokenResponse?.expires_in) {
setTimeout(() => this.generateConversationToken(), (tokenResponse?.expires_in - 1000) * 1000);
}
}
private async _containsDefaultNotebookWorkspace(databaseAccount: DataModels.DatabaseAccount): Promise<boolean> {
if (!databaseAccount) {
return false;

View File

@@ -26,6 +26,7 @@ import { userContext } from "../../../UserContext";
import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import { SupportPaneComponent } from "../../Controls/SupportPaneComponent/SupportPaneComponent";
import Explorer from "../../Explorer";
import { useNotebook } from "../../Notebook/useNotebook";
import { OpenFullScreen } from "../../OpenFullScreen";
@@ -195,6 +196,35 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
if (userContext.authType === AuthType.AAD && userContext.features.enableChatbot) {
const label = "Chat Assistant";
const supportPaneButton: CommandButtonComponentProps = {
iconName: "ChatBot",
iconAlt: label,
onCommandClick: () => {
useSidePanel
.getState()
.openSidePanel(
"Chat Assistant (Beta)",
<SupportPaneComponent
directLineToken={container.conversationToken()}
userToken={userContext.authorizationToken}
subId={userContext.subscriptionId}
rg={userContext.resourceGroup}
accName={userContext.databaseAccount.name}
/>
);
},
commandButtonLabel: null,
ariaLabel: label,
tooltipText: label,
hasPopup: true,
disabled: false,
className: "fonticoncustom",
};
buttons.push(supportPaneButton);
}
if (showOpenFullScreen) {
const label = "Open Full Screen";
const fullScreenButton: CommandButtonComponentProps = {

View File

@@ -42,6 +42,10 @@
width: 18px;
color: rgb(0, 120, 212);
}
.fonticoncustom {
padding-top: 12px;
}
.status {
position: relative;
display: block;

View File

@@ -21,4 +21,4 @@
color: @SelectionHigh;
}
}
}
}

View File

@@ -3,35 +3,25 @@
*/
import { Coachmark, DirectionalHint, Image, Link, Stack, TeachingBubbleContent, Text } from "@fluentui/react";
import { useCarousel } from "hooks/useCarousel";
import { useTabs } from "hooks/useTabs";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import NewQueryIcon from "../../../images/AddSqlQuery_16x16.svg";
import NewStoredProcedureIcon from "../../../images/AddStoredProcedure.svg";
import OpenQueryIcon from "../../../images/BrowseQuery.svg";
import ConnectIcon from "../../../images/Connect_color.svg";
import ContainersIcon from "../../../images/Containers.svg";
import LinkIcon from "../../../images/Link_blue.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import NotebookColorIcon from "../../../images/Notebooks.svg";
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
import ScaleAndSettingsIcon from "../../../images/Scale_15x15.svg";
import CollectionIcon from "../../../images/tree-collection.svg";
import { AuthType } from "../../AuthType";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { useSidePanel } from "../../hooks/useSidePanel";
import { userContext } from "../../UserContext";
import { getCollectionName, getDatabaseName } from "../../Utils/APITypeUtils";
import { getCollectionName } from "../../Utils/APITypeUtils";
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import { useNotebook } from "../Notebook/useNotebook";
import { AddDatabasePanel } from "../Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "../Panes/BrowseQueriesPane/BrowseQueriesPane";
import { useDatabases } from "../useDatabases";
import { useSelectedNode } from "../useSelectedNode";
@@ -234,103 +224,13 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
iconSrc: ConnectIcon,
title: "Connect",
description: "Prefer using your own choice of tooling? Find the connection string you need to connect",
onClick: () => useTabs.getState().openAndActivateConnectTab(),
onClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect),
};
heroes.push(connectBtn);
return heroes;
}
private createCommonTaskItems(): SplashScreenItem[] {
const items: SplashScreenItem[] = [];
if (userContext.authType === AuthType.ResourceToken) {
return items;
}
if (!useSelectedNode.getState().isDatabaseNodeOrNoneSelected()) {
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
items.push({
iconSrc: NewQueryIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, undefined);
},
title: "New SQL Query",
description: undefined,
});
} else if (userContext.apiType === "Mongo") {
items.push({
iconSrc: NewQueryIcon,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, undefined);
},
title: "New Query",
description: undefined,
});
}
if (userContext.apiType === "SQL") {
items.push({
iconSrc: OpenQueryIcon,
title: "Open Query",
description: undefined,
onClick: () =>
useSidePanel
.getState()
.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this.container} />),
});
}
if (userContext.apiType !== "Cassandra") {
items.push({
iconSrc: NewStoredProcedureIcon,
title: "New Stored Procedure",
description: undefined,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, undefined);
},
});
}
/* Scale & Settings */
const isShared = useDatabases.getState().findSelectedDatabase()?.isDatabaseShared();
const label = isShared ? "Settings" : "Scale & Settings";
items.push({
iconSrc: ScaleAndSettingsIcon,
title: label,
description: undefined,
onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && selectedCollection.onSettingsClick();
},
});
} else {
items.push({
iconSrc: AddDatabaseIcon,
title: "New " + getDatabaseName(),
description: undefined,
onClick: async () => {
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
if (throughputCap && throughputCap !== -1) {
await useDatabases.getState().loadAllOffers();
}
useSidePanel
.getState()
.openSidePanel(
"New " + getDatabaseName(),
<AddDatabasePanel explorer={this.container} buttonElement={document.activeElement as HTMLElement} />
);
},
});
}
return items;
}
private decorateOpenCollectionActivity({ databaseId, collectionId }: MostRecentActivity.OpenCollectionItem) {
return {
iconSrc: CollectionIcon,
@@ -372,29 +272,6 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
});
}
private createTipsItems(): SplashScreenItem[] {
return [
{
iconSrc: undefined,
title: "Data Modeling",
description: "Learn more about modeling",
onClick: () => window.open(SplashScreen.dataModelingUrl),
},
{
iconSrc: undefined,
title: "Cost & Throughput Calculation",
description: "Learn more about cost calculation",
onClick: () => window.open(SplashScreen.throughputEstimatorUrl),
},
{
iconSrc: undefined,
title: "Configure automatic failover",
description: "Learn more about Cosmos DB high-availability",
onClick: () => window.open(SplashScreen.failoverUrl),
},
];
}
private onSplashScreenItemKeyPress(event: React.KeyboardEvent, callback: () => void) {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
callback();

View File

@@ -600,56 +600,43 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
metricsMap.forEach((queryMetrics) => {
if (queryMetrics) {
aggregatedMetrics.documentLoadTime =
queryMetrics.documentLoadTime &&
this._normalize(queryMetrics.documentLoadTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.documentLoadTime);
this._normalize(aggregatedMetrics.documentLoadTime);
aggregatedMetrics.documentWriteTime =
queryMetrics.documentWriteTime &&
this._normalize(queryMetrics.documentWriteTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.documentWriteTime);
this._normalize(aggregatedMetrics.documentWriteTime);
aggregatedMetrics.indexHitDocumentCount =
queryMetrics.indexHitDocumentCount &&
this._normalize(queryMetrics.indexHitDocumentCount) +
this._normalize(aggregatedMetrics.indexHitDocumentCount);
this._normalize(aggregatedMetrics.indexHitDocumentCount);
aggregatedMetrics.outputDocumentCount =
queryMetrics.outputDocumentCount &&
this._normalize(queryMetrics.outputDocumentCount) + this._normalize(aggregatedMetrics.outputDocumentCount);
aggregatedMetrics.outputDocumentSize =
queryMetrics.outputDocumentSize &&
this._normalize(queryMetrics.outputDocumentSize) + this._normalize(aggregatedMetrics.outputDocumentSize);
aggregatedMetrics.indexLookupTime =
queryMetrics.indexLookupTime &&
this._normalize(queryMetrics.indexLookupTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.indexLookupTime);
this._normalize(aggregatedMetrics.indexLookupTime);
aggregatedMetrics.retrievedDocumentCount =
queryMetrics.retrievedDocumentCount &&
this._normalize(queryMetrics.retrievedDocumentCount) +
this._normalize(aggregatedMetrics.retrievedDocumentCount);
this._normalize(aggregatedMetrics.retrievedDocumentCount);
aggregatedMetrics.retrievedDocumentSize =
queryMetrics.retrievedDocumentSize &&
this._normalize(queryMetrics.retrievedDocumentSize) +
this._normalize(aggregatedMetrics.retrievedDocumentSize);
this._normalize(aggregatedMetrics.retrievedDocumentSize);
aggregatedMetrics.vmExecutionTime =
queryMetrics.vmExecutionTime &&
this._normalize(queryMetrics.vmExecutionTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.vmExecutionTime);
this._normalize(aggregatedMetrics.vmExecutionTime);
aggregatedMetrics.totalQueryExecutionTime =
queryMetrics.totalQueryExecutionTime &&
this._normalize(queryMetrics.totalQueryExecutionTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.totalQueryExecutionTime);
this._normalize(aggregatedMetrics.totalQueryExecutionTime);
aggregatedMetrics.runtimeExecutionTimes.queryEngineExecutionTime =
aggregatedMetrics.runtimeExecutionTimes &&
this._normalize(queryMetrics.runtimeExecutionTimes.queryEngineExecutionTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.runtimeExecutionTimes.queryEngineExecutionTime);
this._normalize(aggregatedMetrics.runtimeExecutionTimes.queryEngineExecutionTime);
aggregatedMetrics.runtimeExecutionTimes.systemFunctionExecutionTime =
aggregatedMetrics.runtimeExecutionTimes &&
this._normalize(queryMetrics.runtimeExecutionTimes.systemFunctionExecutionTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.runtimeExecutionTimes.systemFunctionExecutionTime);
this._normalize(aggregatedMetrics.runtimeExecutionTimes.systemFunctionExecutionTime);
aggregatedMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime =
aggregatedMetrics.runtimeExecutionTimes &&
this._normalize(queryMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime.totalMilliseconds()) +
this._normalize(aggregatedMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime);
this._normalize(aggregatedMetrics.runtimeExecutionTimes.userDefinedFunctionExecutionTime);
}
});

View File

@@ -1,4 +1,6 @@
import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout";
@@ -6,28 +8,33 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg";
import { useObservable } from "../../hooks/useObservable";
import { useTabs } from "../../hooks/useTabs";
import { ReactTabKind, useTabs } from "../../hooks/useTabs";
import TabsBase from "./TabsBase";
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
export const Tabs = (): JSX.Element => {
const { openedTabs, activeTab } = useTabs();
const isConnectTabOpen = useTabs((state) => state.isConnectTabOpen);
const isConnectTabActive = useTabs((state) => state.isConnectTabActive);
interface TabsProps {
explorer: Explorer;
}
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
return (
<div className="tabsManagerContainer">
<div id="content" className="flexContainer hideOverflows">
<div className="nav-tabs-margin">
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
{isConnectTabOpen && <TabNav key="connect" tab={undefined} active={isConnectTabActive} />}
{openedReactTabs.map((tab) => (
<TabNav key={ReactTabKind[tab]} active={activeReactTab === tab} tabKind={tab} />
))}
{openedTabs.map((tab) => (
<TabNav key={tab.tabId} tab={tab} active={activeTab === tab} />
))}
</ul>
</div>
<div className="tabPanesContainer">
{isConnectTabActive && <ConnectTab />}
{activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)}
{openedTabs.map((tab) => (
<TabPane key={tab.tabId} tab={tab} active={activeTab === tab} />
))}
@@ -37,7 +44,7 @@ export const Tabs = (): JSX.Element => {
);
};
function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?: ReactTabKind }) {
const [hovering, setHovering] = useState(false);
const focusTab = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
const tabId = tab ? tab.tabId : "connect";
@@ -51,8 +58,20 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
<li
onMouseOver={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
onClick={() => (tab ? tab.onTabClick() : useTabs.getState().activateConnectTab())}
onKeyPress={({ nativeEvent: e }) => (tab ? tab.onKeyPressActivate(undefined, e) : onKeyPressConnectTab(e))}
onClick={() => {
if (tab) {
tab.onTabClick();
} else if (tabKind !== undefined) {
useTabs.getState().activateReactTab(tabKind);
}
}}
onKeyPress={({ nativeEvent: e }) => {
if (tab) {
tab.onKeyPressActivate(undefined, e);
} else if (tabKind !== undefined) {
onKeyPressReactTab(e, tabKind);
}
}}
className={active ? "active tabList" : "tabList"}
title={useObservable(tab?.tabPath || ko.observable(""))}
aria-selected={active}
@@ -65,16 +84,18 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
<span className="tabNavContentContainer">
<a data-toggle="tab" href={"#" + tabId} tabIndex={-1}>
<div className="tab_Content">
<span className="statusIconContainer">
<span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}>
{useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />}
{useObservable(tab?.isExecuting || ko.observable(false)) && (
<img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" />
)}
</span>
<span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable("Connect"))}</span>
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} />
</span>
<span className="tabNavText">{useObservable(tab?.tabTitle || ko.observable(ReactTabKind[tabKind]))}</span>
{tabKind !== ReactTabKind.Home && (
<span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
</span>
)}
</div>
</a>
</span>
@@ -82,14 +103,24 @@ function TabNav({ tab, active }: { tab: Tab; active: boolean }) {
);
}
const CloseButton = ({ tab, active, hovering }: { tab: Tab; active: boolean; hovering: boolean }) => (
const CloseButton = ({
tab,
active,
hovering,
tabKind,
}: {
tab: Tab;
active: boolean;
hovering: boolean;
tabKind?: ReactTabKind;
}) => (
<span
style={{ display: hovering || active ? undefined : "none" }}
title="Close"
role="button"
aria-label="Close Tab"
className="cancelButton"
onClick={() => (tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeConnectTab())}
onClick={() => (tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind))}
tabIndex={active ? 0 : undefined}
onKeyPress={({ nativeEvent: e }) => tab.onKeyPressClose(undefined, e)}
>
@@ -144,9 +175,20 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
return <div {...attrs} ref={ref} data-bind="html:html" />;
}
const onKeyPressConnectTab = (e: KeyboardEvent): void => {
const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
if (e.key === "Enter" || e.key === "Space") {
useTabs.getState().activateConnectTab();
useTabs.getState().activateReactTab(tabKind);
e.stopPropagation();
}
};
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
switch (activeReactTab) {
case ReactTabKind.Connect:
return <ConnectTab />;
case ReactTabKind.Home:
return <SplashScreen explorer={explorer} />;
default:
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
}
};

View File

@@ -1,9 +1,9 @@
import { Link, Stack, TeachingBubble, Text } from "@fluentui/react";
import { useTabs } from "hooks/useTabs";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import React from "react";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceCancel } from "Shared/Telemetry/TelemetryProcessor";
import { traceCancel, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
export const QuickstartTutorial: React.FC = (): JSX.Element => {
const { step, isSampleDBExpanded, isDocumentsTabOpened, sampleCollection, setStep } = useTeachingBubble();
@@ -146,7 +146,10 @@ export const QuickstartTutorial: React.FC = (): JSX.Element => {
hasCloseButton
primaryButtonProps={{
text: "Launch connect",
onClick: () => useTabs.getState().openAndActivateConnectTab(),
onClick: () => {
traceSuccess(Action.CompleteUITour);
useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect);
},
}}
secondaryButtonProps={{
text: "Previous",

View File

@@ -1,6 +1,7 @@
// CSS Dependencies
import { initializeIcons } from "@fluentui/react";
import "bootstrap/dist/css/bootstrap.css";
import { ChatButtonAction } from "Explorer/Controls/ChatButton/ChatButtonComponent";
import { QuickstartCarousel } from "Explorer/Tutorials/QuickstartCarousel";
import { QuickstartTutorial } from "Explorer/Tutorials/QuickstartTutorial";
import { useCarousel } from "hooks/useCarousel";
@@ -17,6 +18,7 @@ import "../externals/jquery.typeahead.min.js";
import "../images/CosmosDB_rgb_ui_lighttheme.ico";
import "../images/favicon.ico";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import "../less/chat.less";
import "../less/documentDB.less";
import "../less/forms.less";
import "../less/infobox.less";
@@ -46,12 +48,10 @@ import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
import { NotificationConsole } from "./Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import "./Explorer/Panes/PanelComponent.less";
import { SidePanel } from "./Explorer/Panes/PanelContainerComponent";
import { SplashScreen } from "./Explorer/SplashScreen/SplashScreen";
import "./Explorer/SplashScreen/SplashScreen.less";
import { Tabs } from "./Explorer/Tabs/Tabs";
import { useConfig } from "./hooks/useConfig";
import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
import { useTabs } from "./hooks/useTabs";
import "./Libs/jquery";
import "./Shared/appInsights";
@@ -59,8 +59,6 @@ initializeIcons();
const App: React.FunctionComponent = () => {
const [isLeftPaneExpanded, setIsLeftPaneExpanded] = useState<boolean>(true);
const openedTabs = useTabs((state) => state.openedTabs);
const isConnectTabOpen = useTabs((state) => state.isConnectTabOpen);
const isCarouselOpen = useCarousel((state) => state.shouldOpen);
const config = useConfig();
@@ -104,11 +102,12 @@ const App: React.FunctionComponent = () => {
{/* Collections Tree Collapsed - End */}
</div>
</div>
{/* Collections Tree - End */}
{openedTabs.length === 0 && !isConnectTabOpen && <SplashScreen explorer={explorer} />}
<Tabs />
<Tabs explorer={explorer} />
</div>
{/* Collections Tree and Tabs - End */}
<div className="chat">
<ChatButtonAction container={explorer} />
</div>
<div
className="dataExplorerErrorConsoleContainer"
role="contentinfo"
@@ -117,6 +116,7 @@ const App: React.FunctionComponent = () => {
>
<NotificationConsole />
</div>
</div>
<SidePanel />
<Dialog />

View File

@@ -29,6 +29,9 @@ export type Features = {
readonly mongoProxyEndpoint?: string;
readonly mongoProxyAPIs?: string;
readonly enableThroughputCap: boolean;
readonly enableNewQuickstart: boolean;
readonly enableChatbot?: boolean;
// can be set via both flight and feature flag
autoscaleDefault: boolean;
@@ -90,6 +93,8 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
notebooksDownBanner: "true" === get("notebooksDownBanner"),
enableThroughputCap: "true" === get("enablethroughputcap"),
enableNewQuickstart: "true" === get("enablenewquickstart"),
enableChatbot: "true" === get("enablechatbot"),
};
}

View File

@@ -130,6 +130,7 @@ export enum Action {
CompleteCarousel,
LaunchUITour,
CancelUITour,
CompleteUITour,
}
export const ActionModifiers = {

View File

@@ -36,7 +36,6 @@ interface UserContext {
readonly accessToken?: string;
readonly authorizationToken?: string;
readonly resourceToken?: string;
readonly useSDKOperations: boolean;
readonly subscriptionType?: SubscriptionType;
readonly quotaId?: string;
// API Type is not yet provided by ARM. You need to manually inspect all the capabilities+kind so we abstract that logic in userContext
@@ -61,7 +60,6 @@ export type PortalEnv = "localhost" | "blackforest" | "fairfax" | "mooncake" | "
const ONE_WEEK_IN_MS = 604800000;
const features = extractFeatures();
const { enableSDKoperations: useSDKOperations } = features;
const userContext: UserContext = {
apiType: "SQL",
@@ -69,7 +67,6 @@ const userContext: UserContext = {
isTryCosmosDBSubscription: false,
portalEnv: "prod",
features,
useSDKOperations,
addCollectionFlight: CollectionCreation.DefaultAddCollectionDefaultFlight,
subscriptionType: CollectionCreation.DefaultSubscriptionType,
collectionCreationDefaults: CollectionCreationDefaults,

View File

@@ -6,37 +6,43 @@ import TabsBase from "../Explorer/Tabs/TabsBase";
interface TabsState {
openedTabs: TabsBase[];
openedReactTabs: ReactTabKind[];
activeTab: TabsBase;
isConnectTabOpen: boolean;
isConnectTabActive: boolean;
activeReactTab: ReactTabKind;
activateTab: (tab: TabsBase) => void;
activateNewTab: (tab: TabsBase) => void;
activateReactTab: (tabkind: ReactTabKind) => void;
updateTab: (tab: TabsBase) => void;
getTabs: (tabKind: ViewModels.CollectionTabKind, comparator?: (tab: TabsBase) => boolean) => TabsBase[];
refreshActiveTab: (comparator: (tab: TabsBase) => boolean) => void;
closeTabsByComparator: (comparator: (tab: TabsBase) => boolean) => void;
closeTab: (tab: TabsBase) => void;
closeAllNotebookTabs: (hardClose: boolean) => void;
activateConnectTab: () => void;
openAndActivateConnectTab: () => void;
closeConnectTab: () => void;
openAndActivateReactTab: (tabKind: ReactTabKind) => void;
closeReactTab: (tabKind: ReactTabKind) => void;
}
export enum ReactTabKind {
Connect,
Home,
}
export const useTabs: UseStore<TabsState> = create((set, get) => ({
openedTabs: [],
openedReactTabs: [ReactTabKind.Home],
activeTab: undefined,
isConnectTabOpen: false,
isConnectTabActive: false,
activeReactTab: ReactTabKind.Home,
activateTab: (tab: TabsBase): void => {
if (get().openedTabs.some((openedTab) => openedTab.tabId === tab.tabId)) {
set({ activeTab: tab, isConnectTabActive: false });
set({ activeTab: tab, activeReactTab: undefined });
tab.onActivate();
}
},
activateNewTab: (tab: TabsBase): void => {
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, isConnectTabActive: false }));
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
tab.onActivate();
},
activateReactTab: (tabKind: ReactTabKind): void => set({ activeTab: undefined, activeReactTab: tabKind }),
updateTab: (tab: TabsBase) => {
if (get().activeTab?.tabId === tab.tabId) {
set({ activeTab: tab });
@@ -73,7 +79,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
return true;
});
if (updatedTabs.length === 0) {
set({ activeTab: undefined, isConnectTabActive: get().isConnectTabOpen });
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
}
if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
@@ -111,21 +117,27 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
});
if (get().openedTabs.length === 0) {
set({ activeTab: undefined, isConnectTabActive: get().isConnectTabOpen });
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home });
}
}
},
activateConnectTab: () => {
if (get().isConnectTabOpen) {
set({ isConnectTabActive: true, activeTab: undefined });
openAndActivateReactTab: (tabKind: ReactTabKind) => {
if (get().openedReactTabs.indexOf(tabKind) === -1) {
set((state) => ({
openedReactTabs: [...state.openedReactTabs, tabKind],
}));
}
set({ activeTab: undefined, activeReactTab: tabKind });
},
openAndActivateConnectTab: () => set({ isConnectTabActive: true, isConnectTabOpen: true, activeTab: undefined }),
closeConnectTab: () => {
const { isConnectTabActive, openedTabs } = get();
if (isConnectTabActive && openedTabs?.length > 0) {
set({ activeTab: openedTabs[0] });
closeReactTab: (tabKind: ReactTabKind) => {
const { activeReactTab, openedTabs, openedReactTabs } = get();
if (activeReactTab === tabKind) {
openedTabs?.length > 0
? set({ activeTab: openedTabs[0], activeReactTab: undefined })
: set({ activeTab: undefined, activeReactTab: openedReactTabs[0] });
}
set({ isConnectTabActive: false, isConnectTabOpen: false });
set({ openedReactTabs: openedReactTabs.filter((tab: ReactTabKind) => tabKind !== tab) });
},
}));