Initial Move from Azure DevOps to GitHub

This commit is contained in:
Steve Faulkner
2020-05-25 21:30:55 -05:00
commit 36581fb6d9
986 changed files with 195242 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
import * as ko from "knockout";
import * as sinon from "sinon";
import * as ViewModels from "../../Contracts/ViewModels";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Q from "q";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): ExplorerStub => {
const explorerStub = new ExplorerStub();
explorerStub.nonSystemDatabases = ko.computed(() => [database]);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Q.resolve();
return explorerStub;
};
it("should insert documents for sql API account", async () => {
const sampleCollectionId = "SampleCollection";
const sampleDatabaseId = "SampleDB";
const sampleData = {
databaseId: sampleDatabaseId,
offerThroughput: 400,
databaseLevelThroughput: false,
collectionId: sampleCollectionId,
rupmEnabled: false,
data: [
{
firstname: "Eva",
age: 44
},
{
firstname: "Véronique",
age: 50
},
{
firstname: "亜妃子",
age: 5
},
{
firstname: "John",
age: 23
}
]
};
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
database.findCollectionWithId = () => collection;
const explorerStub = createExplorerStub(database);
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => true);
const fakeDocumentClientUtility = sinon.createStubInstance(DocumentClientUtilityBase);
fakeDocumentClientUtility.getOrCreateDatabaseAndCollection.returns(Q.resolve(collection));
fakeDocumentClientUtility.createDocument.returns(Q.resolve());
explorerStub.documentClientUtility = fakeDocumentClientUtility;
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData);
await generator.createSampleContainerAsync();
expect(fakeDocumentClientUtility.createDocument.called).toBe(true);
});
it("should send gremlin queries for Graph API account", async () => {
sinon.stub(GremlinClient.prototype, "initialize").callsFake(() => {});
const executeStub = sinon.stub(GremlinClient.prototype, "execute").returns(Q.resolve());
sinon.stub(CosmosClient, "databaseAccount").returns({
properties: {}
});
const sampleCollectionId = "SampleCollection";
const sampleDatabaseId = "SampleDB";
const sampleData = {
databaseId: sampleDatabaseId,
offerThroughput: 400,
databaseLevelThroughput: false,
collectionId: sampleCollectionId,
rupmEnabled: false,
data: [
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
]
};
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
database.findCollectionWithId = () => collection;
collection.databaseId = database.id();
const explorerStub = createExplorerStub(database);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => true);
const fakeDocumentClientUtility = sinon.createStubInstance(DocumentClientUtilityBase);
fakeDocumentClientUtility.getOrCreateDatabaseAndCollection.returns(Q.resolve(collection));
fakeDocumentClientUtility.createDocument.returns(Q.resolve());
explorerStub.documentClientUtility = fakeDocumentClientUtility;
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
generator.setData(sampleData);
await generator.createSampleContainerAsync();
expect(fakeDocumentClientUtility.createDocument.called).toBe(false);
expect(executeStub.called).toBe(true);
});
it("should not create any sample for Mongo API account", async () => {
const experience = "not supported api";
const explorerStub = createExplorerStub(undefined);
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => true);
explorerStub.defaultExperience = ko.observable<string>(experience);
// Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});
it("should not create any sample for Table API account", async () => {
const experience = "not supported api";
const explorerStub = createExplorerStub(undefined);
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => true);
explorerStub.defaultExperience = ko.observable<string>(experience);
// Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});
it("should not create any sample for Cassandra API account", async () => {
const experience = "not supported api";
const explorerStub = createExplorerStub(undefined);
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => true);
explorerStub.defaultExperience = ko.observable<string>(experience);
// Rejects with error that contains experience
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});
});

View File

@@ -0,0 +1,120 @@
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import GraphTab from ".././Tabs/GraphTab";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
data: any[];
}
export class ContainerSampleGenerator {
private sampleDataFile: SampleDataFile;
private constructor(private container: ViewModels.Explorer) {}
/**
* Factory function to load the json data file
*/
public static async createSampleGeneratorAsync(container: ViewModels.Explorer): Promise<ContainerSampleGenerator> {
const generator = new ContainerSampleGenerator(container);
let dataFileContent: any;
if (container.isPreferredApiGraph()) {
dataFileContent = await import(
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
);
} else if (container.isPreferredApiDocumentDB()) {
dataFileContent = await import(
/* webpackChunkName: "sqlSampleJsonData" */ "../../../sampleData/sqlSampleData.json"
);
} else {
return Promise.reject(`Sample generation not supported for this API ${container.defaultExperience()}`);
}
generator.setData(dataFileContent);
return generator;
}
public async createSampleContainerAsync(): Promise<void> {
const collection = await this.createContainerAsync();
this.populateContainerAsync(collection);
}
public getDatabaseId(): string {
return this.sampleDataFile.databaseId;
}
public getCollectionId(): string {
return this.sampleDataFile.collectionId;
}
private async createContainerAsync(): Promise<ViewModels.Collection> {
const createRequest: DataModels.CreateDatabaseAndCollectionRequest = {
...this.sampleDataFile
};
const options: any = {};
if (this.container.isPreferredApiMongoDB()) {
options.initialHeaders = options.initialHeaders || {};
options.initialHeaders[Constants.HttpHeaders.supportSpatialLegacyCoordinates] = true;
options.initialHeaders[Constants.HttpHeaders.usePolygonsSmallerThanAHemisphere] = true;
}
await this.container.documentClientUtility.getOrCreateDatabaseAndCollection(createRequest, options);
await this.container.refreshAllDatabases();
const database = this.container.findDatabaseWithId(this.sampleDataFile.databaseId);
if (!database) {
return undefined;
}
return database.findCollectionWithId(this.sampleDataFile.collectionId);
}
private async populateContainerAsync(collection: ViewModels.Collection): Promise<void> {
if (!collection) {
throw new Error("No container to populate");
}
const promises: Q.Promise<any>[] = [];
if (this.container.isPreferredApiGraph()) {
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
// (e.g. adding edge requires vertices to be present)
const queries: string[] = this.sampleDataFile.data;
if (!queries || queries.length < 1) {
return;
}
const account = CosmosClient.databaseAccount();
const databaseId = collection.databaseId;
const gremlinClient = new GremlinClient();
gremlinClient.initialize({
endpoint: `wss://${GraphTab.getGremlinEndpoint(account)}`,
databaseId: databaseId,
collectionId: collection.id(),
masterKey: CosmosClient.masterKey() || "",
maxResultSize: 100
});
await queries
.map(query => () => gremlinClient.execute(query))
.reduce((previous, current) => previous.then(current), Promise.resolve());
} else {
// For SQL all queries are executed at the same time
this.sampleDataFile.data.forEach(doc => {
const subPromise = this.container.documentClientUtility.createDocument(collection, doc);
subPromise.catch(reason => NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, reason));
promises.push(subPromise);
});
await Promise.all(promises);
}
}
/**
* public for unit testing
* @param data
*/
public setData(data: SampleDataFile) {
this.sampleDataFile = data;
}
}

View File

@@ -0,0 +1,32 @@
import { ExplorerStub, DatabaseStub, CollectionStub } from "../OpenActionsStubs";
import { DataSamplesUtil } from "./DataSamplesUtil";
import * as sinon from "sinon";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as ko from "knockout";
describe("DataSampleUtils", () => {
const sampleCollectionId = "sampleCollectionId";
const sampleDatabaseId = "sampleDatabaseId";
it("should not create sample collection if collection already exists", async () => {
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
const explorer = new ExplorerStub();
explorer.nonSystemDatabases = ko.computed(() => [database]);
explorer.showOkModalDialog = () => {};
const dataSamplesUtil = new DataSamplesUtil(explorer);
const fakeGenerator = sinon.createStubInstance<ContainerSampleGenerator>(ContainerSampleGenerator as any);
fakeGenerator.getCollectionId.returns(sampleCollectionId);
fakeGenerator.getDatabaseId.returns(sampleDatabaseId);
fakeGenerator.createSampleContainerAsync.returns(Promise.resolve());
sinon.stub(dataSamplesUtil, "createGeneratorAsync").returns(fakeGenerator);
await dataSamplesUtil.createSampleContainerAsync();
expect((fakeGenerator.createSampleContainerAsync as sinon.SinonStub).called).toBe(false);
});
});

View File

@@ -0,0 +1,60 @@
import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
export class DataSamplesUtil {
private static readonly DialogTitle = "Create Sample Container";
constructor(private container: ViewModels.Explorer) {}
/**
* Check if Database/Container is already there: if so, show modal to delete
* If not, create and show modal to confirm.
*/
public async createSampleContainerAsync(): Promise<void> {
const generator = await this.createGeneratorAsync();
const databaseName = generator.getDatabaseId();
const containerName = generator.getCollectionId();
if (this.hasContainer(databaseName, containerName, this.container.nonSystemDatabases())) {
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
return;
}
await generator
.createSampleContainerAsync()
.catch(error =>
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Error creating sample container: ${error}`)
);
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
}
/**
* Public for unit tests
*/
public async createGeneratorAsync(): Promise<ContainerSampleGenerator> {
return await ContainerSampleGenerator.createSampleGeneratorAsync(this.container);
}
/**
* Public for unit tests
* @param databaseName
* @param containerName
* @param containerDatabases
*/
public hasContainer(databaseName: string, containerName: string, containerDatabases: ViewModels.Database[]): boolean {
const filteredDatabases = containerDatabases.filter(database => database.id() === databaseName);
return (
filteredDatabases.length > 0 &&
filteredDatabases[0].collections().filter(collection => collection.id() === containerName).length > 0
);
}
public isSampleContainerCreationSupported(): boolean {
return this.container.isPreferredApiDocumentDB() || this.container.isPreferredApiGraph();
}
}