Migrate Cassandra Add Container to React (#723)
This commit is contained in:
parent
2bc298fef1
commit
030a4dec3c
|
@ -111,13 +111,9 @@ src/Explorer/OpenActionsStubs.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.ts
|
src/Explorer/Panes/AddDatabasePane.ts
|
||||||
src/Explorer/Panes/AddDatabasePane.test.ts
|
src/Explorer/Panes/AddDatabasePane.test.ts
|
||||||
src/Explorer/Panes/BrowseQueriesPane.ts
|
src/Explorer/Panes/BrowseQueriesPane.ts
|
||||||
src/Explorer/Panes/CassandraAddCollectionPane.ts
|
|
||||||
src/Explorer/Panes/ContextualPaneBase.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/GraphStylingPane.ts
|
||||||
# src/Explorer/Panes/NewVertexPane.ts
|
# src/Explorer/Panes/NewVertexPane.ts
|
||||||
src/Explorer/Panes/PaneComponents.ts
|
|
||||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
||||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
src/Explorer/Panes/SetupNotebooksPane.ts
|
||||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
src/Explorer/Panes/SwitchDirectoryPane.ts
|
||||||
|
|
|
@ -4,13 +4,9 @@ import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponen
|
||||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
import * as PaneComponents from "./Panes/PaneComponents";
|
|
||||||
|
|
||||||
ko.components.register("editor", new EditorComponent());
|
ko.components.register("editor", new EditorComponent());
|
||||||
ko.components.register("json-editor", new JsonEditorComponent());
|
ko.components.register("json-editor", new JsonEditorComponent());
|
||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
|
|
||||||
// Panes
|
|
||||||
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
|
||||||
|
|
|
@ -38,51 +38,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
@ -978,51 +933,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
@ -1931,51 +1841,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
@ -2871,51 +2736,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
|
|
@ -54,7 +54,7 @@ import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "./Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
|
@ -148,7 +148,6 @@ export default class Explorer {
|
||||||
public tabsManager: TabsManager;
|
public tabsManager: TabsManager;
|
||||||
|
|
||||||
// Contextual panes
|
// Contextual panes
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
|
||||||
private gitHubClient: GitHubClient;
|
private gitHubClient: GitHubClient;
|
||||||
public gitHubOAuthService: GitHubOAuthService;
|
public gitHubOAuthService: GitHubOAuthService;
|
||||||
public junoClient: JunoClient;
|
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 = params?.tabsManager ?? new TabsManager();
|
||||||
this.tabsManager.openedTabs.subscribe((tabs) => {
|
this.tabsManager.openedTabs.subscribe((tabs) => {
|
||||||
if (tabs.length === 0) {
|
if (tabs.length === 0) {
|
||||||
|
@ -1138,7 +1130,10 @@ export default class Explorer {
|
||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[]
|
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 newDatabases: DataModels.Database[] = _.filter(updatedDatabaseList, (database: DataModels.Database) => {
|
||||||
const databaseExists = _.some(
|
const databaseExists = _.some(
|
||||||
this.databases(),
|
this.databases(),
|
||||||
|
@ -1791,7 +1786,7 @@ export default class Explorer {
|
||||||
|
|
||||||
public onNewCollectionClicked(databaseId?: string): void {
|
public onNewCollectionClicked(databaseId?: string): void {
|
||||||
if (userContext.apiType === "Cassandra") {
|
if (userContext.apiType === "Cassandra") {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.openCassandraAddCollectionPane();
|
||||||
} else {
|
} else {
|
||||||
this.openAddCollectionPanel(databaseId);
|
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 {
|
public openGitHubReposPanel(header: string, junoClient?: JunoClient): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
header,
|
header,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { ActionContracts } from "../Contracts/ExplorerContracts";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import { handleOpenAction } from "./OpenActions";
|
import { handleOpenAction } from "./OpenActions";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
|
||||||
|
|
||||||
describe("OpenActions", () => {
|
describe("OpenActions", () => {
|
||||||
describe("handleOpenAction", () => {
|
describe("handleOpenAction", () => {
|
||||||
|
@ -15,8 +14,6 @@ describe("OpenActions", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
explorer = {} as Explorer;
|
explorer = {} as Explorer;
|
||||||
explorer.onNewCollectionClicked = jest.fn();
|
explorer.onNewCollectionClicked = jest.fn();
|
||||||
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
|
|
||||||
explorer.cassandraAddCollectionPane.open = jest.fn();
|
|
||||||
|
|
||||||
database = {
|
database = {
|
||||||
id: ko.observable("db"),
|
id: ko.observable("db"),
|
||||||
|
@ -64,28 +61,6 @@ describe("OpenActions", () => {
|
||||||
expect(actionHandled).toBe(true);
|
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", () => {
|
describe("AddCollection pane kind", () => {
|
||||||
it("string value should call explorer.onNewCollectionClicked", () => {
|
it("string value should call explorer.onNewCollectionClicked", () => {
|
||||||
const action = {
|
const action = {
|
||||||
|
|
|
@ -145,7 +145,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
|
||||||
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]
|
||||||
) {
|
) {
|
||||||
explorer.cassandraAddCollectionPane.open();
|
explorer.openCassandraAddCollectionPane();
|
||||||
} else if (
|
} else if (
|
||||||
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
|
||||||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
|
||||||
|
|
|
@ -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>
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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>
|
||||||
|
`;
|
|
@ -27,51 +27,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,51 +17,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
|
|
@ -15,51 +15,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
"arcadiaToken": [Function],
|
"arcadiaToken": [Function],
|
||||||
"canExceedMaximumValue": [Function],
|
"canExceedMaximumValue": [Function],
|
||||||
"canSaveQueries": [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,
|
"closeDialog": undefined,
|
||||||
"closeSidePanel": undefined,
|
"closeSidePanel": undefined,
|
||||||
"collapsedResourceTreeWidth": 36,
|
"collapsedResourceTreeWidth": 36,
|
||||||
|
|
|
@ -226,7 +226,6 @@ const App: React.FunctionComponent = () => {
|
||||||
closePanel={closeSidePanel}
|
closePanel={closeSidePanel}
|
||||||
isConsoleExpanded={isNotificationConsoleExpanded}
|
isConsoleExpanded={isNotificationConsoleExpanded}
|
||||||
/>
|
/>
|
||||||
<div data-bind='component: { name: "cassandra-add-collection-pane", params: { data: cassandraAddCollectionPane} }' />
|
|
||||||
{showDialog && <Dialog {...dialogProps} />}
|
{showDialog && <Dialog {...dialogProps} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,11 +15,11 @@ test("Cassandra keyspace and table CRUD", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await explorer.click('[data-test="New Table"]');
|
await explorer.click('[data-test="New Table"]');
|
||||||
await explorer.click('[data-test="addCollection-keyspaceId"]');
|
await explorer.click('[aria-label="Keyspace id"]');
|
||||||
await explorer.fill('[data-test="addCollection-keyspaceId"]', keyspaceId);
|
await explorer.fill('[aria-label="Keyspace id"]', keyspaceId);
|
||||||
await explorer.click('[data-test="addCollection-tableId"]');
|
await explorer.click('[aria-label="addCollection-tableId"]');
|
||||||
await explorer.fill('[data-test="addCollection-tableId"]', tableId);
|
await explorer.fill('[aria-label="addCollection-tableId"]', tableId);
|
||||||
await explorer.click('[aria-label="Add Table"] [data-test="addCollection-createCollection"]');
|
await explorer.click("#sidePanelOkButton");
|
||||||
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
|
await safeClick(explorer, `.nodeItem >> text=${keyspaceId}`);
|
||||||
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
await safeClick(explorer, `[data-test="${tableId}"] [aria-label="More"]`);
|
||||||
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
await safeClick(explorer, 'button[role="menuitem"]:has-text("Delete Table")');
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
"./src/Explorer/Notebook/NotebookUtil.ts",
|
"./src/Explorer/Notebook/NotebookUtil.ts",
|
||||||
"./src/Explorer/OpenFullScreen.test.tsx",
|
"./src/Explorer/OpenFullScreen.test.tsx",
|
||||||
"./src/Explorer/OpenFullScreen.tsx",
|
"./src/Explorer/OpenFullScreen.tsx",
|
||||||
"./src/Explorer/Panes/PaneComponents.ts",
|
|
||||||
"./src/Explorer/Panes/PanelFooterComponent.tsx",
|
"./src/Explorer/Panes/PanelFooterComponent.tsx",
|
||||||
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
|
"./src/Explorer/Panes/PanelInfoErrorComponent.tsx",
|
||||||
"./src/Explorer/Panes/PanelLoadingScreen.tsx",
|
"./src/Explorer/Panes/PanelLoadingScreen.tsx",
|
||||||
|
|
Loading…
Reference in New Issue