Merge branch 'master' into users/srnara/selfserve

This commit is contained in:
Srinath Narayanan 2021-01-13 06:21:29 -08:00
commit 41f37055ef
35 changed files with 329 additions and 151 deletions

View File

@ -69,6 +69,10 @@ Jest and Puppeteer are used for end to end browser based tests and are contained
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
### Architechture
[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggTFJcbiAgaG9zdGVkKGh0dHBzOi8vY29zbW9zLmF6dXJlLmNvbSlcbiAgcG9ydGFsKFBvcnRhbClcbiAgZW11bGF0b3IoRW11bGF0b3IpXG4gIGFhZFtBQURdXG4gIHJlc291cmNlVG9rZW5bUmVzb3VyY2UgVG9rZW5dXG4gIGNvbm5lY3Rpb25TdHJpbmdbQ29ubmVjdGlvbiBTdHJpbmddXG4gIHBvcnRhbFRva2VuW0VuY3J5cHRlZCBQb3J0YWwgVG9rZW5dXG4gIG1hc3RlcktleVtNYXN0ZXIgS2V5XVxuICBhcm1bQVJNIFJlc291cmNlIFByb3ZpZGVyXVxuICBkYXRhcGxhbmVbRGF0YSBQbGFuZV1cbiAgcHJveHlbUG9ydGFsIEFQSSBQcm94eV1cbiAgc3FsW1NRTF1cbiAgbW9uZ29bTW9uZ29dXG4gIHRhYmxlc1tUYWJsZXNdXG4gIGNhc3NhbmRyYVtDYXNzYW5kcmFdXG4gIGdyYWZbR3JhcGhdXG5cblxuICBlbXVsYXRvciAtLT4gbWFzdGVyS2V5IC0tLS0-IGRhdGFwbGFuZVxuICBwb3J0YWwgLS0-IGFhZFxuICBob3N0ZWQgLS0-IHBvcnRhbFRva2VuICYgcmVzb3VyY2VUb2tlbiAmIGNvbm5lY3Rpb25TdHJpbmcgJiBhYWRcbiAgYWFkIC0tLT4gYXJtXG4gIGFhZCAtLS0-IGRhdGFwbGFuZVxuICBhYWQgLS0tPiBwcm94eVxuICByZXNvdXJjZVRva2VuIC0tLT4gc3FsIC0tPiBkYXRhcGxhbmVcbiAgcG9ydGFsVG9rZW4gLS0tPiBwcm94eVxuICBwcm94eSAtLT4gZGF0YXBsYW5lXG4gIGNvbm5lY3Rpb25TdHJpbmcgLS0-IHNxbCAmIG1vbmdvICYgY2Fzc2FuZHJhICYgZ3JhZiAmIHRhYmxlc1xuICBzcWwgLS0-IGRhdGFwbGFuZVxuICB0YWJsZXMgLS0-IGRhdGFwbGFuZVxuICBtb25nbyAtLT4gcHJveHlcbiAgY2Fzc2FuZHJhIC0tPiBwcm94eVxuICBncmFmIC0tPiBwcm94eVxuXG5cdFx0IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)
# Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md).

View File

@ -13,6 +13,11 @@
@NavMediumSpace: 10px;
@NavLargeSpace: 15px;
.skip-link {
position: fixed;
top: -200px;
}
html {
font-family: wf_segoe-ui_normal, "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
padding: 0px;

View File

@ -1,20 +1,12 @@
@import "./Common/Constants";
.main {
width: 100%;
float: left;
transition: all .0s ease-in-out;
-ms-transition: all 0s ease-in-out;
-webkit-transition: all 0s ease-in-out;
-moz-transition: all .0s ease-in-out;
height: 100%;
background-color: white;
border-left: 0px solid white;
}
.resourceTree {
height: 100%;
flex: 0 0 auto;
.main {
height: 100%;
}
}
.resourceTreeScroll {

View File

@ -133,6 +133,7 @@ export class Features {
export class Flights {
public static readonly SettingsV2 = "settingsv2";
public static readonly MongoIndexEditor = "mongoindexeditor";
public static readonly MongoIndexing = "mongoindexing";
}
export class AfecFeatures {

View File

@ -21,7 +21,7 @@ export const handleError = (error: string | ARMError | Error, area: string, cons
sendNotificationForError(errorMessage, errorCode);
};
export const getErrorMessage = (error: string | Error): string => {
export const getErrorMessage = (error: string | Error = ""): string => {
const errorMessage = typeof error === "string" ? error : error.message;
return replaceKnownError(errorMessage);
};
@ -45,10 +45,10 @@ const sendNotificationForError = (errorMessage: string, errorCode: number | stri
const replaceKnownError = (errorMessage: string): string => {
if (
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
errorMessage?.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";
} else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) {
} else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
}

View File

@ -2,8 +2,11 @@ import { Offer, SDKOfferDefinition } from "../Contracts/DataModels";
import { OfferResponse } from "@azure/cosmos";
import { HttpHeaders } from "./Constants";
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
const offerDefinition: SDKOfferDefinition = offerResponse?.resource;
export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer | undefined => {
const offerDefinition: SDKOfferDefinition | undefined = offerResponse?.resource;
if (!offerDefinition) {
return undefined;
}
const offerContent = offerDefinition.content;
if (!offerContent) {
return undefined;
@ -12,7 +15,7 @@ export const parseSDKOfferResponse = (offerResponse: OfferResponse): Offer => {
const minimumThroughput = offerContent.collectionThroughputInfo?.minimumRUForCollection;
const autopilotSettings = offerContent.offerAutopilotSettings;
if (autopilotSettings) {
if (autopilotSettings && autopilotSettings.maxThroughput && minimumThroughput) {
return {
id: offerDefinition.id,
autoscaleMaxThroughput: autopilotSettings.maxThroughput,

View File

@ -23,10 +23,10 @@ export class Splitter {
public splitterId: string;
public leftSideId: string;
public splitter: HTMLElement;
public leftSide: HTMLElement;
public lastX: number;
public lastWidth: number;
public splitter!: HTMLElement;
public leftSide!: HTMLElement;
public lastX!: number;
public lastWidth!: number;
private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds;
@ -42,9 +42,10 @@ export class Splitter {
}
public initialize() {
this.splitter = document.getElementById(this.splitterId);
this.leftSide = document.getElementById(this.leftSideId);
if (document.getElementById(this.splitterId) !== null && document.getElementById(this.leftSideId) != null) {
this.splitter = <HTMLElement>document.getElementById(this.splitterId);
this.leftSide = <HTMLElement>document.getElementById(this.leftSideId);
}
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
const splitterOptions: JQueryUI.ResizableOptions = {
animate: true,

View File

@ -210,9 +210,9 @@ export interface QueryMetrics {
export interface Offer {
id: string;
autoscaleMaxThroughput: number;
manualThroughput: number;
minimumThroughput: number;
autoscaleMaxThroughput: number | undefined;
manualThroughput: number | undefined;
minimumThroughput: number | undefined;
offerDefinition?: SDKOfferDefinition;
offerReplacePending: boolean;
}

View File

@ -42,7 +42,7 @@ interface CollapsiblePanelParams {
* Use the optional "collapseToLeft" parameter to collapse to the left.
*/
class CollapsiblePanelViewModel {
private params: CollapsiblePanelParams;
public params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) {
@ -50,7 +50,7 @@ class CollapsiblePanelViewModel {
this.isCollapsed = params.isCollapsed || ko.observable(false);
}
private toggleCollapse(): void {
public toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed());
}
}

View File

@ -71,7 +71,7 @@ interface InputTypeaheadParams {
/**
* This function gets called when pressing ENTER on the input box
*/
submitFct?: (inputValue: string, selection: Item) => void;
submitFct?: (inputValue: string | null, selection: Item | null) => void;
/**
* Typehead comes with a Search button that we normally remove.
@ -88,8 +88,8 @@ interface OnClickItem {
}
interface Cache {
inputValue: string;
selection: Item;
inputValue: string | null;
selection: Item | null;
}
class InputTypeaheadViewModel {
@ -98,15 +98,12 @@ class InputTypeaheadViewModel {
private params: InputTypeaheadParams;
private cache: Cache;
private inputValue: string;
private selection: Item;
public constructor(params: InputTypeaheadParams) {
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
this.params = params;
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
this.cache = {
inputValue: null,
selection: null
@ -161,7 +158,7 @@ class InputTypeaheadViewModel {
}
}
$.typeahead(options);
($ as any).typeahead(options);
}
/**
@ -177,11 +174,11 @@ class InputTypeaheadViewModel {
* Use ko's "template: afterRender" callback to do that without actually using any template.
* Another way is to call it within setTimeout() in constructor.
*/
private afterRender(): void {
public afterRender(): void {
this.initializeTypeahead();
}
private submit(): void {
public submit(): void {
if (this.params.submitFct) {
this.params.submitFct(this.cache.inputValue, this.cache.selection);
}

View File

@ -59,10 +59,12 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
this.params = params;
this.params.content.subscribe((newValue: string) => {
if (!!this.editor) {
this.editor.getModel().setValue(newValue);
} else {
this.createEditor(newValue, this.configureEditor.bind(this));
if (newValue) {
if (!!this.editor) {
this.editor.getModel().setValue(newValue);
} else {
this.createEditor(newValue, this.configureEditor.bind(this));
}
}
});

View File

@ -231,7 +231,7 @@ describe("SettingsComponent", () => {
it("getUpdatedConflictResolutionPolicy", () => {
const wrapper = shallow(<SettingsComponent {...baseProps} />);
const conflictResolutionPolicyPath = "_ts";
const conflictResolutionPolicyPath = "/_ts";
const conflictResolutionPolicyProcedure = "sample_sproc";
const expectSprocPath =
"/dbs/" + collection.databaseId + "/colls/" + collection.id() + "/sprocs/" + conflictResolutionPolicyProcedure;

View File

@ -138,8 +138,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
// Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer =
!this.collection.partitionKey ||
(this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey);
this.container.isPreferredApiMongoDB() &&
(!this.collection.partitionKey || this.collection.partitionKey.systemKey);
this.state = {
throughput: undefined,
@ -684,7 +684,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (policy.mode === DataModels.ConflictResolutionMode.LastWriterWins) {
policy.conflictResolutionPath = this.state.conflictResolutionPolicyPath;
if (policy.conflictResolutionPath?.startsWith("/")) {
if (!policy.conflictResolutionPath?.startsWith("/")) {
policy.conflictResolutionPath = "/" + policy.conflictResolutionPath;
}
}

View File

@ -427,7 +427,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
const newThroughput = getSanitizedInputValue(newValue);
this.props.onMaxAutoPilotThroughputChange(newThroughput);
};
@ -435,7 +435,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
const newThroughput = getSanitizedInputValue(newValue);
if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else {

View File

@ -101,13 +101,13 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
return procedureFromBackEnd;
};
export const getSanitizedInputValue = (newValueString: string, max: number): number => {
export const getSanitizedInputValue = (newValueString: string, max?: number): number => {
const newValue = parseInt(newValueString);
if (isNaN(newValue)) {
return zeroValue;
}
// make sure new value does not exceed the maximum throughput
return Math.min(newValue, max);
return max ? Math.min(newValue, max) : newValue;
};
export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {

View File

@ -954,6 +954,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@ -1187,11 +1188,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@ -2237,6 +2236,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@ -2470,11 +2470,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@ -3533,6 +3531,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@ -3766,11 +3765,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {
@ -4816,6 +4813,7 @@ exports[`SettingsComponent renders 1`] = `
"isHostedDataExplorerEnabled": [Function],
"isLeftPaneExpanded": [Function],
"isLinkInjectionEnabled": [Function],
"isMongoIndexingEnabled": [Function],
"isNotebookEnabled": [Function],
"isNotebooksEnabledForAccount": [Function],
"isNotificationConsoleExpanded": [Function],
@ -5049,11 +5047,9 @@ exports[`SettingsComponent renders 1`] = `
},
"direction": "vertical",
"isCollapsed": [Function],
"leftSide": null,
"leftSideId": "resourcetree",
"onResizeStart": [Function],
"onResizeStop": [Function],
"splitter": null,
"splitterId": "h_splitter1",
},
"stringInputPane": StringInputPane {

View File

@ -212,6 +212,7 @@ export default class Explorer {
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>;
public isMongoIndexingEnabled: ko.Observable<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>;
@ -409,6 +410,7 @@ export default class Explorer {
this.isFeatureEnabled(Constants.Features.enableLinkInjection)
);
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
@ -1918,6 +1920,9 @@ export default class Explorer {
if (!flights) {
return;
}
if (flights.indexOf(Constants.Flights.MongoIndexing) !== -1) {
this.isMongoIndexingEnabled(true);
}
}
public findSelectedCollection(): ViewModels.Collection {

View File

@ -11,7 +11,9 @@ export class ArraysByKeyCache<T> {
public constructor(maxNbElements: number) {
this.maxNbElements = maxNbElements;
this.clear();
this.keyQueue = [];
this.cache = {};
this.totalElements = 0;
}
public clear(): void {
@ -58,7 +60,7 @@ export class ArraysByKeyCache<T> {
* @param startIndex
* @param pageSize
*/
public retrieve(key: string, startIndex: number, pageSize: number): T[] {
public retrieve(key: string, startIndex: number, pageSize: number): T[] | null {
if (!this.cache.hasOwnProperty(key)) {
return null;
}
@ -77,8 +79,10 @@ export class ArraysByKeyCache<T> {
private reduceCacheSize(): void {
// remove an key and its array
const oldKey = this.keyQueue.shift();
this.totalElements -= this.cache[oldKey].length;
delete this.cache[oldKey];
if (oldKey) {
this.totalElements -= this.cache[oldKey].length;
delete this.cache[oldKey];
}
}
/**

View File

@ -413,13 +413,13 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
* @param node
* @param prop
*/
public static getNodePropValue(node: D3Node, prop: string): string | number | boolean {
public static getNodePropValue(node: D3Node, prop: string): undefined | string | number | boolean {
if (node.hasOwnProperty(prop)) {
return (node as any)[prop];
}
// This is DocDB specific
if (node.hasOwnProperty("properties") && node.properties.hasOwnProperty(prop)) {
if (node.properties && node.properties.hasOwnProperty(prop)) {
return node.properties[prop][0]["value"];
}
@ -496,7 +496,7 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
* Get list of children ids of a given vertex
* @param vertex
*/
private static getChildrenId(vertex: GremlinVertex): string[] {
public static getChildrenId(vertex: GremlinVertex): string[] {
const ids = <any>{}; // HashSet
if (vertex.hasOwnProperty("outE")) {
let outE = vertex.outE;

View File

@ -1,18 +1,17 @@
import { Observable, of } from "rxjs";
import { AjaxResponse } from "rxjs/ajax";
import { ServerConfig } from "rx-jupyter";
import { AjaxRequest, AjaxResponse } from "rxjs/ajax";
let fakeAjaxResponse: AjaxResponse = {
originalEvent: undefined,
originalEvent: <Event>(<unknown>undefined),
xhr: new XMLHttpRequest(),
request: null,
request: <AjaxRequest>(<unknown>null),
status: 200,
response: {},
responseText: null,
responseText: "",
responseType: "json"
};
export const sessions = {
create: (serverConfig: ServerConfig, body: object): Observable<AjaxResponse> => of(fakeAjaxResponse),
create: (serverConfig: unknown, body: object): Observable<AjaxResponse> => of(fakeAjaxResponse),
__setResponse: (response: AjaxResponse) => {
fakeAjaxResponse = response;
},

View File

@ -11,7 +11,7 @@ import { stringifyNotebook } from "@nteract/commutable";
export class NotebookContentClient {
constructor(
private notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>,
private notebookBasePath: ko.Observable<string>,
public notebookBasePath: ko.Observable<string>,
private contentProvider: IContentProvider
) {}
@ -117,8 +117,11 @@ export class NotebookContentClient {
private async checkIfFilepathExists(filepath: string): Promise<boolean> {
const parentDirPath = NotebookUtil.getParentPath(filepath);
const items = await this.fetchNotebookFiles(parentDirPath);
return items.some(value => FileSystemUtil.isPathEqual(value.path, filepath));
if (parentDirPath) {
const items = await this.fetchNotebookFiles(parentDirPath);
return items.some(value => FileSystemUtil.isPathEqual(value.path, filepath));
}
return false;
}
/**
@ -189,7 +192,7 @@ export class NotebookContentClient {
const dir = xhr.response;
const item = NotebookUtil.createNotebookContentItem(dir.name, dir.path, dir.type);
item.parent = parent;
parent.children.push(item);
parent.children!.push(item);
return item;
});
}
@ -225,7 +228,7 @@ export class NotebookContentClient {
* Convert rx-jupyter type to our type
* @param type
*/
private static getType(type: FileType): NotebookContentItemType {
public static getType(type: FileType): NotebookContentItemType {
switch (type) {
case "directory":
return NotebookContentItemType.Directory;

View File

@ -257,7 +257,7 @@
range of values and is likely to have evenly distributed access patterns.</span>
</span>
</p>
<input type="text" id="partitionKeyValue" data-test="addCollection-partitionKeyValue" aria-required="true" size="40"
<input type="text" id="addCollection-partitionKeyValue" data-test="addCollection-partitionKeyValue" aria-required="true" size="40"
class="textfontclr collid" data-bind="textInput: partitionKey,
attr: {
placeholder: partitionKeyPlaceholder,

View File

@ -654,7 +654,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
});
});
this.shouldCreateMongoWildcardIndex = ko.observable(false);
this.shouldCreateMongoWildcardIndex = ko.observable(this.container.isMongoIndexingEnabled());
}
public getSharedThroughputDefault(): boolean {
@ -679,6 +679,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
// TODO: Figure out if a database level partition split is about to happen once shared throughput read is available
this.formWarnings("");
this.databaseCreateNewShared(this.getSharedThroughputDefault());
this.shouldCreateMongoWildcardIndex(this.container.isMongoIndexingEnabled());
if (this.isPreferredApiTable() && !databaseId) {
databaseId = SharedConstants.CollectionCreation.TablesAPIDefaultDatabase;
}
@ -933,6 +934,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.shouldCreateMongoWildcardIndex = ko.observable(this.container.isMongoIndexingEnabled());
this.uniqueKeys([]);
this.useIndexingForSharedThroughput(true);

View File

@ -1,5 +1,5 @@
abstract class CacheBase<T> {
public data: T[];
public data: T[] | null;
public sortOrder: any;
public serverCallInProgress: boolean;

View File

@ -16,14 +16,75 @@ import * as Entities from "../Entities";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import * as TableEntityProcessor from "../TableEntityProcessor";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
interface IListTableEntitiesSegmentedResult extends Entities.IListTableEntitiesResult {
ExceedMaximumRetries?: boolean;
}
export interface ErrorDataModel {
message: string;
severity?: string;
location?: {
start: string;
end: string;
};
code?: string;
}
function parseError(err: any): ErrorDataModel[] {
try {
return _parse(err);
} catch (e) {
return [<ErrorDataModel>{ message: JSON.stringify(err) }];
}
}
function _parse(err: any): ErrorDataModel[] {
var normalizedErrors: ErrorDataModel[] = [];
if (err.message && !err.code) {
normalizedErrors.push(err);
} else {
const innerErrors: any[] = _getInnerErrors(err.message);
normalizedErrors = innerErrors.map(innerError =>
typeof innerError === "string" ? { message: innerError } : innerError
);
}
return normalizedErrors;
}
function _getInnerErrors(message: string): any[] {
/*
The backend error message has an inner-message which is a stringified object.
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
Example:
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
For non-SQL errors the "Errors" propery is an array of string.
Example:
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
*/
let innerMessage: any = null;
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
try {
// Multi-Partition error flavor
const regExp = /^(.*)ActivityId: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
} catch (e) {
// Single-partition error flavor
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
}
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
}
/**
* Storage Table Entity List ViewModel
*/
@ -387,8 +448,17 @@ export default class TableEntityListViewModel extends DataTableViewModel {
}
})
.catch((error: any) => {
const errorMessage = getErrorMessage(error);
this.queryErrorMessage(errorMessage);
const parsedErrors = parseError(error);
var errors = parsedErrors.map(error => {
return <ViewModels.QueryError>{
message: error.message,
start: error.location ? error.location.start : undefined,
end: error.location ? error.location.end : undefined,
code: error.code,
severity: error.severity
};
});
this.queryErrorMessage(errors[0].message);
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
TelemetryProcessor.traceFailure(
Action.Tab,
@ -399,8 +469,7 @@ export default class TableEntityListViewModel extends DataTableViewModel {
defaultExperience: this.queryTablesTab.collection.container.defaultExperience(),
dataExplorerArea: Areas.Tab,
tabTitle: this.queryTablesTab.tabTitle(),
error: errorMessage,
errorStack: getErrorStack(error)
error: error
},
this.queryTablesTab.onLoadStartKey
);
@ -421,47 +490,53 @@ export default class TableEntityListViewModel extends DataTableViewModel {
* Note that this also means that we can get less entities than the requested download size in a successful call.
* See Microsoft Azure API Documentation at: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx
*/
private async prefetchData(
private prefetchData(
tableQuery: Entities.ITableQuery,
downloadSize: number,
currentRetry: number = 0
): Promise<IListTableEntitiesSegmentedResult> {
): Q.Promise<any> {
if (!this.cache.serverCallInProgress) {
this.cache.serverCallInProgress = true;
this.allDownloaded = false;
this.lastPrefetchTime = new Date().getTime();
const time = this.lastPrefetchTime;
var time = this.lastPrefetchTime;
var promise: Q.Promise<IListTableEntitiesSegmentedResult>;
if (this._documentIterator && this.continuationToken) {
// TODO handle Cassandra case
const response = await this._documentIterator.fetchNext();
const entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(response?.resources);
return {
Results: entities,
ContinuationToken: this._documentIterator.hasMoreResults()
};
}
try {
let documents: IListTableEntitiesSegmentedResult;
if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
promise = Q(this._documentIterator.fetchNext().then(response => response.resources)).then(
(documents: any[]) => {
let entities: Entities.ITableEntity[] = TableEntityProcessor.convertDocumentsToEntities(documents);
let finalEntities: IListTableEntitiesSegmentedResult = <IListTableEntitiesSegmentedResult>{
Results: entities,
ContinuationToken: this._documentIterator.hasMoreResults()
};
return Q.resolve(finalEntities);
}
);
} else if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) {
promise = Q(
this.queryTablesTab.container.tableDataClient.queryDocuments(
this.queryTablesTab.collection,
this.cqlQuery(),
true,
this.continuationToken
);
} else {
const query = this.queryTablesTab.container.isPreferredApiCassandra() ? this.cqlQuery() : this.sqlQuery();
documents = await this.queryTablesTab.container.tableDataClient.queryDocuments(
this.queryTablesTab.collection,
query,
true
);
)
);
} else {
let query = this.sqlQuery();
if (this.queryTablesTab.container.isPreferredApiCassandra()) {
query = this.cqlQuery();
}
promise = Q(
this.queryTablesTab.container.tableDataClient.queryDocuments(this.queryTablesTab.collection, query, true)
);
}
return promise
.then((result: IListTableEntitiesSegmentedResult) => {
if (!this._documentIterator) {
this._documentIterator = documents.iterator;
this._documentIterator = result.iterator;
}
var actualDownloadSize: number = 0;
@ -472,11 +547,11 @@ export default class TableEntityListViewModel extends DataTableViewModel {
return Q.resolve(null);
}
var entities = documents.Results;
var entities = result.Results;
actualDownloadSize = entities.length;
// Queries can fetch no results and still return a continuation header. See prefetchAndRender() method.
this.continuationToken = this.isCancelled ? null : documents.ContinuationToken;
this.continuationToken = this.isCancelled ? null : result.ContinuationToken;
if (!this.continuationToken) {
this.allDownloaded = true;
@ -508,22 +583,20 @@ export default class TableEntityListViewModel extends DataTableViewModel {
// For #2.1, set prefetch exceeds maximum retry number and end prefetch.
// For #2.2, go to next round prefetch.
if (this.allDownloaded || nextDownloadSize === 0) {
return documents;
return Q.resolve(result);
}
if (currentRetry >= TableEntityListViewModel._maximumNumberOfPrefetchRetries) {
documents.ExceedMaximumRetries = true;
return documents;
result.ExceedMaximumRetries = true;
return Q.resolve(result);
}
return await this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
}
} catch (error) {
this.cache.serverCallInProgress = false;
throw error;
}
return this.prefetchData(tableQuery, nextDownloadSize, currentRetry + 1);
})
.catch((error: Error) => {
this.cache.serverCallInProgress = false;
return Q.reject(error);
});
}
return undefined;
return null;
}
}

View File

@ -7,7 +7,7 @@ export interface ITableEntity {
export interface ITableEntityForTablesAPI extends ITableEntity {
PartitionKey: ITableEntityAttribute;
RowKey: ITableEntityAttribute;
Timestamp?: ITableEntityAttribute;
Timestamp: ITableEntityAttribute;
}
export interface ITableEntityAttribute {

View File

@ -11,6 +11,17 @@
Start by writing a Mongo query, for example: <strong>{'id':'foo'}</strong> or <strong>{ }</strong> to get all the
documents.
</div>
<div class="warningErrorContainer" aria-live="assertive" data-bind="visible: maybeSubQuery">
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/info_color.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
We have detected you may be using a subquery. Non-correlated subqueries are not currently supported.
<a href="https://docs.microsoft.com/en-us/azure/cosmos-db/sql-query-subquery"
>Please see Cosmos sub query documentation for further information</a
>
</span>
</div>
</div>
<div class="queryEditorWithSplitter" data-bind="attr: { id: queryEditorId }">
<editor
class="queryEditor"

View File

@ -1,5 +1,4 @@
import * as ko from "knockout";
import Q from "q";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
@ -7,7 +6,6 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import TabsBase from "./TabsBase";
import { HashMap } from "../../Common/HashMap";
import * as HeadersUtility from "../../Common/HeadersUtility";
import * as Logger from "../../Common/Logger";
import { Splitter, SplitterBounds, SplitterDirection } from "../../Common/Splitter";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
@ -31,6 +29,7 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
public fetchNextPageButton: ViewModels.Button;
public saveQueryButton: ViewModels.Button;
public initialEditorContent: ko.Observable<string>;
public maybeSubQuery: ko.Computed<boolean>;
public sqlQueryEditorContent: ko.Observable<string>;
public selectedContent: ko.Observable<string>;
public sqlStatementToExecute: ko.Observable<string>;
@ -120,6 +119,11 @@ export default class QueryTab extends TabsBase implements ViewModels.WaitsForTem
return (container && (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph())) || false;
});
this.maybeSubQuery = ko.computed<boolean>(function() {
const sql = this.sqlQueryEditorContent();
return sql && /.*\(.*SELECT.*\)/i.test(sql);
}, this);
this.saveQueryButton = {
enabled: this._isSaveQueriesEnabled,
visible: this._isSaveQueriesEnabled

View File

@ -8,7 +8,7 @@ enum ScrollPosition {
export class AccessibleVerticalList {
private items: any[] = [];
private onSelect: (item: any) => void;
private onSelect?: (item: any) => void;
public currentItemIndex: ko.Observable<number>;
public currentItem: ko.Computed<any>;
@ -42,7 +42,9 @@ export class AccessibleVerticalList {
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
if (event.keyCode === 40) {
@ -52,7 +54,9 @@ export class AccessibleVerticalList {
const targetElement = targetContainer
.getElementsByClassName("accessibleListElement")
.item(this.currentItemIndex());
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Bottom);
if (targetElement) {
this.scrollElementIntoContainerViewIfNeeded(targetElement, targetContainer, ScrollPosition.Top);
}
return false;
}
return true;

View File

@ -4,7 +4,7 @@ import * as DataModels from "../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
export class DefaultExperienceUtility {
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string {
public static getDefaultExperienceFromDatabaseAccount(databaseAccount: DataModels.DatabaseAccount): string | null {
if (!databaseAccount) {
return null;
}
@ -81,11 +81,9 @@ export class DefaultExperienceUtility {
private static _getDefaultExperience(kind: string, capabilities: DataModels.Capability[]): string {
const defaultDefaultExperience: string = Constants.DefaultAccountExperience.DocumentDB;
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind);
const defaultExperienceFromCapabilities: string = DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(
capabilities
);
const defaultExperienceFromKind: string = DefaultExperienceUtility._getDefaultExperienceFromAccountKind(kind) || "";
const defaultExperienceFromCapabilities: string =
DefaultExperienceUtility._getDefaultExperienceFromAccountCapabilities(capabilities) || "";
if (!!defaultExperienceFromKind) {
return defaultExperienceFromKind;
}
@ -97,7 +95,7 @@ export class DefaultExperienceUtility {
return defaultDefaultExperience;
}
private static _getDefaultExperienceFromAccountKind(kind: string): string {
private static _getDefaultExperienceFromAccountKind(kind: string): string | null {
if (!kind) {
return null;
}
@ -113,7 +111,7 @@ export class DefaultExperienceUtility {
return null;
}
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string {
private static _getDefaultExperienceFromAccountCapabilities(capabilities: DataModels.Capability[]): string | null {
if (!capabilities) {
return null;
}

View File

@ -19,8 +19,8 @@ const getUrlVars = (): { [key: string]: string } => {
};
const createServerSettings = (urlVars: { [key: string]: string }): ServerConnection.ISettings => {
let body: BodyInit;
let headers: HeadersInit;
let body: BodyInit | undefined;
let headers: HeadersInit | undefined;
if (urlVars.hasOwnProperty(TerminalQueryParams.TerminalEndpoint)) {
body = JSON.stringify({
endpoint: urlVars[TerminalQueryParams.TerminalEndpoint]

View File

@ -1,5 +1,7 @@
import { armRequest } from "./request";
import fetch from "node-fetch";
import { updateUserContext } from "../../UserContext";
import { AuthType } from "../../AuthType";
interface Global {
Headers: unknown;
@ -8,6 +10,11 @@ interface Global {
((global as unknown) as Global).Headers = ((fetch as unknown) as Global).Headers;
describe("ARM request", () => {
window.authType = AuthType.AAD;
updateUserContext({
authorizationToken: "some-token"
});
it("should call window.fetch", async () => {
window.fetch = jest.fn().mockResolvedValue({
ok: true,
@ -48,4 +55,24 @@ describe("ARM request", () => {
).rejects.toThrow();
expect(window.fetch).toHaveBeenCalledTimes(2);
});
it("should throw token error", async () => {
window.authType = AuthType.AAD;
updateUserContext({
authorizationToken: undefined
});
const headers = new Headers();
headers.set("location", "https://foo.com/operationStatus");
window.fetch = jest.fn().mockResolvedValue({
ok: true,
headers,
status: 200,
json: async () => {
return { status: "Failed" };
}
});
await expect(() =>
armRequest({ apiVersion: "2001-01-01", host: "https://foo.com", path: "foo", method: "GET" })
).rejects.toThrow("No authority token provided");
});
});

View File

@ -30,7 +30,7 @@ export class ARMError extends Error {
Object.setPrototypeOf(this, ARMError.prototype);
}
public code: string | number;
public code?: string | number;
}
interface ARMQueryParams {
@ -63,6 +63,10 @@ export async function armRequest<T>({
queryParams.metricNames && url.searchParams.append("metricnames", queryParams.metricNames);
}
if (!userContext.authorizationToken) {
throw new Error("No authority token provided");
}
const response = await window.fetch(url.href, {
method,
headers: {
@ -98,6 +102,10 @@ export async function armRequest<T>({
}
async function getOperationStatus(operationStatusUrl: string) {
if (!userContext.authorizationToken) {
throw new Error("No authority token provided");
}
const response = await window.fetch(operationStatusUrl, {
headers: {
Authorization: userContext.authorizationToken

View File

@ -7,6 +7,7 @@
</head>
<body>
<a class="skip-link" href="#data-explorer-content">Skip to content</a>
<header>
<div class="items" role="menubar">
<div class="cosmosDBTitle">
@ -48,16 +49,18 @@
<switch-directory-pane params="{data: switchDirectoryPane}"></switch-directory-pane>
<!-- TODO generate version number dynamically -->
<iframe
id="explorerMenu"
name="explorer"
class="iframe"
title="explorer"
src="explorer.html?v=1.0.1&platform=Hosted"
data-bind="visible: navigationSelection() === 'explorer'"
>
</iframe>
<div id="data-explorer-content">
<!-- TODO generate version number dynamically -->
<iframe
id="explorerMenu"
name="explorer"
class="iframe"
title="explorer"
src="explorer.html?v=1.0.1&platform=Hosted"
data-bind="visible: navigationSelection() === 'explorer'"
>
</iframe>
</div>
<div data-bind="react: firewallWarningComponentAdapter"></div>
<div data-bind="react: dialogComponentAdapter"></div>

View File

@ -13,6 +13,7 @@
"./src/Common/ArrayHashMap.ts",
"./src/Common/Constants.ts",
"./src/Common/DeleteFeedback.ts",
"./src/Common/DocumentUtility.ts",
"./src/Common/EnvironmentUtility.ts",
"./src/Common/HashMap.ts",
"./src/Common/HeadersUtility.ts",
@ -20,8 +21,10 @@
"./src/Common/MessageHandler.ts",
"./src/Common/MongoUtility.ts",
"./src/Common/ObjectCache.ts",
"./src/Common/OfferUtility.ts",
"./src/Common/ThemeUtility.ts",
"./src/Common/UrlUtility.ts",
"./src/Common/Splitter.ts",
"./src/ConfigContext.ts",
"./src/Contracts/ActionContracts.ts",
"./src/Contracts/DataModels.ts",
@ -39,9 +42,15 @@
"./src/Definitions/plotly.js-cartesian-dist.d-min.ts",
"./src/Definitions/svg.d.ts",
"./src/Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.ts",
"./src/Explorer/Controls/CollapsiblePanel/CollapsiblePanelComponent.ts",
"./src/Explorer/Controls/GitHub/GitHubStyleConstants.ts",
"./src/Explorer/Controls/InputTypeahead/InputTypeahead.ts",
"./src/Explorer/Controls/SmartUi/InputUtils.ts",
"./src/Explorer/Graph/GraphExplorerComponent/__mocks__/GremlinClient.ts",
"./src/Explorer/Graph/GraphExplorerComponent/ArraysByKeyCache.ts",
"./src/Explorer/Graph/GraphExplorerComponent/EdgeInfoCache.ts",
"./src/Explorer/Graph/GraphExplorerComponent/GraphData.ts",
"./src/Explorer/Notebook/NotebookComponent/__mocks__/rx-jupyter.ts",
"./src/Explorer/Notebook/FileSystemUtil.ts",
"./src/Explorer/Notebook/NTeractUtil.ts",
"./src/Explorer/Notebook/NotebookComponent/actions.ts",
@ -50,13 +59,18 @@
"./src/Explorer/Notebook/NotebookComponent/types.ts",
"./src/Explorer/Notebook/NotebookContentItem.ts",
"./src/Explorer/Notebook/NotebookUtil.ts",
"./src/Explorer/Tree/AccessibleVerticalList.ts",
"./src/Explorer/Panes/PaneComponents.ts",
"./src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts",
"./src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts",
"./src/Explorer/Tables/Constants.ts",
"./src/Explorer/Tables/CqlUtilities.ts",
"./src/Explorer/Tables/QueryBuilder/DateTimeUtilities.ts",
"./src/Explorer/Tables/DataTable/CacheBase.ts",
"./src/Explorer/Tables/Entities.ts",
"./src/Explorer/Tabs/TabComponents.ts",
"./src/Explorer/Notebook/NotebookComponent/__mocks__/rx-jupyter.ts",
"./src/Explorer/Notebook/NotebookContentClient.ts",
"./src/GitHub/GitHubConnector.ts",
"./src/Index.ts",
"./src/NotebookWorkspaceManager/NotebookWorkspaceResourceProviderMockClients.ts",
@ -70,6 +84,8 @@
"./src/Shared/Telemetry/TelemetryConstants.ts",
"./src/Shared/Telemetry/TelemetryProcessor.ts",
"./src/Shared/appInsights.ts",
"./src/Shared/DefaultExperienceUtility.ts",
"./src/Terminal/index.ts",
"./src/Terminal/JupyterLabAppFactory.ts",
"./src/UserContext.ts",
"./src/Utils/Base64Utils.ts",
@ -79,6 +95,25 @@
"./src/Utils/StringUtils.ts",
"./src/Utils/WindowUtils.ts",
"./src/Utils/arm/generatedClients/2020-04-01/types.ts",
"./src/Utils/arm/generatedClients/2020-04-01/cassandraResources.ts",
"./src/Utils/arm/generatedClients/2020-04-01/collection.ts",
"./src/Utils/arm/generatedClients/2020-04-01/collectionPartition.ts",
"./src/Utils/arm/generatedClients/2020-04-01/collectionPartitionRegion.ts",
"./src/Utils/arm/generatedClients/2020-04-01/collectionRegion.ts",
"./src/Utils/arm/generatedClients/2020-04-01/database.ts",
"./src/Utils/arm/generatedClients/2020-04-01/databaseAccountRegion.ts",
"./src/Utils/arm/generatedClients/2020-04-01/databaseAccounts.ts",
"./src/Utils/arm/generatedClients/2020-04-01/gremlinResources.ts",
"./src/Utils/arm/generatedClients/2020-04-01/mongoDBResources.ts",
"./src/Utils/arm/generatedClients/2020-04-01/operations.ts",
"./src/Utils/arm/generatedClients/2020-04-01/partitionKeyRangeId.ts",
"./src/Utils/arm/generatedClients/2020-04-01/partitionKeyRangeIdRegion.ts",
"./src/Utils/arm/generatedClients/2020-04-01/percentile.ts",
"./src/Utils/arm/generatedClients/2020-04-01/percentileSourceTarget.ts",
"./src/Utils/arm/generatedClients/2020-04-01/percentileTarget.ts",
"./src/Utils/arm/generatedClients/2020-04-01/sqlResources.ts",
"./src/Utils/arm/generatedClients/2020-04-01/tableResources.ts",
"./src/Utils/arm/request.ts",
"./src/quickstart.ts",
"./src/setupTests.ts",
"./src/workers/upload/definitions.ts"