Compare commits

...

7 Commits

Author SHA1 Message Date
dependabot[bot]
d16690095e Bump xmldom from 0.4.0 to 0.6.0
Bumps [xmldom](https://github.com/xmldom/xmldom) from 0.4.0 to 0.6.0.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.4.0...0.6.0)

---
updated-dependencies:
- dependency-name: xmldom
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-04 05:27:42 +00:00
victor-meng
ee4404c439 Fix enable synapse link error (#918)
* Fix enable synapse error

* Default all ARM requests to JSON

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-08-02 21:11:42 -05:00
Steve Faulkner
b65456f754 Fix bug in Dialog State store (#969) 2021-08-02 20:54:30 -05:00
victor-meng
56699ccb1b Fix new resource tree (#962) 2021-07-30 16:23:36 -07:00
victor-meng
042f980b89 Replace window.confirm and window.alert with modal dialog (#965) 2021-07-30 10:27:27 -07:00
t-tarabhatia
7e0c4b7290 Add changes to Partition Key A/B Test (#954) 2021-07-29 08:48:03 -05:00
victor-meng
a66fc06dad Turn off react resource tree (#963) 2021-07-28 21:29:45 -07:00
33 changed files with 514 additions and 314 deletions

6
package-lock.json generated
View File

@@ -26061,9 +26061,9 @@
"dev": true
},
"xmldom": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz",
"integrity": "sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA=="
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz",
"integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg=="
},
"xpath.js": {
"version": "1.1.0",

View File

@@ -95,6 +95,7 @@ export class Flights {
public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest";
public static readonly PartitionKeyTest = "partitionkeytest";
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
}
export class AfecFeatures {

View File

@@ -54,7 +54,7 @@ export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps
</div>
{userContext.authType === AuthType.ResourceToken ? (
<ResourceTokenTree />
) : userContext.features.enableKOResourceTree ? (
) : userContext.features.enableKoResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : (
<ResourceTree container={container} />

View File

@@ -23,13 +23,75 @@ export interface DialogState {
dialogProps?: DialogProps;
openDialog: (props: DialogProps) => void;
closeDialog: () => void;
showOkCancelModalDialog: (
title: string,
subText: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean
) => void;
showOkModalDialog: (title: string, subText: string) => void;
}
export const useDialog: UseStore<DialogState> = create((set) => ({
export const useDialog: UseStore<DialogState> = create((set, get) => ({
visible: false,
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
closeDialog: () =>
set((state) => ({ visible: false, openDialog: state.openDialog, closeDialog: state.closeDialog }), true),
set(
(state) => ({
visible: false,
openDialog: state.openDialog,
closeDialog: state.closeDialog,
showOkCancelModalDialog: state.showOkCancelModalDialog,
showOkModalDialog: state.showOkModalDialog,
}),
true // TODO: This probably should not be true but its causing a prod bug so easier to just set the proper state above
),
showOkCancelModalDialog: (
title: string,
subText: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps,
primaryButtonDisabled?: boolean
): void =>
get().openDialog({
isModal: true,
title,
subText,
primaryButtonText: okLabel,
secondaryButtonText: cancelLabel,
onPrimaryButtonClick: () => {
get().closeDialog();
onOk && onOk();
},
onSecondaryButtonClick: () => {
get().closeDialog();
onCancel && onCancel();
},
choiceGroupProps,
textFieldProps,
primaryButtonDisabled,
}),
showOkModalDialog: (title: string, subText: string): void =>
get().openDialog({
isModal: true,
title,
subText,
primaryButtonText: "Close",
secondaryButtonText: undefined,
onPrimaryButtonClick: () => {
get().closeDialog();
},
onSecondaryButtonClick: undefined,
}),
}));
export interface TextFieldProps extends ITextFieldProps {

View File

@@ -29,6 +29,7 @@ import { QueriesClient } from "../../../Common/QueriesClient";
import * as DataModels from "../../../Contracts/DataModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { useDialog } from "../Dialog";
const title = "Open Saved Queries";
@@ -222,35 +223,42 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
key: "Delete",
text: "Delete query",
onClick: async () => {
if (window.confirm("Are you sure you want to delete this query?")) {
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
});
try {
await this.props.queriesClient.deleteQuery(query);
TelemetryProcessor.traceSuccess(
Action.DeleteSavedQuery,
{
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
},
startKey
);
} catch (error) {
TelemetryProcessor.traceFailure(
Action.DeleteSavedQuery,
{
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
}
await this.fetchSavedQueries(); // get latest state
}
useDialog.getState().showOkCancelModalDialog(
"Confirm delete",
"Are you sure you want to delete this query?",
"Delete",
async () => {
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
});
try {
await this.props.queriesClient.deleteQuery(query);
TelemetryProcessor.traceSuccess(
Action.DeleteSavedQuery,
{
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
},
startKey
);
} catch (error) {
TelemetryProcessor.traceFailure(
Action.DeleteSavedQuery,
{
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: title,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
startKey
);
}
await this.fetchSavedQueries(); // get latest state
},
"Cancel",
undefined
);
},
},
],

View File

@@ -17,7 +17,6 @@ describe("DataSampleUtils", () => {
collections: ko.observableArray<Collection>([collection]),
} as Database;
const explorer = {} as Explorer;
explorer.showOkModalDialog = () => {};
useDatabases.getState().addDatabases([database]);
const dataSamplesUtil = new DataSamplesUtil(explorer);

View File

@@ -1,6 +1,7 @@
import * as ViewModels from "../../Contracts/ViewModels";
import { userContext } from "../../UserContext";
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import { useDatabases } from "../useDatabases";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
@@ -20,7 +21,7 @@ export class DataSamplesUtil {
const containerName = generator.getCollectionId();
if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
logConsoleError(msg);
return;
}
@@ -29,7 +30,7 @@ export class DataSamplesUtil {
.createSampleContainerAsync()
.catch((error) => logConsoleError(`Error creating sample container: ${error}`));
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
useDialog.getState().showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
logConsoleInfo(msg);
}

View File

@@ -1,4 +1,3 @@
import { IChoiceGroupProps } from "@fluentui/react";
import * as ko from "knockout";
import React from "react";
import _ from "underscore";
@@ -35,7 +34,7 @@ import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import "./ComponentRegisterer";
import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
import { DialogProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
@@ -538,17 +537,22 @@ export default class Explorer {
}
}
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
public uploadFile(
name: string,
content: string,
parent: NotebookContentItem,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to upload notebook, but notebook is not enabled";
handleError(error, "Explorer/uploadFile");
throw new Error(error);
}
const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent);
const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent, isGithubTree);
promise
.then(() => this.resourceTree.triggerRender())
.catch((reason) => this.showOkModalDialog("Unable to upload file", reason));
.catch((reason) => useDialog.getState().showOkModalDialog("Unable to upload file", getErrorMessage(reason)));
return promise;
}
@@ -614,51 +618,6 @@ export default class Explorer {
this.notebookManager?.openCopyNotebookPane(name, content);
}
public showOkModalDialog(title: string, msg: string): void {
useDialog.getState().openDialog({
isModal: true,
title,
subText: msg,
primaryButtonText: "Close",
secondaryButtonText: undefined,
onPrimaryButtonClick: () => {
useDialog.getState().closeDialog();
},
onSecondaryButtonClick: undefined,
});
}
public showOkCancelModalDialog(
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
choiceGroupProps?: IChoiceGroupProps,
textFieldProps?: TextFieldProps,
isPrimaryButtonDisabled?: boolean
): void {
useDialog.getState().openDialog({
isModal: true,
title,
subText: msg,
primaryButtonText: okLabel,
secondaryButtonText: cancelLabel,
onPrimaryButtonClick: () => {
useDialog.getState().closeDialog();
onOk && onOk();
},
onSecondaryButtonClick: () => {
useDialog.getState().closeDialog();
onCancel && onCancel();
},
choiceGroupProps,
textFieldProps,
primaryButtonDisabled: isPrimaryButtonDisabled,
});
}
/**
* Note: To keep it simple, this creates a disconnected NotebookContentItem that is not connected to the resource tree.
* Connecting it to a tree possibly requires the intermediate missing folders if the item is nested in a subfolder.
@@ -718,7 +677,7 @@ export default class Explorer {
return true;
}
public renameNotebook(notebookFile: NotebookContentItem): void {
public renameNotebook(notebookFile: NotebookContentItem, isGithubTree?: boolean): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled";
handleError(error, "Explorer/renameNotebook");
@@ -732,7 +691,9 @@ export default class Explorer {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
});
if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
useDialog
.getState()
.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
} else {
useSidePanel.getState().openSidePanel(
"Rename Notebook",
@@ -749,7 +710,7 @@ export default class Explorer {
paneTitle="Rename Notebook"
defaultInput={FileSystemUtil.stripExtension(notebookFile.name, "ipynb")}
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input, isGithubTree)
}
notebookFile={notebookFile}
/>
@@ -757,7 +718,7 @@ export default class Explorer {
}
}
public onCreateDirectory(parent: NotebookContentItem): void {
public onCreateDirectory(parent: NotebookContentItem, isGithubTree?: boolean): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled";
handleError(error, "Explorer/onCreateDirectory");
@@ -779,7 +740,7 @@ export default class Explorer {
submitButtonLabel="Create"
defaultInput=""
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input)
this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input, isGithubTree)
}
notebookFile={parent}
/>
@@ -848,7 +809,7 @@ export default class Explorer {
}
};
public deleteNotebookFile(item: NotebookContentItem): Promise<void> {
public deleteNotebookFile(item: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to delete notebook file, but notebook is not enabled";
handleError(error, "Explorer/deleteNotebookFile");
@@ -862,7 +823,9 @@ export default class Explorer {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
});
if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
useDialog
.getState()
.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
return Promise.reject();
}
@@ -879,7 +842,7 @@ export default class Explorer {
return Promise.reject();
}
return this.notebookManager?.notebookContentClient.deleteContentItem(item).then(
return this.notebookManager?.notebookContentClient.deleteContentItem(item, isGithubTree).then(
() => logConsoleInfo(`Successfully deleted: ${item.path}`),
(reason) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`)
);
@@ -888,7 +851,7 @@ export default class Explorer {
/**
* This creates a new notebook file, then opens the notebook
*/
public onNewNotebookClicked(parent?: NotebookContentItem): void {
public onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create new notebook, but notebook is not enabled";
handleError(error, "Explorer/onNewNotebookClicked");
@@ -903,7 +866,7 @@ export default class Explorer {
});
this.notebookManager?.notebookContentClient
.createNewNotebookFile(parent)
.createNewNotebookFile(parent, isGithubTree)
.then((newFile: NotebookContentItem) => {
logConsoleInfo(`Successfully created: ${newFile.name}`);
TelemetryProcessor.traceSuccess(

View File

@@ -38,6 +38,7 @@ import { useTabs } from "../../../hooks/useTabs";
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
import { useDialog } from "../../Controls/Dialog";
import * as FileSystemUtil from "../FileSystemUtil";
import * as cdbActions from "../NotebookComponent/actions";
import { NotebookUtil } from "../NotebookUtil";
@@ -686,10 +687,8 @@ const handleKernelConnectionLostEpic = (
logConsoleError(msg);
logFailureToTelemetry(state, "Kernel restart error", msg);
const explorer = window.dataExplorer;
if (explorer) {
explorer.showOkModalDialog("kernel restarts", msg);
}
useDialog.getState().showOkModalDialog("kernel restarts", msg);
return of(EMPTY);
}
@@ -773,8 +772,7 @@ const closeUnsupportedMimetypesEpic = (
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap((action) => {
const mimetype = action.payload.model.mimetype;
const explorer = window.dataExplorer;
if (explorer && !TextFile.handles(mimetype)) {
if (!TextFile.handles(mimetype)) {
const filepath = action.payload.filepath;
// Close tab and show error message
useTabs
@@ -783,7 +781,7 @@ const closeUnsupportedMimetypesEpic = (
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
explorer.showOkModalDialog("File cannot be rendered", msg);
useDialog.getState().showOkModalDialog("File cannot be rendered", msg);
logConsoleError(msg);
}
return EMPTY;
@@ -803,19 +801,16 @@ const closeContentFailedToFetchEpic = (
return action$.pipe(
ofType(actions.FETCH_CONTENT_FAILED),
mergeMap((action) => {
const explorer = window.dataExplorer;
if (explorer) {
const filepath = action.payload.filepath;
// Close tab and show error message
useTabs
.getState()
.closeTabsByComparator(
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `Failed to load file: ${filepath}.`;
explorer.showOkModalDialog("Failure to load", msg);
logConsoleError(msg);
}
const filepath = action.payload.filepath;
// Close tab and show error message
useTabs
.getState()
.closeTabsByComparator(
(tab: any) => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `Failed to load file: ${filepath}.`;
useDialog.getState().showOkModalDialog("Failure to load", msg);
logConsoleError(msg);
return EMPTY;
})
);

View File

@@ -36,7 +36,7 @@ export class NotebookContentClient {
*
* @param parent parent folder
*/
public createNewNotebookFile(parent: NotebookContentItem): Promise<NotebookContentItem> {
public createNewNotebookFile(parent: NotebookContentItem, isGithubTree?: boolean): Promise<NotebookContentItem> {
if (!parent || parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent must be a directory: ${parent}`);
}
@@ -57,6 +57,8 @@ export class NotebookContentClient {
const notebookFile = xhr.response;
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
if (parent.children) {
item.parent = parent;
parent.children.push(item);
@@ -66,9 +68,9 @@ export class NotebookContentClient {
});
}
public async deleteContentItem(item: NotebookContentItem): Promise<void> {
public async deleteContentItem(item: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
const path = await this.deleteNotebookFile(item.path);
useNotebook.getState().deleteNotebookItem(item);
useNotebook.getState().deleteNotebookItem(item, isGithubTree);
// TODO: Delete once old resource tree is removed
if (!path || path !== item.path) {
@@ -91,7 +93,8 @@ export class NotebookContentClient {
public async uploadFileAsync(
name: string,
content: string,
parent: NotebookContentItem
parent: NotebookContentItem,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
if (!parent || parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent must be a directory: ${parent}`);
@@ -115,6 +118,8 @@ export class NotebookContentClient {
.then((xhr: AjaxResponse) => {
const notebookFile = xhr.response;
const item = NotebookUtil.createNotebookContentItem(notebookFile.name, notebookFile.path, notebookFile.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
if (parent.children) {
item.parent = parent;
parent.children.push(item);
@@ -137,7 +142,11 @@ export class NotebookContentClient {
* @param sourcePath
* @param targetName is not prefixed with path
*/
public renameNotebook(item: NotebookContentItem, targetName: string): Promise<NotebookContentItem> {
public renameNotebook(
item: NotebookContentItem,
targetName: string,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
const sourcePath = item.path;
// Match extension
if (sourcePath.indexOf(".") !== -1) {
@@ -163,6 +172,9 @@ export class NotebookContentClient {
item.name = notebookFile.name;
item.path = notebookFile.path;
item.timestamp = NotebookUtil.getCurrentTimestamp();
useNotebook.getState().updateNotebookItem(item, isGithubTree);
return item;
});
}
@@ -172,7 +184,11 @@ export class NotebookContentClient {
* @param parent
* @param newDirectoryName basename of the new directory
*/
public async createDirectory(parent: NotebookContentItem, newDirectoryName: string): Promise<NotebookContentItem> {
public async createDirectory(
parent: NotebookContentItem,
newDirectoryName: string,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
if (parent.type !== NotebookContentItemType.Directory) {
throw new Error(`Parent is not a directory: ${parent.path}`);
}
@@ -199,8 +215,11 @@ export class NotebookContentClient {
const dir = xhr.response;
const item = NotebookUtil.createNotebookContentItem(dir.name, dir.path, dir.type);
useNotebook.getState().insertNotebookItem(parent, cloneDeep(item), isGithubTree);
// TODO: delete when ResourceTreeAdapter is removed
item.parent = parent;
parent.children?.push(item);
return item;
});
}

View File

@@ -18,6 +18,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { getFullName } from "../../Utils/UserUtils";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel";
@@ -172,31 +173,33 @@ export default class NotebookManager {
if (error.status === HttpStatusCodes.Unauthorized) {
this.gitHubOAuthService.resetToken();
this.params.container.showOkCancelModalDialog(
undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub",
() =>
useSidePanel
.getState()
.openSidePanel(
"Connect to GitHub",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
),
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
undefined,
"Cosmos DB cannot access your Github account anymore. Please connect to GitHub again.",
"Connect to GitHub",
() =>
useSidePanel
.getState()
.openSidePanel(
"Connect to GitHub",
<GitHubReposPanel
explorer={this.params.container}
gitHubClientProp={this.params.container.notebookManager.gitHubClient}
junoClientProp={this.junoClient}
/>
),
"Cancel",
undefined
);
}
};
private promptForCommitMsg = (title: string, primaryButtonLabel: string) => {
return new Promise<string>((resolve, reject) => {
let commitMsg = "Committed from Azure Cosmos DB Notebooks";
this.params.container.showOkCancelModalDialog(
useDialog.getState().showOkCancelModalDialog(
title || "Commit",
undefined,
primaryButtonLabel || "Commit",

View File

@@ -38,8 +38,9 @@ interface NotebookState {
setNotebookBasePath: (notebookBasePath: string) => void;
refreshNotebooksEnabledStateForAccount: () => Promise<void>;
findItem: (root: NotebookContentItem, item: NotebookContentItem) => NotebookContentItem;
updateNotebookItem: (item: NotebookContentItem) => void;
deleteNotebookItem: (item: NotebookContentItem) => void;
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean) => void;
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean) => void;
initializeNotebooksTree: (notebookManager: NotebookManager) => Promise<void>;
initializeGitHubRepos: (pinnedRepos: IPinnedRepo[]) => void;
}
@@ -141,19 +142,30 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
return undefined;
},
updateNotebookItem: (item: NotebookContentItem): void => {
const root = cloneDeep(get().myNotebooksContentRoot);
insertNotebookItem: (parent: NotebookContentItem, item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, parent);
item.parent = parentItem;
if (parentItem.children) {
parentItem.children.push(item);
} else {
parentItem.children = [item];
}
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
updateNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, item.parent);
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
parentItem.children.push(item);
item.parent = parentItem;
set({ myNotebooksContentRoot: root });
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
deleteNotebookItem: (item: NotebookContentItem): void => {
const root = cloneDeep(get().myNotebooksContentRoot);
deleteNotebookItem: (item: NotebookContentItem, isGithubTree?: boolean): void => {
const root = isGithubTree ? cloneDeep(get().gitHubNotebooksContentRoot) : cloneDeep(get().myNotebooksContentRoot);
const parentItem = get().findItem(root, item.parent);
parentItem.children = parentItem.children.filter((child) => child.path !== item.path);
set({ myNotebooksContentRoot: root });
isGithubTree ? set({ gitHubNotebooksContentRoot: root }) : set({ myNotebooksContentRoot: root });
},
initializeNotebooksTree: async (notebookManager: NotebookManager): Promise<void> => {
const myNotebooksContentRoot = {
@@ -216,6 +228,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
path: "PsuedoDir",
type: NotebookContentItemType.Directory,
children: [],
parent: gitHubNotebooksContentRoot,
};
pinnedRepo.branches.forEach((branch) => {
@@ -223,6 +236,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
name: branch.name,
path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""),
type: NotebookContentItemType.Directory,
parent: repoTreeItem,
});
});

View File

@@ -113,11 +113,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
collectionId: "",
enableIndexing: true,
isSharded: userContext.apiType !== "Tables",
partitionKey:
(userContext.features.partitionKeyDefault && userContext.apiType === "SQL") ||
(userContext.features.partitionKeyDefault && userContext.apiType === "Mongo")
? "/id"
: "",
partitionKey: this.getPartitionKey(),
enableDedicatedThroughput: false,
createMongoWildCardIndex: isCapabilityEnabled("EnableMongo"),
useHashV2: false,
@@ -815,6 +811,19 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return tooltipText;
}
private getPartitionKey(): string {
if (userContext.apiType !== "SQL" && userContext.apiType !== "Mongo") {
return "";
}
if (userContext.features.partitionKeyDefault) {
return userContext.apiType === "SQL" ? "/id" : "_id";
}
if (userContext.features.partitionKeyDefault2) {
return userContext.apiType === "SQL" ? "/pk" : "pk";
}
return "";
}
private getPartitionKeySubtext(): string {
if (
userContext.features.partitionKeyDefault &&

View File

@@ -98,6 +98,7 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
const copyNotebook = async (location: Location): Promise<NotebookContentItem> => {
let parent: NotebookContentItem;
let isGithubTree: boolean;
switch (location.type) {
case "MyNotebooks":
parent = {
@@ -105,21 +106,23 @@ export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
path: useNotebook.getState().notebookBasePath,
type: NotebookContentItemType.Directory,
};
isGithubTree = false;
break;
case "GitHub":
parent = {
name: ResourceTreeAdapter.GitHubReposTitle,
name: selectedLocation.branch,
path: GitHubUtils.toContentUri(selectedLocation.owner, selectedLocation.repo, selectedLocation.branch, ""),
type: NotebookContentItemType.Directory,
};
isGithubTree = true;
break;
default:
throw new Error(`Unsupported location type ${location.type}`);
}
return container.uploadFile(name, content, parent);
return container.uploadFile(name, content, parent, isGithubTree);
};
const onDropDownChange = (_: FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {

View File

@@ -1,5 +1,6 @@
import Q from "q";
import { userContext } from "../../../UserContext";
import { useDialog } from "../../Controls/Dialog";
import Explorer from "../../Explorer";
import * as Entities from "../Entities";
import * as DataTableUtilities from "./DataTableUtilities";
@@ -69,19 +70,28 @@ export default class TableCommands {
return null; // Error
}
var entitiesToDelete: Entities.ITableEntity[] = viewModel.selected();
let deleteMessage: string = "Are you sure you want to delete the selected entities?";
if (userContext.apiType === "Cassandra") {
deleteMessage = "Are you sure you want to delete the selected rows?";
}
if (window.confirm(deleteMessage)) {
viewModel.queryTablesTab.container.tableDataClient
.deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete)
.then((results: any) => {
return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => {
viewModel.redrawTableThrottled();
const deleteMessage: string =
userContext.apiType === "Cassandra"
? "Are you sure you want to delete the selected rows?"
: "Are you sure you want to delete the selected entities?";
useDialog.getState().showOkCancelModalDialog(
"Confirm delete",
deleteMessage,
"Delete",
() => {
viewModel.queryTablesTab.container.tableDataClient
.deleteDocuments(viewModel.queryTablesTab.collection, entitiesToDelete)
.then((results: any) => {
return viewModel.removeEntitiesFromCache(entitiesToDelete).then(() => {
viewModel.redrawTableThrottled();
});
});
});
}
},
"Cancel",
undefined
);
return null;
}

View File

@@ -21,6 +21,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
import ConflictId from "../Tree/ConflictId";
@@ -228,7 +229,7 @@ export default class ConflictsTab extends TabsBase {
this._documentsIterator = this.createIterator();
await this.loadNextPage();
} catch (error) {
window.alert(getErrorMessage(error));
useDialog.getState().showOkModalDialog("Refresh documents grid failed", getErrorMessage(error));
}
}
@@ -252,10 +253,23 @@ export default class ConflictsTab extends TabsBase {
}
public onAcceptChangesClick = async (): Promise<void> => {
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
return;
if (this.isEditorDirty()) {
useDialog
.getState()
.showOkCancelModalDialog(
"Unsaved changes",
"Changes will be lost. Do you want to continue?",
"OK",
async () => await this.resolveConflict(),
"Cancel",
undefined
);
} else {
await this.resolveConflict();
}
};
private resolveConflict = async (): Promise<void> => {
this.isExecutionError(false);
this.isExecuting(true);
@@ -318,7 +332,7 @@ export default class ConflictsTab extends TabsBase {
} catch (error) {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Resolve conflict failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.ResolveConflict,
{
@@ -372,7 +386,7 @@ export default class ConflictsTab extends TabsBase {
} catch (error) {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Delete conflict failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.DeleteConflict,
{
@@ -662,11 +676,6 @@ export default class ConflictsTab extends TabsBase {
return jsonObject;
}
private _isIgnoreDirtyEditor = (): boolean => {
var msg: string = "Changes will be lost. Do you want to continue?";
return window.confirm(msg);
};
private _getPartitionKeyPropertyHeader(): string {
return (
(this.partitionKey &&

View File

@@ -25,6 +25,7 @@ import { userContext } from "../../UserContext";
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
import * as QueryUtils from "../../Utils/QueryUtils";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
import DocumentId from "../Tree/DocumentId";
@@ -378,7 +379,7 @@ export default class DocumentsTab extends TabsBase {
this.isFilterExpanded(false);
document.getElementById("errorStatusIcon")?.focus();
} catch (error) {
window.alert(getErrorMessage(error));
useDialog.getState().showOkModalDialog("Refresh documents grid failed", getErrorMessage(error));
}
}
@@ -401,18 +402,29 @@ export default class DocumentsTab extends TabsBase {
return Q();
}
public onNewDocumentClick = (): Q.Promise<any> => {
if (this.isEditorDirty() && !this._isIgnoreDirtyEditor()) {
return Q();
public onNewDocumentClick = (): void => {
if (this.isEditorDirty()) {
useDialog
.getState()
.showOkCancelModalDialog(
"Unsaved changes",
"Changes will be lost. Do you want to continue?",
"OK",
() => this.initializeNewDocument(),
"Cancel",
undefined
);
} else {
this.initializeNewDocument();
}
this.selectedDocumentId(null);
};
private initializeNewDocument = (): void => {
this.selectedDocumentId(null);
const defaultDocument: string = this.renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4);
this.initialDocumentContent(defaultDocument);
this.selectedDocumentContent.setBaseline(defaultDocument);
this.editorState(ViewModels.DocumentExplorerState.newDocumentValid);
return Q();
};
public onSaveNewDocumentClick = (): Promise<any> => {
@@ -453,7 +465,7 @@ export default class DocumentsTab extends TabsBase {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Create document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateDocument,
{
@@ -516,7 +528,7 @@ export default class DocumentsTab extends TabsBase {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateDocument,
{
@@ -546,9 +558,16 @@ export default class DocumentsTab extends TabsBase {
? "Are you sure you want to delete the selected item ?"
: "Are you sure you want to delete the selected document ?";
if (window.confirm(msg)) {
await this._deleteDocument(selectedDocumentId);
}
useDialog
.getState()
.showOkCancelModalDialog(
"Confirm delete",
msg,
"Delete",
async () => await this._deleteDocument(selectedDocumentId),
"Cancel",
undefined
);
};
public onValidDocumentEdit(): Q.Promise<any> {
@@ -617,11 +636,6 @@ export default class DocumentsTab extends TabsBase {
}
}
private _isIgnoreDirtyEditor = (): boolean => {
var msg: string = "Changes will be lost. Do you want to continue?";
return window.confirm(msg);
};
protected __deleteDocument(documentId: DocumentId): Promise<void> {
return deleteDocument(this.collection, documentId);
}

View File

@@ -16,6 +16,7 @@ import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { useDialog } from "../Controls/Dialog";
import DocumentId from "../Tree/DocumentId";
import ObjectId from "../Tree/ObjectId";
import DocumentsTab from "./DocumentsTab";
@@ -111,7 +112,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Create document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateDocument,
{
@@ -169,7 +170,7 @@ export default class MongoDocumentsTab extends DocumentsTab {
(error) => {
this.isExecutionError(true);
const errorMessage = getErrorMessage(error);
window.alert(errorMessage);
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateDocument,
{

View File

@@ -17,6 +17,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils";
import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { useDialog } from "../Controls/Dialog";
import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory";
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
import * as CdbActions from "../Notebook/NotebookComponent/actions";
@@ -59,14 +60,16 @@ export default class NotebookTabV2 extends NotebookTabBase {
};
if (this.notebookComponentAdapter.isContentDirty()) {
this.container.showOkCancelModalDialog(
"Close without saving?",
`File has unsaved changes, close without saving?`,
"Close",
cleanup,
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
"Close without saving?",
`File has unsaved changes, close without saving?`,
"Close",
cleanup,
"Cancel",
undefined
);
return Q.resolve(null);
} else {
cleanup();

View File

@@ -1,12 +1,11 @@
import Q from "q";
import { extractPartitionKey } from "@azure/cosmos";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import DocumentId from "./DocumentId";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import { extractPartitionKey } from "@azure/cosmos";
import ConflictsTab from "../Tabs/ConflictsTab";
import { readDocument } from "../../Common/dataAccess/readDocument";
import * as DataModels from "../../Contracts/DataModels";
import { useDialog } from "../Controls/Dialog";
import ConflictsTab from "../Tabs/ConflictsTab";
import DocumentId from "./DocumentId";
export default class ConflictId {
public container: ConflictsTab;
@@ -50,13 +49,20 @@ export default class ConflictId {
}
public click() {
if (
!this.container.isEditorDirty() ||
window.confirm("Your unsaved changes will be lost. Do you want to continue?")
) {
if (this.container.isEditorDirty()) {
useDialog
.getState()
.showOkCancelModalDialog(
"Unsaved changes",
"Your unsaved changes will be lost. Do you want to continue?",
"OK",
() => this.loadConflict(),
"Cancel",
undefined
);
} else {
this.loadConflict();
}
return;
}
public async loadConflict(): Promise<void> {

View File

@@ -1,5 +1,6 @@
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import { useDialog } from "../Controls/Dialog";
import DocumentsTab from "../Tabs/DocumentsTab";
export default class DocumentId {
@@ -28,10 +29,20 @@ export default class DocumentId {
}
public click() {
if (!this.container.isEditorDirty() || window.confirm("Your unsaved changes will be lost.")) {
if (this.container.isEditorDirty()) {
useDialog
.getState()
.showOkCancelModalDialog(
"Unsaved changes",
"Your unsaved changes will be lost. Do you want to continue?",
"OK",
() => this.loadDocument(),
"Cancel",
undefined
);
} else {
this.loadDocument();
}
return;
}
public partitionKeyHeader(): Object {

View File

@@ -1,5 +1,6 @@
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import * as React from "react";
import shallow from "zustand/shallow";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import DeleteIcon from "../../../images/delete.svg";
import GalleryIcon from "../../../images/GalleryIcon.svg";
@@ -24,6 +25,7 @@ import { isServerlessAccount } from "../../Utils/CapabilityUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { useDialog } from "../Controls/Dialog";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
@@ -54,7 +56,16 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
galleryContentRoot,
gitHubNotebooksContentRoot,
updateNotebookItem,
} = useNotebook();
} = useNotebook(
(state) => ({
isNotebookEnabled: state.isNotebookEnabled,
myNotebooksContentRoot: state.myNotebooksContentRoot,
galleryContentRoot: state.galleryContentRoot,
gitHubNotebooksContentRoot: state.gitHubNotebooksContentRoot,
updateNotebookItem: state.updateNotebookItem,
}),
shallow
);
const { activeTab, refreshActiveTab } = useTabs();
const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
const pseudoDirPath = "PsuedoDir";
@@ -165,7 +176,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
}
});
}
},
true
);
gitHubNotebooksTree.contextMenu = [
@@ -201,9 +213,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
};
const buildChildNodes = (
container: Explorer,
item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void
onFileClick: (item: NotebookContentItem) => void,
isGithubTree?: boolean
): TreeNode[] => {
if (!item || !item.children) {
return [];
@@ -211,8 +223,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
return item.children.map((item) => {
const result =
item.type === NotebookContentItemType.Directory
? buildNotebookDirectoryNode(item, onFileClick)
: buildNotebookFileNode(item, onFileClick);
? buildNotebookDirectoryNode(item, onFileClick, isGithubTree)
: buildNotebookFileNode(item, onFileClick, isGithubTree);
result.timestamp = item.timestamp;
return result;
});
@@ -221,7 +233,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
const buildNotebookFileNode = (
item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void
onFileClick: (item: NotebookContentItem) => void,
isGithubTree?: boolean
): TreeNode => {
return {
label: item.name,
@@ -238,30 +251,36 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
(activeTab as any).notebookPath() === item.path
);
},
contextMenu: createFileContextMenu(container, item),
contextMenu: createFileContextMenu(container, item, isGithubTree),
data: item,
};
};
const createFileContextMenu = (container: Explorer, item: NotebookContentItem): TreeNodeMenuItem[] => {
const createFileContextMenu = (
container: Explorer,
item: NotebookContentItem,
isGithubTree?: boolean
): TreeNodeMenuItem[] => {
let items: TreeNodeMenuItem[] = [
{
label: "Rename",
iconSrc: NotebookIcon,
onClick: () => container.renameNotebook(item),
onClick: () => container.renameNotebook(item, isGithubTree),
},
{
label: "Delete",
iconSrc: DeleteIcon,
onClick: () => {
container.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}"`,
"Delete",
() => container.deleteNotebookFile(item),
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}"`,
"Delete",
() => container.deleteNotebookFile(item, isGithubTree),
"Cancel",
undefined
);
},
},
{
@@ -308,41 +327,47 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
}
};
const createDirectoryContextMenu = (container: Explorer, item: NotebookContentItem): TreeNodeMenuItem[] => {
const createDirectoryContextMenu = (
container: Explorer,
item: NotebookContentItem,
isGithubTree?: boolean
): TreeNodeMenuItem[] => {
let items: TreeNodeMenuItem[] = [
{
label: "Refresh",
iconSrc: RefreshIcon,
onClick: () => loadSubitems(item),
onClick: () => loadSubitems(item, isGithubTree),
},
{
label: "Delete",
iconSrc: DeleteIcon,
onClick: () => {
container.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}?"`,
"Delete",
() => container.deleteNotebookFile(item),
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}?"`,
"Delete",
() => container.deleteNotebookFile(item, isGithubTree),
"Cancel",
undefined
);
},
},
{
label: "Rename",
iconSrc: NotebookIcon,
onClick: () => container.renameNotebook(item),
onClick: () => container.renameNotebook(item, isGithubTree),
},
{
label: "New Directory",
iconSrc: NewNotebookIcon,
onClick: () => container.onCreateDirectory(item),
onClick: () => container.onCreateDirectory(item, isGithubTree),
},
{
label: "New Notebook",
iconSrc: NewNotebookIcon,
onClick: () => container.onNewNotebookClicked(item),
onClick: () => container.onNewNotebookClicked(item, isGithubTree),
},
{
label: "Upload File",
@@ -367,7 +392,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
const buildNotebookDirectoryNode = (
item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void
onFileClick: (item: NotebookContentItem) => void,
isGithubTree?: boolean
): TreeNode => {
return {
label: item.name,
@@ -377,7 +403,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
isLeavesParentsSeparate: true,
onClick: () => {
if (!item.children) {
loadSubitems(item);
loadSubitems(item, isGithubTree);
}
},
isSelected: () => {
@@ -390,9 +416,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
(activeTab as any).notebookPath() === item.path
);
},
contextMenu: item.path !== pseudoDirPath ? createDirectoryContextMenu(container, item) : undefined,
contextMenu: item.path !== pseudoDirPath ? createDirectoryContextMenu(container, item, isGithubTree) : undefined,
data: item,
children: buildChildNodes(container, item, onFileClick),
children: buildChildNodes(item, onFileClick, isGithubTree),
};
};
@@ -694,9 +720,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
return traverse(schema);
};
const loadSubitems = async (item: NotebookContentItem): Promise<void> => {
const loadSubitems = async (item: NotebookContentItem, isGithubTree?: boolean): Promise<void> => {
const updatedItem = await container.notebookManager?.notebookContentClient?.updateItemChildren(item);
updateNotebookItem(updatedItem);
updateNotebookItem(updatedItem, isGithubTree);
};
const dataRootNode = buildDataTree();

View File

@@ -27,6 +27,7 @@ import { isServerlessAccount } from "../../Utils/CapabilityUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { useDialog } from "../Controls/Dialog";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
@@ -712,14 +713,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Delete",
iconSrc: DeleteIcon,
onClick: () => {
this.container.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}"`,
"Delete",
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}"`,
"Delete",
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
"Cancel",
undefined
);
},
},
{
@@ -777,14 +780,16 @@ export class ResourceTreeAdapter implements ReactAdapter {
label: "Delete",
iconSrc: DeleteIcon,
onClick: () => {
this.container.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}?"`,
"Delete",
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
"Cancel",
undefined
);
useDialog
.getState()
.showOkCancelModalDialog(
"Confirm delete",
`Are you sure you want to delete "${item.name}?"`,
"Delete",
() => this.container.deleteNotebookFile(item).then(() => this.triggerRender()),
"Cancel",
undefined
);
},
},
{

View File

@@ -8,6 +8,7 @@ import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import { getErrorMessage } from "../Tables/Utilities";
import { NewStoredProcedureTab } from "../Tabs/StoredProcedureTab/StoredProcedureTab";
@@ -138,16 +139,21 @@ export default class StoredProcedure {
}
};
public delete() {
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
return;
}
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
useDialog.getState().showOkCancelModalDialog(
"Confirm delete",
"Are you sure you want to delete the stored procedure?",
"Delete",
() => {
useTabs.getState().closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
deleteStoredProcedure(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
useTabs.getState().closeTabsByComparator((tab: TabsBase) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
);
},
(reason) => {}
"Cancel",
undefined
);
}

View File

@@ -6,6 +6,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import TriggerTab from "../Tabs/TriggerTab";
import { useSelectedNode } from "../useSelectedNode";
@@ -99,16 +100,21 @@ export default class Trigger {
};
public delete() {
if (!window.confirm("Are you sure you want to delete the trigger?")) {
return;
}
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
useDialog.getState().showOkCancelModalDialog(
"Confirm delete",
"Are you sure you want to delete the trigger?",
"Delete",
() => {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
deleteTrigger(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
(reason) => {}
);
},
(reason) => {}
"Cancel",
undefined
);
}
}

View File

@@ -6,6 +6,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { useTabs } from "../../hooks/useTabs";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { useDialog } from "../Controls/Dialog";
import Explorer from "../Explorer";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
import { useSelectedNode } from "../useSelectedNode";
@@ -95,18 +96,23 @@ export default class UserDefinedFunction {
}
public delete() {
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
return;
}
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
useDialog.getState().showOkCancelModalDialog(
"Confirm delete",
"Are you sure you want to delete the user defined function?",
"Delete",
() => {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
deleteUserDefinedFunction(this.collection.databaseId, this.collection.id(), this.id()).then(
() => {
useTabs.getState().closeTabsByComparator((tab) => tab.node && tab.node.rid === this.rid);
this.collection.children.remove(this);
},
() => {
/**/
}
);
},
() => {
/**/
}
"Cancel",
undefined
);
}
}

View File

@@ -10,12 +10,13 @@ export type Features = {
readonly enableSchema: boolean;
autoscaleDefault: boolean;
partitionKeyDefault: boolean;
partitionKeyDefault2: boolean;
readonly enableSDKoperations: boolean;
readonly enableSpark: boolean;
readonly enableTtl: boolean;
readonly executeSproc: boolean;
readonly enableAadDataPlane: boolean;
readonly enableKOResourceTree: boolean;
readonly enableKoResourceTree: boolean;
readonly hostedDataExplorer: boolean;
readonly junoEndpoint?: string;
readonly livyEndpoint?: string;
@@ -57,7 +58,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
enableSDKoperations: "true" === get("enablesdkoperations"),
enableSpark: "true" === get("enablespark"),
enableTtl: "true" === get("enablettl"),
enableKOResourceTree: "true" === get("enablekoresourcetree"),
enableKoResourceTree: "true" === get("enablekoresourcetree"),
executeSproc: "true" === get("dataexplorerexecutesproc"),
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
junoEndpoint: get("junoendpoint"),
@@ -72,5 +73,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
ttl90Days: "true" === get("ttl90days"),
autoscaleDefault: "true" === get("autoscaledefault"),
partitionKeyDefault: "true" === get("partitionkeytest"),
partitionKeyDefault2: "true" === get("pkpartitionkeytest"),
};
}

View File

@@ -1,8 +1,9 @@
import * as GalleryUtils from "./GalleryUtils";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { HttpStatusCodes } from "../Common/Constants";
import { useDialog } from "../Explorer/Controls/Dialog";
import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import Explorer from "../Explorer/Explorer";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "./GalleryUtils";
const galleryItem: IGalleryItem = {
id: "id",
@@ -29,11 +30,11 @@ describe("GalleryUtils", () => {
it("downloadItem shows dialog in data explorer", () => {
const container = {} as Explorer;
container.showOkCancelModalDialog = jest.fn().mockImplementation();
GalleryUtils.downloadItem(container, undefined, galleryItem, undefined);
expect(container.showOkCancelModalDialog).toBeCalled();
expect(useDialog.getState().visible).toBe(true);
expect(useDialog.getState().dialogProps).toBeDefined();
expect(useDialog.getState().dialogProps.title).toBe("Download to My Notebooks");
});
it("favoriteItem favorites item", async () => {
@@ -66,11 +67,11 @@ describe("GalleryUtils", () => {
it("deleteItem shows dialog in data explorer", () => {
const container = {} as Explorer;
container.showOkCancelModalDialog = jest.fn().mockImplementation();
GalleryUtils.deleteItem(container, undefined, galleryItem, undefined);
expect(container.showOkCancelModalDialog).toBeCalled();
expect(useDialog.getState().visible).toBe(true);
expect(useDialog.getState().dialogProps).toBeDefined();
expect(useDialog.getState().dialogProps.title).toBe("Remove published notebook");
});
it("getGalleryViewerProps gets gallery viewer props correctly", () => {

View File

@@ -3,7 +3,7 @@ import { Notebook } from "@nteract/commutable";
import { NotebookV4 } from "@nteract/commutable/lib/v4";
import { HttpStatusCodes } from "../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
import { TextFieldProps } from "../Explorer/Controls/Dialog";
import { TextFieldProps, useDialog } from "../Explorer/Controls/Dialog";
import {
GalleryTab,
GalleryViewerComponent,
@@ -222,7 +222,7 @@ export function downloadItem(
});
const name = data.name;
container.showOkCancelModalDialog(
useDialog.getState().showOkCancelModalDialog(
"Download to My Notebooks",
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
"Download",
@@ -388,7 +388,7 @@ export function deleteItem(
if (container) {
trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id });
container.showOkCancelModalDialog(
useDialog.getState().showOkCancelModalDialog(
"Remove published notebook",
`Would you like to remove ${data.name} from the gallery?`,
"Remove",

View File

@@ -29,7 +29,13 @@ export async function update(
body: Types.DatabaseAccountUpdateParameters
): Promise<Types.DatabaseAccountGetResults> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}`;
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PATCH", apiVersion, body });
return armRequest({
host: configContext.ARM_ENDPOINT,
path,
method: "PATCH",
apiVersion,
body,
});
}
/* Creates or updates an Azure Cosmos DB database account. The "Update" method is preferred when performing updates on an account. */

View File

@@ -6,6 +6,7 @@ Instead, generate ARM clients that consume this function with stricter typing.
*/
import promiseRetry, { AbortError } from "p-retry";
import { HttpHeaders } from "../../Common/Constants";
import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext";
@@ -45,6 +46,7 @@ interface Options {
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
body?: unknown;
queryParams?: ARMQueryParams;
contentType?: string;
}
export async function armRequestWithoutPolling<T>({
@@ -54,6 +56,7 @@ export async function armRequestWithoutPolling<T>({
method,
body: requestBody,
queryParams,
contentType,
}: Options): Promise<{ result: T; operationStatusUrl: string }> {
const url = new URL(path, host);
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
@@ -70,6 +73,7 @@ export async function armRequestWithoutPolling<T>({
method,
headers: {
Authorization: userContext.authorizationToken,
[HttpHeaders.contentType]: contentType || "application/json",
},
body: requestBody ? JSON.stringify(requestBody) : undefined,
});
@@ -104,6 +108,7 @@ export async function armRequest<T>({
method,
body: requestBody,
queryParams,
contentType,
}: Options): Promise<T> {
const armRequestResult = await armRequestWithoutPolling<T>({
host,
@@ -112,6 +117,7 @@ export async function armRequest<T>({
method,
body: requestBody,
queryParams,
contentType,
});
const operationStatusUrl = armRequestResult.operationStatusUrl;
if (operationStatusUrl) {

View File

@@ -331,6 +331,12 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.flights.indexOf(Flights.PartitionKeyTest) !== -1) {
userContext.features.partitionKeyDefault = true;
}
if (inputs.flights.indexOf(Flights.PartitionKeyTest) !== -1) {
userContext.features.partitionKeyDefault = true;
}
if (inputs.flights.indexOf(Flights.PKPartitionKeyTest) !== -1) {
userContext.features.partitionKeyDefault2 = true;
}
}
}

View File

@@ -37,7 +37,6 @@
"./src/Contracts/SelfServeContracts.ts",
"./src/Contracts/SubscriptionType.ts",
"./src/Contracts/Versions.ts",
"./src/Explorer/Controls/Dialog.tsx",
"./src/Explorer/Controls/GitHub/GitHubStyleConstants.ts",
"./src/Explorer/Controls/SmartUi/InputUtils.ts",
"./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts",