diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index c6b5520dd..af8e34f64 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -5,6 +5,7 @@ import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent"; import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; +import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; import * as PaneComponents from "./Panes/PaneComponents"; @@ -15,10 +16,13 @@ ko.components.register("editor", new EditorComponent()); ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("dynamic-list", DynamicListComponent); +ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); // Panes ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); +ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); +ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts b/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts new file mode 100644 index 000000000..eef3c7193 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3.ts @@ -0,0 +1,308 @@ +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 { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; +/** + * Throughput Input: + * + * Creates a set of controls to input, sanitize and increase/decrease throughput + * + * How to use in your markup: + * + * + * + */ + +/** + * Parameters for this component + */ +export interface ThroughputInputParams { + /** + * Callback triggered when the template is bound to the component (for testing purposes) + */ + onTemplateReady?: () => void; + + /** + * Observable to bind the Throughput value to + */ + value: ViewModels.Editable; + + /** + * Text to use as id for testing + */ + testId: string; + + /** + * Text to use as aria-label + */ + ariaLabel?: ko.Observable; + + /** + * Minimum value in the range + */ + minimum: ko.Observable; + + /** + * Maximum value in the range + */ + maximum: ko.Observable; + + /** + * Step value for increase/decrease + */ + step?: number; + + /** + * Observable to bind the Throughput enabled status + */ + isEnabled?: ko.Observable; + + /** + * Should show pricing controls + */ + costsVisible: ko.Observable; + + /** + * RU price + */ + requestUnitsUsageCost: ko.Computed; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable + + /** + * State of the spending acknowledge checkbox + */ + spendAckChecked?: ko.Observable; + + /** + * id of the spending acknowledge checkbox + */ + spendAckId?: ko.Observable; + + /** + * spending acknowledge text + */ + spendAckText?: ko.Observable; + + /** + * Show spending acknowledge controls + */ + spendAckVisible?: ko.Observable; + + /** + * Display * to the left of the label + */ + showAsMandatory: boolean; + + /** + * If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed + */ + isFixed: boolean; + + /** + * Label of the provisioned throughut control + */ + label: ko.Observable; + + /** + * Text of the info bubble for provisioned throughut control + */ + infoBubbleText?: ko.Observable; + + /** + * Computed value that decides if value can exceed maximum allowable value + */ + canExceedMaximumValue?: ko.Computed; + + /** + * CSS classes to apply on input element + */ + cssClass?: string; + + isAutoPilotSelected: ko.Observable; + throughputAutoPilotRadioId: string; + throughputProvisionedRadioId: string; + throughputModeRadioName: string; + maxAutoPilotThroughputSet: ViewModels.Editable; + autoPilotUsageCost: ko.Computed; + overrideWithAutoPilotSettings: ko.Observable; + overrideWithProvisionedThroughputSettings: ko.Observable; + freeTierExceedThroughputTooltip?: ko.Observable; + freeTierExceedThroughputWarning?: ko.Observable; +} + +export class ThroughputInputViewModel extends WaitsForTemplateViewModel { + public ariaLabel: ko.Observable; + public canExceedMaximumValue: ko.Computed; + public step: ko.Computed; + public testId: string; + public value: ViewModels.Editable; + public minimum: ko.Observable; + public maximum: ko.Observable; + public isEnabled: ko.Observable; + public cssClass: string; + public decreaseButtonAriaLabel: string; + public increaseButtonAriaLabel: string; + public costsVisible: ko.Observable; + public requestUnitsUsageCost: ko.Computed; + public spendAckChecked: ko.Observable; + public spendAckId: ko.Observable; + public spendAckText: ko.Observable; + public spendAckVisible: ko.Observable; + public showAsMandatory: boolean; + public infoBubbleText: string | ko.Observable; + public label: ko.Observable; + public isFixed: boolean; + public isAutoPilotSelected: ko.Observable; + public throughputAutoPilotRadioId: string; + public throughputProvisionedRadioId: string; + public throughputModeRadioName: string; + public maxAutoPilotThroughputSet: ko.Observable; + public autoPilotUsageCost: ko.Computed; + public minAutoPilotThroughput: ko.Observable; + public overrideWithAutoPilotSettings: ko.Observable; + public overrideWithProvisionedThroughputSettings: ko.Observable; + public isManualThroughputInputFieldRequired: ko.Computed; + public isAutoscaleThroughputInputFieldRequired: ko.Computed; + public freeTierExceedThroughputTooltip: ko.Observable; + public freeTierExceedThroughputWarning: ko.Observable; + public showFreeTierExceedThroughputTooltip: ko.Computed; + public showFreeTierExceedThroughputWarning: ko.Computed; + + public constructor(options: ThroughputInputParams) { + super(); + super.onTemplateReady((isTemplateReady: boolean) => { + if (isTemplateReady && options.onTemplateReady) { + options.onTemplateReady(); + } + }); + + const params: ThroughputInputParams = options; + this.testId = params.testId || "ThroughputValue"; + this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || ""); + this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false); + this.isEnabled = params.isEnabled || ko.observable(true); + this.cssClass = params.cssClass || "textfontclr collid migration"; + this.minimum = params.minimum; + this.maximum = params.maximum; + this.value = params.value; + this.costsVisible = options.costsVisible; + this.requestUnitsUsageCost = options.requestUnitsUsageCost; + this.spendAckChecked = options.spendAckChecked || ko.observable(false); + this.spendAckId = options.spendAckId || ko.observable(); + this.spendAckText = options.spendAckText || ko.observable(); + this.spendAckVisible = options.spendAckVisible || ko.observable(false); + this.showAsMandatory = !!options.showAsMandatory; + this.isFixed = !!options.isFixed; + this.infoBubbleText = options.infoBubbleText || ko.observable(); + this.label = options.label || ko.observable(); + this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable(false); + this.isAutoPilotSelected.subscribe((value) => { + TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, { + changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff, + dataExplorerArea: "Scale Tab V1", + }); + }); + + this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId; + this.throughputProvisionedRadioId = options.throughputProvisionedRadioId; + this.throughputModeRadioName = options.throughputModeRadioName; + this.overrideWithAutoPilotSettings = options.overrideWithAutoPilotSettings || ko.observable(false); + this.overrideWithProvisionedThroughputSettings = + options.overrideWithProvisionedThroughputSettings || ko.observable(false); + + this.maxAutoPilotThroughputSet = + options.maxAutoPilotThroughputSet || ko.observable(AutoPilotUtils.minAutoPilotThroughput); + this.autoPilotUsageCost = options.autoPilotUsageCost; + this.minAutoPilotThroughput = ko.observable(AutoPilotUtils.minAutoPilotThroughput); + + this.step = ko.pureComputed(() => { + if (this.isAutoPilotSelected()) { + return AutoPilotUtils.autoPilotIncrementStep; + } + return params.step || ThroughputInputViewModel._defaultStep; + }); + this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step().toString(); + this.increaseButtonAriaLabel = "Increase throughput by " + this.step().toString(); + this.isManualThroughputInputFieldRequired = ko.pureComputed(() => this.isEnabled() && !this.isAutoPilotSelected()); + this.isAutoscaleThroughputInputFieldRequired = ko.pureComputed( + () => this.isEnabled() && this.isAutoPilotSelected() + ); + + this.freeTierExceedThroughputTooltip = options.freeTierExceedThroughputTooltip || ko.observable(); + this.freeTierExceedThroughputWarning = options.freeTierExceedThroughputWarning || ko.observable(); + this.showFreeTierExceedThroughputTooltip = ko.pureComputed( + () => !!this.freeTierExceedThroughputTooltip() && this.value() > 400 + ); + + this.showFreeTierExceedThroughputWarning = ko.pureComputed( + () => !!this.freeTierExceedThroughputWarning() && this.value() > 400 + ); + } + + public decreaseThroughput() { + let offerThroughput: number = this._getSanitizedValue(); + + if (offerThroughput > this.minimum()) { + offerThroughput -= this.step(); + if (offerThroughput < this.minimum()) { + offerThroughput = this.minimum(); + } + + this.value(offerThroughput); + } + } + + public increaseThroughput() { + let offerThroughput: number = this._getSanitizedValue(); + + if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) { + offerThroughput += this.step(); + if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) { + offerThroughput = this.maximum(); + } + + this.value(offerThroughput); + } + } + + public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => { + if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { + this.increaseThroughput(); + event.stopPropagation(); + return false; + } + + return true; + }; + + public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => { + if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) { + this.decreaseThroughput(); + event.stopPropagation(); + return false; + } + + return true; + }; + + private _getSanitizedValue(): number { + let throughput = this.value(); + + if (this.isAutoPilotSelected()) { + throughput = this.maxAutoPilotThroughputSet(); + } + return isNaN(throughput) ? 0 : Number(throughput); + } + + private static _defaultStep: number = 100; +} + +export const ThroughputInputComponentAutoPilotV3 = { + viewModel: ThroughputInputViewModel, + template: ThroughputInputComponentAutoscaleV3, +}; diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoscaleV3.html b/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoscaleV3.html new file mode 100644 index 000000000..b8ad196b2 --- /dev/null +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInputComponentAutoscaleV3.html @@ -0,0 +1,194 @@ +
+
+

+ + * + + + + + + + More information + + + +

+
+ + +
+ + Autoscale + + + + Manual + +
+ + +
+

+ Provision maximum RU/s required by this resource. Estimate your required RU/s with + capacity calculator. +

+

+ Max RU/s +

+
+ +
+

+ +

+

+ +

+ + +

+ + +

+ + + +

Choose unlimited storage capacity for more than 10,000 RU/s.

+ +
+ +
+

+ Estimate your required throughput with + capacity calculator +

+ +
+ +
+ +
+ +
+ +
+ Warning + +
+ +

+ +

+ + +

+ + +

+ + + +

Choose unlimited storage capacity for more than 10,000 RU/s.

+ +
+
diff --git a/src/Explorer/Panes/PaneComponents.ts b/src/Explorer/Panes/PaneComponents.ts index 34ff76ed3..708f24b4b 100644 --- a/src/Explorer/Panes/PaneComponents.ts +++ b/src/Explorer/Panes/PaneComponents.ts @@ -2,6 +2,8 @@ import AddCollectionPaneTemplate from "./AddCollectionPane.html"; import AddDatabasePaneTemplate from "./AddDatabasePane.html"; import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html"; import GraphStylingPaneTemplate from "./GraphStylingPane.html"; +import TableAddEntityPaneTemplate from "./Tables/TableAddEntityPane.html"; +import TableEditEntityPaneTemplate from "./Tables/TableEditEntityPane.html"; export class PaneComponent { constructor(data: any) { @@ -35,6 +37,23 @@ export class GraphStylingPaneComponent { } } +export class TableAddEntityPaneComponent { + constructor() { + return { + viewModel: PaneComponent, + template: TableAddEntityPaneTemplate, + }; + } +} + +export class TableEditEntityPaneComponent { + constructor() { + return { + viewModel: PaneComponent, + template: TableEditEntityPaneTemplate, + }; + } +} export class CassandraAddCollectionPaneComponent { constructor() { return {