Compare commits

..

8 Commits

Author SHA1 Message Date
sunilyadav840
17fa6aff31 fixed typescript strict GremlinSimpleClient file 2021-05-25 17:54:25 +05:30
Zachary Foster
c42a10faa5 Rollback DB endpoint use by AAD (#822) 2021-05-24 21:21:52 -07:00
victor-meng
0d79f01304 Update free tier limits and messages (#786) 2021-05-24 15:42:54 -05:00
Srinath Narayanan
eae5b2219e Added self serve component telemetry (#820) 2021-05-25 01:06:57 +05:30
Zachary Foster
2fda881770 Use customer endpoint for RBAC AAD auth (#818) 2021-05-24 13:03:51 -05:00
Sunil Kumar Yadav
35f8fa8324 Add files to TS Strict (#803)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2021-05-21 19:28:08 -05:00
Steve Faulkner
0e413430dc Remove Unused Components (#819) 2021-05-21 19:02:39 -05:00
Sunil Kumar Yadav
afd7f43eb8 Add files to strict mode (#775) 2021-05-21 18:45:54 -05:00
27 changed files with 76 additions and 2685 deletions

View File

@@ -214,10 +214,3 @@ jobs:
name: packages
with:
path: "*.nupkg"
allcheckspass:
name: "All Checks Pass"
runs-on: ubuntu-latest
steps:
- uses: "WyriHaximus/github-action-wait-for-status@v1.3.1"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -32,7 +32,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
<DatePicker
className="addEntityDatePicker"
placeholder={entityValuePlaceholder}
value={entityValue && new Date(entityValue)}
value={entityValue ? new Date(entityValue) : new Date()}
ariaLabel={entityValuePlaceholder}
onSelectDate={onSelectDate}
disabled={isEntityValueDisable}
@@ -59,7 +59,7 @@ export const EntityValue: FunctionComponent<TableEntityProps> = ({
disabled={isEntityValueDisable}
type={entityValueType}
placeholder={entityValuePlaceholder}
value={typeof entityValue === "string" && entityValue}
value={typeof entityValue === "string" ? entityValue : ""}
onChange={onEntityValueChange}
/>
);

View File

@@ -1,105 +0,0 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
import { Tenant } from "../../../Contracts/DataModels";
const createBlankProps = (): DefaultDirectoryDropdownProps => {
return {
defaultDirectoryId: "",
directories: [],
onDefaultDirectoryChange: jest.fn(),
};
};
const createBlankDirectory = (): Tenant => {
return {
countryCode: "",
displayName: "",
domains: [],
id: "",
tenantId: "",
};
};
describe("test render", () => {
it("renders with no directories", () => {
const props = createBlankProps();
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories but no default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "asdfghjklzxcvbnm9876543210";
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and last visit default", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "lastVisited";
const wrapper = shallow(<DefaultDirectoryDropdownComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});
describe("test function", () => {
it("on default directory change", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.defaultDirectoryId = "lastVisited";
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
});
});

View File

@@ -1,71 +0,0 @@
/**
* React component for Switch Directory
*/
import { Dropdown, IDropdownOption, IDropdownProps } from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DefaultDirectoryDropdownProps {
directories: Array<Tenant>;
defaultDirectoryId: string;
onDefaultDirectoryChange: (newDirectory: Tenant) => void;
}
export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDirectoryDropdownProps> {
public static readonly lastVisitedKey: string = "lastVisited";
public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory",
};
const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
(dirc): IDropdownOption => {
return {
key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`,
};
}
);
const dropDownOptions: Array<IDropdownOption> = [lastVisitedOption, ...directoryOptions];
const dropDownProps: IDropdownProps = {
label: "Set your default directory",
options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown",
};
return <Dropdown {...dropDownProps} />;
}
private _onDropdownChange = (e: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number): void => {
if (!option || !option.key) {
return;
}
if (option.key === this.props.defaultDirectoryId) {
return;
}
if (option.key === DefaultDirectoryDropdownComponent.lastVisitedKey) {
this.props.onDefaultDirectoryChange({
tenantId: option.key,
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined,
});
return;
}
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) {
return;
}
this.props.onDefaultDirectoryChange(selectedDirectory);
};
}

View File

@@ -1,36 +0,0 @@
import * as ko from "knockout";
import * as React from "react";
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
import { DefaultDirectoryDropdownComponent, DefaultDirectoryDropdownProps } from "./DefaultDirectoryDropdownComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DirectoryComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(
private _dropdownProps: ko.Observable<DefaultDirectoryDropdownProps>,
private _listProps: ko.Observable<DirectoryListProps>
) {
this._dropdownProps.subscribe(() => this.forceRender());
this._listProps.subscribe(() => this.forceRender());
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return (
<div>
<div className="directoryDropdownContainer">
<DefaultDirectoryDropdownComponent {...this._dropdownProps()} />
</div>
<div className="directoryDivider" />
<div className="directoryListContainer">
<DirectoryListComponent {...this._listProps()} />
</div>
</div>
);
}
public forceRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,78 +0,0 @@
import React from "react";
import { shallow, mount } from "enzyme";
import { DirectoryListComponent, DirectoryListProps } from "./DirectoryListComponent";
import { Tenant } from "../../../Contracts/DataModels";
const createBlankProps = (): DirectoryListProps => {
return {
selectedDirectoryId: undefined,
directories: [],
onNewDirectorySelected: jest.fn(),
};
};
const createBlankDirectory = (): Tenant => {
return {
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined,
tenantId: undefined,
};
};
describe("test render", () => {
it("renders with no directories", () => {
const props = createBlankProps();
const wrapper = shallow(<DirectoryListComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with directories and selected", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "asdfghjklzxcvbnm9876543210";
props.directories = [tenant1, tenant2];
props.selectedDirectoryId = "asdfghjklzxcvbnm9876543210";
const wrapper = shallow(<DirectoryListComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders with filters", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "1234567890";
const tenant2 = createBlankDirectory();
tenant1.displayName = "Macrohard";
tenant1.tenantId = "9876543210";
props.directories = [tenant1, tenant2];
props.selectedDirectoryId = "9876543210";
const wrapper = mount(<DirectoryListComponent {...props} />);
wrapper.find("input.ms-TextField-field").simulate("change", { target: { value: "Macro" } });
expect(wrapper).toMatchSnapshot();
});
});
describe("test function", () => {
it("on new directory selected", () => {
const props = createBlankProps();
const tenant1 = createBlankDirectory();
tenant1.displayName = "Microsoft";
tenant1.tenantId = "asdfghjklzxcvbnm1234567890";
props.directories = [tenant1];
const wrapper = mount(<DirectoryListComponent {...props} />);
wrapper.find("button.directoryListButton").simulate("click");
expect(props.onNewDirectorySelected).toBeCalled();
expect(props.onNewDirectorySelected).toHaveBeenCalled();
});
});

View File

@@ -1,126 +0,0 @@
import {
DefaultButton,
IButtonProps,
ITextFieldProps,
List,
ScrollablePane,
Sticky,
StickyPositionType,
TextField,
} from "@fluentui/react";
import * as React from "react";
import _ from "underscore";
import { Tenant } from "../../../Contracts/DataModels";
export interface DirectoryListProps {
directories: Array<Tenant>;
selectedDirectoryId: string;
onNewDirectorySelected: (newDirectory: Tenant) => void;
}
export interface DirectoryListComponentState {
filterText: string;
}
// onRenderCell is not called when selectedDirectoryId changed, so add a selected state to force render
interface ListTenant extends Tenant {
selected?: boolean;
}
export class DirectoryListComponent extends React.Component<DirectoryListProps, DirectoryListComponentState> {
constructor(props: DirectoryListProps) {
super(props);
this.state = {
filterText: "",
};
}
public render(): JSX.Element {
const { directories: originalItems, selectedDirectoryId } = this.props;
const { filterText } = this.state;
const filteredItems =
originalItems && originalItems.length && filterText
? originalItems.filter(
(directory) =>
directory.displayName &&
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
)
: originalItems;
const filteredItemsSelected = filteredItems.map((t) => {
let tenant: ListTenant = t;
tenant.selected = t.tenantId === selectedDirectoryId;
return tenant;
});
const textFieldProps: ITextFieldProps = {
className: "directoryListFilterTextBox",
placeholder: "Filter by directory name",
onChange: this._onFilterChanged,
ariaLabel: "Directory filter text box",
};
// TODO: add magnify glass to search bar with onRenderSuffix
return (
<ScrollablePane data-is-scrollable="true">
<Sticky stickyPosition={StickyPositionType.Header}>
<TextField {...textFieldProps} />
</Sticky>
<List items={filteredItemsSelected} onRenderCell={this._onRenderCell} />
</ScrollablePane>
);
}
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
this.setState({
filterText: text,
});
};
private _onRenderCell = (directory: ListTenant): JSX.Element => {
const buttonProps: IButtonProps = {
disabled: directory.selected || false,
className: "directoryListButton",
onClick: this._onNewDirectoryClick,
styles: {
root: {
backgroundColor: "transparent",
height: "auto",
borderBottom: "1px solid #ccc",
padding: "1px 0",
width: "100%",
},
rootDisabled: {
backgroundColor: "#f1f1f8",
},
rootHovered: {
backgroundColor: "rgba(85,179,255,.1)",
},
flexContainer: {
height: "auto",
justifyContent: "flex-start",
},
},
};
return (
<DefaultButton {...buttonProps}>
<div className="directoryListItem" data-is-focusable={true}>
<div className="directoryListItemName">{directory.displayName}</div>
<div className="directoryListItemId">{directory.tenantId}</div>
</div>
</DefaultButton>
);
};
private _onNewDirectoryClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
if (!e || !e.currentTarget) {
return;
}
const buttonElement = e.currentTarget;
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
this.props.onNewDirectorySelected(selectedDirectory);
};
}

View File

@@ -1,93 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`test render renders with directories and default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="asdfghjklzxcvbnm9876543210"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with directories and last visit default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with directories but no default 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
Object {
"key": "asdfghjklzxcvbnm9876543210",
"text": "Macrohard(asdfghjklzxcvbnm9876543210)",
},
Object {
"key": "",
"text": "()",
},
]
}
/>
`;
exports[`test render renders with no directories 1`] = `
<Dropdown
className="defaultDirectoryDropdown"
defaultSelectedKey="lastVisited"
label="Set your default directory"
onChange={[Function]}
options={
Array [
Object {
"key": "lastVisited",
"text": "Sign in to your last visited directory",
},
]
}
/>
`;

View File

@@ -1,16 +0,0 @@
@import "../../../../less/Common/Constants.less";
.radioSwitchComponent {
cursor: pointer;
display: flex;
&>span:nth-child(n+2) {
margin-left: 10px;
}
.caption {
color: @BaseDark;
padding-left: @SmallSpace;
vertical-align: top;
}
}

View File

@@ -1,51 +0,0 @@
/**
* Horizontal switch component
*/
import { Icon } from "@fluentui/react";
import * as React from "react";
import { NormalizedEventKey } from "../../../Common/Constants";
import "./RadioSwitchComponent.less";
export interface Choice {
key: string;
onSelect: () => void;
label: string;
}
export interface RadioSwitchComponentProps {
choices: Choice[];
selectedKey: string;
onSelectionKeyChange?: (newValue: string) => void;
}
export class RadioSwitchComponent extends React.Component<RadioSwitchComponentProps> {
public render(): JSX.Element {
return (
<div className="radioSwitchComponent">
{this.props.choices.map((choice: Choice) => (
<span
tabIndex={0}
key={choice.key}
onClick={() => this.onSelect(choice)}
onKeyPress={(event) => this.onKeyPress(event, choice)}
>
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
<span className="caption">{choice.label}</span>
</span>
))}
</div>
);
}
private onSelect(choice: Choice): void {
this.props.onSelectionKeyChange && this.props.onSelectionKeyChange(choice.key);
choice.onSelect();
}
private onKeyPress(event: React.KeyboardEvent<HTMLSpanElement>, choice: Choice): void {
if (event.key === NormalizedEventKey.Enter || event.key === NormalizedEventKey.Space) {
this.onSelect(choice);
}
}
}

View File

@@ -1,45 +0,0 @@
/**
* Generic abstract React component that senses its dimensions.
* It updates its state and re-renders if dimensions change.
*/
import * as React from "react";
import * as ResizeSensor from "css-element-queries/src/ResizeSensor";
export abstract class ResizeSensorComponent<P, S> extends React.Component<P, S> {
private isSensing: boolean = false;
private resizeSensor: any;
public constructor(props: P) {
super(props);
}
protected abstract onDimensionsChanged(width: number, height: number): void;
protected abstract getSensorTarget(): HTMLElement;
public componentDidUpdate(): void {
if (this.isSensing) {
return;
}
const bar = this.getSensorTarget();
if (bar.clientWidth > 0 || bar.clientHeight > 0) {
const oldPosition = bar.style.position;
// TODO Find a better way to use constructor
this.resizeSensor = new (ResizeSensor as any)(bar, () => {
this.onDimensionsChanged(bar.clientWidth, bar.clientHeight);
});
this.isSensing = true;
// ResizeSensor.js sets position to 'relative' which makes the dropdown menu appear clipped.
// Undoing doesn't seem to affect resize sensing functionality.
bar.style.position = oldPosition;
}
}
public componentWillUnmount(): void {
if (!!this.resizeSensor) {
this.resizeSensor.detach();
}
}
}

View File

@@ -202,10 +202,12 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}
private getFreeTierInfoMessage(): JSX.Element {
const freeTierLimits = SharedConstants.FreeTierLimits;
return (
<Text>
With free tier, you will get the first 400 RU/s and 5 GB of storage in this account for free. To keep your
account free, keep the total RU/s across all resources in the account to 400 RU/s.
With free tier, you will get the first {freeTierLimits.RU} RU/s and {freeTierLimits.Storage} GB of storage in
this account for free. To keep your account free, keep the total RU/s across all resources in the account to{" "}
{freeTierLimits.RU} RU/s.
<Link
href="https://docs.microsoft.com/en-us/azure/cosmos-db/understand-your-bill#billing-examples-with-free-tier-accounts"
target="_blank"

View File

@@ -155,7 +155,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.state = {
spendAckChecked: this.props.spendAckChecked,
exceedFreeTierThroughput:
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
this.props.isFreeTierAccount &&
!this.props.isAutoPilotSelected &&
this.props.throughput > SharedConstants.FreeTierLimits.RU,
};
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
@@ -441,7 +443,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else {
this.setState({ exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > 400 });
this.setState({
exceedFreeTierThroughput: this.props.isFreeTierAccount && newThroughput > SharedConstants.FreeTierLimits.RU,
});
this.props.onThroughputChange(newThroughput);
}
};
@@ -581,9 +585,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
styles={messageBarStyles}
>
{
"Billing will apply if you provision more than 400 RU/s of manual throughput, or if the resource scales beyond 400 RU/s with autoscale."
}
{`Billing will apply if you provision more than ${SharedConstants.FreeTierLimits.RU} RU/s of manual throughput, or if the resource scales beyond ${SharedConstants.FreeTierLimits.RU} RU/s with autoscale.`}
</MessageBar>
)}
{this.props.getThroughputWarningMessage() && (

View File

@@ -187,9 +187,8 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
content={
showFreeTierExceedThroughputTooltip &&
throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs400
? "The first 400 RU/s in this account are free. Billing will apply to any throughput beyond 400 RU/s."
showFreeTierExceedThroughputTooltip && throughput > SharedConstants.FreeTierLimits.RU
? `The first ${SharedConstants.FreeTierLimits.RU} RU/s in this account are free. Billing will apply to any throughput beyond ${SharedConstants.FreeTierLimits.RU} RU/s.`
: undefined
}
>

View File

@@ -1,13 +1,13 @@
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutoscaleV3.html";
import { KeyCodes } from "../../../Common/Constants";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import { userContext } from "../../../UserContext";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as ViewModels from "../../../Contracts/ViewModels";
import { FreeTierLimits } from "../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import ThroughputInputComponentAutoscaleV3 from "./ThroughputInputComponentAutoscaleV3.html";
/**
* Throughput Input:
*
@@ -236,11 +236,11 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
this.freeTierExceedThroughputTooltip = options.freeTierExceedThroughputTooltip || ko.observable<string>();
this.freeTierExceedThroughputWarning = options.freeTierExceedThroughputWarning || ko.observable<string>();
this.showFreeTierExceedThroughputTooltip = ko.pureComputed<boolean>(
() => !!this.freeTierExceedThroughputTooltip() && this.value() > 400
() => !!this.freeTierExceedThroughputTooltip() && this.value() > FreeTierLimits.RU
);
this.showFreeTierExceedThroughputWarning = ko.pureComputed<boolean>(
() => !!this.freeTierExceedThroughputWarning() && this.value() > 400
() => !!this.freeTierExceedThroughputWarning() && this.value() > FreeTierLimits.RU
);
}

View File

@@ -12,7 +12,7 @@ export interface GremlinSimpleClientParameters {
password: string;
successCallback: (result: Result) => void;
progressCallback: (result: Result) => void;
failureCallback: (result: Result, error: string) => void;
failureCallback: (result: Result | null, error: string) => void;
infoCallback: (msg: string) => void;
}
@@ -62,7 +62,6 @@ export class GremlinSimpleClient {
private static readonly requestChargeHeader = "x-ms-request-charge";
public params: GremlinSimpleClientParameters;
private protocols: string | string[];
private ws: WebSocket;
public requestsToSend: { [requestId: string]: GremlinRequestMessage };
@@ -72,6 +71,7 @@ export class GremlinSimpleClient {
this.params = params;
this.pendingRequests = {};
this.requestsToSend = {};
this.ws = GremlinSimpleClient.createWebSocket(this.params.endpoint);
}
public connect() {
@@ -117,7 +117,7 @@ export class GremlinSimpleClient {
}
}
public decodeMessage(msg: MessageEvent): GremlinResponseMessage {
public decodeMessage(msg: MessageEvent): GremlinResponseMessage | null {
if (msg.data === null) {
return null;
}
@@ -280,7 +280,7 @@ export class GremlinSimpleClient {
public static utf8ToB64(utf8Str: string) {
return btoa(
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
encodeURIComponent(utf8Str).replace(/%([0-9A-F]{2})/g, function (_match, p1) {
return String.fromCharCode(parseInt(p1, 16));
})
);
@@ -305,7 +305,7 @@ export class GremlinSimpleClient {
return binaryMessage;
}
private onOpen(event: any) {
private onOpen(_event: any) {
this.executeRequestsToSend();
}

View File

@@ -1,12 +1,11 @@
/* eslint jsx-a11y/no-static-element-interactions: 0 */
/* eslint jsx-a11y/click-events-have-key-events: 0 */
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { actions, selectors, ContentRef, AppState } from "@nteract/core";
interface ComponentProps {
id: string;
contentRef: ContentRef;
@@ -70,7 +69,7 @@ export class HijackScroll extends React.Component<Props> {
}
}
const makeMapStateToProps = (initialState: AppState, ownProps: ComponentProps) => {
const makeMapStateToProps = (_initialState: AppState, ownProps: ComponentProps) => {
const mapStateToProps = (state: AppState) => {
const { id, contentRef } = ownProps;
const model = selectors.model(state, { contentRef });
@@ -87,7 +86,7 @@ const makeMapStateToProps = (initialState: AppState, ownProps: ComponentProps) =
return mapStateToProps;
};
const makeMapDispatchToProps = (initialDispatch: Dispatch, ownProps: ComponentProps) => {
const makeMapDispatchToProps = (_initialDispatch: Dispatch, ownProps: ComponentProps) => {
const mapDispatchToProps = (dispatch: Dispatch) => ({
selectCell: () => dispatch(actions.focusCell({ id: ownProps.id, contentRef: ownProps.contentRef })),
});

View File

@@ -1,11 +1,10 @@
import { CellId } from "@nteract/commutable";
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
import Immutable from "immutable";
import React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { CellId } from "@nteract/commutable";
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
interface ComponentProps {
contentRef: ContentRef;
children: React.ReactNode;
@@ -107,7 +106,7 @@ export class KeyboardShortcuts extends React.Component<Props> {
}
}
export const makeMapStateToProps = (state: AppState, ownProps: ComponentProps) => {
export const makeMapStateToProps = (_state: AppState, ownProps: ComponentProps) => {
const { contentRef } = ownProps;
const mapStateToProps = (state: AppState) => {
const model = selectors.model(state, { contentRef });

View File

@@ -36,7 +36,7 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
{message}
{link && linkText && (
<Link target="_blank" href={link}>
{linkText}
{" " + linkText}
</Link>
)}
</Text>

View File

@@ -17,6 +17,8 @@ import * as _ from "underscore";
import { sendMessage } from "../Common/MessageHandler";
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import { trace } from "../Shared/Telemetry/TelemetryProcessor";
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
import {
AnyDisplay,
@@ -27,6 +29,7 @@ import {
Node,
NumberInput,
RefreshResult,
SelfServeComponentTelemetryType,
SelfServeDescriptor,
SmartUiInput,
StringInput,
@@ -87,6 +90,12 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
}
});
this.initializeSmartUiComponent();
const telemetryData = {
selfServeClassName: this.props.descriptor.root.id,
eventType: SelfServeComponentTelemetryType.Load,
};
trace(Action.SelfServeComponent, ActionModifiers.Mark, telemetryData, SelfServeMessageTypes.TelemetryInfo);
}
constructor(props: SelfServeComponentProps) {
@@ -262,6 +271,12 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
};
public performSave = async (): Promise<void> => {
const telemetryData = {
selfServeClassName: this.props.descriptor.root.id,
eventType: SelfServeComponentTelemetryType.Save,
};
trace(Action.SelfServeComponent, ActionModifiers.Mark, telemetryData, SelfServeMessageTypes.TelemetryInfo);
this.setState({ isSaving: true, notification: undefined });
try {
const onSaveResult = await this.props.descriptor.onSave(

View File

@@ -70,6 +70,12 @@ export interface SelfServeDescriptor {
refreshParams?: RefreshParams;
}
/**@internal */
export enum SelfServeComponentTelemetryType {
Load = "Load",
Save = "Save",
}
/**@internal */
export type AnyDisplay = NumberInput | BooleanInput | StringInput | ChoiceInput | DescriptionDisplay;

View File

@@ -201,3 +201,8 @@ export class SubscriptionUtilMappings {
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

@@ -117,6 +117,7 @@ export enum Action {
SelfServe,
ExpandAddCollectionPaneAdvancedSection,
SchemaAnalyzerClickAnalyze,
SelfServeComponent,
}
export const ActionModifiers = {

View File

@@ -267,9 +267,11 @@ export function getUpsellMessage(
if (isFreeTier) {
const collectionName = getCollectionName().toLocaleLowerCase();
const resourceType = isCollection ? collectionName : "database";
const freeTierMaxRU = Constants.FreeTierLimits.RU;
const freeTierMaxStorage = Constants.FreeTierLimits.Storage;
return isFirstResourceCreated
? `The free tier discount of 400 RU/s has already been applied to a database or ${collectionName} in this account. Billing will apply to this ${resourceType} after it is created.`
: `With free tier, you'll get the first 400 RU/s and 5 GB of storage in this account for free. Billing will apply if you provision more than 400 RU/s of manual throughput, or if the ${resourceType} scales beyond 400 RU/s with autoscale.`;
? `Your account currently has at least 1 database or ${collectionName} with provisioned RU/s. Billing will apply to this ${resourceType} if the total RU/s in your account exceeds ${freeTierMaxRU} RU/s.`
: `With free tier, you'll get the first ${freeTierMaxRU} RU/s and ${freeTierMaxStorage} GB of storage in this account for free. Billing will apply if you provision more than ${freeTierMaxRU} RU/s of manual throughput, or if the ${resourceType} scales beyond ${freeTierMaxRU} RU/s with autoscale.`;
} else {
let price: number = Constants.OfferPricing.MonthlyPricing.default.Standard.StartingPrice;

View File

@@ -4,8 +4,8 @@ import { ConfigContext, initializeConfiguration } from "../ConfigContext";
// This hook initializes global configuration from a config.json file that is injected at deploy time
// This allows the same main Data Explorer build to be exactly the same in all clouds/platforms,
// but override some of the configuration as nesssary
export function useConfig(): Readonly<ConfigContext> {
const [state, setState] = useState<ConfigContext>();
export function useConfig(): Readonly<ConfigContext | undefined> {
const [state, setState] = useState<ConfigContext | undefined>();
useEffect(() => {
initializeConfiguration().then((response) => setState(response));

View File

@@ -8,6 +8,7 @@
"noUnusedParameters": true
},
"files": [
"src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts",
"./src/AuthType.ts",
"./src/Bindings/ReactBindingHandler.ts",
"./src/Common/ArrayHashMap.ts",
@@ -114,6 +115,7 @@
"./src/Utils/StyleUtils.ts",
"./src/Utils/WindowUtils.test.ts",
"./src/Utils/WindowUtils.ts",
"./src/hooks/useConfig.ts",
"./src/hooks/useDirectories.tsx",
"./src/hooks/useFullScreenURLs.tsx",
"./src/hooks/useGraphPhoto.tsx",
@@ -121,7 +123,8 @@
"./src/i18n.ts",
"./src/quickstart.ts",
"./src/setupTests.ts",
"./src/userContext.test.ts"
"./src/userContext.test.ts",
"src/Common/EntityValue.tsx"
],
"include": [
"src/CellOutputViewer/transforms/**/*",
@@ -135,6 +138,8 @@
"src/Explorer/Controls/ResizeSensorReactComponent/**/*",
"src/Explorer/Graph/GraphExplorerComponent/__mocks__/**/*",
"src/Explorer/Notebook/NotebookComponent/__mocks__/**/*",
"src/Explorer/Notebook/NotebookRenderer/decorators/hijack-scroll/**/*",
"src/Explorer/Notebook/NotebookRenderer/decorators/kbd-shortcuts/**/*",
"src/Explorer/Panes/RightPaneForm/**/*",
"src/Libs/**/*",
"src/Localization/**/*",
@@ -144,4 +149,4 @@
"src/Terminal/**/*",
"src/Utils/arm/**/*"
]
}
}