Move databases to zustand (#898)
This commit is contained in:
parent
c9fa44f6f4
commit
96e6bba38b
|
@ -4,6 +4,7 @@ import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer/Explorer";
|
import Explorer from "../Explorer/Explorer";
|
||||||
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
||||||
|
@ -176,7 +177,7 @@ export class QueriesClient {
|
||||||
|
|
||||||
private findQueriesCollection(): ViewModels.Collection {
|
private findQueriesCollection(): ViewModels.Collection {
|
||||||
const queriesDatabase: ViewModels.Database = _.find(
|
const queriesDatabase: ViewModels.Database = _.find(
|
||||||
this.container.databases(),
|
useDatabases.getState().databases,
|
||||||
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
|
||||||
);
|
);
|
||||||
if (!queriesDatabase) {
|
if (!queriesDatabase) {
|
||||||
|
|
|
@ -30,8 +30,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
|
@ -124,8 +122,6 @@ exports[`SettingsComponent renders 1`] = `
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
|
|
|
@ -2,22 +2,22 @@ jest.mock("../Graph/GraphExplorerComponent/GremlinClient");
|
||||||
jest.mock("../../Common/dataAccess/createCollection");
|
jest.mock("../../Common/dataAccess/createCollection");
|
||||||
jest.mock("../../Common/dataAccess/createDocument");
|
jest.mock("../../Common/dataAccess/createDocument");
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
|
|
||||||
describe("ContainerSampleGenerator", () => {
|
describe("ContainerSampleGenerator", () => {
|
||||||
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
let explorerStub: Explorer;
|
||||||
const explorerStub = {} as Explorer;
|
|
||||||
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
beforeAll(() => {
|
||||||
explorerStub.findDatabaseWithId = () => database;
|
explorerStub = {
|
||||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
refreshAllDatabases: () => {},
|
||||||
return explorerStub;
|
} as Explorer;
|
||||||
};
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
(createDocument as jest.Mock).mockResolvedValue(undefined);
|
||||||
|
@ -59,8 +59,7 @@ describe("ContainerSampleGenerator", () => {
|
||||||
loadCollections: () => {},
|
loadCollections: () => {},
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
database.findCollectionWithId = () => collection;
|
database.findCollectionWithId = () => collection;
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
const explorerStub = createExplorerStub(database);
|
|
||||||
|
|
||||||
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
||||||
generator.setData(sampleData);
|
generator.setData(sampleData);
|
||||||
|
@ -108,8 +107,8 @@ describe("ContainerSampleGenerator", () => {
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
database.findCollectionWithId = () => collection;
|
database.findCollectionWithId = () => collection;
|
||||||
collection.databaseId = database.id();
|
collection.databaseId = database.id();
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
|
||||||
const explorerStub = createExplorerStub(database);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -126,7 +125,6 @@ describe("ContainerSampleGenerator", () => {
|
||||||
|
|
||||||
it("should not create any sample for Mongo API account", async () => {
|
it("should not create any sample for Mongo API account", async () => {
|
||||||
const experience = "Sample generation not supported for this API Mongo";
|
const experience = "Sample generation not supported for this API Mongo";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -141,7 +139,6 @@ describe("ContainerSampleGenerator", () => {
|
||||||
|
|
||||||
it("should not create any sample for Table API account", async () => {
|
it("should not create any sample for Table API account", async () => {
|
||||||
const experience = "Sample generation not supported for this API Tables";
|
const experience = "Sample generation not supported for this API Tables";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
databaseAccount: {
|
databaseAccount: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -163,7 +160,6 @@ describe("ContainerSampleGenerator", () => {
|
||||||
},
|
},
|
||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
});
|
});
|
||||||
const explorerStub = createExplorerStub(undefined);
|
|
||||||
// Rejects with error that contains experience
|
// Rejects with error that contains experience
|
||||||
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
|
||||||
import GraphTab from ".././Tabs/GraphTab";
|
import GraphTab from ".././Tabs/GraphTab";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
data: any[];
|
data: any[];
|
||||||
|
@ -59,7 +60,7 @@ export class ContainerSampleGenerator {
|
||||||
|
|
||||||
await createCollection(createRequest);
|
await createCollection(createRequest);
|
||||||
await this.container.refreshAllDatabases();
|
await this.container.refreshAllDatabases();
|
||||||
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
|
const database = useDatabases.getState().findDatabaseWithId(this.sampleDataFile.databaseId);
|
||||||
if (!database) {
|
if (!database) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as ko from "knockout";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
import { Collection, Database } from "../../Contracts/ViewModels";
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
import { DataSamplesUtil } from "./DataSamplesUtil";
|
import { DataSamplesUtil } from "./DataSamplesUtil";
|
||||||
|
|
||||||
|
@ -16,8 +17,8 @@ describe("DataSampleUtils", () => {
|
||||||
collections: ko.observableArray<Collection>([collection]),
|
collections: ko.observableArray<Collection>([collection]),
|
||||||
} as Database;
|
} as Database;
|
||||||
const explorer = {} as Explorer;
|
const explorer = {} as Explorer;
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
|
||||||
explorer.showOkModalDialog = () => {};
|
explorer.showOkModalDialog = () => {};
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
const dataSamplesUtil = new DataSamplesUtil(explorer);
|
||||||
|
|
||||||
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
|
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
|
|
||||||
export class DataSamplesUtil {
|
export class DataSamplesUtil {
|
||||||
|
@ -17,7 +18,7 @@ export class DataSamplesUtil {
|
||||||
|
|
||||||
const databaseName = generator.getDatabaseId();
|
const databaseName = generator.getDatabaseId();
|
||||||
const containerName = generator.getCollectionId();
|
const containerName = generator.getCollectionId();
|
||||||
if (this.hasContainer(databaseName, containerName, this.container.databases())) {
|
if (this.hasContainer(databaseName, containerName, useDatabases.getState().databases)) {
|
||||||
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
||||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||||
logConsoleError(msg);
|
logConsoleError(msg);
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
jest.mock("./../Common/dataAccess/deleteDatabase");
|
|
||||||
jest.mock("./../Shared/Telemetry/TelemetryProcessor");
|
|
||||||
import * as ko from "knockout";
|
|
||||||
import { deleteDatabase } from "./../Common/dataAccess/deleteDatabase";
|
|
||||||
import * as ViewModels from "./../Contracts/ViewModels";
|
|
||||||
import Explorer from "./Explorer";
|
|
||||||
|
|
||||||
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
|
|
||||||
let explorer: Explorer;
|
|
||||||
beforeAll(() => {
|
|
||||||
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if only 1 database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastDatabase()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false if only 2 databases", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
const database2 = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
|
|
||||||
expect(explorer.isLastDatabase()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be false if not last empty database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if last non empty database", () => {
|
|
||||||
const database = {} as ViewModels.Database;
|
|
||||||
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
|
|
||||||
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
|
||||||
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -69,6 +69,7 @@ import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||||
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
||||||
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
|
import { useDatabases } from "./useDatabases";
|
||||||
|
|
||||||
BindingHandlersRegisterer.registerBindingHandlers();
|
BindingHandlersRegisterer.registerBindingHandlers();
|
||||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||||
|
@ -81,12 +82,10 @@ export interface ExplorerParams {
|
||||||
export default class Explorer {
|
export default class Explorer {
|
||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
public isAccountReady: ko.Observable<boolean>;
|
public isAccountReady: ko.Observable<boolean>;
|
||||||
public canSaveQueries: ko.Computed<boolean>;
|
|
||||||
public queriesClient: QueriesClient;
|
public queriesClient: QueriesClient;
|
||||||
public tableDataClient: TableDataClient;
|
public tableDataClient: TableDataClient;
|
||||||
|
|
||||||
// Resource Tree
|
// Resource Tree
|
||||||
public databases: ko.ObservableArray<ViewModels.Database>;
|
|
||||||
public selectedDatabaseId: ko.Computed<string>;
|
public selectedDatabaseId: ko.Computed<string>;
|
||||||
public selectedCollectionId: ko.Computed<string>;
|
public selectedCollectionId: ko.Computed<string>;
|
||||||
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
||||||
|
@ -168,26 +167,6 @@ export default class Explorer {
|
||||||
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
|
this.resourceTokenCollection = ko.observable<ViewModels.CollectionBase>();
|
||||||
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
this.isSchemaEnabled = ko.computed<boolean>(() => userContext.features.enableSchema);
|
||||||
|
|
||||||
this.databases = ko.observableArray<ViewModels.Database>();
|
|
||||||
this.canSaveQueries = ko.computed<boolean>(() => {
|
|
||||||
const savedQueriesDatabase: ViewModels.Database = _.find(
|
|
||||||
this.databases(),
|
|
||||||
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName
|
|
||||||
);
|
|
||||||
if (!savedQueriesDatabase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const savedQueriesCollection: ViewModels.Collection =
|
|
||||||
savedQueriesDatabase &&
|
|
||||||
_.find(
|
|
||||||
savedQueriesDatabase.collections(),
|
|
||||||
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName
|
|
||||||
);
|
|
||||||
if (!savedQueriesCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
this.selectedNode = ko.observable<ViewModels.TreeNode>();
|
this.selectedNode = ko.observable<ViewModels.TreeNode>();
|
||||||
this.selectedNode.subscribe((nodeSelected: ViewModels.TreeNode) => {
|
this.selectedNode.subscribe((nodeSelected: ViewModels.TreeNode) => {
|
||||||
// Make sure switching tabs restores tabs display
|
// Make sure switching tabs restores tabs display
|
||||||
|
@ -641,34 +620,14 @@ export default class Explorer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (this.selectedNode().nodeKind === "Database") {
|
if (this.selectedNode().nodeKind === "Database") {
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.id() === this.selectedNode().id());
|
return _.find(
|
||||||
|
useDatabases.getState().databases,
|
||||||
|
(database: ViewModels.Database) => database.id() === this.selectedNode().id()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this.findSelectedCollection().database;
|
return this.findSelectedCollection().database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public findDatabaseWithId(databaseId: string): ViewModels.Database {
|
|
||||||
return _.find(this.databases(), (database: ViewModels.Database) => database.id() === databaseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastNonEmptyDatabase(): boolean {
|
|
||||||
if (
|
|
||||||
this.isLastDatabase() &&
|
|
||||||
this.databases()[0] &&
|
|
||||||
this.databases()[0].collections &&
|
|
||||||
this.databases()[0].collections().length > 0
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastDatabase(): boolean {
|
|
||||||
if (this.databases().length > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isSelectedDatabaseShared(): boolean {
|
public isSelectedDatabaseShared(): boolean {
|
||||||
const database = this.findSelectedDatabase();
|
const database = this.findSelectedDatabase();
|
||||||
if (!!database) {
|
if (!!database) {
|
||||||
|
@ -691,10 +650,11 @@ export default class Explorer {
|
||||||
let loadCollectionPromises: Q.Promise<void>[] = [];
|
let loadCollectionPromises: Q.Promise<void>[] = [];
|
||||||
|
|
||||||
// If the user has a lot of databases, only load expanded databases.
|
// If the user has a lot of databases, only load expanded databases.
|
||||||
|
const databases = useDatabases.getState().databases;
|
||||||
const databasesToLoad =
|
const databasesToLoad =
|
||||||
this.databases().length <= Explorer.MaxNbDatabasesToAutoExpand
|
databases.length <= Explorer.MaxNbDatabasesToAutoExpand
|
||||||
? this.databases()
|
? databases
|
||||||
: this.databases().filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
: databases.filter((db) => db.isDatabaseExpanded() || db.id() === Constants.SavedQueries.DatabaseName);
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadCollections, {
|
||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
|
@ -739,37 +699,16 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public findCollection(databaseId: string, collectionId: string): ViewModels.Collection {
|
|
||||||
const database: ViewModels.Database = this.databases().find(
|
|
||||||
(database: ViewModels.Database) => database.id() === databaseId
|
|
||||||
);
|
|
||||||
return database?.collections().find((collection: ViewModels.Collection) => collection.id() === collectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isLastCollection(): boolean {
|
|
||||||
let collectionCount = 0;
|
|
||||||
if (this.databases().length == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.databases().length; i++) {
|
|
||||||
const database = this.databases()[i];
|
|
||||||
collectionCount += database.collections().length;
|
|
||||||
if (collectionCount > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeltaDatabases(
|
private getDeltaDatabases(
|
||||||
updatedDatabaseList: DataModels.Database[]
|
updatedDatabaseList: DataModels.Database[]
|
||||||
): {
|
): {
|
||||||
toAdd: ViewModels.Database[];
|
toAdd: ViewModels.Database[];
|
||||||
toDelete: ViewModels.Database[];
|
toDelete: ViewModels.Database[];
|
||||||
} {
|
} {
|
||||||
|
const databases = useDatabases.getState().databases;
|
||||||
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(),
|
databases,
|
||||||
(existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
|
(existingDatabase: ViewModels.Database) => existingDatabase.id() === database.id
|
||||||
);
|
);
|
||||||
return !databaseExists;
|
return !databaseExists;
|
||||||
|
@ -779,7 +718,7 @@ export default class Explorer {
|
||||||
);
|
);
|
||||||
|
|
||||||
let databasesToDelete: ViewModels.Database[] = [];
|
let databasesToDelete: ViewModels.Database[] = [];
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
ko.utils.arrayForEach(databases, (database: ViewModels.Database) => {
|
||||||
const databasePresentInUpdatedList = _.some(
|
const databasePresentInUpdatedList = _.some(
|
||||||
updatedDatabaseList,
|
updatedDatabaseList,
|
||||||
(db: DataModels.Database) => db.id === database.id()
|
(db: DataModels.Database) => db.id === database.id()
|
||||||
|
@ -793,24 +732,12 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDatabasesToList(databases: ViewModels.Database[]): void {
|
private addDatabasesToList(databases: ViewModels.Database[]): void {
|
||||||
this.databases(
|
useDatabases.getState().addDatabases(databases);
|
||||||
this.databases()
|
|
||||||
.concat(databases)
|
|
||||||
.sort((database1, database2) => database1.id().localeCompare(database2.id()))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteDatabasesFromList(databasesToRemove: ViewModels.Database[]): void {
|
private deleteDatabasesFromList(databasesToRemove: ViewModels.Database[]): void {
|
||||||
const databasesToKeep: ViewModels.Database[] = [];
|
const deleteDatabase = useDatabases.getState().deleteDatabase;
|
||||||
|
databasesToRemove.forEach((database) => deleteDatabase(database));
|
||||||
ko.utils.arrayForEach(this.databases(), (database: ViewModels.Database) => {
|
|
||||||
const shouldRemoveDatabase = _.some(databasesToRemove, (db: ViewModels.Database) => db.id === database.id);
|
|
||||||
if (!shouldRemoveDatabase) {
|
|
||||||
databasesToKeep.push(database);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.databases(databasesToKeep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise<NotebookContentItem> {
|
||||||
|
@ -1414,34 +1341,6 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadDatabaseOffers(): Promise<void> {
|
|
||||||
await Promise.all(
|
|
||||||
this.databases()?.map(async (database: ViewModels.Database) => {
|
|
||||||
await database.loadOffer();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isFirstResourceCreated(): boolean {
|
|
||||||
const databases: ViewModels.Database[] = this.databases();
|
|
||||||
|
|
||||||
if (!databases || databases.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases.some((database) => {
|
|
||||||
// user has created at least one collection
|
|
||||||
if (database.collections()?.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// user has created a database with shared throughput
|
|
||||||
if (database.offer()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// use has created an empty database without shared throughput
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public openDeleteCollectionConfirmationPane(): void {
|
public openDeleteCollectionConfirmationPane(): void {
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
|
@ -1466,7 +1365,7 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openAddCollectionPanel(databaseId?: string): Promise<void> {
|
public async openAddCollectionPanel(databaseId?: string): Promise<void> {
|
||||||
await this.loadDatabaseOffers();
|
await useDatabases.getState().loadDatabaseOffers();
|
||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
.openSidePanel("New " + getCollectionName(), <AddCollectionPanel explorer={this} databaseId={databaseId} />);
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { getUpsellMessage } from "../../Utils/PricingUtils";
|
||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { PanelFooterComponent } from "./PanelFooterComponent";
|
import { PanelFooterComponent } from "./PanelFooterComponent";
|
||||||
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "./PanelInfoErrorComponent";
|
||||||
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
||||||
|
@ -125,6 +126,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
|
const isFirstResourceCreated = useDatabases.getState().isFirstResourceCreated();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
|
<form className="panelFormWrapper" onSubmit={this.submit.bind(this)}>
|
||||||
{this.state.errorMessage && (
|
{this.state.errorMessage && (
|
||||||
|
@ -137,7 +140,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
|
|
||||||
{!this.state.errorMessage && this.isFreeTierAccount() && (
|
{!this.state.errorMessage && this.isFreeTierAccount() && (
|
||||||
<PanelInfoErrorComponent
|
<PanelInfoErrorComponent
|
||||||
message={getUpsellMessage(userContext.portalEnv, true, this.props.explorer.isFirstResourceCreated(), true)}
|
message={getUpsellMessage(userContext.portalEnv, true, isFirstResourceCreated, true)}
|
||||||
messageType="info"
|
messageType="info"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
|
@ -240,9 +243,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
|
|
||||||
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
|
||||||
}
|
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
|
@ -469,9 +470,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
|
|
||||||
{this.shouldShowCollectionThroughputInput() && (
|
{this.shouldShowCollectionThroughputInput() && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
|
||||||
}
|
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
|
@ -680,7 +679,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDatabaseOptions(): IDropdownOption[] {
|
private getDatabaseOptions(): IDropdownOption[] {
|
||||||
return this.props.explorer?.databases()?.map((database) => ({
|
return useDatabases.getState().databases?.map((database) => ({
|
||||||
key: database.id(),
|
key: database.id(),
|
||||||
text: database.id(),
|
text: database.id(),
|
||||||
}));
|
}));
|
||||||
|
@ -772,9 +771,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedDatabase = this.props.explorer
|
const selectedDatabase = useDatabases
|
||||||
.databases()
|
.getState()
|
||||||
?.find((database) => database.id() === this.state.selectedDatabaseId);
|
.databases?.find((database) => database.id() === this.state.selectedDatabaseId);
|
||||||
return !!selectedDatabase?.offer();
|
return !!selectedDatabase?.offer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { getUpsellMessage } from "../../../Utils/PricingUtils";
|
import { getUpsellMessage } from "../../../Utils/PricingUtils";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
import { getTextFieldStyles } from "../PanelStyles";
|
import { getTextFieldStyles } from "../PanelStyles";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
@ -172,7 +173,12 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
{!formErrors && isFreeTierAccount && (
|
{!formErrors && isFreeTierAccount && (
|
||||||
<PanelInfoErrorComponent
|
<PanelInfoErrorComponent
|
||||||
message={getUpsellMessage(userContext.portalEnv, true, container.isFirstResourceCreated(), true)}
|
message={getUpsellMessage(
|
||||||
|
userContext.portalEnv,
|
||||||
|
true,
|
||||||
|
useDatabases.getState().isFirstResourceCreated(),
|
||||||
|
true
|
||||||
|
)}
|
||||||
messageType="info"
|
messageType="info"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
link={Constants.Urls.freeTierInformation}
|
link={Constants.Urls.freeTierInformation}
|
||||||
|
@ -225,7 +231,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
||||||
|
|
||||||
{!isServerlessAccount() && databaseCreateNewShared && (
|
{!isServerlessAccount() && databaseCreateNewShared && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={databaseCreateNewShared}
|
isSharded={databaseCreateNewShared}
|
||||||
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { SavedQueries } from "../../../Common/Constants";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
|
import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { BrowseQueriesPane } from "./BrowseQueriesPane";
|
import { BrowseQueriesPane } from "./BrowseQueriesPane";
|
||||||
|
|
||||||
describe("Browse queries panel", () => {
|
describe("Browse queries panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
const fakeClientQuery = {} as QueriesClient;
|
const fakeClientQuery = {} as QueriesClient;
|
||||||
const fakeQueryData = [] as Query[];
|
const fakeQueryData = [] as Query[];
|
||||||
fakeClientQuery.getQueries = async () => fakeQueryData;
|
fakeClientQuery.getQueries = async () => fakeQueryData;
|
||||||
|
@ -17,6 +19,16 @@ describe("Browse queries panel", () => {
|
||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
};
|
};
|
||||||
|
useDatabases.getState().addDatabases([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.DatabaseName),
|
||||||
|
collections: ko.observableArray([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.CollectionName),
|
||||||
|
} as Collection,
|
||||||
|
]),
|
||||||
|
} as Database,
|
||||||
|
]);
|
||||||
|
|
||||||
it("Should render Default properly", () => {
|
it("Should render Default properly", () => {
|
||||||
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from "../../Controls/QueriesGridReactComponent/QueriesGridComponent";
|
} from "../../Controls/QueriesGridReactComponent/QueriesGridComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
|
|
||||||
interface BrowseQueriesPaneProps {
|
interface BrowseQueriesPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
|
@ -45,12 +46,13 @@ export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
||||||
});
|
});
|
||||||
closeSidePanel();
|
closeSidePanel();
|
||||||
};
|
};
|
||||||
|
const isSaveQueryEnabled = useDatabases((state) => state.isSaveQueryEnabled);
|
||||||
|
|
||||||
const props: QueriesGridComponentProps = {
|
const props: QueriesGridComponentProps = {
|
||||||
queriesClient: explorer.queriesClient,
|
queriesClient: explorer.queriesClient,
|
||||||
onQuerySelect: loadSavedQuery,
|
onQuerySelect: loadSavedQuery,
|
||||||
containerVisible: true,
|
containerVisible: true,
|
||||||
saveQueryEnabled: explorer.canSaveQueries(),
|
saveQueryEnabled: isSaveQueryEnabled(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,6 @@ exports[`Browse queries panel Should render Default properly 1`] = `
|
||||||
closePanel={[Function]}
|
closePanel={[Function]}
|
||||||
explorer={
|
explorer={
|
||||||
Object {
|
Object {
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"queriesClient": Object {
|
"queriesClient": Object {
|
||||||
"getQueries": [Function],
|
"getQueries": [Function],
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { getTextFieldStyles } from "../PanelStyles";
|
import { getTextFieldStyles } from "../PanelStyles";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
|
@ -236,7 +237,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||||
styles={{ root: { width: 300 }, title: { fontSize: 12 }, dropdownItem: { fontSize: 12 } }}
|
styles={{ root: { width: 300 }, title: { fontSize: 12 }, dropdownItem: { fontSize: 12 } }}
|
||||||
placeholder="Choose existing keyspace id"
|
placeholder="Choose existing keyspace id"
|
||||||
defaultSelectedKey={existingKeyspaceId}
|
defaultSelectedKey={existingKeyspaceId}
|
||||||
options={container?.databases()?.map((keyspace) => ({
|
options={useDatabases.getState().databases?.map((keyspace) => ({
|
||||||
key: keyspace.id(),
|
key: keyspace.id(),
|
||||||
text: keyspace.id(),
|
text: keyspace.id(),
|
||||||
data: {
|
data: {
|
||||||
|
@ -253,7 +254,9 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||||
|
|
||||||
{!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && (
|
{!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={
|
||||||
|
isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()
|
||||||
|
}
|
||||||
isDatabase
|
isDatabase
|
||||||
isSharded
|
isSharded
|
||||||
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
|
@ -324,7 +327,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
||||||
)}
|
)}
|
||||||
{!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
|
{!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isSharded={false}
|
isSharded={false}
|
||||||
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
|
|
|
@ -11,44 +11,42 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./DeleteCollectionConfirmationPane";
|
||||||
|
|
||||||
describe("Delete Collection Confirmation Pane", () => {
|
describe("Delete Collection Confirmation Pane", () => {
|
||||||
describe("Explorer.isLastCollection()", () => {
|
describe("useDatabases.isLastCollection()", () => {
|
||||||
let explorer: Explorer;
|
beforeAll(() => useDatabases.getState().clearDatabases());
|
||||||
|
afterEach(() => useDatabases.getState().clearDatabases());
|
||||||
beforeEach(() => {
|
|
||||||
explorer = new Explorer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should be true if 1 database and 1 collection", () => {
|
it("should be true if 1 database and 1 collection", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
useDatabases.getState().addDatabases([database]);
|
||||||
expect(explorer.isLastCollection()).toBe(true);
|
expect(useDatabases.getState().isLastCollection()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if if 1 database and 2 collection", () => {
|
it("should be false if if 1 database and 2 collection", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection, {} as Collection]);
|
database.collections = ko.observableArray<Collection>([
|
||||||
explorer.databases = ko.observableArray<Database>([database]);
|
{ id: ko.observable("coll1") } as Collection,
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
{ id: ko.observable("coll2") } as Collection,
|
||||||
|
]);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if 2 database and 1 collection each", () => {
|
it("should be false if 2 database and 1 collection each", () => {
|
||||||
const database = {} as Database;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("coll1") } as Collection]);
|
||||||
const database2 = {} as Database;
|
const database2 = { id: ko.observable("testDB2") } as Database;
|
||||||
database2.collections = ko.observableArray<Collection>([{} as Collection]);
|
database2.collections = ko.observableArray<Collection>([{ id: ko.observable("coll2") } as Collection]);
|
||||||
explorer.databases = ko.observableArray<Database>([database, database2]);
|
useDatabases.getState().addDatabases([database, database2]);
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if 0 databases", () => {
|
it("should be false if 0 databases", () => {
|
||||||
const database = {} as Database;
|
expect(useDatabases.getState().isLastCollection()).toBe(false);
|
||||||
explorer.databases = ko.observableArray<Database>();
|
|
||||||
database.collections = ko.observableArray<Collection>();
|
|
||||||
expect(explorer.isLastCollection()).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,7 +54,6 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||||
it("should return true if last collection and database does not have shared throughput else false", () => {
|
it("should return true if last collection and database does not have shared throughput else false", () => {
|
||||||
const fakeExplorer = new Explorer();
|
const fakeExplorer = new Explorer();
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
|
@ -65,15 +62,15 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||||
collectionName: "container",
|
collectionName: "container",
|
||||||
};
|
};
|
||||||
const wrapper = shallow(<DeleteCollectionConfirmationPane {...props} />);
|
const wrapper = shallow(<DeleteCollectionConfirmationPane {...props} />);
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
|
||||||
|
|
||||||
props.explorer.isLastCollection = () => true;
|
|
||||||
props.explorer.isSelectedDatabaseShared = () => true;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
|
|
||||||
props.explorer.isLastCollection = () => false;
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
props.explorer.isSelectedDatabaseShared = () => false;
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
wrapper.setProps(props);
|
||||||
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(true);
|
||||||
|
|
||||||
|
props.explorer.isSelectedDatabaseShared = () => true;
|
||||||
wrapper.setProps(props);
|
wrapper.setProps(props);
|
||||||
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
expect(wrapper.exists(".deleteCollectionFeedback")).toBe(false);
|
||||||
});
|
});
|
||||||
|
@ -94,8 +91,10 @@ describe("Delete Collection Confirmation Pane", () => {
|
||||||
fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
|
fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
|
||||||
fakeExplorer.selectedNode = ko.observable<TreeNode>();
|
fakeExplorer.selectedNode = ko.observable<TreeNode>();
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
const database = { id: ko.observable("testDB") } as Database;
|
||||||
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
|
|
@ -13,7 +13,9 @@ import { userContext } from "../../../UserContext";
|
||||||
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../../Utils/APITypeUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
export interface DeleteCollectionConfirmationPaneProps {
|
export interface DeleteCollectionConfirmationPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
}
|
}
|
||||||
|
@ -22,13 +24,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
|
||||||
explorer,
|
explorer,
|
||||||
}: DeleteCollectionConfirmationPaneProps) => {
|
}: DeleteCollectionConfirmationPaneProps) => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
const isLastCollection = useDatabases((state) => state.isLastCollection);
|
||||||
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
const [deleteCollectionFeedback, setDeleteCollectionFeedback] = useState<string>("");
|
||||||
const [inputCollectionName, setInputCollectionName] = useState<string>("");
|
const [inputCollectionName, setInputCollectionName] = useState<string>("");
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
const [isExecuting, setIsExecuting] = useState(false);
|
const [isExecuting, setIsExecuting] = useState(false);
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean => {
|
const shouldRecordFeedback = (): boolean => {
|
||||||
return explorer.isLastCollection() && !explorer.isSelectedDatabaseShared();
|
return isLastCollection() && !explorer.isSelectedDatabaseShared();
|
||||||
};
|
};
|
||||||
const collectionName = getCollectionName().toLocaleLowerCase();
|
const collectionName = getCollectionName().toLocaleLowerCase();
|
||||||
const paneTitle = "Delete " + collectionName;
|
const paneTitle = "Delete " + collectionName;
|
||||||
|
|
|
@ -7,7 +7,6 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
|
||||||
explorer={
|
explorer={
|
||||||
Object {
|
Object {
|
||||||
"findSelectedCollection": [Function],
|
"findSelectedCollection": [Function],
|
||||||
"isLastCollection": [Function],
|
|
||||||
"isSelectedDatabaseShared": [Function],
|
"isSelectedDatabaseShared": [Function],
|
||||||
"refreshAllDatabases": [Function],
|
"refreshAllDatabases": [Function],
|
||||||
"selectedCollectionId": [Function],
|
"selectedCollectionId": [Function],
|
||||||
|
|
|
@ -11,19 +11,20 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { TabsManager } from "../Tabs/TabsManager";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||||
|
|
||||||
describe("Delete Database Confirmation Pane", () => {
|
describe("Delete Database Confirmation Pane", () => {
|
||||||
describe("shouldRecordFeedback()", () => {
|
describe("shouldRecordFeedback()", () => {
|
||||||
it("should return true if last non empty database or is last database that has shared throughput, else false", () => {
|
it("should return true if last non empty database or is last database that has shared throughput, else false", () => {
|
||||||
const fakeExplorer = new Explorer();
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
|
||||||
const database = {} as Database;
|
const database = {} as Database;
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
database.id = ko.observable<string>("testDatabse");
|
database.id = ko.observable<string>("testDatabase");
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
|
@ -33,29 +34,26 @@ describe("Delete Database Confirmation Pane", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<DeleteDatabaseConfirmationPanel {...props} />);
|
const wrapper = shallow(<DeleteDatabaseConfirmationPanel {...props} />);
|
||||||
props.explorer.isLastNonEmptyDatabase = () => true;
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
|
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
|
|
||||||
wrapper.setProps(props);
|
wrapper.setProps(props);
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||||
|
useDatabases.getState().clearDatabases();
|
||||||
props.explorer.isLastNonEmptyDatabase = () => false;
|
|
||||||
props.explorer.isLastDatabase = () => false;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
|
||||||
|
|
||||||
props.explorer.isLastNonEmptyDatabase = () => false;
|
|
||||||
props.explorer.isLastDatabase = () => true;
|
|
||||||
props.explorer.isSelectedDatabaseShared = () => false;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("submit()", () => {
|
describe("submit()", () => {
|
||||||
const selectedDatabaseId = "testDatabse";
|
const selectedDatabaseId = "testDatabse";
|
||||||
const fakeExplorer = new Explorer();
|
const database = { id: ko.observable("testDatabase") } as Database;
|
||||||
|
database.collections = ko.observableArray<Collection>([{ id: ko.observable("testCollection") } as Collection]);
|
||||||
|
database.id = ko.observable<string>(selectedDatabaseId);
|
||||||
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.refreshAllDatabases = () => undefined;
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
fakeExplorer.isLastCollection = () => true;
|
|
||||||
fakeExplorer.isSelectedDatabaseShared = () => false;
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
fakeExplorer.tabsManager = new TabsManager();
|
||||||
|
fakeExplorer.selectedNode = ko.observable();
|
||||||
|
|
||||||
let wrapper: ReactWrapper;
|
let wrapper: ReactWrapper;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -71,13 +69,10 @@ describe("Delete Database Confirmation Pane", () => {
|
||||||
});
|
});
|
||||||
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
||||||
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);
|
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);
|
||||||
|
useDatabases.getState().addDatabases([database]);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const database = {} as Database;
|
|
||||||
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
|
||||||
database.id = ko.observable<string>(selectedDatabaseId);
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
|
@ -86,10 +81,10 @@ describe("Delete Database Confirmation Pane", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
wrapper = mount(<DeleteDatabaseConfirmationPanel {...props} />);
|
wrapper = mount(<DeleteDatabaseConfirmationPanel {...props} />);
|
||||||
props.explorer.isLastNonEmptyDatabase = () => true;
|
|
||||||
wrapper.setProps(props);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(() => useDatabases.getState().clearDatabases());
|
||||||
|
|
||||||
it("Should call delete database", () => {
|
it("Should call delete database", () => {
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "./RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||||
selectedDatabase,
|
selectedDatabase,
|
||||||
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
}: DeleteDatabaseConfirmationPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
||||||
|
const isLastNonEmptyDatabase = useDatabases((state) => state.isLastNonEmptyDatabase);
|
||||||
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
|
||||||
const [formError, setFormError] = useState<string>("");
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
@ -70,7 +72,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shouldRecordFeedback()) {
|
if (isLastNonEmptyDatabase()) {
|
||||||
const deleteFeedback = new DeleteFeedback(
|
const deleteFeedback = new DeleteFeedback(
|
||||||
userContext?.databaseAccount.id,
|
userContext?.databaseAccount.id,
|
||||||
userContext?.databaseAccount.name,
|
userContext?.databaseAccount.name,
|
||||||
|
@ -100,10 +102,6 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldRecordFeedback = (): boolean => {
|
|
||||||
return explorer.isLastNonEmptyDatabase() || (explorer.isLastDatabase() && explorer.isSelectedDatabaseShared());
|
|
||||||
};
|
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError,
|
formError,
|
||||||
isExecuting: isLoading,
|
isExecuting: isLoading,
|
||||||
|
@ -134,7 +132,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shouldRecordFeedback() && (
|
{isLastNonEmptyDatabase() && (
|
||||||
<div className="deleteDatabaseFeedback">
|
<div className="deleteDatabaseFeedback">
|
||||||
<Text variant="small" block>
|
<Text variant="small" block>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
|
|
|
@ -19,8 +19,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = `
|
||||||
"container": Explorer {
|
"container": Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
|
|
|
@ -1,32 +1,38 @@
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { SavedQueries } from "../../../Common/Constants";
|
||||||
|
import { Collection, Database } from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { SaveQueryPane } from "./SaveQueryPane";
|
import { SaveQueryPane } from "./SaveQueryPane";
|
||||||
|
|
||||||
describe("Save Query Pane", () => {
|
describe("Save Query Pane", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
explorer: fakeExplorer,
|
explorer: fakeExplorer,
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(<SaveQueryPane {...props} />);
|
|
||||||
|
|
||||||
it("should return true if can save Queries else false", () => {
|
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists("#saveQueryInput")).toBe(true);
|
|
||||||
|
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => false);
|
|
||||||
wrapper.setProps(props);
|
|
||||||
expect(wrapper.exists("#saveQueryInput")).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
const wrapper = shallow(<SaveQueryPane {...props} />);
|
const wrapper = shallow(<SaveQueryPane {...props} />);
|
||||||
|
expect(wrapper.exists("#saveQueryInput")).toBe(false);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return true if can save Queries else false", () => {
|
||||||
|
useDatabases.getState().addDatabases([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.DatabaseName),
|
||||||
|
collections: ko.observableArray([
|
||||||
|
{
|
||||||
|
id: ko.observable(SavedQueries.CollectionName),
|
||||||
|
} as Collection,
|
||||||
|
]),
|
||||||
|
} as Database,
|
||||||
|
]);
|
||||||
|
const wrapper = shallow(<SaveQueryPane {...props} />);
|
||||||
|
expect(wrapper.exists("#saveQueryInput")).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { traceFailure, traceStart, traceSuccess } from "../../../Shared/Telemetr
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
import { NewQueryTab } from "../../Tabs/QueryTab/QueryTab";
|
||||||
|
import { useDatabases } from "../../useDatabases";
|
||||||
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
|
|
||||||
interface SaveQueryPaneProps {
|
interface SaveQueryPaneProps {
|
||||||
|
@ -24,11 +25,11 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||||
|
|
||||||
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
const setupSaveQueriesText = `For compliance reasons, we save queries in a container in your Azure Cosmos account, in a separate database called “${SavedQueries.DatabaseName}”. To proceed, we need to create a container in your account, estimated additional cost is $0.77 daily.`;
|
||||||
const title = "Save Query";
|
const title = "Save Query";
|
||||||
const { canSaveQueries } = explorer;
|
const isSaveQueryEnabled = useDatabases((state) => state.isSaveQueryEnabled);
|
||||||
|
|
||||||
const submit = async (): Promise<void> => {
|
const submit = async (): Promise<void> => {
|
||||||
setFormError("");
|
setFormError("");
|
||||||
if (!canSaveQueries()) {
|
if (!isSaveQueryEnabled()) {
|
||||||
setFormError("Cannot save query");
|
setFormError("Cannot save query");
|
||||||
logConsoleError("Failed to save query: account not setup to save queries");
|
logConsoleError("Failed to save query: account not setup to save queries");
|
||||||
}
|
}
|
||||||
|
@ -129,16 +130,16 @@ export const SaveQueryPane: FunctionComponent<SaveQueryPaneProps> = ({ explorer
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
formError: formError,
|
formError: formError,
|
||||||
isExecuting: isLoading,
|
isExecuting: isLoading,
|
||||||
submitButtonText: canSaveQueries() ? "Save" : "Complete setup",
|
submitButtonText: isSaveQueryEnabled() ? "Save" : "Complete setup",
|
||||||
onSubmit: () => {
|
onSubmit: () => {
|
||||||
canSaveQueries() ? submit() : setupQueries();
|
isSaveQueryEnabled() ? submit() : setupQueries();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="panelFormWrapper">
|
<div className="panelFormWrapper">
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
{!canSaveQueries() ? (
|
{!isSaveQueryEnabled() ? (
|
||||||
<Text variant="small">{setupSaveQueriesText}</Text>
|
<Text variant="small">{setupSaveQueriesText}</Text>
|
||||||
) : (
|
) : (
|
||||||
<TextField
|
<TextField
|
||||||
|
|
|
@ -9,8 +9,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
|
||||||
Explorer {
|
Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
"_resetNotebookWorkspace": [Function],
|
"_resetNotebookWorkspace": [Function],
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
"isAccountReady": [Function],
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
"isNotebookEnabled": [Function],
|
"isNotebookEnabled": [Function],
|
||||||
|
|
|
@ -4,50 +4,10 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
<DeleteDatabaseConfirmationPanel
|
<DeleteDatabaseConfirmationPanel
|
||||||
closePanel={[Function]}
|
closePanel={[Function]}
|
||||||
explorer={
|
explorer={
|
||||||
Explorer {
|
Object {
|
||||||
"_isInitializingNotebooks": false,
|
|
||||||
"_resetNotebookWorkspace": [Function],
|
|
||||||
"canSaveQueries": [Function],
|
|
||||||
"databases": [Function],
|
|
||||||
"isAccountReady": [Function],
|
|
||||||
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
|
||||||
"isLastCollection": [Function],
|
|
||||||
"isLastNonEmptyDatabase": [Function],
|
|
||||||
"isNotebookEnabled": [Function],
|
|
||||||
"isNotebooksEnabledForAccount": [Function],
|
|
||||||
"isResourceTokenCollectionNodeSelected": [Function],
|
|
||||||
"isSchemaEnabled": [Function],
|
|
||||||
"isSelectedDatabaseShared": [Function],
|
"isSelectedDatabaseShared": [Function],
|
||||||
"isShellEnabled": [Function],
|
|
||||||
"isSynapseLinkUpdating": [Function],
|
|
||||||
"isTabsContentExpanded": [Function],
|
|
||||||
"memoryUsageInfo": [Function],
|
|
||||||
"notebookBasePath": [Function],
|
|
||||||
"notebookServerInfo": [Function],
|
|
||||||
"onRefreshDatabasesKeyPress": [Function],
|
|
||||||
"onRefreshResourcesClick": [Function],
|
|
||||||
"provideFeedbackEmail": [Function],
|
|
||||||
"queriesClient": QueriesClient {
|
|
||||||
"container": [Circular],
|
|
||||||
},
|
|
||||||
"refreshAllDatabases": [Function],
|
"refreshAllDatabases": [Function],
|
||||||
"refreshNotebookList": [Function],
|
|
||||||
"resourceTokenCollection": [Function],
|
|
||||||
"resourceTree": ResourceTreeAdapter {
|
|
||||||
"container": [Circular],
|
|
||||||
"copyNotebook": [Function],
|
|
||||||
"databaseCollectionIdMap": Map {},
|
|
||||||
"koSubsCollectionIdMap": Map {},
|
|
||||||
"koSubsDatabaseIdMap": Map {},
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"resourceTreeForResourceToken": ResourceTreeAdapterForResourceToken {
|
|
||||||
"container": [Circular],
|
|
||||||
"parameters": [Function],
|
|
||||||
},
|
|
||||||
"selectedDatabaseId": [Function],
|
|
||||||
"selectedNode": [Function],
|
"selectedNode": [Function],
|
||||||
"sparkClusterConnectionInfo": [Function],
|
|
||||||
"tabsManager": TabsManager {
|
"tabsManager": TabsManager {
|
||||||
"activeTab": [Function],
|
"activeTab": [Function],
|
||||||
"openedTabs": [Function],
|
"openedTabs": [Function],
|
||||||
|
@ -749,7 +709,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-77"
|
className="css-69"
|
||||||
>
|
>
|
||||||
Help us improve Azure Cosmos DB!
|
Help us improve Azure Cosmos DB!
|
||||||
</span>
|
</span>
|
||||||
|
@ -759,7 +719,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
variant="small"
|
variant="small"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="css-77"
|
className="css-69"
|
||||||
>
|
>
|
||||||
What is the reason why you are deleting this database?
|
What is the reason why you are deleting this database?
|
||||||
</span>
|
</span>
|
||||||
|
@ -1067,11 +1027,11 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
className="ms-TextField-wrapper"
|
className="ms-TextField-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="ms-TextField-fieldGroup fieldGroup-78"
|
className="ms-TextField-fieldGroup fieldGroup-70"
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
aria-invalid={false}
|
aria-invalid={false}
|
||||||
className="ms-TextField-field field-79"
|
className="ms-TextField-field field-71"
|
||||||
id="deleteDatabaseFeedbackInput"
|
id="deleteDatabaseFeedbackInput"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -2797,7 +2757,7 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="OK"
|
aria-label="OK"
|
||||||
className="ms-Button ms-Button--primary root-69"
|
className="ms-Button ms-Button--primary root-73"
|
||||||
data-is-focusable={true}
|
data-is-focusable={true}
|
||||||
id="sidePanelOkButton"
|
id="sidePanelOkButton"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
|
@ -2809,16 +2769,16 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-flexContainer flexContainer-70"
|
className="ms-Button-flexContainer flexContainer-74"
|
||||||
data-automationid="splitbuttonprimary"
|
data-automationid="splitbuttonprimary"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-textContainer textContainer-71"
|
className="ms-Button-textContainer textContainer-75"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="ms-Button-label label-73"
|
className="ms-Button-label label-77"
|
||||||
id="id__3"
|
id="id__6"
|
||||||
key="id__3"
|
key="id__6"
|
||||||
>
|
>
|
||||||
OK
|
OK
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLaunc
|
||||||
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
|
|
||||||
export interface SplashScreenItem {
|
export interface SplashScreenItem {
|
||||||
iconSrc: string;
|
iconSrc: string;
|
||||||
|
@ -308,8 +309,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
|
||||||
title: collectionId,
|
title: collectionId,
|
||||||
description: "Data",
|
description: "Data",
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const collection = this.container.findCollection(databaseId, collectionId);
|
const collection = useDatabases.getState().findCollection(databaseId, collectionId);
|
||||||
collection && collection.openTab();
|
collection?.openTab();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import MongoShellTab from "../Tabs/MongoShellTab";
|
||||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import ConflictId from "./ConflictId";
|
import ConflictId from "./ConflictId";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId from "./DocumentId";
|
||||||
import StoredProcedure from "./StoredProcedure";
|
import StoredProcedure from "./StoredProcedure";
|
||||||
|
@ -1153,7 +1154,7 @@ export default class Collection implements ViewModels.Collection {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDatabase(): ViewModels.Database {
|
public getDatabase(): ViewModels.Database {
|
||||||
return this.container.findDatabaseWithId(this.databaseId);
|
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadOffer(): Promise<void> {
|
public async loadOffer(): Promise<void> {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Explorer from "../Explorer";
|
||||||
import DocumentsTab from "../Tabs/DocumentsTab";
|
import DocumentsTab from "../Tabs/DocumentsTab";
|
||||||
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
import { NewQueryTab } from "../Tabs/QueryTab/QueryTab";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId from "./DocumentId";
|
||||||
|
|
||||||
export default class ResourceTokenCollection implements ViewModels.CollectionBase {
|
export default class ResourceTokenCollection implements ViewModels.CollectionBase {
|
||||||
|
@ -151,6 +152,6 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDatabase(): ViewModels.Database {
|
public getDatabase(): ViewModels.Database {
|
||||||
return this.container.findDatabaseWithId(this.databaseId);
|
return useDatabases.getState().findDatabaseWithId(this.databaseId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
||||||
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
import { NotebookUtil } from "../Notebook/NotebookUtil";
|
||||||
import TabsBase from "../Tabs/TabsBase";
|
import TabsBase from "../Tabs/TabsBase";
|
||||||
|
import { useDatabases } from "../useDatabases";
|
||||||
import StoredProcedure from "./StoredProcedure";
|
import StoredProcedure from "./StoredProcedure";
|
||||||
import Trigger from "./Trigger";
|
import Trigger from "./Trigger";
|
||||||
import UserDefinedFunction from "./UserDefinedFunction";
|
import UserDefinedFunction from "./UserDefinedFunction";
|
||||||
|
@ -66,15 +67,18 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
this.koSubsCollectionIdMap = new ArrayHashMap();
|
this.koSubsCollectionIdMap = new ArrayHashMap();
|
||||||
this.databaseCollectionIdMap = new ArrayHashMap();
|
this.databaseCollectionIdMap = new ArrayHashMap();
|
||||||
|
|
||||||
this.container.databases.subscribe((databases: ViewModels.Database[]) => {
|
useDatabases.subscribe(
|
||||||
|
(databases: ViewModels.Database[]) => {
|
||||||
// Clean up old databases
|
// Clean up old databases
|
||||||
this.cleanupDatabasesKoSubs();
|
this.cleanupDatabasesKoSubs();
|
||||||
|
|
||||||
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
});
|
},
|
||||||
|
(state) => state.databases
|
||||||
|
);
|
||||||
|
|
||||||
this.container.databases().forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
useDatabases.getState().databases.forEach((database: ViewModels.Database) => this.watchDatabase(database));
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +196,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildDataTree(): TreeNode {
|
private buildDataTree(): TreeNode {
|
||||||
const databaseTreeNodes: TreeNode[] = this.container.databases().map((database: ViewModels.Database) => {
|
const databaseTreeNodes: TreeNode[] = useDatabases.getState().databases.map((database: ViewModels.Database) => {
|
||||||
const databaseNode: TreeNode = {
|
const databaseNode: TreeNode = {
|
||||||
label: database.id(),
|
label: database.id(),
|
||||||
iconSrc: CosmosDBIcon,
|
iconSrc: CosmosDBIcon,
|
||||||
|
@ -882,7 +886,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
this.addKoSubToCollectionId(
|
this.addKoSubToCollectionId(
|
||||||
databaseId,
|
databaseId,
|
||||||
collection.id(),
|
collection.id(),
|
||||||
collection.storedProcedures.subscribe(() => {
|
collection.storedProcedures?.subscribe(() => {
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -890,7 +894,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
this.addKoSubToCollectionId(
|
this.addKoSubToCollectionId(
|
||||||
databaseId,
|
databaseId,
|
||||||
collection.id(),
|
collection.id(),
|
||||||
collection.isCollectionExpanded.subscribe(() => {
|
collection.isCollectionExpanded?.subscribe(() => {
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -898,7 +902,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||||
this.addKoSubToCollectionId(
|
this.addKoSubToCollectionId(
|
||||||
databaseId,
|
databaseId,
|
||||||
collection.id(),
|
collection.id(),
|
||||||
collection.isStoredProceduresExpanded.subscribe(() => {
|
collection.isStoredProceduresExpanded?.subscribe(() => {
|
||||||
this.triggerRender();
|
this.triggerRender();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
import _ from "underscore";
|
||||||
|
import create, { UseStore } from "zustand";
|
||||||
|
import * as Constants from "../Common/Constants";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
|
||||||
|
interface DatabasesState {
|
||||||
|
databases: ViewModels.Database[];
|
||||||
|
updateDatabase: (database: ViewModels.Database) => void;
|
||||||
|
addDatabases: (databases: ViewModels.Database[]) => void;
|
||||||
|
deleteDatabase: (database: ViewModels.Database) => void;
|
||||||
|
clearDatabases: () => void;
|
||||||
|
isSaveQueryEnabled: () => boolean;
|
||||||
|
findDatabaseWithId: (databaseId: string) => ViewModels.Database;
|
||||||
|
isLastNonEmptyDatabase: () => boolean;
|
||||||
|
findCollection: (databaseId: string, collectionId: string) => ViewModels.Collection;
|
||||||
|
isLastCollection: () => boolean;
|
||||||
|
loadDatabaseOffers: () => Promise<void>;
|
||||||
|
isFirstResourceCreated: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDatabases: UseStore<DatabasesState> = create((set, get) => ({
|
||||||
|
databases: [],
|
||||||
|
updateDatabase: (updatedDatabase: ViewModels.Database) =>
|
||||||
|
set((state) => {
|
||||||
|
const updatedDatabases = state.databases.map((database: ViewModels.Database) => {
|
||||||
|
if (database.id() === updatedDatabase.id()) {
|
||||||
|
return updatedDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
return database;
|
||||||
|
});
|
||||||
|
return { databases: updatedDatabases };
|
||||||
|
}),
|
||||||
|
addDatabases: (databases: ViewModels.Database[]) =>
|
||||||
|
set((state) => ({
|
||||||
|
databases: [...state.databases, ...databases].sort((db1, db2) => db1.id().localeCompare(db2.id())),
|
||||||
|
})),
|
||||||
|
deleteDatabase: (database: ViewModels.Database) =>
|
||||||
|
set((state) => ({ databases: state.databases.filter((db) => database.id() !== db.id()) })),
|
||||||
|
clearDatabases: () => set(() => ({ databases: [] })),
|
||||||
|
isSaveQueryEnabled: () => {
|
||||||
|
const savedQueriesDatabase: ViewModels.Database = _.find(
|
||||||
|
get().databases,
|
||||||
|
(database: ViewModels.Database) => database.id() === Constants.SavedQueries.DatabaseName
|
||||||
|
);
|
||||||
|
if (!savedQueriesDatabase) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const savedQueriesCollection: ViewModels.Collection =
|
||||||
|
savedQueriesDatabase &&
|
||||||
|
_.find(
|
||||||
|
savedQueriesDatabase.collections(),
|
||||||
|
(collection: ViewModels.Collection) => collection.id() === Constants.SavedQueries.CollectionName
|
||||||
|
);
|
||||||
|
if (!savedQueriesCollection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
findDatabaseWithId: (databaseId: string) => get().databases.find((db) => databaseId === db.id()),
|
||||||
|
isLastNonEmptyDatabase: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
return databases.length === 1 && (databases[0].collections()?.length > 0 || !!databases[0].offer());
|
||||||
|
},
|
||||||
|
findCollection: (databaseId: string, collectionId: string) => {
|
||||||
|
const database = get().findDatabaseWithId(databaseId);
|
||||||
|
return database?.collections()?.find((collection) => collection.id() === collectionId);
|
||||||
|
},
|
||||||
|
isLastCollection: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
if (databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let collectionCount = 0;
|
||||||
|
for (let i = 0; i < databases.length; i++) {
|
||||||
|
const database = databases[i];
|
||||||
|
collectionCount += database.collections().length;
|
||||||
|
if (collectionCount > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
loadDatabaseOffers: async () => {
|
||||||
|
await Promise.all(
|
||||||
|
get().databases?.map(async (database: ViewModels.Database) => {
|
||||||
|
await database.loadOffer();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isFirstResourceCreated: () => {
|
||||||
|
const databases = get().databases;
|
||||||
|
|
||||||
|
if (!databases || databases.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases.some((database) => {
|
||||||
|
// user has created at least one collection
|
||||||
|
if (database.collections()?.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// user has created a database with shared throughput
|
||||||
|
if (database.offer()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// use has created an empty database without shared throughput
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
|
@ -5,6 +5,7 @@ import * as Constants from "../Common/Constants";
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ScriptTabBase from "../Explorer/Tabs/ScriptTabBase";
|
import ScriptTabBase from "../Explorer/Tabs/ScriptTabBase";
|
||||||
import TabsBase from "../Explorer/Tabs/TabsBase";
|
import TabsBase from "../Explorer/Tabs/TabsBase";
|
||||||
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
export class TabRouteHandler {
|
export class TabRouteHandler {
|
||||||
|
@ -248,12 +249,11 @@ export class TabRouteHandler {
|
||||||
|
|
||||||
private _openDatabaseSettingsTabForResource(databaseId: string): void {
|
private _openDatabaseSettingsTabForResource(databaseId: string): void {
|
||||||
this._executeActionHelper(() => {
|
this._executeActionHelper(() => {
|
||||||
const explorer = window.dataExplorer;
|
|
||||||
const database: ViewModels.Database = _.find(
|
const database: ViewModels.Database = _.find(
|
||||||
explorer.databases(),
|
useDatabases.getState().databases,
|
||||||
(database: ViewModels.Database) => database.id() === databaseId
|
(database: ViewModels.Database) => database.id() === databaseId
|
||||||
);
|
);
|
||||||
database && database.onSettingsClick();
|
database?.onSettingsClick();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +391,7 @@ export class TabRouteHandler {
|
||||||
|
|
||||||
private _findMatchingCollectionForResource(databaseId: string, collectionId: string): ViewModels.Collection {
|
private _findMatchingCollectionForResource(databaseId: string, collectionId: string): ViewModels.Collection {
|
||||||
const explorer = window.dataExplorer;
|
const explorer = window.dataExplorer;
|
||||||
const matchedDatabase: ViewModels.Database = explorer.findDatabaseWithId(databaseId);
|
const matchedDatabase: ViewModels.Database = useDatabases.getState().findDatabaseWithId(databaseId);
|
||||||
const matchedCollection: ViewModels.Collection =
|
const matchedCollection: ViewModels.Collection =
|
||||||
matchedDatabase && matchedDatabase.findCollectionWithId(collectionId);
|
matchedDatabase && matchedDatabase.findCollectionWithId(collectionId);
|
||||||
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import * as ko from "knockout";
|
|
||||||
import { Collection, Database } from "../Contracts/ViewModels";
|
|
||||||
import { getMaxThroughput } from "./AddCollectionUtility";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
|
||||||
|
|
||||||
describe("getMaxThroughput", () => {
|
|
||||||
it("default unlimited throughput setting", () => {
|
|
||||||
const defaults = {
|
|
||||||
storage: "100",
|
|
||||||
throughput: {
|
|
||||||
fixed: 400,
|
|
||||||
unlimited: 400,
|
|
||||||
unlimitedmax: 1000000,
|
|
||||||
unlimitedmin: 400,
|
|
||||||
shared: 400,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(getMaxThroughput(defaults, {} as Explorer)).toEqual(defaults.throughput.unlimited);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("no unlimited throughput setting", () => {
|
|
||||||
const defaults = {
|
|
||||||
storage: "100",
|
|
||||||
throughput: {
|
|
||||||
fixed: 400,
|
|
||||||
unlimited: {
|
|
||||||
collectionThreshold: 3,
|
|
||||||
lessThanOrEqualToThreshold: 400,
|
|
||||||
greatThanThreshold: 500,
|
|
||||||
},
|
|
||||||
unlimitedmax: 1000000,
|
|
||||||
unlimitedmin: 400,
|
|
||||||
shared: 400,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCollection1 = { id: ko.observable("collection1") } as Collection;
|
|
||||||
const mockCollection2 = { id: ko.observable("collection2") } as Collection;
|
|
||||||
const mockCollection3 = { id: ko.observable("collection3") } as Collection;
|
|
||||||
const mockCollection4 = { id: ko.observable("collection4") } as Collection;
|
|
||||||
const mockDatabase = {} as Database;
|
|
||||||
const mockContainer = {
|
|
||||||
databases: ko.observableArray([mockDatabase]),
|
|
||||||
} as Explorer;
|
|
||||||
|
|
||||||
it("less than or equal to collection threshold", () => {
|
|
||||||
mockDatabase.collections = ko.observableArray([mockCollection1, mockCollection2]);
|
|
||||||
expect(getMaxThroughput(defaults, mockContainer)).toEqual(
|
|
||||||
defaults.throughput.unlimited.lessThanOrEqualToThreshold
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("exceeds collection threshold", () => {
|
|
||||||
mockDatabase.collections = ko.observableArray([
|
|
||||||
mockCollection1,
|
|
||||||
mockCollection2,
|
|
||||||
mockCollection3,
|
|
||||||
mockCollection4,
|
|
||||||
]);
|
|
||||||
expect(getMaxThroughput(defaults, mockContainer)).toEqual(defaults.throughput.unlimited.greatThanThreshold);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { any } from "underscore";
|
|
||||||
import Explorer from "../Explorer/Explorer";
|
|
||||||
import { CollectionCreationDefaults } from "../UserContext";
|
|
||||||
|
|
||||||
export const getMaxThroughput = (defaults: CollectionCreationDefaults, container: Explorer): number => {
|
|
||||||
const throughput = defaults.throughput.unlimited;
|
|
||||||
if (typeof throughput === "number") {
|
|
||||||
return throughput;
|
|
||||||
} else {
|
|
||||||
return _exceedsThreshold(throughput.collectionThreshold, container)
|
|
||||||
? throughput.greatThanThreshold
|
|
||||||
: throughput.lessThanOrEqualToThreshold;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const _exceedsThreshold = (unlimitedThreshold: number, container: Explorer): boolean => {
|
|
||||||
const databases = (container && container.databases && container.databases()) || [];
|
|
||||||
return any(
|
|
||||||
databases,
|
|
||||||
(database) =>
|
|
||||||
database && database.collections && database.collections() && database.collections().length > unlimitedThreshold
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -10,6 +10,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
|
||||||
import Explorer, { ExplorerParams } from "../Explorer/Explorer";
|
import Explorer, { ExplorerParams } from "../Explorer/Explorer";
|
||||||
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
import { handleOpenAction } from "../Explorer/OpenActions/OpenActions";
|
||||||
|
import { useDatabases } from "../Explorer/useDatabases";
|
||||||
import {
|
import {
|
||||||
AAD,
|
AAD,
|
||||||
ConnectionString,
|
ConnectionString,
|
||||||
|
@ -253,7 +254,7 @@ async function configurePortal(explorerParams: ExplorerParams): Promise<Explorer
|
||||||
const explorer = new Explorer(explorerParams);
|
const explorer = new Explorer(explorerParams);
|
||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
if (openAction) {
|
if (openAction) {
|
||||||
handleOpenAction(openAction, explorer.databases(), explorer);
|
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||||
}
|
}
|
||||||
} else if (shouldForwardMessage(message, event.origin)) {
|
} else if (shouldForwardMessage(message, event.origin)) {
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
|
|
Loading…
Reference in New Issue