Migrate Cassandra Add Container to React (#723)

This commit is contained in:
Hardikkumar Nai 2021-05-19 08:00:11 +05:30 committed by GitHub
parent 2bc298fef1
commit 030a4dec3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 645 additions and 1194 deletions

View File

@ -111,13 +111,9 @@ src/Explorer/OpenActionsStubs.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
# src/Explorer/Panes/GraphStylingPane.ts
# src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts

View File

@ -4,13 +4,9 @@ import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponen
import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import * as PaneComponents from "./Panes/PaneComponents";
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("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());

View File

@ -38,51 +38,6 @@ exports[`SettingsComponent renders 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
@ -978,51 +933,6 @@ exports[`SettingsComponent renders 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
@ -1931,51 +1841,6 @@ exports[`SettingsComponent renders 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,
@ -2871,51 +2736,6 @@ exports[`SettingsComponent renders 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,

View File

@ -54,7 +54,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
@ -148,7 +148,6 @@ export default class Explorer {
public tabsManager: TabsManager;
// Contextual panes
public cassandraAddCollectionPane: CassandraAddCollectionPane;
private gitHubClient: GitHubClient;
public gitHubOAuthService: GitHubOAuthService;
public junoClient: JunoClient;
@ -398,13 +397,6 @@ export default class Explorer {
}
});
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
id: "cassandraaddcollectionpane",
visible: ko.observable<boolean>(false),
container: this,
});
this.tabsManager = params?.tabsManager ?? new TabsManager();
this.tabsManager.openedTabs.subscribe((tabs) => {
if (tabs.length === 0) {
@ -1138,7 +1130,10 @@ export default class Explorer {
private getDeltaDatabases(
updatedDatabaseList: DataModels.Database[]
): { toAdd: ViewModels.Database[]; toDelete: ViewModels.Database[] } {
): {
toAdd: ViewModels.Database[];
toDelete: ViewModels.Database[];
} {
const newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
const databaseExists = _.some(
this.databases(),
@ -1791,7 +1786,7 @@ export default class Explorer {
public onNewCollectionClicked(databaseId?: string): void {
if (userContext.apiType === "Cassandra") {
this.cassandraAddCollectionPane.open();
this.openCassandraAddCollectionPane();
} else {
this.openAddCollectionPanel(databaseId);
}
@ -1983,6 +1978,16 @@ export default class Explorer {
);
}
public openCassandraAddCollectionPane(): void {
this.openSidePanel(
"Add Table",
<CassandraAddCollectionPane
explorer={this}
closePanel={() => this.closeSidePanel()}
cassandraApiClient={new CassandraAPIDataClient()}
/>
);
}
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
this.openSidePanel(
header,

View File

@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "./Explorer";
import { handleOpenAction } from "./OpenActions";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
describe("OpenActions", () => {
describe("handleOpenAction", () => {
@ -15,8 +14,6 @@ describe("OpenActions", () => {
beforeEach(() => {
explorer = {} as Explorer;
explorer.onNewCollectionClicked = jest.fn();
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
explorer.cassandraAddCollectionPane.open = jest.fn();
database = {
id: ko.observable("db"),
@ -64,28 +61,6 @@ describe("OpenActions", () => {
expect(actionHandled).toBe(true);
});
describe("CassandraAddCollection pane kind", () => {
it("string value should call cassandraAddCollectionPane.open", () => {
const action = {
actionType: "OpenPane",
paneKind: "CassandraAddCollection",
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
});
it("enum value should call cassandraAddCollectionPane.open", () => {
const action = {
actionType: "OpenPane",
paneKind: ActionContracts.PaneKind.CassandraAddCollection,
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
});
});
describe("AddCollection pane kind", () => {
it("string value should call explorer.onNewCollectionClicked", () => {
const action = {

View File

@ -145,7 +145,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
) {
explorer.cassandraAddCollectionPane.open();
explorer.openCassandraAddCollectionPane();
} else if (
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]

View File

@ -1,273 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div
class="contextual-pane-out"
data-bind="
click: cancel,
clickBubble: false"
></div>
<div class="contextual-pane" id="cassandraaddcollectionpane">
<!-- Add Cassandra collection form - Start -->
<div class="contextual-pane-in">
<form
class="paneContentContainer"
role="dialog"
aria-label="Add Table"
data-bind="
submit: submit"
>
<!-- Add Cassandra collection header - Start -->
<div class="firstdivbg headerline">
<span role="heading" aria-level="2" data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="
click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Add Cassandra collection header - End -->
<!-- Add Cassandra collection errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error" /></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
</span>
</div>
</div>
<!-- Add Cassandra collection errors - End -->
<div class="paneMainContent">
<div class="seconddivpadding">
<p>
<span class="mandatoryStar">*</span> Keyspace name
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="tooltiptext infoTooltipWidth"
>Select an existing keyspace or enter a new keyspace id.</span
>
</span>
</p>
<div class="createNewDatabaseOrUseExisting">
<input
class="createNewDatabaseOrUseExistingRadio"
aria-label="Create new keyspace"
name="databaseType"
type="radio"
role="radio"
id="keyspaceCreateNew"
data-test="addCollection-newDatabase"
tabindex="0"
data-bind="checked: keyspaceCreateNew, checkedValue: true, attr: { 'aria-checked': keyspaceCreateNew() ? 'true' : 'false' }"
/>
<span class="createNewDatabaseOrUseExistingSpace" for="keyspaceCreateNew">Create new</span>
<input
class="createNewDatabaseOrUseExistingRadio"
aria-label="Use existing keyspace"
name="databaseType"
type="radio"
role="radio"
id="keyspaceUseExisting"
data-test="addCollection-existingDatabase"
tabindex="0"
data-bind="checked: keyspaceCreateNew, checkedValue: false, attr: { 'aria-checked': !keyspaceCreateNew() ? 'true' : 'false' }"
/>
<span class="createNewDatabaseOrUseExistingSpace" for="keyspaceUseExisting">Use existing</span>
</div>
<input
id="keyspace-id"
data-test="addCollection-keyspaceId"
type="text"
autocomplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Type a new keyspace id"
size="40"
class="collid"
data-bind="visible: keyspaceCreateNew, textInput: keyspaceId, hasFocus: firstFieldHasFocus"
aria-label="Keyspace id"
aria-required="true"
autofocus
/>
<input
type="text"
aria-required="true"
autocomplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
list="keyspacesList"
placeholder="Choose existing keyspace id"
size="40"
class="collid"
data-bind="visible: !keyspaceCreateNew(), textInput: keyspaceId, hasFocus: firstFieldHasFocus"
aria-label="Keyspace id"
/>
<datalist id="keyspacesList" data-bind="foreach: container.databases">
<option data-bind="value: $data.id"></option>
</datalist>
<!-- Database provisioned throughput - Start -->
<!-- ko if: canConfigureThroughput -->
<div
class="databaseProvision"
aria-label="New database provision support"
data-bind="visible: keyspaceCreateNew"
>
<input
tabindex="0"
type="checkbox"
id="keyspaceSharedThroughput"
title="Provision shared throughput"
data-bind="checked: keyspaceHasSharedOffer"
/>
<span class="databaseProvisionText" for="keyspaceSharedThroughput">Provision keyspace throughput</span>
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="tooltiptext provisionDatabaseThroughput"
>Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
the keyspace</span
>
</span>
</div>
<!-- 1 -->
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
<throughput-input-autopilot-v3
params="{
testId: 'cassandraThroughputValue-v3-shared',
value: keyspaceThroughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: keyspaceCreateNew() && keyspaceHasSharedOffer(),
label: sharedThroughputRangeText,
ariaLabel: sharedThroughputRangeText,
requestUnitsUsageCost: requestUnitsUsageCostShared,
spendAckChecked: sharedThroughputSpendAck,
spendAckId: 'sharedThroughputSpendAck-v3-shared',
spendAckText: sharedThroughputSpendAckText,
spendAckVisible: sharedThroughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-databaseThroughput-autoPilotRadio-v3-shared',
throughputProvisionedRadioId: 'newKeyspace-databaseThroughput-manualRadio-v3-shared',
isAutoPilotSelected: isSharedAutoPilotSelected,
maxAutoPilotThroughputSet: sharedAutoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
costsVisible: costsVisible,
}"
>
</throughput-input-autopilot-v3>
</div>
<!-- /ko -->
<!-- Database provisioned throughput - End -->
</div>
<div class="seconddivpadding">
<p>
<span class="mandatoryStar">*</span> Enter CQL command to create the table.
<a href="https://aka.ms/cassandra-create-table" target="_blank">Learn More</a>
</p>
<div data-bind="text: createTableQuery" style="float: left; padding-top: 3px; padding-right: 3px"></div>
<input
type="text"
data-test="addCollection-tableId"
aria-required="true"
autocomplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
data-test="addCollection-tableId"
placeholder="Enter tableId"
size="20"
class="textfontclr"
data-bind="value: tableId"
style="margin-bottom: 5px"
/>
<textarea
id="editor-area"
rows="15"
aria-label="Table Schema"
data-bind="value: userTableQuery"
style="height: 125px; width: calc(100% - 80px); resize: vertical"
></textarea>
</div>
<!-- Provision table throughput - start -->
<!-- ko if: canConfigureThroughput -->
<div class="seconddivpadding" data-bind="visible: keyspaceHasSharedOffer() && !keyspaceCreateNew()">
<input
type="checkbox"
id="tableSharedThroughput"
title="Provision dedicated throughput for this table"
data-bind="checked: dedicateTableThroughput"
/>
<span for="tableSharedThroughput">Provision dedicated throughput for this table</span>
<span class="leftAlignInfoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information" />
<span class="tooltiptext sharedCollectionThroughputTooltipWidth"
>You can optionally provision dedicated throughput for a table within a keyspace that has throughput
provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
billed in addition to the throughput amount you provisioned at the keyspace level.</span
>
</span>
</div>
<!-- 2 -->
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
<throughput-input-autopilot-v3
params="{
testId: 'cassandraSharedThroughputValue-v3-dedicated',
value: throughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: !keyspaceHasSharedOffer() || dedicateTableThroughput(),
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCostDedicated,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckCassandra-v3-dedicated',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-containerThroughput-autoPilotRadio-v3-dedicated',
throughputProvisionedRadioId: 'newKeyspace-containerThroughput-manualRadio-v3-dedicated',
isAutoPilotSelected: isAutoPilotSelected,
maxAutoPilotThroughputSet: selectedAutoPilotThroughput,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
overrideWithAutoPilotSettings: false,
overrideWithProvisionedThroughputSettings: false
}"
>
</throughput-input-autopilot-v3>
</div>
<!-- /ko -->
<!-- Provision table throughput - end -->
</div>
<div class="paneFooter">
<div class="leftpanel-okbut">
<input type="submit" data-test="addCollection-createCollection" value="OK" class="btncreatecoll1" />
</div>
</div>
</form>
<!-- Add Cassandra collection form - End -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" alt="loading indicator" />
</div>
<!-- Loader - End -->
</div>
</div>
</div>

View File

@ -1,539 +0,0 @@
import * as ko from "knockout";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
import { configContext, Platform } from "../../ConfigContext";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../Shared/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../Utils/PricingUtils";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable<string>;
public keyspaceId: ko.Observable<string>;
public maxThroughputRU: ko.Observable<number>;
public minThroughputRU: ko.Observable<number>;
public tableId: ko.Observable<string>;
public throughput: ko.Observable<number>;
public throughputRangeText: ko.Computed<string>;
public sharedThroughputRangeText: ko.Computed<string>;
public userTableQuery: ko.Observable<string>;
public requestUnitsUsageCostDedicated: ko.Computed<string>;
public requestUnitsUsageCostShared: ko.Computed<string>;
public costsVisible: ko.PureComputed<boolean>;
public keyspaceHasSharedOffer: ko.Observable<boolean>;
public keyspaceIds: ko.ObservableArray<string>;
public keyspaceThroughput: ko.Observable<number>;
public keyspaceCreateNew: ko.Observable<boolean>;
public dedicateTableThroughput: ko.Observable<boolean>;
public canRequestSupport: ko.PureComputed<boolean>;
public throughputSpendAckText: ko.Observable<string>;
public throughputSpendAck: ko.Observable<boolean>;
public sharedThroughputSpendAck: ko.Observable<boolean>;
public sharedThroughputSpendAckText: ko.Observable<string>;
public isAutoPilotSelected: ko.Observable<boolean>;
public isSharedAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotThroughput: ko.Observable<number>;
public sharedAutoPilotThroughput: ko.Observable<number>;
public autoPilotUsageCost: ko.Computed<string>;
public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
public throughputSpendAckVisible: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>;
public isFreeTierAccount: ko.Computed<boolean>;
public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>;
private keyspaceOffers: Map<string, DataModels.Offer>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Add Table");
this.createTableQuery = ko.observable<string>("CREATE TABLE ");
this.keyspaceCreateNew = ko.observable<boolean>(true);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText());
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.keyspaceOffers = new Map();
this.keyspaceIds = ko.observableArray<string>();
this.keyspaceHasSharedOffer = ko.observable<boolean>(false);
this.keyspaceThroughput = ko.observable<number>();
this.keyspaceId = ko.observable<string>("");
this.keyspaceId.subscribe((keyspaceId: string) => {
if (this.keyspaceIds.indexOf(keyspaceId) >= 0) {
this.keyspaceHasSharedOffer(this.keyspaceOffers.has(keyspaceId));
}
});
this.keyspaceId.extend({ rateLimit: 100 });
this.dedicateTableThroughput = ko.observable<boolean>(false);
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
this.maxThroughputRU = ko.observable<number>(throughputDefaults.unlimitedmax);
this.minThroughputRU = ko.observable<number>(throughputDefaults.unlimitedmin);
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
this.isFreeTierAccount = ko.computed<boolean>(() => {
return userContext?.databaseAccount?.properties?.enableFreeTier;
});
this.tableId = ko.observable<string>("");
this.isAutoPilotSelected = ko.observable<boolean>(false);
this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
this.selectedAutoPilotThroughput = ko.observable<number>();
this.sharedAutoPilotThroughput = ko.observable<number>();
this.throughput = ko.observable<number>();
this.throughputRangeText = ko.pureComputed<string>(() => {
const enableAutoPilot = this.isAutoPilotSelected();
if (!enableAutoPilot) {
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}
return AutoPilotUtils.getAutoPilotHeaderText();
});
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
if (this.isSharedAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText();
}
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
});
this.userTableQuery = ko.observable<string>("(userid int, name text, email text, PRIMARY KEY (userid))");
this.keyspaceId.subscribe((keyspaceId) => {
this.createTableQuery(`CREATE TABLE ${keyspaceId}.`);
});
this.throughputSpendAckText = ko.observable<string>();
this.throughputSpendAck = ko.observable<boolean>(false);
this.sharedThroughputSpendAck = ko.observable<boolean>(false);
this.sharedThroughputSpendAckText = ko.observable<string>();
this.resetData();
this.requestUnitsUsageCostDedicated = ko.computed(() => {
const { databaseAccount: account } = userContext;
if (!account) {
return "";
}
const regions =
(account &&
account.properties &&
account.properties.readLocations &&
account.properties.readLocations.length) ||
1;
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
const offerThroughput: number = this.throughput();
let estimatedSpend: string;
let estimatedDedicatedSpendAcknowledge: string;
if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput,
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.selectedAutoPilotThroughput(),
userContext.portalEnv,
regions,
multimaster
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.selectedAutoPilotThroughput(),
userContext.portalEnv,
regions,
multimaster,
this.isAutoPilotSelected()
);
}
this.throughputSpendAckText(estimatedDedicatedSpendAcknowledge);
return estimatedSpend;
});
this.requestUnitsUsageCostShared = ko.computed(() => {
const { databaseAccount: account } = userContext;
if (!account) {
return "";
}
const regions = account?.properties?.readLocations?.length || 1;
const multimaster = account?.properties?.enableMultipleWriteLocations || false;
let estimatedSpend: string;
let estimatedSharedSpendAcknowledge: string;
if (!this.isSharedAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.keyspaceThroughput(),
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.keyspaceThroughput(),
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
} else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
this.sharedAutoPilotThroughput(),
userContext.portalEnv,
regions,
multimaster
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(),
userContext.portalEnv,
regions,
multimaster,
this.isSharedAutoPilotSelected()
);
}
this.sharedThroughputSpendAckText(estimatedSharedSpendAcknowledge);
return estimatedSpend;
});
this.costsVisible = ko.pureComputed(() => {
return configContext.platform !== Platform.Emulator;
});
this.canRequestSupport = ko.pureComputed(() => {
if (configContext.platform !== Platform.Emulator && !userContext.isTryCosmosDBSubscription) {
const offerThroughput: number = this.throughput();
return offerThroughput <= 100000;
}
return false;
});
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if (this.isSharedAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
}
return this.keyspaceThroughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
});
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if (this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
}
return this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
});
if (!!this.container) {
const updateKeyspaceIds: (keyspaces: ViewModels.Database[]) => void = (
newKeyspaceIds: ViewModels.Database[]
): void => {
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
if (keyspace && keyspace.offer && !!keyspace.offer()) {
this.keyspaceOffers.set(keyspace.id(), keyspace.offer());
}
return keyspace.id();
});
this.keyspaceIds(cachedKeyspaceIdsList);
};
this.container.databases.subscribe((newDatabases: ViewModels.Database[]) => updateKeyspaceIds(newDatabases));
updateKeyspaceIds(this.container.databases());
}
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this._getAutoPilot();
if (!autoPilot) {
return "";
}
const isDatabaseThroughput: boolean = this.keyspaceCreateNew();
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput);
});
}
public decreaseThroughput() {
let offerThroughput: number = this.throughput();
if (offerThroughput > this.minThroughputRU()) {
offerThroughput -= 100;
this.throughput(offerThroughput);
}
}
public increaseThroughput() {
let offerThroughput: number = this.throughput();
if (offerThroughput < this.maxThroughputRU()) {
offerThroughput += 100;
this.throughput(offerThroughput);
}
}
public open() {
super.open();
if (!this.container.isServerlessEnabled()) {
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
}
const addCollectionPaneOpenMessage = {
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(),
partitionKey: "",
databaseId: this.keyspaceId(),
}),
subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput: this.throughput(),
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};
const focusElement = document.getElementById("keyspace-id");
focusElement && focusElement.focus();
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
}
public submit() {
if (!this._isValid()) {
return;
}
this.isExecuting(true);
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
let createTableAndKeyspacePromise: Q.Promise<any>;
const toCreateKeyspace: boolean = this.keyspaceCreateNew();
const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput();
const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
const createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
? useAutoPilotForKeyspace
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
: `${createKeyspaceQueryPrefix};`;
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
let createTableQuery: string;
if (this.canConfigureThroughput() && (this.dedicateTableThroughput() || !this.keyspaceHasSharedOffer())) {
if (this.isAutoPilotSelected() && this.selectedAutoPilotThroughput()) {
createTableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${this.selectedAutoPilotThroughput()};`;
} else {
createTableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${this.throughput()};`;
}
} else {
createTableQuery = `${createTableQueryPrefix};`;
}
const addCollectionPaneStartMessage = {
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(),
partitionKey: "",
databaseId: this.keyspaceId(),
hasDedicatedThroughput: this.dedicateTableThroughput(),
}),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput: this.throughput(),
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
toCreateKeyspace: toCreateKeyspace,
createKeyspaceQuery: createKeyspaceQuery,
createTableQuery: createTableQuery,
};
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
const { databaseAccount } = userContext;
if (toCreateKeyspace) {
createTableAndKeyspacePromise = (<CassandraAPIDataClient>this.container.tableDataClient).createTableAndKeyspace(
databaseAccount?.properties.cassandraEndpoint,
databaseAccount?.id,
this.container,
createTableQuery,
createKeyspaceQuery
);
} else {
createTableAndKeyspacePromise = (<CassandraAPIDataClient>this.container.tableDataClient).createTableAndKeyspace(
databaseAccount?.properties.cassandraEndpoint,
databaseAccount?.id,
this.container,
createTableQuery
);
}
createTableAndKeyspacePromise.then(
() => {
this.container.refreshAllDatabases();
this.isExecuting(false);
this.close();
const addCollectionPaneSuccessMessage = {
collection: ko.toJS({
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(),
partitionKey: "",
databaseId: this.keyspaceId(),
hasDedicatedThroughput: this.dedicateTableThroughput(),
}),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput: this.throughput(),
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
toCreateKeyspace: toCreateKeyspace,
createKeyspaceQuery: createKeyspaceQuery,
createTableQuery: createTableQuery,
};
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
},
(error) => {
const errorMessage = getErrorMessage(error);
this.formErrors(errorMessage);
this.isExecuting(false);
const addCollectionPaneFailedMessage = {
collection: {
id: this.tableId(),
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(),
partitionKey: "",
databaseId: this.keyspaceId(),
hasDedicatedThroughput: this.dedicateTableThroughput(),
},
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput: this.throughput(),
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
toCreateKeyspace: toCreateKeyspace,
createKeyspaceQuery: createKeyspaceQuery,
createTableQuery: createTableQuery,
error: errorMessage,
errorStack: getErrorStack(error),
};
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
}
);
}
public resetData() {
super.resetData();
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
this.keyspaceThroughput(throughputDefaults.shared);
this.maxThroughputRU(throughputDefaults.unlimitedmax);
this.minThroughputRU(throughputDefaults.unlimitedmin);
this.createTableQuery("CREATE TABLE ");
this.userTableQuery("(userid int, name text, email text, PRIMARY KEY (userid))");
this.tableId("");
this.keyspaceId("");
this.throughputSpendAck(false);
this.keyspaceHasSharedOffer(false);
this.keyspaceCreateNew(true);
}
private _isValid(): boolean {
const throughput = this.throughput();
const keyspaceThroughput = this.keyspaceThroughput();
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if (
this.isSharedAutoPilotSelected() &&
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.sharedThroughputSpendAck()
) {
this.formErrors(`Please acknowledge the estimated monthly spend.`);
return false;
}
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if (
this.isAutoPilotSelected() &&
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck()
) {
this.formErrors(`Please acknowledge the estimated monthly spend.`);
return false;
}
if (
(this.keyspaceCreateNew() && this.keyspaceHasSharedOffer() && this.isSharedAutoPilotSelected()) ||
this.isAutoPilotSelected()
) {
const autoPilot = this._getAutoPilot();
if (
!autoPilot ||
!autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
) {
this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
);
return false;
}
return true;
}
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
this.formErrors(`Please acknowledge the estimated daily spend.`);
return false;
}
if (
this.keyspaceHasSharedOffer() &&
this.keyspaceCreateNew() &&
keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.sharedThroughputSpendAck()
) {
this.formErrors("Please acknowledge the estimated daily spend");
return false;
}
return true;
}
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
if (
this.keyspaceCreateNew() &&
this.keyspaceHasSharedOffer() &&
this.isSharedAutoPilotSelected() &&
this.sharedAutoPilotThroughput()
) {
return {
maxThroughput: this.sharedAutoPilotThroughput() * 1,
};
}
if (this.selectedAutoPilotThroughput()) {
return {
maxThroughput: this.selectedAutoPilotThroughput() * 1,
};
}
return undefined;
}
}

View File

@ -0,0 +1,32 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { shallow } from "enzyme";
import React from "react";
import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane";
const props = {
explorer: new Explorer(),
closePanel: (): void => undefined,
cassandraApiClient: new CassandraAPIDataClient(),
};
describe("CassandraAddCollectionPane Pane", () => {
beforeEach(() => render(<CassandraAddCollectionPane {...props} />));
it("should render Default properly", () => {
const wrapper = shallow(<CassandraAddCollectionPane {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("click on is Create new keyspace", () => {
fireEvent.click(screen.getByLabelText("Create new keyspace"));
expect(screen.getByLabelText("Provision keyspace throughput")).toBeDefined();
});
it("click on Use existing", () => {
fireEvent.click(screen.getByLabelText("Use existing keyspace"));
});
it("Enter Keyspace name ", () => {
fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } });
expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined();
});
});

View File

@ -0,0 +1,427 @@
import { Label, Stack, TextField } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react";
import * as _ from "underscore";
import * as Constants from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface CassandraAddCollectionPaneProps {
explorer: Explorer;
closePanel: () => void;
cassandraApiClient: CassandraAPIDataClient;
}
export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectionPaneProps> = ({
explorer: container,
closePanel,
cassandraApiClient,
}: CassandraAddCollectionPaneProps) => {
const throughputDefaults = container.collectionCreationDefaults.throughput;
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE ");
const [keyspaceId, setKeyspaceId] = useState<string>("");
const [tableId, setTableId] = useState<string>("");
const [throughput, setThroughput] = useState<number>(
AddCollectionUtility.getMaxThroughput(container.collectionCreationDefaults, container)
);
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(container.isAutoscaleDefaultEnabled());
const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState<boolean>(
container.isAutoscaleDefaultEnabled()
);
const [userTableQuery, setUserTableQuery] = useState<string>(
"(userid int, name text, email text, PRIMARY KEY (userid))"
);
const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState<boolean>(false);
const [keyspaceIds, setKeyspaceIds] = useState<string[]>([]);
const [keyspaceThroughput, setKeyspaceThroughput] = useState<number>(throughputDefaults.shared);
const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true);
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState<boolean>(false);
const { minAutoPilotThroughput: selectedAutoPilotThroughput } = AutoPilotUtils;
const { minAutoPilotThroughput: sharedAutoPilotThroughput } = AutoPilotUtils;
const _getAutoPilot = (): DataModels.AutoPilotCreationSettings => {
if (keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected && sharedAutoPilotThroughput) {
return {
maxThroughput: sharedAutoPilotThroughput * 1,
};
}
if (selectedAutoPilotThroughput) {
return {
maxThroughput: selectedAutoPilotThroughput * 1,
};
}
return undefined;
};
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
const canConfigureThroughput = !container.isServerlessEnabled();
const keyspaceOffers = new Map();
const [isExecuting, setIsExecuting] = useState<boolean>();
const [formErrors, setFormErrors] = useState<string>("");
useEffect(() => {
if (keyspaceIds.indexOf(keyspaceId) >= 0) {
setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId));
}
setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`);
}, [keyspaceId]);
const addCollectionPaneOpenMessage = {
collection: {
id: tableId,
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: throughput,
partitionKey: "",
databaseId: keyspaceId,
},
subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput,
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};
useEffect(() => {
if (!container.isServerlessEnabled()) {
setIsAutoPilotSelected(container.isAutoscaleDefaultEnabled());
}
TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage);
}, []);
useEffect(() => {
if (container) {
const newKeyspaceIds: ViewModels.Database[] = container.databases();
const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => {
if (keyspace && keyspace.offer && !!keyspace.offer()) {
keyspaceOffers.set(keyspace.id(), keyspace.offer());
}
return keyspace.id();
});
setKeyspaceIds(cachedKeyspaceIdsList);
}
}, []);
const _isValid = () => {
const sharedAutoscaleThroughput = sharedAutoPilotThroughput * 1;
if (
isSharedAutoPilotSelected &&
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!sharedThroughputSpendAck
) {
setFormErrors(`Please acknowledge the estimated monthly spend.`);
return false;
}
const dedicatedAutoscaleThroughput = selectedAutoPilotThroughput * 1;
if (
isAutoPilotSelected &&
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!throughputSpendAck
) {
setFormErrors(`Please acknowledge the estimated monthly spend.`);
return false;
}
if ((keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected) || isAutoPilotSelected) {
const autoPilot = _getAutoPilot();
if (
!autoPilot ||
!autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
) {
setFormErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
);
return false;
}
return true;
}
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
setFormErrors(`Please acknowledge the estimated daily spend.`);
return false;
}
if (
keyspaceHasSharedOffer &&
keyspaceCreateNew &&
keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!sharedThroughputSpendAck
) {
setFormErrors("Please acknowledge the estimated daily spend");
return false;
}
return true;
};
const onSubmit = async () => {
if (!_isValid()) {
return;
}
setIsExecuting(true);
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
const toCreateKeyspace: boolean = keyspaceCreateNew;
const useAutoPilotForKeyspace: boolean = isSharedAutoPilotSelected && !!sharedAutoPilotThroughput;
const createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
const createKeyspaceQuery: string = keyspaceHasSharedOffer
? useAutoPilotForKeyspace
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};`
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};`
: `${createKeyspaceQueryPrefix};`;
let tableQuery: string;
const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`;
if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) {
if (isAutoPilotSelected && selectedAutoPilotThroughput) {
tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`;
} else {
tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`;
}
} else {
tableQuery = `${createTableQueryPrefix};`;
}
const addCollectionPaneStartMessage = {
...addCollectionPaneOpenMessage,
collection: {
...addCollectionPaneOpenMessage.collection,
hasDedicatedThroughput: dedicateTableThroughput,
},
keyspaceHasSharedOffer,
toCreateKeyspace,
createKeyspaceQuery,
createTableQuery: tableQuery,
};
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
try {
if (toCreateKeyspace) {
await cassandraApiClient.createTableAndKeyspace(
userContext?.databaseAccount?.properties?.cassandraEndpoint,
userContext?.databaseAccount?.id,
container,
tableQuery,
createKeyspaceQuery
);
} else {
await cassandraApiClient.createTableAndKeyspace(
userContext?.databaseAccount?.properties?.cassandraEndpoint,
userContext?.databaseAccount?.id,
container,
tableQuery
);
}
container.refreshAllDatabases();
setIsExecuting(false);
closePanel();
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
} catch (error) {
const errorMessage = getErrorMessage(error);
setFormErrors(errorMessage);
setIsExecuting(false);
const addCollectionPaneFailedMessage = {
...addCollectionPaneStartMessage,
error: errorMessage,
errorStack: getErrorStack(error),
};
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
}
};
const handleOnChangeKeyspaceType = (ev: React.FormEvent<HTMLInputElement>, mode: string): void => {
setKeyspaceCreateNew(mode === "Create new");
};
const props: RightPaneFormProps = {
expandConsole: () => container.expandConsole(),
formError: formErrors,
isExecuting,
submitButtonText: "Apply",
onSubmit,
};
return (
<RightPaneForm {...props}>
<div className="paneMainContent">
<div className="seconddivpadding">
<p>
<Label required>
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
</Label>
</p>
<Stack horizontal verticalAlign="center">
<input
className="throughputInputRadioBtn"
aria-label="Create new keyspace"
checked={keyspaceCreateNew}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeKeyspaceType(e, "Create new")}
/>
<span className="throughputInputRadioBtnLabel">Create new</span>
<input
className="throughputInputRadioBtn"
aria-label="Use existing keyspace"
checked={!keyspaceCreateNew}
type="radio"
role="radio"
tabIndex={0}
onChange={(e) => handleOnChangeKeyspaceType(e, "Use existing")}
/>
<span className="throughputInputRadioBtnLabel">Use existing</span>
</Stack>
<TextField
aria-required="true"
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
list={keyspaceCreateNew ? "" : "keyspacesList"}
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
size={40}
data-test="addCollection-keyspaceId"
value={keyspaceId}
onChange={(e, newValue) => setKeyspaceId(newValue)}
ariaLabel="Keyspace id"
autoFocus
/>
<datalist id="keyspacesList">
{keyspaceIds?.map((id: string, index: number) => (
<option key={index}>{id}</option>
))}
</datalist>
{canConfigureThroughput && keyspaceCreateNew && (
<div className="databaseProvision">
<input
tabIndex={0}
type="checkbox"
id="keyspaceSharedThroughput"
title="Provision shared throughput"
checked={keyspaceHasSharedOffer}
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
/>
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
Provision keyspace throughput
</span>
<InfoTooltip>
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
keyspace
</InfoTooltip>
</div>
)}
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
<div>
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
isDatabase
isSharded
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
setSharedThroughputSpendAck(isAcknowledge);
}}
/>
</div>
)}
</div>
<div className="seconddivpadding">
<p>
<Label required>
Enter CQL command to create the table.
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
Learn More
</a>
</Label>
</p>
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
{createTableQuery}
</div>
<TextField
aria-required="true"
ariaLabel="addCollection-tableId"
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Enter tableId"
size={20}
className="textfontclr"
value={tableId}
onChange={(e, newValue) => setTableId(newValue)}
style={{ marginBottom: "5px" }}
/>
<TextField
multiline
id="editor-area"
rows={5}
aria-label="Table Schema"
value={userTableQuery}
onChange={(e, newValue) => setUserTableQuery(newValue)}
/>
</div>
{canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && (
<div className="seconddivpadding">
<input
type="checkbox"
id="tableSharedThroughput"
title="Provision dedicated throughput for this table"
checked={dedicateTableThroughput}
onChange={(e) => setDedicateTableThroughput(e.target.checked)}
/>
<span>Provision dedicated throughput for this table</span>
<InfoTooltip>
You can optionally provision dedicated throughput for a table within a keyspace that has throughput
provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be
billed in addition to the throughput amount you provisioned at the keyspace level.
</InfoTooltip>
</div>
)}
{canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && (
<div>
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
isDatabase={false}
isSharded={false}
setThroughputValue={(throughput: number) => setThroughput(throughput)}
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
setThroughputSpendAck(isAcknowledge);
}}
/>
</div>
)}
</div>
</RightPaneForm>
);
};

View File

@ -0,0 +1,164 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
<RightPaneForm
expandConsole={[Function]}
formError=""
onSubmit={[Function]}
submitButtonText="Apply"
>
<div
className="paneMainContent"
>
<div
className="seconddivpadding"
>
<p>
<StyledLabelBase
required={true}
>
Keyspace name
<InfoTooltip>
Select an existing keyspace or enter a new keyspace id.
</InfoTooltip>
</StyledLabelBase>
</p>
<Stack
horizontal={true}
verticalAlign="center"
>
<input
aria-label="Create new keyspace"
checked={true}
className="throughputInputRadioBtn"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="throughputInputRadioBtnLabel"
>
Create new
</span>
<input
aria-label="Use existing keyspace"
checked={false}
className="throughputInputRadioBtn"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="throughputInputRadioBtnLabel"
>
Use existing
</span>
</Stack>
<StyledTextFieldBase
aria-required="true"
ariaLabel="Keyspace id"
autoComplete="off"
autoFocus={true}
data-test="addCollection-keyspaceId"
list=""
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new keyspace id"
size={40}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
value=""
/>
<datalist
id="keyspacesList"
/>
<div
className="databaseProvision"
>
<input
checked={false}
id="keyspaceSharedThroughput"
onChange={[Function]}
tabIndex={0}
title="Provision shared throughput"
type="checkbox"
/>
<span
aria-label="Provision keyspace throughput"
className="databaseProvisionText"
>
Provision keyspace throughput
</span>
<InfoTooltip>
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace
</InfoTooltip>
</div>
</div>
<div
className="seconddivpadding"
>
<p>
<StyledLabelBase
required={true}
>
Enter CQL command to create the table.
<a
href="https://aka.ms/cassandra-create-table"
rel="noreferrer"
target="_blank"
>
Learn More
</a>
</StyledLabelBase>
</p>
<div
aria-label="CREATE TABLE "
style={
Object {
"float": "left",
"paddingRight": "3px",
"paddingTop": "3px",
}
}
>
CREATE TABLE
</div>
<StyledTextFieldBase
aria-required="true"
ariaLabel="addCollection-tableId"
autoComplete="off"
className="textfontclr"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Enter tableId"
size={20}
style={
Object {
"marginBottom": "5px",
}
}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
value=""
/>
<StyledTextFieldBase
aria-label="Table Schema"
id="editor-area"
multiline={true}
onChange={[Function]}
rows={5}
value="(userid int, name text, email text, PRIMARY KEY (userid))"
/>
</div>
<div>
<ThroughputInput
isDatabase={false}
isSharded={false}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}
/>
</div>
</div>
</RightPaneForm>
`;

View File

@ -27,51 +27,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,

View File

@ -1,15 +0,0 @@
import CassandraAddCollectionPaneTemplate from "./CassandraAddCollectionPane.html";
export class PaneComponent {
constructor(data: any) {
return data.data;
}
}
export class CassandraAddCollectionPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: CassandraAddCollectionPaneTemplate,
};
}
}

View File

@ -17,51 +17,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,

View File

@ -15,51 +15,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
"arcadiaToken": [Function],
"canExceedMaximumValue": [Function],
"canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function],
"canRequestSupport": [Function],
"container": [Circular],
"costsVisible": [Function],
"createTableQuery": [Function],
"dedicateTableThroughput": [Function],
"firstFieldHasFocus": [Function],
"formErrors": [Function],
"formErrorsDetails": [Function],
"id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function],
"isExecuting": [Function],
"isFreeTierAccount": [Function],
"isSharedAutoPilotSelected": [Function],
"isTemplateReady": [Function],
"keyspaceCreateNew": [Function],
"keyspaceHasSharedOffer": [Function],
"keyspaceId": [Function],
"keyspaceIds": [Function],
"keyspaceOffers": Map {},
"keyspaceThroughput": [Function],
"maxThroughputRU": [Function],
"minThroughputRU": [Function],
"requestUnitsUsageCostDedicated": [Function],
"requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function],
"sharedAutoPilotThroughput": [Function],
"sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function],
"sharedThroughputSpendAckVisible": [Function],
"tableId": [Function],
"throughput": [Function],
"throughputRangeText": [Function],
"throughputSpendAck": [Function],
"throughputSpendAckText": [Function],
"throughputSpendAckVisible": [Function],
"title": [Function],
"userTableQuery": [Function],
"visible": [Function],
},
"closeDialog": undefined,
"closeSidePanel": undefined,
"collapsedResourceTreeWidth": 36,

View File

@ -226,7 +226,6 @@ const App: React.FunctionComponent = () => {
closePanel={closeSidePanel}
isConsoleExpanded={isNotificationConsoleExpanded}
/>
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
{showDialog && <Dialog {...dialogProps} />}
</div>
);

View File

@ -15,11 +15,11 @@ test("Cassandra keyspace and table CRUD", async () => {
});
await explorer.click('[data-test="New Table"]');
await explorer.click('[data-test="addCollection-keyspaceId"]');
await explorer.fill('[data-test="addCollection-keyspaceId"]', keyspaceId);
await explorer.click('[data-test="addCollection-tableId"]');
await explorer.fill('[data-test="addCollection-tableId"]', tableId);
await explorer.click('[aria-label="Add Table"] [data-test="addCollection-createCollection"]');
await explorer.click('[aria-label="Keyspace id"]');
await explorer.fill('[aria-label="Keyspace id"]', keyspaceId);
await explorer.click('[aria-label="addCollection-tableId"]');
await explorer.fill('[aria-label="addCollection-tableId"]', tableId);
await explorer.click("#sidePanelOkButton");
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');

View File

@ -60,7 +60,6 @@
"./src/Explorer/Notebook/NotebookUtil.ts",
"./src/Explorer/OpenFullScreen.test.tsx",
"./src/Explorer/OpenFullScreen.tsx",
"./src/Explorer/Panes/PaneComponents.ts",
"./src/Explorer/Panes/PanelFooterComponent.tsx",
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
"./src/Explorer/Panes/PanelLoadingScreen.tsx",