Compare commits

..

3 Commits

Author SHA1 Message Date
sunilyadav840
4ddf46f370 Merge branch 'master' of https://github.com/Azure/cosmos-explorer into tsStrict/fixed-hoverablecell-inputparameter-file 2021-07-26 18:08:24 +05:30
sunilyadav840
1e32838f60 make cellId optional 2021-05-21 14:31:45 +05:30
sunilyadav840
1e0a0b73e0 fixed typescript strict hoverableCell and InputParameter file 2021-05-17 18:08:31 +05:30
78 changed files with 3601 additions and 3229 deletions

View File

@@ -153,6 +153,7 @@ src/Juno/JunoClient.test.ts
src/Juno/JunoClient.ts src/Juno/JunoClient.ts
src/Platform/Hosted/Authorization.ts src/Platform/Hosted/Authorization.ts
src/ReactDevTools.ts src/ReactDevTools.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts src/Shared/DefaultExperienceUtility.ts
src/Shared/appInsights.ts src/Shared/appInsights.ts

View File

@@ -143,7 +143,7 @@ jobs:
- ./test/mongo/container.spec.ts - ./test/mongo/container.spec.ts
- ./test/mongo/container32.spec.ts - ./test/mongo/container32.spec.ts
- ./test/selfServe/selfServeExample.spec.ts - ./test/selfServe/selfServeExample.spec.ts
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off - ./test/notebooks/upload.spec.ts
- ./test/sql/resourceToken.spec.ts - ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts - ./test/tables/container.spec.ts
steps: steps:

View File

Before

Width:  |  Height:  |  Size: 842 B

After

Width:  |  Height:  |  Size: 842 B

View File

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 371 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -9,7 +9,6 @@
@DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @DataExplorerFont: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif; @SemiboldFont: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
@GrayScale: "grayscale()";
@xSmallFontSize: 4px; @xSmallFontSize: 4px;
@smallFontSize: 8px; @smallFontSize: 8px;

View File

@@ -19,10 +19,6 @@
.notebookHeader { .notebookHeader {
font-size: 12px; font-size: 12px;
} }
.clickDisabled {
pointer-events: none;
}
} }

4641
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -48,9 +48,9 @@
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "file:./canvas",
"clean-webpack-plugin": "3.0.0", "clean-webpack-plugin": "0.1.19",
"clipboard-copy": "4.0.1", "clipboard-copy": "4.0.1",
"copy-webpack-plugin": "9.0.1", "copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.1.1",
"d3": "6.1.1", "d3": "6.1.1",
@@ -81,10 +81,10 @@
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42", "post-robot": "10.0.42",
"q": "1.5.1", "q": "1.5.1",
"react": "16.14.0", "react": "16.13.1",
"react-animate-height": "2.0.8", "react-animate-height": "2.0.8",
"react-dnd": "14.0.2", "react-dnd": "9.4.0",
"react-dnd-html5-backend": "14.0.0", "react-dnd-html5-backend": "9.4.0",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-i18next": "11.8.5", "react-i18next": "11.8.5",
@@ -98,7 +98,7 @@
"sanitize-html": "2.3.3", "sanitize-html": "2.3.3",
"styled-components": "4.3.2", "styled-components": "4.3.2",
"swr": "0.4.0", "swr": "0.4.0",
"terser-webpack-plugin": "5.1.4", "terser-webpack-plugin": "3.1.0",
"underscore": "1.9.1", "underscore": "1.9.1",
"utility-types": "3.10.0", "utility-types": "3.10.0",
"zustand": "3.5.0" "zustand": "3.5.0"
@@ -132,7 +132,6 @@
"@types/underscore": "1.7.36", "@types/underscore": "1.7.36",
"@typescript-eslint/eslint-plugin": "4.22.0", "@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0", "@typescript-eslint/parser": "4.22.0",
"@webpack-cli/serve": "1.5.2",
"babel-jest": "24.9.0", "babel-jest": "24.9.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"buffer": "5.1.0", "buffer": "5.1.0",
@@ -154,45 +153,44 @@
"html-inline-css-webpack-plugin": "1.11.0", "html-inline-css-webpack-plugin": "1.11.0",
"html-loader": "0.5.5", "html-loader": "0.5.5",
"html-loader-jest": "0.2.1", "html-loader-jest": "0.2.1",
"html-webpack-plugin": "5.3.2", "html-webpack-plugin": "4.5.2",
"jest": "26.6.3", "jest": "25.5.4",
"jest-canvas-mock": "2.3.1", "jest-canvas-mock": "2.1.0",
"jest-playwright-preset": "1.5.1", "jest-playwright-preset": "1.5.1",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "0.0.7",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "2.1.0", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"playwright": "1.13.0", "playwright": "1.13.0",
"prettier": "2.2.1", "prettier": "2.2.1",
"process": "0.11.10",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"react-dev-utils": "11.0.4", "react-dev-utils": "11.0.4",
"rimraf": "3.0.0", "rimraf": "3.0.0",
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"ts-loader": "9.2.4", "ts-loader": "6.2.2",
"tslint": "5.11.0", "tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0", "tslint-microsoft-contrib": "6.0.0",
"typedoc": "0.20.36", "typedoc": "0.20.36",
"typescript": "4.3.4", "typescript": "4.3.4",
"url-loader": "1.1.1", "url-loader": "1.1.1",
"wait-on": "4.0.2", "wait-on": "4.0.2",
"webpack": "5.47.0", "webpack": "4.46.0",
"webpack-bundle-analyzer": "4.4.2", "webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "4.7.2", "webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.2" "webpack-dev-server": "3.11.0"
}, },
"scripts": { "scripts": {
"start": "webpack serve --mode development", "start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build", "dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
"build:dataExplorer:ci": "npm run build:ci", "build:dataExplorer:ci": "npm run build:ci",
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers", "build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast", "build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
"pack:prod": "webpack --mode production", "pack:prod": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode production",
"pack:fast": "webpack --mode development --progress", "pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
"copyToConsumers": "node copyToConsumers", "copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest", "test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles", "test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",

View File

@@ -95,7 +95,6 @@ export class Flights {
public static readonly MongoIndexing = "mongoindexing"; public static readonly MongoIndexing = "mongoindexing";
public static readonly AutoscaleTest = "autoscaletest"; public static readonly AutoscaleTest = "autoscaletest";
public static readonly PartitionKeyTest = "partitionkeytest"; public static readonly PartitionKeyTest = "partitionkeytest";
public static readonly PKPartitionKeyTest = "pkpartitionkeytest";
} }
export class AfecFeatures { export class AfecFeatures {
@@ -350,11 +349,6 @@ export class Notebook {
public static readonly kernelRestartInitialDelayMs = 1000; public static readonly kernelRestartInitialDelayMs = 1000;
public static readonly kernelRestartMaxDelayMs = 20000; public static readonly kernelRestartMaxDelayMs = 20000;
public static readonly autoSaveIntervalMs = 120000; public static readonly autoSaveIntervalMs = 120000;
public static readonly temporarilyDownMsg = "Notebooks is currently not available. We are working on it.";
public static readonly mongoShellTemporarilyDownMsg =
"We have identified an issue with the Mongo Shell and it is unavailable right now. We are actively working on the mitigation.";
public static readonly cassandraShellTemporarilyDownMsg =
"We have identified an issue with the Cassandra Shell and it is unavailable right now. We are actively working on the mitigation.";
} }
export class SparkLibrary { export class SparkLibrary {

View File

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

View File

@@ -83,7 +83,6 @@ export const createCollectionContextMenuButton = (
items.push({ items.push({
iconSrc: HostedTerminalIcon, iconSrc: HostedTerminalIcon,
isDisabled: useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown,
onClick: () => { onClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection(); const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { if (useNotebook.getState().isShellEnabled) {

View File

@@ -23,75 +23,13 @@ export interface DialogState {
dialogProps?: DialogProps; dialogProps?: DialogProps;
openDialog: (props: DialogProps) => void; openDialog: (props: DialogProps) => void;
closeDialog: () => 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, get) => ({ export const useDialog: UseStore<DialogState> = create((set) => ({
visible: false, visible: false,
openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })), openDialog: (props: DialogProps) => set(() => ({ visible: true, dialogProps: props })),
closeDialog: () => closeDialog: () =>
set( set((state) => ({ visible: false, openDialog: state.openDialog, closeDialog: state.closeDialog }), true),
(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 { export interface TextFieldProps extends ITextFieldProps {

View File

@@ -5,9 +5,6 @@
display: inline-block; display: inline-block;
width: 100%; width: 100%;
.input-type-head-text-field {
width: 100%;
}
textarea { textarea {
width: 100%; width: 100%;
line-height: 1; line-height: 1;
@@ -24,11 +21,4 @@
} }
} }
} }
.input-typeahead-chocies-container {
border: 1px solid lightgrey;
padding: 5px 10px 5px 10px;
cursor: pointer;
.choice-caption{
font-size: 14px;
}
}

View File

@@ -6,13 +6,14 @@
* typeaheadOverrideOptions: { dynamic:false } * typeaheadOverrideOptions: { dynamic:false }
* *
*/ */
import { getTheme, IconButton, IIconProps, List, Stack, TextField } from "@fluentui/react"; import "jquery-typeahead";
import * as React from "react"; import * as React from "react";
import { KeyCodes } from "../../../Common/Constants";
import "./InputTypeahead.less"; import "./InputTypeahead.less";
export interface Item { export interface Item {
caption: string; caption: string;
value: string; value: any;
} }
/** /**
@@ -74,125 +75,170 @@ export interface InputTypeaheadComponentProps {
useTextarea?: boolean; useTextarea?: boolean;
} }
interface InputTypeaheadComponentState { interface OnClickItem {
isSuggestionVisible: boolean; matchedKey: string;
selectedChoice: Item; value: any;
filteredChoices: Item[]; caption: string;
group: string;
} }
interface Cache {
inputValue: string;
selection: Item;
}
interface InputTypeaheadComponentState {}
export class InputTypeaheadComponent extends React.Component< export class InputTypeaheadComponent extends React.Component<
InputTypeaheadComponentProps, InputTypeaheadComponentProps,
InputTypeaheadComponentState InputTypeaheadComponentState
> { > {
constructor(props: InputTypeaheadComponentProps) { private inputElt: HTMLElement;
private containerElt: HTMLElement;
private cache: Cache;
private inputValue: string;
private selection: Item;
public constructor(props: InputTypeaheadComponentProps) {
super(props); super(props);
this.state = { this.cache = {
isSuggestionVisible: false, inputValue: null,
filteredChoices: [], selection: null,
selectedChoice: {
caption: "",
value: "",
},
}; };
} }
private onRenderCell = (item: Item): JSX.Element => { /**
return ( * Props have changed
<div className="input-typeahead-chocies-container" onClick={() => this.onChoiceClick(item)}> * @param prevProps
<p className="choice-caption">{item.caption}</p> * @param prevState
<span>{item.value}</span> * @param snapshot
</div> */
); public componentDidUpdate(
}; prevProps: InputTypeaheadComponentProps,
prevState: InputTypeaheadComponentState,
private onChoiceClick = (item: Item): void => { snapshot: any
this.props.onNewValue(item.caption); ): void {
this.setState({ isSuggestionVisible: false, selectedChoice: item }); if (prevProps.defaultValue !== this.props.defaultValue) {
}; $(this.inputElt).val(this.props.defaultValue);
this.initializeTypeahead();
private handleChange = (value: string): void => {
if (!value) {
this.setState({ isSuggestionVisible: true });
} }
this.props.onNewValue(value); }
const filteredChoices = this.filterChoiceByValue(this.props.choices, value);
this.setState({ filteredChoices });
};
private onSubmit = (event: React.KeyboardEvent<HTMLElement>): void => { /**
if (event.key === "Enter") { * Executed once react is done building the DOM for this component
*/
public componentDidMount(): void {
this.initializeTypeahead();
}
public render(): JSX.Element {
return (
<span className="input-typeahead-container">
<div
className="input-typehead"
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onKeyDown(event)}
>
<div className="typeahead__container" ref={(input) => (this.containerElt = input)}>
<div className="typeahead__field">
<span className="typeahead__query">
{this.props.useTextarea ? (
<textarea
rows={1}
name="q"
autoComplete="off"
aria-label="Input query"
ref={(input) => (this.inputElt = input)}
defaultValue={this.props.defaultValue}
/>
) : (
<input
name="q"
type="search"
autoComplete="off"
aria-label="Input query"
ref={(input) => (this.inputElt = input)}
defaultValue={this.props.defaultValue}
/>
)}
</span>
{this.props.showSearchButton && (
<span className="typeahead__button">
<button type="submit">
<span className="typeahead__search-icon" />
</button>
</span>
)}
</div>
</div>
</div>
</span>
);
}
private onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
if (event.keyCode === KeyCodes.Enter) {
if (this.props.submitFct) { if (this.props.submitFct) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.props.submitFct(this.props.defaultValue, this.state.selectedChoice); this.props.submitFct(this.cache.inputValue, this.cache.selection);
this.setState({ isSuggestionVisible: false }); $(this.containerElt).children(".typeahead__result").hide();
} }
} }
}; }
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => { /**
return choices.filter((choice) => * Must execute once ko is rendered, so that it can find the input element by id
// @ts-ignore */
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase())) private initializeTypeahead(): void {
); const props = this.props;
}; let cache = this.cache;
let options: any = {
public render(): JSX.Element { input: this.inputElt,
const { defaultValue, useTextarea, placeholder, onNewValue } = this.props; order: "asc",
const { isSuggestionVisible, selectedChoice, filteredChoices } = this.state; minLength: 0,
const theme = getTheme(); searchOnFocus: true,
source: {
const iconButtonStyles = { display: "caption",
root: { data: () => {
color: theme.palette.neutralPrimary, return props.choices;
marginLeft: "10px !important", },
marginTop: "0px",
marginRight: "2px",
width: "42px",
}, },
rootHovered: { callback: {
color: theme.palette.neutralDark, onClick: (node: any, a: any, item: OnClickItem, event: any) => {
cache.selection = item;
if (props.onSelected) {
props.onSelected(item);
}
},
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
cache.inputValue = query;
if (props.onNewValue) {
props.onNewValue(query);
}
},
}, },
template: (query: string, item: any) => {
// Don't display id if caption *IS* the id
return item.caption === item.value
? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
},
dynamic: true,
}; };
const cancelIcon: IIconProps = { iconName: "cancel" };
const searchIcon: IIconProps = { iconName: "Search" };
return ( // Override options
<div className="input-typeahead-container"> if (props.typeaheadOverrideOptions) {
<Stack horizontal> for (const p in props.typeaheadOverrideOptions) {
<TextField options[p] = props.typeaheadOverrideOptions[p];
multiline={useTextarea} }
rows={1} }
defaultValue={defaultValue}
ariaLabel="Input query" if (props.hasOwnProperty("showCancelButton")) {
placeholder={placeholder} options.cancelButton = props.showCancelButton;
className="input-type-head-text-field" }
value={defaultValue}
onKeyDown={this.onSubmit} $(this.inputElt).typeahead(options);
onFocus={() => this.setState({ isSuggestionVisible: true })}
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
/>
{this.props.showCancelButton && (
<IconButton
styles={iconButtonStyles}
iconProps={cancelIcon}
ariaLabel="cancel Button"
onClick={() => onNewValue("")}
/>
)}
{this.props.showSearchButton && (
<IconButton
styles={iconButtonStyles}
iconProps={searchIcon}
ariaLabel="Search Button"
onClick={() => this.props.submitFct(defaultValue, selectedChoice)}
/>
)}
</Stack>
{filteredChoices.length && isSuggestionVisible ? (
<List items={filteredChoices} onRenderCell={this.onRenderCell} />
) : undefined}
</div>
);
} }
} }

View File

@@ -1,43 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`inputTypeahead renders <input /> 1`] = ` exports[`inputTypeahead renders <input /> 1`] = `
<div <span
className="input-typeahead-container" className="input-typeahead-container"
> >
<Stack <div
horizontal={true} className="input-typehead"
onKeyDown={[Function]}
> >
<StyledTextFieldBase <div
ariaLabel="Input query" className="typeahead__container"
className="input-type-head-text-field" >
multiline={false} <div
onChange={[Function]} className="typeahead__field"
onFocus={[Function]} >
onKeyDown={[Function]} <span
placeholder="placeholder" className="typeahead__query"
rows={1} >
/> <input
</Stack> aria-label="Input query"
</div> autoComplete="off"
name="q"
type="search"
/>
</span>
</div>
</div>
</div>
</span>
`; `;
exports[`inputTypeahead renders <textarea /> 1`] = ` exports[`inputTypeahead renders <textarea /> 1`] = `
<div <span
className="input-typeahead-container" className="input-typeahead-container"
> >
<Stack <div
horizontal={true} className="input-typehead"
onKeyDown={[Function]}
> >
<StyledTextFieldBase <div
ariaLabel="Input query" className="typeahead__container"
className="input-type-head-text-field" >
multiline={true} <div
onChange={[Function]} className="typeahead__field"
onFocus={[Function]} >
onKeyDown={[Function]} <span
placeholder="placeholder" className="typeahead__query"
rows={1} >
/> <textarea
</Stack> aria-label="Input query"
</div> autoComplete="off"
name="q"
rows={1}
/>
</span>
</div>
</div>
</div>
</span>
`; `;

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import { IChoiceGroupProps } from "@fluentui/react";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
import _ from "underscore"; import _ from "underscore";
@@ -34,7 +35,7 @@ import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
import { DialogProps, useDialog } from "./Controls/Dialog"; import { DialogProps, TextFieldProps, useDialog } from "./Controls/Dialog";
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter";
import * as FileSystemUtil from "./Notebook/FileSystemUtil"; import * as FileSystemUtil from "./Notebook/FileSystemUtil";
@@ -537,22 +538,17 @@ export default class Explorer {
} }
} }
public uploadFile( public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
name: string,
content: string,
parent: NotebookContentItem,
isGithubTree?: boolean
): Promise<NotebookContentItem> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to upload notebook, but notebook is not enabled"; const error = "Attempt to upload notebook, but notebook is not enabled";
handleError(error, "Explorer/uploadFile"); handleError(error, "Explorer/uploadFile");
throw new Error(error); throw new Error(error);
} }
const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent, isGithubTree); const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent);
promise promise
.then(() => this.resourceTree.triggerRender()) .then(() => this.resourceTree.triggerRender())
.catch((reason) => useDialog.getState().showOkModalDialog("Unable to upload file", getErrorMessage(reason))); .catch((reason) => this.showOkModalDialog("Unable to upload file", reason));
return promise; return promise;
} }
@@ -618,6 +614,51 @@ export default class Explorer {
this.notebookManager?.openCopyNotebookPane(name, content); 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. * 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. * Connecting it to a tree possibly requires the intermediate missing folders if the item is nested in a subfolder.
@@ -677,7 +718,7 @@ export default class Explorer {
return true; return true;
} }
public renameNotebook(notebookFile: NotebookContentItem, isGithubTree?: boolean): void { public renameNotebook(notebookFile: NotebookContentItem): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled"; const error = "Attempt to rename notebook, but notebook is not enabled";
handleError(error, "Explorer/renameNotebook"); handleError(error, "Explorer/renameNotebook");
@@ -691,9 +732,7 @@ export default class Explorer {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path); return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
}); });
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
useDialog this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
.getState()
.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
} else { } else {
useSidePanel.getState().openSidePanel( useSidePanel.getState().openSidePanel(
"Rename Notebook", "Rename Notebook",
@@ -710,7 +749,7 @@ export default class Explorer {
paneTitle="Rename Notebook" paneTitle="Rename Notebook"
defaultInput={FileSystemUtil.stripExtension(notebookFile.name, "ipynb")} defaultInput={FileSystemUtil.stripExtension(notebookFile.name, "ipynb")}
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> => onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input, isGithubTree) this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
} }
notebookFile={notebookFile} notebookFile={notebookFile}
/> />
@@ -718,7 +757,7 @@ export default class Explorer {
} }
} }
public onCreateDirectory(parent: NotebookContentItem, isGithubTree?: boolean): void { public onCreateDirectory(parent: NotebookContentItem): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled"; const error = "Attempt to create notebook directory, but notebook is not enabled";
handleError(error, "Explorer/onCreateDirectory"); handleError(error, "Explorer/onCreateDirectory");
@@ -740,7 +779,7 @@ export default class Explorer {
submitButtonLabel="Create" submitButtonLabel="Create"
defaultInput="" defaultInput=""
onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> => onSubmit={(notebookFile: NotebookContentItem, input: string): Promise<NotebookContentItem> =>
this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input, isGithubTree) this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input)
} }
notebookFile={parent} notebookFile={parent}
/> />
@@ -809,7 +848,7 @@ export default class Explorer {
} }
}; };
public deleteNotebookFile(item: NotebookContentItem, isGithubTree?: boolean): Promise<void> { public deleteNotebookFile(item: NotebookContentItem): Promise<void> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to delete notebook file, but notebook is not enabled"; const error = "Attempt to delete notebook file, but notebook is not enabled";
handleError(error, "Explorer/deleteNotebookFile"); handleError(error, "Explorer/deleteNotebookFile");
@@ -823,9 +862,7 @@ export default class Explorer {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path); return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
}); });
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
useDialog this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
.getState()
.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
return Promise.reject(); return Promise.reject();
} }
@@ -842,7 +879,7 @@ export default class Explorer {
return Promise.reject(); return Promise.reject();
} }
return this.notebookManager?.notebookContentClient.deleteContentItem(item, isGithubTree).then( return this.notebookManager?.notebookContentClient.deleteContentItem(item).then(
() => logConsoleInfo(`Successfully deleted: ${item.path}`), () => logConsoleInfo(`Successfully deleted: ${item.path}`),
(reason) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`) (reason) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`)
); );
@@ -851,7 +888,7 @@ export default class Explorer {
/** /**
* This creates a new notebook file, then opens the notebook * This creates a new notebook file, then opens the notebook
*/ */
public onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): void { public onNewNotebookClicked(parent?: NotebookContentItem): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create new notebook, but notebook is not enabled"; const error = "Attempt to create new notebook, but notebook is not enabled";
handleError(error, "Explorer/onNewNotebookClicked"); handleError(error, "Explorer/onNewNotebookClicked");
@@ -866,7 +903,7 @@ export default class Explorer {
}); });
this.notebookManager?.notebookContentClient this.notebookManager?.notebookContentClient
.createNewNotebookFile(parent, isGithubTree) .createNewNotebookFile(parent)
.then((newFile: NotebookContentItem) => { .then((newFile: NotebookContentItem) => {
logConsoleInfo(`Successfully created: ${newFile.name}`); logConsoleInfo(`Successfully created: ${newFile.name}`);
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
@@ -1084,13 +1121,11 @@ export default class Explorer {
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
}); });
if (!userContext.features.notebooksTemporarilyDown) { if (isNotebookEnabled) {
if (isNotebookEnabled) { await this.initNotebooks(userContext.databaseAccount);
await this.initNotebooks(userContext.databaseAccount); } else if (this.notebookToImport) {
} else if (this.notebookToImport) { // if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane this._openSetupNotebooksPaneForQuickstart();
this._openSetupNotebooksPaneForQuickstart();
}
} }
} }
} }

View File

@@ -6,9 +6,9 @@
import * as React from "react"; import * as React from "react";
import CancelIcon from "../../../../images/cancel.svg"; import CancelIcon from "../../../../images/cancel.svg";
import CheckIcon from "../../../../images/check-1.svg"; import CheckIcon from "../../../../images/check.svg";
import DeleteIcon from "../../../../images/delete.svg"; import DeleteIcon from "../../../../images/delete.svg";
import EditIcon from "../../../../images/edit-1.svg"; import EditIcon from "../../../../images/edit.svg";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement";
import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel"; import { CollapsiblePanel } from "../../Controls/CollapsiblePanel/CollapsiblePanel";

View File

@@ -103,25 +103,19 @@ describe("CommandBarComponentButtonFactory tests", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
//TODO: modify once notebooks are available expect(enableNotebookBtn.disabled).toBe(false);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn.tooltipText).toBe("");
//expect(enableNotebookBtn).toBeDefined();
//expect(enableNotebookBtn.disabled).toBe(false);
//expect(enableNotebookBtn.tooltipText).toBe("");
}); });
it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel);
expect(enableNotebookBtn).toBeDefined();
//TODO: modify once notebooks are available expect(enableNotebookBtn.disabled).toBe(true);
expect(enableNotebookBtn).toBeUndefined(); expect(enableNotebookBtn.tooltipText).toBe(
//expect(enableNotebookBtn).toBeDefined(); "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
//expect(enableNotebookBtn.disabled).toBe(true); );
//expect(enableNotebookBtn.tooltipText).toBe(
// "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."
//);
}); });
}); });
@@ -198,11 +192,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
//TODO: modify once notebooks are available expect(openMongoShellBtn.tooltipText).toBe("");
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available - button should be shown and enabled", () => { it("Notebooks is enabled and is available - button should be shown and enabled", () => {
@@ -212,11 +203,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel);
expect(openMongoShellBtn).toBeDefined(); expect(openMongoShellBtn).toBeDefined();
expect(openMongoShellBtn.disabled).toBe(false);
//TODO: modify once notebooks are available expect(openMongoShellBtn.tooltipText).toBe("");
expect(openMongoShellBtn.disabled).toBe(true);
//expect(openMongoShellBtn.disabled).toBe(false);
//expect(openMongoShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => {
@@ -302,13 +290,9 @@ describe("CommandBarComponentButtonFactory tests", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
//TODO: modify once notebooks are available expect(openCassandraShellBtn.tooltipText).toBe("");
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
}); });
it("Notebooks is enabled and is available - button should be shown and enabled", () => { it("Notebooks is enabled and is available - button should be shown and enabled", () => {
@@ -318,11 +302,8 @@ describe("CommandBarComponentButtonFactory tests", () => {
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
expect(openCassandraShellBtn).toBeDefined(); expect(openCassandraShellBtn).toBeDefined();
expect(openCassandraShellBtn.disabled).toBe(false);
//TODO: modify once notebooks are available expect(openCassandraShellBtn.tooltipText).toBe("");
expect(openCassandraShellBtn.disabled).toBe(true);
//expect(openCassandraShellBtn.disabled).toBe(false);
//expect(openCassandraShellBtn.tooltipText).toBe("");
}); });
}); });

View File

@@ -67,50 +67,35 @@ export function createStaticCommandBarButtons(
newCollectionBtn.children.push(newDatabaseBtn); newCollectionBtn.children.push(newDatabaseBtn);
} }
if (useNotebook.getState().isNotebookEnabled) { buttons.push(createDivider());
buttons.push(createDivider());
const notebookButtons: CommandButtonComponentProps[] = [];
if (useNotebook.getState().isNotebookEnabled) {
const newNotebookButton = createNewNotebookButton(container); const newNotebookButton = createNewNotebookButton(container);
newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)];
notebookButtons.push(newNotebookButton); buttons.push(newNotebookButton);
if (container.notebookManager?.gitHubOAuthService) { if (container.notebookManager?.gitHubOAuthService) {
notebookButtons.push(createManageGitHubAccountButton(container)); buttons.push(createManageGitHubAccountButton(container));
} }
notebookButtons.push(createOpenTerminalButton(container)); buttons.push(createOpenTerminalButton(container));
notebookButtons.push(createNotebookWorkspaceResetButton(container)); buttons.push(createNotebookWorkspaceResetButton(container));
if ( if (
(userContext.apiType === "Mongo" && (userContext.apiType === "Mongo" &&
useNotebook.getState().isShellEnabled && useNotebook.getState().isShellEnabled &&
selectedNodeState.isDatabaseNodeOrNoneSelected()) || selectedNodeState.isDatabaseNodeOrNoneSelected()) ||
userContext.apiType === "Cassandra" userContext.apiType === "Cassandra"
) { ) {
notebookButtons.push(createDivider()); buttons.push(createDivider());
if (userContext.apiType === "Cassandra") { if (userContext.apiType === "Cassandra") {
notebookButtons.push(createOpenCassandraTerminalButton(container)); buttons.push(createOpenCassandraTerminalButton(container));
} else { } else {
notebookButtons.push(createOpenMongoTerminalButton(container)); buttons.push(createOpenMongoTerminalButton(container));
} }
} }
notebookButtons.forEach((btn) => {
if (userContext.features.notebooksTemporarilyDown) {
if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg);
} else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg);
} else {
applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg);
}
}
buttons.push(btn);
});
} else { } else {
if (!isRunningOnNationalCloud() && !userContext.features.notebooksTemporarilyDown) { if (!isRunningOnNationalCloud()) {
buttons.push(createDivider());
buttons.push(createEnableNotebooksButton(container)); buttons.push(createEnableNotebooksButton(container));
} }
} }
@@ -167,9 +152,7 @@ export function createContextCommandBarButtons(
onCommandClick: () => { onCommandClick: () => {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection(); const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
if (useNotebook.getState().isShellEnabled) { if (useNotebook.getState().isShellEnabled) {
if (!userContext.features.notebooksTemporarilyDown) { container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
}
} else { } else {
selectedCollection && selectedCollection.onNewMongoShellClick(); selectedCollection && selectedCollection.onNewMongoShellClick();
} }
@@ -177,13 +160,7 @@ export function createContextCommandBarButtons(
commandButtonLabel: label, commandButtonLabel: label,
ariaLabel: label, ariaLabel: label,
hasPopup: true, hasPopup: true,
tooltipText: disabled: selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo",
useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown
? Constants.Notebook.mongoShellTemporarilyDownMsg
: undefined,
disabled:
(selectedNodeState.isDatabaseNodeOrNoneSelected() && userContext.apiType === "Mongo") ||
(useNotebook.getState().isShellEnabled && userContext.features.notebooksTemporarilyDown),
}; };
buttons.push(newMongoShellBtn); buttons.push(newMongoShellBtn);
} }
@@ -411,13 +388,6 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState)
return buttons; return buttons;
} }
function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void {
if (!buttonProps.isDivider) {
buttonProps.disabled = true;
buttonProps.tooltipText = tooltip;
}
}
function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { function createNewNotebookButton(container: Explorer): CommandButtonComponentProps {
const label = "New Notebook"; const label = "New Notebook";
return { return {

View File

@@ -22,13 +22,6 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => { export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight; const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
const getFilter = (isDisabled: boolean): string => {
if (isDisabled) {
return StyleConstants.GrayScale;
}
return undefined;
};
return btns return btns
.filter((btn) => btn) .filter((btn) => btn)
.map( .map(
@@ -44,7 +37,6 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
style: { style: {
width: StyleConstants.CommandBarIconWidth, // 16 width: StyleConstants.CommandBarIconWidth, // 16
alignSelf: btn.iconName ? "baseline" : undefined, alignSelf: btn.iconName ? "baseline" : undefined,
filter: getFilter(btn.disabled),
}, },
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined, imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
iconName: btn.iconName, iconName: btn.iconName,
@@ -131,12 +123,8 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
width: 12, width: 12,
paddingLeft: 1, paddingLeft: 1,
paddingTop: 6, paddingTop: 6,
filter: getFilter(btn.disabled),
},
imageProps: {
src: ChevronDownIcon,
alt: btn.iconAlt,
}, },
imageProps: { src: ChevronDownIcon, alt: btn.iconAlt },
}; };
} }

View File

@@ -6,7 +6,7 @@ import { Dropdown, IDropdownOption } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif"; import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
import ClearIcon from "../../../../images/Clear-1.svg"; import ClearIcon from "../../../../images/Clear.svg";
import ErrorBlackIcon from "../../../../images/error_black.svg"; import ErrorBlackIcon from "../../../../images/error_black.svg";
import ErrorRedIcon from "../../../../images/error_red.svg"; import ErrorRedIcon from "../../../../images/error_red.svg";
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg"; import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";

View File

@@ -277,10 +277,6 @@ export class NotebookComponentBootstrapper {
return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>); return selectors.notebook.isDirty(content.model as Immutable.RecordOf<DocumentRecordProps>);
} }
public isNotebookUntrusted(): boolean {
return NotebookUtil.isNotebookUntrusted(this.getStore().getState(), this.contentRef);
}
/** /**
* For display purposes, only return non-killed kernels * For display purposes, only return non-killed kernels
*/ */

View File

@@ -1,14 +1,12 @@
import { AppState, ContentRef, selectors } from "@nteract/core"; import { AppState, ContentRef, selectors } from "@nteract/core";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { NotebookUtil } from "../NotebookUtil";
import * as NteractUtil from "../NTeractUtil"; import * as NteractUtil from "../NTeractUtil";
interface VirtualCommandBarComponentProps { interface VirtualCommandBarComponentProps {
kernelSpecName: string; kernelSpecName: string;
kernelStatus: string; kernelStatus: string;
currentCellType: string; currentCellType: string;
isNotebookUntrusted: boolean;
onRender: () => void; onRender: () => void;
} }
@@ -22,8 +20,7 @@ class VirtualCommandBarComponent extends React.Component<VirtualCommandBarCompon
return ( return (
this.props.kernelStatus !== nextProps.kernelStatus || this.props.kernelStatus !== nextProps.kernelStatus ||
this.props.kernelSpecName !== nextProps.kernelSpecName || this.props.kernelSpecName !== nextProps.kernelSpecName ||
this.props.currentCellType !== nextProps.currentCellType || this.props.currentCellType !== nextProps.currentCellType
this.props.isNotebookUntrusted !== nextProps.isNotebookUntrusted
); );
} }
@@ -53,7 +50,6 @@ const makeMapStateToProps = (
kernelStatus, kernelStatus,
kernelSpecName, kernelSpecName,
currentCellType, currentCellType,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
} as VirtualCommandBarComponentProps; } as VirtualCommandBarComponentProps;
} }
@@ -73,7 +69,6 @@ const makeMapStateToProps = (
kernelStatus, kernelStatus,
kernelSpecName, kernelSpecName,
currentCellType, currentCellType,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
onRender: initialProps.onRender, onRender: initialProps.onRender,
}; };
}; };

View File

@@ -54,11 +54,11 @@ export const SET_HOVERED_CELL = "SET_HOVERED_CELL";
export interface SetHoveredCellAction { export interface SetHoveredCellAction {
type: "SET_HOVERED_CELL"; type: "SET_HOVERED_CELL";
payload: { payload: {
cellId: CellId; cellId?: CellId;
}; };
} }
export const setHoveredCell = (payload: { cellId: CellId }): SetHoveredCellAction => { export const setHoveredCell = (payload: { cellId?: CellId }): SetHoveredCellAction => {
return { return {
type: SET_HOVERED_CELL, type: SET_HOVERED_CELL,
payload, payload,

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import CodeMirrorEditor from "@nteract/stateful-components/lib/inputs/connected-
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor"; import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
import * as React from "react"; import * as React from "react";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import HTML5Backend from "react-dnd-html5-backend";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
@@ -14,7 +14,6 @@ import * as cdbActions from "../NotebookComponent/actions";
import loadTransform from "../NotebookComponent/loadTransform"; import loadTransform from "../NotebookComponent/loadTransform";
import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types"; import { CdbAppState, SnapshotFragment, SnapshotRequest } from "../NotebookComponent/types";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import SecurityWarningBar from "../SecurityWarningBar/SecurityWarningBar";
import { AzureTheme } from "./AzureTheme"; import { AzureTheme } from "./AzureTheme";
import "./base.css"; import "./base.css";
import CellCreator from "./decorators/CellCreator"; import CellCreator from "./decorators/CellCreator";
@@ -108,7 +107,6 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
return ( return (
<> <>
<div className="NotebookRendererContainer"> <div className="NotebookRendererContainer">
<SecurityWarningBar contentRef={this.props.contentRef} />
<div className="NotebookRenderer" ref={this.notebookRendererRef}> <div className="NotebookRenderer" ref={this.notebookRendererRef}>
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<KeyboardShortcuts contentRef={this.props.contentRef}> <KeyboardShortcuts contentRef={this.props.contentRef}>

View File

@@ -19,12 +19,6 @@
} }
} }
.disabledRunCellButton {
.runCellButton .ms-Button-flexContainer .ms-Button-icon {
color: @BaseMediumHigh;
}
}
.greyStopButton { .greyStopButton {
.runCellButton .ms-Button-flexContainer .ms-Button-icon { .runCellButton .ms-Button-flexContainer .ms-Button-icon {
color: @BaseMediumHigh; color: @BaseMediumHigh;

View File

@@ -5,7 +5,6 @@ import { Dispatch } from "redux";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as cdbActions from "../NotebookComponent/actions"; import * as cdbActions from "../NotebookComponent/actions";
import { CdbAppState } from "../NotebookComponent/types"; import { CdbAppState } from "../NotebookComponent/types";
import { NotebookUtil } from "../NotebookUtil";
export interface PassedPromptProps { export interface PassedPromptProps {
id: string; id: string;
@@ -13,7 +12,6 @@ export interface PassedPromptProps {
status?: string; status?: string;
executionCount?: number; executionCount?: number;
isHovered?: boolean; isHovered?: boolean;
isRunDisabled?: boolean;
runCell?: () => void; runCell?: () => void;
stopCell?: () => void; stopCell?: () => void;
} }
@@ -22,7 +20,6 @@ interface ComponentProps {
id: string; id: string;
contentRef: ContentRef; contentRef: ContentRef;
isHovered?: boolean; isHovered?: boolean;
isNotebookUntrusted?: boolean;
children: (props: PassedPromptProps) => React.ReactNode; children: (props: PassedPromptProps) => React.ReactNode;
} }
@@ -50,7 +47,6 @@ export class PromptPure extends React.Component<Props> {
runCell: this.props.executeCell, runCell: this.props.executeCell,
stopCell: this.props.stopExecution, stopCell: this.props.stopExecution,
isHovered: this.props.isHovered, isHovered: this.props.isHovered,
isRunDisabled: this.props.isNotebookUntrusted,
})} })}
</div> </div>
); );
@@ -79,7 +75,6 @@ const makeMapStateToProps = (_state: CdbAppState, ownProps: ComponentProps): ((s
status, status,
executionCount, executionCount,
isHovered, isHovered,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
}; };
}; };
return mapStateToProps; return mapStateToProps;

View File

@@ -1,27 +0,0 @@
import { shallow } from "enzyme";
import { PassedPromptProps } from "./Prompt";
import { promptContent } from "./PromptContent";
describe("PromptContent", () => {
it("renders for busy status", () => {
const props: PassedPromptProps = {
id: "id",
contentRef: "contentRef",
status: "busy",
};
const wrapper = shallow(promptContent(props));
expect(wrapper).toMatchSnapshot();
});
it("renders when hovered", () => {
const props: PassedPromptProps = {
id: "id",
contentRef: "contentRef",
isHovered: true,
};
const wrapper = shallow(promptContent(props));
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,6 +1,5 @@
import { IconButton, Spinner, SpinnerSize } from "@fluentui/react"; import { IconButton, Spinner, SpinnerSize } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { NotebookUtil } from "../NotebookUtil";
import { PassedPromptProps } from "./Prompt"; import { PassedPromptProps } from "./Prompt";
import "./Prompt.less"; import "./Prompt.less";
@@ -24,18 +23,15 @@ export const promptContent = (props: PassedPromptProps): JSX.Element => {
</div> </div>
); );
} else if (props.isHovered) { } else if (props.isHovered) {
const playButtonText = props.isRunDisabled ? NotebookUtil.UntrustedNotebookRunHint : "Run cell"; const playButtonText = "Run cell";
return ( return (
<div className={props.isRunDisabled ? "disabledRunCellButton" : ""}> <IconButton
<IconButton className="runCellButton"
className="runCellButton" iconProps={{ iconName: "MSNVideosSolid" }}
iconProps={{ iconName: "MSNVideosSolid" }} title={playButtonText}
title={playButtonText} ariaLabel={playButtonText}
ariaLabel={playButtonText} onClick={props.runCell}
disabled={props.isRunDisabled} />
onClick={props.runCell}
/>
</div>
); );
} else { } else {
return <div style={{ paddingTop: 7 }}>{promptText(props)}</div>; return <div style={{ paddingTop: 7 }}>{promptText(props)}</div>;

View File

@@ -36,7 +36,6 @@ interface StateProps {
cellIdAbove: CellId; cellIdAbove: CellId;
cellIdBelow: CellId; cellIdBelow: CellId;
hasCodeOutput: boolean; hasCodeOutput: boolean;
isNotebookUntrusted: boolean;
} }
class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> { class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & StateProps> {
@@ -44,16 +43,12 @@ class BaseToolbar extends React.PureComponent<ComponentProps & DispatchProps & S
render(): JSX.Element { render(): JSX.Element {
let items: IContextualMenuItem[] = []; let items: IContextualMenuItem[] = [];
const isNotebookUntrusted = this.props.isNotebookUntrusted;
const runTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined;
if (this.props.cellType === "code") { if (this.props.cellType === "code") {
items = items.concat([ items = items.concat([
{ {
key: "Run", key: "Run",
text: "Run", text: "Run",
title: runTooltip,
disabled: isNotebookUntrusted,
onClick: () => { onClick: () => {
this.props.executeCell(); this.props.executeCell();
this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark); this.props.traceNotebookTelemetry(Action.NotebooksExecuteCellFromMenu, ActionModifiers.Mark);
@@ -228,7 +223,6 @@ const makeMapStateToProps = (state: AppState, ownProps: ComponentProps): ((state
cellIdAbove, cellIdAbove,
cellIdBelow, cellIdBelow,
hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell), hasCodeOutput: cellType === "code" && NotebookUtil.hasCodeCellOutput(cell as ImmutableCodeCell),
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, ownProps.contentRef),
}; };
}; };
return mapStateToProps; return mapStateToProps;

View File

@@ -1,60 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PromptContent renders for busy status 1`] = `
<div
className="greyStopButton"
style={
Object {
"left": 0,
"maxHeight": "100%",
"position": "sticky",
"top": 0,
"width": "100%",
"zIndex": 300,
}
}
>
<CustomizedIconButton
ariaLabel="Stop cell execution"
className="runCellButton"
iconProps={
Object {
"iconName": "CircleStopSolid",
}
}
style={
Object {
"position": "absolute",
}
}
title="Stop cell execution"
/>
<StyledSpinnerBase
size={3}
style={
Object {
"paddingTop": 5,
"position": "absolute",
"width": "100%",
}
}
/>
</div>
`;
exports[`PromptContent renders when hovered 1`] = `
<div
className=""
>
<CustomizedIconButton
ariaLabel="Run cell"
className="runCellButton"
iconProps={
Object {
"iconName": "MSNVideosSolid",
}
}
title="Run cell"
/>
</div>
`;

View File

@@ -11,6 +11,7 @@ import {
DropTargetConnector, DropTargetConnector,
DropTargetMonitor, DropTargetMonitor,
} from "react-dnd"; } from "react-dnd";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import styled, { StyledComponent } from "styled-components"; import styled, { StyledComponent } from "styled-components";
@@ -122,10 +123,9 @@ export const cellTarget = {
drop(props: Props, monitor: DropTargetMonitor, component: any): void { drop(props: Props, monitor: DropTargetMonitor, component: any): void {
if (monitor) { if (monitor) {
const hoverUpperHalf = isDragUpper(props, monitor, component.el); const hoverUpperHalf = isDragUpper(props, monitor, component.el);
const item: Props = monitor.getItem();
// DropTargetSpec monitor definition could be undefined. we'll need a check for monitor in order to pass validation. // DropTargetSpec monitor definition could be undefined. we'll need a check for monitor in order to pass validation.
props.moveCell({ props.moveCell({
id: item.id, id: monitor.getItem().id,
destinationId: props.id, destinationId: props.id,
above: hoverUpperHalf, above: hoverUpperHalf,
contentRef: props.contentRef, contentRef: props.contentRef,

View File

@@ -4,7 +4,6 @@ import Immutable from "immutable";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { NotebookUtil } from "../../../NotebookUtil";
interface ComponentProps { interface ComponentProps {
contentRef: ContentRef; contentRef: ContentRef;
@@ -15,7 +14,6 @@ interface StateProps {
cellMap: Immutable.Map<string, any>; cellMap: Immutable.Map<string, any>;
cellOrder: Immutable.List<string>; cellOrder: Immutable.List<string>;
focusedCell?: string | null; focusedCell?: string | null;
isNotebookUntrusted: boolean;
} }
interface DispatchProps { interface DispatchProps {
@@ -61,13 +59,8 @@ export class KeyboardShortcuts extends React.Component<Props> {
cellOrder, cellOrder,
focusedCell, focusedCell,
cellMap, cellMap,
isNotebookUntrusted,
} = this.props; } = this.props;
if (isNotebookUntrusted) {
return;
}
let ctrlKeyPressed = e.ctrlKey; let ctrlKeyPressed = e.ctrlKey;
// Allow cmd + enter (macOS) to operate like ctrl + enter // Allow cmd + enter (macOS) to operate like ctrl + enter
if (process.platform === "darwin") { if (process.platform === "darwin") {
@@ -132,7 +125,6 @@ export const makeMapStateToProps = (_state: AppState, ownProps: ComponentProps)
cellOrder, cellOrder,
cellMap, cellMap,
focusedCell, focusedCell,
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, contentRef),
}; };
}; };
return mapStateToProps; return mapStateToProps;

View File

@@ -1,5 +1,4 @@
import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable"; import { ImmutableCodeCell, ImmutableNotebook } from "@nteract/commutable";
import { AppState, selectors } from "@nteract/core";
import domtoimage from "dom-to-image"; import domtoimage from "dom-to-image";
import Html2Canvas from "html2canvas"; import Html2Canvas from "html2canvas";
import path from "path"; import path from "path";
@@ -12,8 +11,6 @@ import { NotebookContentItem, NotebookContentItemType } from "./NotebookContentI
export type FileType = "directory" | "file" | "notebook"; export type FileType = "directory" | "file" | "notebook";
// Utilities for notebooks // Utilities for notebooks
export class NotebookUtil { export class NotebookUtil {
public static UntrustedNotebookRunHint = "Please trust notebook first before running any code cells";
/** /**
* It's a notebook file if the filename ends with .ipynb. * It's a notebook file if the filename ends with .ipynb.
*/ */
@@ -156,16 +153,6 @@ export class NotebookUtil {
); );
} }
public static isNotebookUntrusted(state: AppState, contentRef: string): boolean {
const content = selectors.content(state, { contentRef });
if (content?.type === "notebook") {
const metadata = selectors.notebook.metadata(content.model);
return metadata.getIn(["untrusted"]) as boolean;
}
return false;
}
/** /**
* Find code cells with display * Find code cells with display
* @param notebookObject * @param notebookObject

View File

@@ -1,31 +0,0 @@
import { shallow } from "enzyme";
import React from "react";
import { SecurityWarningBar } from "./SecurityWarningBar";
describe("SecurityWarningBar", () => {
it("renders if notebook is untrusted", () => {
const wrapper = shallow(
<SecurityWarningBar
contentRef={"contentRef"}
isNotebookUntrusted={true}
markNotebookAsTrusted={undefined}
saveNotebook={undefined}
/>
);
expect(wrapper).toMatchSnapshot();
});
it("renders if notebook is trusted", () => {
const wrapper = shallow(
<SecurityWarningBar
contentRef={"contentRef"}
isNotebookUntrusted={false}
markNotebookAsTrusted={undefined}
saveNotebook={undefined}
/>
);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,93 +0,0 @@
import { MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
import { actions, AppState } from "@nteract/core";
import React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { NotebookUtil } from "../NotebookUtil";
export interface SecurityWarningBarPureProps {
contentRef: string;
}
interface SecurityWarningBarDispatchProps {
markNotebookAsTrusted: (contentRef: string) => void;
saveNotebook: (contentRef: string) => void;
}
type SecurityWarningBarProps = SecurityWarningBarPureProps & StateProps & SecurityWarningBarDispatchProps;
interface SecurityWarningBarState {
isBarDismissed: boolean;
}
export class SecurityWarningBar extends React.Component<SecurityWarningBarProps, SecurityWarningBarState> {
constructor(props: SecurityWarningBarProps) {
super(props);
this.state = {
isBarDismissed: false,
};
}
render(): JSX.Element {
return this.props.isNotebookUntrusted && !this.state.isBarDismissed ? (
<MessageBar
messageBarType={MessageBarType.warning}
isMultiline={false}
onDismiss={() => this.setState({ isBarDismissed: true })}
dismissButtonAriaLabel="Close"
actions={
<MessageBarButton
onClick={() => {
this.props.markNotebookAsTrusted(this.props.contentRef);
this.props.saveNotebook(this.props.contentRef);
}}
>
Trust Notebook
</MessageBarButton>
}
>
{" "}
This notebook was downloaded from the public gallery. Running code cells from a notebook authored by someone
else may involve security risks.
</MessageBar>
) : (
<></>
);
}
}
interface StateProps {
isNotebookUntrusted: boolean;
}
interface InitialProps {
contentRef: string;
}
// Redux
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
const mapStateToProps = (state: AppState): StateProps => ({
isNotebookUntrusted: NotebookUtil.isNotebookUntrusted(state, initialProps.contentRef),
});
return mapStateToProps;
};
const makeMapDispatchToProps = () => {
const mapDispatchToProps = (dispatch: Dispatch): SecurityWarningBarDispatchProps => {
return {
markNotebookAsTrusted: (contentRef: string) => {
return dispatch(
actions.deleteMetadataField({
contentRef,
field: "untrusted",
})
);
},
saveNotebook: (contentRef: string) => dispatch(actions.save({ contentRef })),
};
};
return mapDispatchToProps;
};
export default connect(makeMapStateToProps, makeMapDispatchToProps)(SecurityWarningBar);

View File

@@ -1,22 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SecurityWarningBar renders if notebook is trusted 1`] = `<Fragment />`;
exports[`SecurityWarningBar renders if notebook is untrusted 1`] = `
<StyledMessageBar
actions={
<CustomizedMessageBarButton
onClick={[Function]}
>
Trust Notebook
</CustomizedMessageBarButton>
}
dismissButtonAriaLabel="Close"
isMultiline={false}
messageBarType={5}
onDismiss={[Function]}
>
This notebook was downloaded from the public gallery. Running code cells from a notebook authored by someone else may involve security risks.
</StyledMessageBar>
`;

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ export interface InputParameterProps {
onDeleteParamKeyPress?: () => void; onDeleteParamKeyPress?: () => void;
onAddNewParamKeyPress?: () => void; onAddNewParamKeyPress?: () => void;
onParamValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void; onParamValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
onParamKeyChange: (event: React.FormEvent<HTMLElement>, selectedParam: IDropdownOption) => void; onParamKeyChange: (event: React.FormEvent<HTMLElement>, selectedParam?: IDropdownOption) => void;
paramValue: string; paramValue: string;
selectedKey: string | number; selectedKey: string | number;
} }

View File

@@ -105,6 +105,7 @@ export const PublishNotebookPane: FunctionComponent<PublishNotebookPaneAProps> =
notebookName, notebookName,
notebookDescription, notebookDescription,
notebookTags?.split(","), notebookTags?.split(","),
author,
imageSrc, imageSrc,
content content
); );

View File

@@ -91,7 +91,9 @@ export const UploadItemsPane: FunctionComponent = () => {
accept="application/json" accept="application/json"
multiple multiple
tabIndex={0} tabIndex={0}
tooltip="Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON documents. The combined size of all files in an individual upload operation must be less than 2 MB. You can perform multiple upload operations for larger data sets." tooltip="Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON
documents. The combined size of all files in an individual upload operation must be less than 2 MB. You
can perform multiple upload operations for larger data sets."
/> />
{uploadFileData?.length > 0 && ( {uploadFileData?.length > 0 && (
<div className="fileUploadSummaryContainer"> <div className="fileUploadSummaryContainer">

View File

@@ -83,11 +83,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
public render(): JSX.Element { public render(): JSX.Element {
const mainItems = this.createMainItems(); const mainItems = this.createMainItems();
const commonTaskItems = this.createCommonTaskItems(); const commonTaskItems = this.createCommonTaskItems();
let recentItems = this.createRecentItems(); const recentItems = this.createRecentItems();
if (userContext.features.notebooksTemporarilyDown) {
recentItems = recentItems.filter((item) => item.description !== "Notebook");
}
const tipsItems = this.createTipsItems(); const tipsItems = this.createTipsItems();
const onClearRecent = this.clearMostRecent; const onClearRecent = this.clearMostRecent;
@@ -223,7 +219,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}); });
} }
if (useNotebook.getState().isNotebookEnabled && !userContext.features.notebooksTemporarilyDown) { if (useNotebook.getState().isNotebookEnabled) {
heroes.push({ heroes.push({
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
title: "New Notebook", title: "New Notebook",

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,14 +17,12 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils";
import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
import { useDialog } from "../Controls/Dialog";
import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory";
import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2"; import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2";
import * as CdbActions from "../Notebook/NotebookComponent/actions"; import * as CdbActions from "../Notebook/NotebookComponent/actions";
import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter";
import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types"; import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types";
import { NotebookContentItem } from "../Notebook/NotebookContentItem"; import { NotebookContentItem } from "../Notebook/NotebookContentItem";
import { NotebookUtil } from "../Notebook/NotebookUtil";
import { useNotebook } from "../Notebook/useNotebook"; import { useNotebook } from "../Notebook/useNotebook";
import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase"; import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase";
@@ -61,16 +59,14 @@ export default class NotebookTabV2 extends NotebookTabBase {
}; };
if (this.notebookComponentAdapter.isContentDirty()) { if (this.notebookComponentAdapter.isContentDirty()) {
useDialog this.container.showOkCancelModalDialog(
.getState() "Close without saving?",
.showOkCancelModalDialog( `File has unsaved changes, close without saving?`,
"Close without saving?", "Close",
`File has unsaved changes, close without saving?`, cleanup,
"Close", "Cancel",
cleanup, undefined
"Cancel", );
undefined
);
return Q.resolve(null); return Q.resolve(null);
} else { } else {
cleanup(); cleanup();
@@ -88,13 +84,11 @@ export default class NotebookTabV2 extends NotebookTabBase {
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs(); const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs();
const isNotebookUntrusted = this.notebookComponentAdapter.isNotebookUntrusted();
const runBtnTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined;
const saveLabel = "Save"; const saveLabel = "Save";
const copyToLabel = "Copy to ..."; const copyToLabel = "Copy to ...";
const publishLabel = "Publish to gallery"; const publishLabel = "Publish to gallery";
const workspaceLabel = "No Workspace";
const kernelLabel = "No Kernel"; const kernelLabel = "No Kernel";
const runLabel = "Run"; const runLabel = "Run";
const runActiveCellLabel = "Run Active Cell"; const runActiveCellLabel = "Run Active Cell";
@@ -111,6 +105,8 @@ export default class NotebookTabV2 extends NotebookTabBase {
const copyLabel = "Copy"; const copyLabel = "Copy";
const cutLabel = "Cut"; const cutLabel = "Cut";
const pasteLabel = "Paste"; const pasteLabel = "Paste";
const undoLabel = "Undo";
const redoLabel = "Redo";
const cellCodeType = "code"; const cellCodeType = "code";
const cellMarkdownType = "markdown"; const cellMarkdownType = "markdown";
const cellRawType = "raw"; const cellRawType = "raw";
@@ -191,10 +187,9 @@ export default class NotebookTabV2 extends NotebookTabBase {
this.traceTelemetry(Action.ExecuteCell); this.traceTelemetry(Action.ExecuteCell);
}, },
commandButtonLabel: runLabel, commandButtonLabel: runLabel,
tooltipText: runBtnTooltip,
ariaLabel: runLabel, ariaLabel: runLabel,
hasPopup: false, hasPopup: false,
disabled: isNotebookUntrusted, disabled: false,
children: [ children: [
{ {
iconSrc: RunIcon, iconSrc: RunIcon,

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import shallow from "zustand/shallow";
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
import DeleteIcon from "../../../images/delete.svg"; import DeleteIcon from "../../../images/delete.svg";
import GalleryIcon from "../../../images/GalleryIcon.svg"; import GalleryIcon from "../../../images/GalleryIcon.svg";
@@ -11,7 +10,7 @@ import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import PublishIcon from "../../../images/notebook/publish_content.svg"; import PublishIcon from "../../../images/notebook/publish_content.svg";
import RefreshIcon from "../../../images/refresh-cosmos.svg"; import RefreshIcon from "../../../images/refresh-cosmos.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import { Areas, Notebook } from "../../Common/Constants"; import { Areas } from "../../Common/Constants";
import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
@@ -25,7 +24,6 @@ import { isServerlessAccount } from "../../Utils/CapabilityUtils";
import * as GitHubUtils from "../../Utils/GitHubUtils"; import * as GitHubUtils from "../../Utils/GitHubUtils";
import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory";
import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent"; import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent";
import { useDialog } from "../Controls/Dialog";
import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent"; import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
@@ -56,16 +54,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
galleryContentRoot, galleryContentRoot,
gitHubNotebooksContentRoot, gitHubNotebooksContentRoot,
updateNotebookItem, 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 { activeTab, refreshActiveTab } = useTabs();
const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; const showScriptNodes = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
const pseudoDirPath = "PsuedoDir"; const pseudoDirPath = "PsuedoDir";
@@ -121,34 +110,23 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
children: [], children: [],
}; };
if (userContext.features.notebooksTemporarilyDown) { if (galleryContentRoot) {
notebooksTree.children.push(buildNotebooksTemporarilyDownTree()); notebooksTree.children.push(buildGalleryNotebooksTree());
} else { }
if (galleryContentRoot) {
notebooksTree.children.push(buildGalleryNotebooksTree());
}
if (myNotebooksContentRoot) { if (myNotebooksContentRoot) {
notebooksTree.children.push(buildMyNotebooksTree()); notebooksTree.children.push(buildMyNotebooksTree());
} }
if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) { if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) {
// collapse all other notebook nodes // collapse all other notebook nodes
notebooksTree.children.forEach((node) => (node.isExpanded = false)); notebooksTree.children.forEach((node) => (node.isExpanded = false));
notebooksTree.children.push(buildGitHubNotebooksTree()); notebooksTree.children.push(buildGitHubNotebooksTree());
}
} }
return notebooksTree; return notebooksTree;
}; };
const buildNotebooksTemporarilyDownTree = (): TreeNode => {
return {
label: Notebook.temporarilyDownMsg,
className: "clickDisabled",
};
};
const buildGalleryNotebooksTree = (): TreeNode => { const buildGalleryNotebooksTree = (): TreeNode => {
return { return {
label: "Gallery", label: "Gallery",
@@ -187,8 +165,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item);
} }
}); });
}, }
true
); );
gitHubNotebooksTree.contextMenu = [ gitHubNotebooksTree.contextMenu = [
@@ -224,9 +201,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
}; };
const buildChildNodes = ( const buildChildNodes = (
container: Explorer,
item: NotebookContentItem, item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void, onFileClick: (item: NotebookContentItem) => void
isGithubTree?: boolean
): TreeNode[] => { ): TreeNode[] => {
if (!item || !item.children) { if (!item || !item.children) {
return []; return [];
@@ -234,8 +211,8 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
return item.children.map((item) => { return item.children.map((item) => {
const result = const result =
item.type === NotebookContentItemType.Directory item.type === NotebookContentItemType.Directory
? buildNotebookDirectoryNode(item, onFileClick, isGithubTree) ? buildNotebookDirectoryNode(item, onFileClick)
: buildNotebookFileNode(item, onFileClick, isGithubTree); : buildNotebookFileNode(item, onFileClick);
result.timestamp = item.timestamp; result.timestamp = item.timestamp;
return result; return result;
}); });
@@ -244,8 +221,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
const buildNotebookFileNode = ( const buildNotebookFileNode = (
item: NotebookContentItem, item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void, onFileClick: (item: NotebookContentItem) => void
isGithubTree?: boolean
): TreeNode => { ): TreeNode => {
return { return {
label: item.name, label: item.name,
@@ -262,36 +238,30 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
(activeTab as any).notebookPath() === item.path (activeTab as any).notebookPath() === item.path
); );
}, },
contextMenu: createFileContextMenu(container, item, isGithubTree), contextMenu: createFileContextMenu(container, item),
data: item, data: item,
}; };
}; };
const createFileContextMenu = ( const createFileContextMenu = (container: Explorer, item: NotebookContentItem): TreeNodeMenuItem[] => {
container: Explorer,
item: NotebookContentItem,
isGithubTree?: boolean
): TreeNodeMenuItem[] => {
let items: TreeNodeMenuItem[] = [ let items: TreeNodeMenuItem[] = [
{ {
label: "Rename", label: "Rename",
iconSrc: NotebookIcon, iconSrc: NotebookIcon,
onClick: () => container.renameNotebook(item, isGithubTree), onClick: () => container.renameNotebook(item),
}, },
{ {
label: "Delete", label: "Delete",
iconSrc: DeleteIcon, iconSrc: DeleteIcon,
onClick: () => { onClick: () => {
useDialog container.showOkCancelModalDialog(
.getState() "Confirm delete",
.showOkCancelModalDialog( `Are you sure you want to delete "${item.name}"`,
"Confirm delete", "Delete",
`Are you sure you want to delete "${item.name}"`, () => container.deleteNotebookFile(item),
"Delete", "Cancel",
() => container.deleteNotebookFile(item, isGithubTree), undefined
"Cancel", );
undefined
);
}, },
}, },
{ {
@@ -338,47 +308,41 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
} }
}; };
const createDirectoryContextMenu = ( const createDirectoryContextMenu = (container: Explorer, item: NotebookContentItem): TreeNodeMenuItem[] => {
container: Explorer,
item: NotebookContentItem,
isGithubTree?: boolean
): TreeNodeMenuItem[] => {
let items: TreeNodeMenuItem[] = [ let items: TreeNodeMenuItem[] = [
{ {
label: "Refresh", label: "Refresh",
iconSrc: RefreshIcon, iconSrc: RefreshIcon,
onClick: () => loadSubitems(item, isGithubTree), onClick: () => loadSubitems(item),
}, },
{ {
label: "Delete", label: "Delete",
iconSrc: DeleteIcon, iconSrc: DeleteIcon,
onClick: () => { onClick: () => {
useDialog container.showOkCancelModalDialog(
.getState() "Confirm delete",
.showOkCancelModalDialog( `Are you sure you want to delete "${item.name}?"`,
"Confirm delete", "Delete",
`Are you sure you want to delete "${item.name}?"`, () => container.deleteNotebookFile(item),
"Delete", "Cancel",
() => container.deleteNotebookFile(item, isGithubTree), undefined
"Cancel", );
undefined
);
}, },
}, },
{ {
label: "Rename", label: "Rename",
iconSrc: NotebookIcon, iconSrc: NotebookIcon,
onClick: () => container.renameNotebook(item, isGithubTree), onClick: () => container.renameNotebook(item),
}, },
{ {
label: "New Directory", label: "New Directory",
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
onClick: () => container.onCreateDirectory(item, isGithubTree), onClick: () => container.onCreateDirectory(item),
}, },
{ {
label: "New Notebook", label: "New Notebook",
iconSrc: NewNotebookIcon, iconSrc: NewNotebookIcon,
onClick: () => container.onNewNotebookClicked(item, isGithubTree), onClick: () => container.onNewNotebookClicked(item),
}, },
{ {
label: "Upload File", label: "Upload File",
@@ -403,8 +367,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
const buildNotebookDirectoryNode = ( const buildNotebookDirectoryNode = (
item: NotebookContentItem, item: NotebookContentItem,
onFileClick: (item: NotebookContentItem) => void, onFileClick: (item: NotebookContentItem) => void
isGithubTree?: boolean
): TreeNode => { ): TreeNode => {
return { return {
label: item.name, label: item.name,
@@ -414,7 +377,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
isLeavesParentsSeparate: true, isLeavesParentsSeparate: true,
onClick: () => { onClick: () => {
if (!item.children) { if (!item.children) {
loadSubitems(item, isGithubTree); loadSubitems(item);
} }
}, },
isSelected: () => { isSelected: () => {
@@ -427,9 +390,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
(activeTab as any).notebookPath() === item.path (activeTab as any).notebookPath() === item.path
); );
}, },
contextMenu: item.path !== pseudoDirPath ? createDirectoryContextMenu(container, item, isGithubTree) : undefined, contextMenu: item.path !== pseudoDirPath ? createDirectoryContextMenu(container, item) : undefined,
data: item, data: item,
children: buildChildNodes(item, onFileClick, isGithubTree), children: buildChildNodes(container, item, onFileClick),
}; };
}; };
@@ -514,12 +477,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection), contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
}); });
if ( if (isNotebookEnabled && userContext.apiType === "Mongo" && isPublicInternetAccessAllowed()) {
isNotebookEnabled &&
userContext.apiType === "Mongo" &&
isPublicInternetAccessAllowed() &&
!userContext.features.notebooksTemporarilyDown
) {
children.push({ children.push({
label: "Schema (Preview)", label: "Schema (Preview)",
onClick: collection.onSchemaAnalyzerClick.bind(collection), onClick: collection.onSchemaAnalyzerClick.bind(collection),
@@ -736,9 +694,9 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
return traverse(schema); return traverse(schema);
}; };
const loadSubitems = async (item: NotebookContentItem, isGithubTree?: boolean): Promise<void> => { const loadSubitems = async (item: NotebookContentItem): Promise<void> => {
const updatedItem = await container.notebookManager?.notebookContentClient?.updateItemChildren(item); const updatedItem = await container.notebookManager?.notebookContentClient?.updateItemChildren(item);
updateNotebookItem(updatedItem, isGithubTree); updateNotebookItem(updatedItem);
}; };
const dataRootNode = buildDataTree(); const dataRootNode = buildDataTree();

View File

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

View File

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

View File

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

View File

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

View File

@@ -364,6 +364,7 @@ describe("Gallery", () => {
const name = "name"; const name = "name";
const description = "description"; const description = "description";
const tags = ["tag"]; const tags = ["tag"];
const author = "author";
const thumbnailUrl = "thumbnailUrl"; const thumbnailUrl = "thumbnailUrl";
const content = `{ "key": "value" }`; const content = `{ "key": "value" }`;
const addLinkToNotebookViewer = true; const addLinkToNotebookViewer = true;
@@ -372,7 +373,7 @@ describe("Gallery", () => {
json: () => undefined as any, json: () => undefined as any,
}); });
const response = await junoClient.publishNotebook(name, description, tags, thumbnailUrl, content); const response = await junoClient.publishNotebook(name, description, tags, author, thumbnailUrl, content);
const authorizationHeader = getAuthorizationHeader(); const authorizationHeader = getAuthorizationHeader();
expect(response.status).toBe(HttpStatusCodes.OK); expect(response.status).toBe(HttpStatusCodes.OK);
@@ -390,6 +391,7 @@ describe("Gallery", () => {
name, name,
description, description,
tags, tags,
author,
thumbnailUrl, thumbnailUrl,
content: JSON.parse(content), content: JSON.parse(content),
addLinkToNotebookViewer, addLinkToNotebookViewer,

View File

@@ -61,6 +61,7 @@ export interface IPublishNotebookRequest {
name: string; name: string;
description: string; description: string;
tags: string[]; tags: string[];
author: string;
thumbnailUrl: string; thumbnailUrl: string;
content: any; content: any;
addLinkToNotebookViewer: boolean; addLinkToNotebookViewer: boolean;
@@ -358,6 +359,7 @@ export class JunoClient {
name: string, name: string,
description: string, description: string,
tags: string[], tags: string[],
author: string,
thumbnailUrl: string, thumbnailUrl: string,
content: string content: string
): Promise<IJunoResponse<IGalleryItem>> { ): Promise<IJunoResponse<IGalleryItem>> {
@@ -368,6 +370,7 @@ export class JunoClient {
name, name,
description, description,
tags, tags,
author,
thumbnailUrl, thumbnailUrl,
content: JSON.parse(content), content: JSON.parse(content),
addLinkToNotebookViewer: true, addLinkToNotebookViewer: true,

View File

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

208
src/Shared/Constants.ts Normal file
View File

@@ -0,0 +1,208 @@
import { SubscriptionType } from "../Contracts/SubscriptionType";
export const hoursInAMonth = 730;
export class AutoscalePricing {
public static MonthlyPricing = {
default: {
singleMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24,
PricePerRU: 0.09,
PricePerGB: 0.25,
},
},
multiMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24,
PricePerRU: 0.12,
PricePerGB: 0.25,
},
},
},
mooncake: {
singleMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: 152,
PricePerRU: 0.57,
PricePerGB: 2.576,
},
},
multiMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: 152,
PricePerRU: 0.76,
PricePerGB: 2.576,
},
},
},
};
public static HourlyPricing = {
default: {
singleMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24 / hoursInAMonth,
PricePerRU: 0.00012,
PricePerGB: 0.25 / hoursInAMonth,
},
},
multiMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24 / hoursInAMonth,
PricePerRU: 0.00016,
PricePerGB: 0.25 / hoursInAMonth,
},
},
},
mooncake: {
singleMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: AutoscalePricing.MonthlyPricing.mooncake.singleMaster.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.000765,
PricePerGB: AutoscalePricing.MonthlyPricing.mooncake.singleMaster.Standard.PricePerGB / hoursInAMonth,
},
},
multiMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: AutoscalePricing.MonthlyPricing.mooncake.multiMaster.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00102,
PricePerGB: AutoscalePricing.MonthlyPricing.mooncake.multiMaster.Standard.PricePerGB / hoursInAMonth,
},
},
},
};
}
export class OfferPricing {
public static MonthlyPricing = {
default: {
Currency: "USD",
CurrencySign: "$",
S1Price: 25,
S2Price: 50,
S3Price: 100,
Standard: {
StartingPrice: 24,
PricePerRU: 0.06,
PricePerGB: 0.25,
},
},
mooncake: {
Currency: "RMB",
CurrencySign: "¥",
S1Price: 110.3,
S2Price: 220.6,
S3Price: 441.2,
Standard: {
StartingPrice: 152,
PricePerRU: 0.3794,
PricePerGB: 2.576,
},
},
};
public static HourlyPricing = {
default: {
Currency: "USD",
CurrencySign: "$",
S1Price: 0.0336,
S2Price: 0.0672,
S3Price: 0.1344,
Standard: {
StartingPrice: 24 / hoursInAMonth, // per hour
PricePerRU: 0.00008,
PricePerGB: 0.25 / hoursInAMonth,
},
},
mooncake: {
Currency: "RMB",
CurrencySign: "¥",
S1Price: 0.15,
S2Price: 0.3,
S3Price: 0.6,
Standard: {
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00051,
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth,
},
},
};
}
export class CollectionCreation {
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
public static readonly MinRUPerPartitionBelow7Partitions: number = 400;
public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
public static readonly MaxRUPerPartition: number = 10000;
public static readonly MinPartitionedCollectionRUs: number = 2500;
public static readonly NumberOfPartitionsInFixedCollection: number = 1;
public static readonly NumberOfPartitionsInUnlimitedCollection: number = 10;
public static storage10Gb: string = "10";
public static storage100Gb: string = "100";
public static readonly DefaultCollectionRUs1000: number = 1000;
public static readonly DefaultCollectionRUs10K: number = 10000;
public static readonly DefaultCollectionRUs400: number = 400;
public static readonly DefaultCollectionRUs2000: number = 2000;
public static readonly DefaultCollectionRUs2500: number = 2500;
public static readonly DefaultCollectionRUs5000: number = 5000;
public static readonly DefaultCollectionRUs15000: number = 15000;
public static readonly DefaultCollectionRUs20000: number = 20000;
public static readonly DefaultCollectionRUs25000: number = 25000;
public static readonly DefaultCollectionRUs100K: number = 100000;
public static readonly DefaultCollectionRUs1Million: number = 1000000;
public static readonly DefaultAddCollectionDefaultFlight: string = "0";
public static readonly DefaultSubscriptionType: SubscriptionType = SubscriptionType.Free;
public static readonly TablesAPIDefaultDatabase: string = "TablesDB";
}
export const CollectionCreationDefaults = {
storage: CollectionCreation.storage100Gb,
throughput: {
fixed: CollectionCreation.DefaultCollectionRUs400,
unlimited: CollectionCreation.DefaultCollectionRUs400,
unlimitedmax: CollectionCreation.DefaultCollectionRUs1Million,
unlimitedmin: CollectionCreation.DefaultCollectionRUs400,
shared: CollectionCreation.DefaultCollectionRUs400,
},
} as const;
export class SubscriptionUtilMappings {
public static FreeTierSubscriptionIds: string[] = [
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
"41f6d14d-ece1-46e4-942c-02c00d67f7d6",
"11dc62e3-77dc-4ef5-a46b-480ec6caa8fe",
"199d0919-60bd-448e-b64d-8461a0fe9747",
"a57b6849-d443-44cf-a3b7-7dd07ead9401",
];
}
export class AutopilotDocumentation {
public static Url: string = "https://aka.ms/cosmos-autoscale-info";
}
export class FreeTierLimits {
public static RU: number = 1000;
public static Storage: number = 25;
}

View File

@@ -1,87 +0,0 @@
import { hoursInAMonth } from "./";
export const MonthlyPricing = {
default: {
singleMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24,
PricePerRU: 0.09,
PricePerGB: 0.25,
},
},
multiMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24,
PricePerRU: 0.12,
PricePerGB: 0.25,
},
},
},
mooncake: {
singleMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: 152,
PricePerRU: 0.57,
PricePerGB: 2.576,
},
},
multiMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: 152,
PricePerRU: 0.76,
PricePerGB: 2.576,
},
},
},
};
export const HourlyPricing = {
default: {
singleMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24 / hoursInAMonth,
PricePerRU: 0.00012,
PricePerGB: 0.25 / hoursInAMonth,
},
},
multiMaster: {
Currency: "USD",
CurrencySign: "$",
Standard: {
StartingPrice: 24 / hoursInAMonth,
PricePerRU: 0.00016,
PricePerGB: 0.25 / hoursInAMonth,
},
},
},
mooncake: {
singleMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: MonthlyPricing.mooncake.singleMaster.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.000765,
PricePerGB: MonthlyPricing.mooncake.singleMaster.Standard.PricePerGB / hoursInAMonth,
},
},
multiMaster: {
Currency: "RMB",
CurrencySign: "¥",
Standard: {
StartingPrice: MonthlyPricing.mooncake.multiMaster.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00102,
PricePerGB: MonthlyPricing.mooncake.multiMaster.Standard.PricePerGB / hoursInAMonth,
},
},
},
};

View File

@@ -1,53 +0,0 @@
import { hoursInAMonth } from "./";
export const MonthlyPricing = {
default: {
Currency: "USD",
CurrencySign: "$",
S1Price: 25,
S2Price: 50,
S3Price: 100,
Standard: {
StartingPrice: 24,
PricePerRU: 0.06,
PricePerGB: 0.25,
},
},
mooncake: {
Currency: "RMB",
CurrencySign: "¥",
S1Price: 110.3,
S2Price: 220.6,
S3Price: 441.2,
Standard: {
StartingPrice: 152,
PricePerRU: 0.3794,
PricePerGB: 2.576,
},
},
};
export const HourlyPricing = {
default: {
Currency: "USD",
CurrencySign: "$",
S1Price: 0.0336,
S2Price: 0.0672,
S3Price: 0.1344,
Standard: {
StartingPrice: 24 / hoursInAMonth, // per hour
PricePerRU: 0.00008,
PricePerGB: 0.25 / hoursInAMonth,
},
},
mooncake: {
Currency: "RMB",
CurrencySign: "¥",
S1Price: 0.15,
S2Price: 0.3,
S3Price: 0.6,
Standard: {
StartingPrice: MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00051,
PricePerGB: MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth,
},
},
};

View File

@@ -1,69 +0,0 @@
import { SubscriptionType } from "../../Contracts/SubscriptionType";
import * as AutoscalePricing from "./AutoscalePricing";
import * as OfferPricing from "./OfferPricing";
export const hoursInAMonth = 730;
export { AutoscalePricing, OfferPricing };
export const CollectionCreation = {
// TODO generate these values based on Product\Services\Documents\ImageStore\GatewayApplication\Settings.xml
MinRUPerPartitionBelow7Partitions: 400,
MinRU7PartitionsTo25Partitions: 2500,
MinRUPerPartitionAbove25Partitions: 100,
MaxRUPerPartition: 10000,
MinPartitionedCollectionRUs: 2500,
NumberOfPartitionsInFixedCollection: 1,
NumberOfPartitionsInUnlimitedCollection: 10,
storage10Gb: "10",
storage100Gb: "100",
DefaultCollectionRUs1000: 1000,
DefaultCollectionRUs10K: 10000,
DefaultCollectionRUs400: 400,
DefaultCollectionRUs2000: 2000,
DefaultCollectionRUs2500: 2500,
DefaultCollectionRUs5000: 5000,
DefaultCollectionRUs15000: 15000,
DefaultCollectionRUs20000: 20000,
DefaultCollectionRUs25000: 25000,
DefaultCollectionRUs100K: 100000,
DefaultCollectionRUs1Million: 1000000,
DefaultAddCollectionDefaultFlight: "0",
DefaultSubscriptionType: SubscriptionType.Free,
TablesAPIDefaultDatabase: "TablesDB",
};
export const CollectionCreationDefaults = {
storage: CollectionCreation.storage100Gb,
throughput: {
fixed: CollectionCreation.DefaultCollectionRUs400,
unlimited: CollectionCreation.DefaultCollectionRUs400,
unlimitedmax: CollectionCreation.DefaultCollectionRUs1Million,
unlimitedmin: CollectionCreation.DefaultCollectionRUs400,
shared: CollectionCreation.DefaultCollectionRUs400,
},
};
export const SubscriptionUtilMappings = {
FreeTierSubscriptionIds: [
"b8f2ff04-0a81-4cf9-95ef-5828d16981d2",
"39b1fdff-e5b2-4f83-adb4-33cb3aabf5ea",
"41f6d14d-ece1-46e4-942c-02c00d67f7d6",
"11dc62e3-77dc-4ef5-a46b-480ec6caa8fe",
"199d0919-60bd-448e-b64d-8461a0fe9747",
"a57b6849-d443-44cf-a3b7-7dd07ead9401",
],
};
export const AutopilotDocumentation = {
Url: "https://aka.ms/cosmos-autoscale-info",
};
export const FreeTierLimits = {
RU: 1000,
Storage: 25,
};

View File

@@ -1,9 +1,8 @@
import * as GalleryUtils from "./GalleryUtils";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { HttpStatusCodes } from "../Common/Constants"; import { HttpStatusCodes } from "../Common/Constants";
import { useDialog } from "../Explorer/Controls/Dialog";
import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent"; import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "./GalleryUtils";
const galleryItem: IGalleryItem = { const galleryItem: IGalleryItem = {
id: "id", id: "id",
@@ -30,11 +29,11 @@ describe("GalleryUtils", () => {
it("downloadItem shows dialog in data explorer", () => { it("downloadItem shows dialog in data explorer", () => {
const container = {} as Explorer; const container = {} as Explorer;
container.showOkCancelModalDialog = jest.fn().mockImplementation();
GalleryUtils.downloadItem(container, undefined, galleryItem, undefined); GalleryUtils.downloadItem(container, undefined, galleryItem, undefined);
expect(useDialog.getState().visible).toBe(true); expect(container.showOkCancelModalDialog).toBeCalled();
expect(useDialog.getState().dialogProps).toBeDefined();
expect(useDialog.getState().dialogProps.title).toBe("Download to My Notebooks");
}); });
it("favoriteItem favorites item", async () => { it("favoriteItem favorites item", async () => {
@@ -67,11 +66,11 @@ describe("GalleryUtils", () => {
it("deleteItem shows dialog in data explorer", () => { it("deleteItem shows dialog in data explorer", () => {
const container = {} as Explorer; const container = {} as Explorer;
container.showOkCancelModalDialog = jest.fn().mockImplementation();
GalleryUtils.deleteItem(container, undefined, galleryItem, undefined); GalleryUtils.deleteItem(container, undefined, galleryItem, undefined);
expect(useDialog.getState().visible).toBe(true); expect(container.showOkCancelModalDialog).toBeCalled();
expect(useDialog.getState().dialogProps).toBeDefined();
expect(useDialog.getState().dialogProps.title).toBe("Remove published notebook");
}); });
it("getGalleryViewerProps gets gallery viewer props correctly", () => { 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 { NotebookV4 } from "@nteract/commutable/lib/v4";
import { HttpStatusCodes } from "../Common/Constants"; import { HttpStatusCodes } from "../Common/Constants";
import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils";
import { TextFieldProps, useDialog } from "../Explorer/Controls/Dialog"; import { TextFieldProps } from "../Explorer/Controls/Dialog";
import { import {
GalleryTab, GalleryTab,
GalleryViewerComponent, GalleryViewerComponent,
@@ -222,7 +222,7 @@ export function downloadItem(
}); });
const name = data.name; const name = data.name;
useDialog.getState().showOkCancelModalDialog( container.showOkCancelModalDialog(
"Download to My Notebooks", "Download to My Notebooks",
`Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`, `Download ${name} from gallery as a copy to your notebooks to run and/or edit the notebook.`,
"Download", "Download",
@@ -243,11 +243,6 @@ export function downloadItem(
const notebook = JSON.parse(response.data) as Notebook; const notebook = JSON.parse(response.data) as Notebook;
removeNotebookViewerLink(notebook, data.newCellId); removeNotebookViewerLink(notebook, data.newCellId);
if (!data.isSample) {
const metadata = notebook.metadata as { [name: string]: unknown };
metadata.untrusted = true;
}
await container.importAndOpenContent(data.name, JSON.stringify(notebook)); await container.importAndOpenContent(data.name, JSON.stringify(notebook));
logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`); logConsoleInfo(`Successfully downloaded ${name} to My Notebooks`);
@@ -393,7 +388,7 @@ export function deleteItem(
if (container) { if (container) {
trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id }); trace(Action.NotebooksGalleryClickDelete, ActionModifiers.Mark, { notebookId: data.id });
useDialog.getState().showOkCancelModalDialog( container.showOkCancelModalDialog(
"Remove published notebook", "Remove published notebook",
`Would you like to remove ${data.name} from the gallery?`, `Would you like to remove ${data.name} from the gallery?`,
"Remove", "Remove",

View File

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

View File

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

View File

@@ -331,12 +331,6 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.flights.indexOf(Flights.PartitionKeyTest) !== -1) { if (inputs.flights.indexOf(Flights.PartitionKeyTest) !== -1) {
userContext.features.partitionKeyDefault = true; 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

@@ -16,7 +16,7 @@ test("Mongo CRUD", async () => {
await explorer.click('[data-test="New Collection"]'); await explorer.click('[data-test="New Collection"]');
await explorer.fill('[aria-label="New database id"]', databaseId); await explorer.fill('[aria-label="New database id"]', databaseId);
await explorer.fill('[aria-label="Collection id"]', containerId); await explorer.fill('[aria-label="Collection id"]', containerId);
await explorer.fill('[aria-label="Shard key"]', "pk"); await explorer.fill('[aria-label="Shard key"]', "/pk");
await explorer.click("#sidePanelOkButton"); await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${databaseId}`); await explorer.click(`.nodeItem >> text=${databaseId}`);
await explorer.click(`.nodeItem >> text=${containerId}`); await explorer.click(`.nodeItem >> text=${containerId}`);

View File

@@ -8,6 +8,7 @@
"noUnusedParameters": true "noUnusedParameters": true
}, },
"files": [ "files": [
"./src/Explorer/Panes/ExecuteSprocParamsPane/InputParameter.tsx",
"./src/AuthType.ts", "./src/AuthType.ts",
"./src/Bindings/ReactBindingHandler.ts", "./src/Bindings/ReactBindingHandler.ts",
"./src/Common/ArrayHashMap.ts", "./src/Common/ArrayHashMap.ts",
@@ -37,6 +38,7 @@
"./src/Contracts/SelfServeContracts.ts", "./src/Contracts/SelfServeContracts.ts",
"./src/Contracts/SubscriptionType.ts", "./src/Contracts/SubscriptionType.ts",
"./src/Contracts/Versions.ts", "./src/Contracts/Versions.ts",
"./src/Explorer/Controls/Dialog.tsx",
"./src/Explorer/Controls/GitHub/GitHubStyleConstants.ts", "./src/Explorer/Controls/GitHub/GitHubStyleConstants.ts",
"./src/Explorer/Controls/SmartUi/InputUtils.ts", "./src/Explorer/Controls/SmartUi/InputUtils.ts",
"./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts", "./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.test.ts",
@@ -59,6 +61,7 @@
"./src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx", "./src/Explorer/Notebook/NotebookRenderer/StatusBar.tsx",
"./src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx", "./src/Explorer/Notebook/NotebookRenderer/decorators/CellCreator.tsx",
"./src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx", "./src/Explorer/Notebook/NotebookRenderer/decorators/CellLabeler.tsx",
"./src/Explorer/Notebook/NotebookRenderer/decorators/HoverableCell.tsx",
"./src/Explorer/Notebook/NotebookUtil.ts", "./src/Explorer/Notebook/NotebookUtil.ts",
"./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerSplashScreen.tsx", "./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerSplashScreen.tsx",
"./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerUtils.ts", "./src/Explorer/Notebook/SchemaAnalyzer/SchemaAnalyzerUtils.ts",

View File

@@ -9,13 +9,12 @@ const HTMLInlineCSSWebpackPlugin = require("html-inline-css-webpack-plugin").def
const { EnvironmentPlugin } = require("webpack"); const { EnvironmentPlugin } = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const CleanWebpackPlugin = require("clean-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin"); const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const CreateFileWebpack = require("create-file-webpack"); const CreateFileWebpack = require("create-file-webpack");
const childProcess = require("child_process"); const childProcess = require("child_process");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const webpack = require("webpack");
const isCI = require("is-ci"); const isCI = require("is-ci");
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8"); const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
@@ -110,10 +109,7 @@ module.exports = function (_env = {}, argv = {}) {
} }
const plugins = [ const plugins = [
new CleanWebpackPlugin(), new CleanWebpackPlugin(["dist"]),
new webpack.ProvidePlugin({
process: "process/browser",
}),
new CreateFileWebpack({ new CreateFileWebpack({
path: "./dist", path: "./dist",
fileName: "version.txt", fileName: "version.txt",
@@ -214,25 +210,23 @@ module.exports = function (_env = {}, argv = {}) {
selfServe: "./src/SelfServe/SelfServe.tsx", selfServe: "./src/SelfServe/SelfServe.tsx",
connectToGitHub: "./src/GitHub/GitHubConnector.ts", connectToGitHub: "./src/GitHub/GitHubConnector.ts",
}, },
node: {
util: true,
tls: "empty",
net: "empty",
fs: "empty",
},
output: { output: {
chunkFilename: "[name].[chunkhash:6].js", chunkFilename: "[name].[chunkhash:6].js",
filename: "[name].[chunkhash:6].js", filename: "[name].[chunkhash:6].js",
path: path.resolve(__dirname, "dist"), path: path.resolve(__dirname, "dist"),
publicPath: "",
}, },
devtool: mode === "development" ? "eval-source-map" : "source-map", devtool: mode === "development" ? "cheap-eval-source-map" : "source-map",
plugins, plugins,
module: { module: {
rules, rules,
}, },
resolve: { resolve: {
alias: {
process: "process/browser",
},
fallback: {
crypto: false,
fs: false,
},
extensions: [".tsx", ".ts", ".js"], extensions: [".tsx", ".ts", ".js"],
}, },
optimization: { optimization: {
@@ -250,7 +244,7 @@ module.exports = function (_env = {}, argv = {}) {
}), }),
], ],
}, },
watch: false, watch: isCI || mode === "production" ? false : true,
// Hack since it is hard to disable watch entirely with webpack dev server https://github.com/webpack/webpack-dev-server/issues/1251#issuecomment-654240734 // Hack since it is hard to disable watch entirely with webpack dev server https://github.com/webpack/webpack-dev-server/issues/1251#issuecomment-654240734
watchOptions: isCI ? { poll: 24 * 60 * 60 * 1000 } : {}, watchOptions: isCI ? { poll: 24 * 60 * 60 * 1000 } : {},
devServer: { devServer: {