Update to Prettier 2.0

This commit is contained in:
Steve Faulkner 2020-07-01 23:33:09 -05:00
parent 9a95c7d069
commit 1636f20978
441 changed files with 46537 additions and 46860 deletions

View File

@ -1,44 +1,44 @@
module.exports = { module.exports = {
env: { env: {
browser: true, browser: true,
es6: true es6: true,
}, },
plugins: ["@typescript-eslint", "no-null"], plugins: ["@typescript-eslint", "no-null"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
SharedArrayBuffer: "readonly" SharedArrayBuffer: "readonly",
}, },
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
ecmaFeatures: { ecmaFeatures: {
jsx: true jsx: true,
}, },
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: "module" sourceType: "module",
}, },
overrides: [ overrides: [
{ {
files: ["**/*.tsx"], files: ["**/*.tsx"],
env: { env: {
jest: true jest: true,
}, },
extends: ["plugin:react/recommended"], extends: ["plugin:react/recommended"],
plugins: ["react"] plugins: ["react"],
}, },
{ {
files: ["**/*.test.{ts,tsx}"], files: ["**/*.test.{ts,tsx}"],
env: { env: {
jest: true jest: true,
}, },
extends: ["plugin:jest/recommended"], extends: ["plugin:jest/recommended"],
plugins: ["jest"] plugins: ["jest"],
} },
], ],
rules: { rules: {
curly: "error", curly: "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error" "no-null/no-null": "error",
} },
}; };

View File

@ -1,3 +1,3 @@
module.exports = { module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"] presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"],
}; };

View File

@ -23,7 +23,7 @@ context("Cassandra API Test - createDatabase", () => {
const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`; const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`;
const tableId = `TableId112`; const tableId = `TableId112`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -32,27 +32,15 @@ context("Cassandra API Test - createDatabase", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[id="keyspace-id"]').should("be.visible").type(keyspaceId);
.find('input[id="keyspace-id"]')
.should("be.visible")
.type(keyspaceId);
cy.wrap($body) cy.wrap($body).find('input[class="textfontclr"]').type(tableId);
.find('input[class="textfontclr"]')
.type(tableId);
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('data-test="addCollection-createCollection"').click();
.find('data-test="addCollection-createCollection"')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -24,7 +24,7 @@ context("Graph API Test", () => {
const graphId = `TestGraph${crypt.randomBytes(8).toString("hex")}`; const graphId = `TestGraph${crypt.randomBytes(8).toString("hex")}`;
const partitionKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const partitionKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -33,39 +33,21 @@ context("Graph API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').should("be.visible").type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.should("be.visible")
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(graphId);
.find('input[data-test="addCollection-collectionId"]')
.type(graphId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(partitionKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(partitionKey);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -24,7 +24,7 @@ context("Mongo API Test - createDatabase", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -33,38 +33,21 @@ context("Mongo API Test - createDatabase", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find("#submitBtnAddCollection").click();
.find("#submitBtnAddCollection")
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -24,7 +24,7 @@ context("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -33,34 +33,23 @@ context("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body)
.find('div[class="throughputModeContainer"]') .find('div[class="throughputModeContainer"]')
.should("be.visible") .should("be.visible")
.and(input => { .and((input) => {
expect(input.get(0).textContent, "first item").contains("Autopilot (preview)"); expect(input.get(0).textContent, "first item").contains("Autopilot (preview)");
expect(input.get(1).textContent, "second item").contains("Manual"); expect(input.get(1).textContent, "second item").contains("Manual");
}); });
cy.wrap($body) cy.wrap($body).find('input[id="newContainer-databaseThroughput-autoPilotRadio"]').check();
.find('input[id="newContainer-databaseThroughput-autoPilotRadio"]')
.check();
cy.wrap($body) cy.wrap($body)
.find('select[name="autoPilotTiers"]') .find('select[name="autoPilotTiers"]')
@ -68,19 +57,13 @@ context("Mongo API Test", () => {
// // .select('4,000 RU/s').should('have.value', '1'); // // .select('4,000 RU/s').should('have.value', '1');
.find('option[value="2"]') .find('option[value="2"]')
.then($element => $element.get(1).setAttribute("selected", "selected")); .then(($element) => $element.get(1).setAttribute("selected", "selected"));
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -11,13 +11,13 @@ context("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('span[class="nodeLabel"]') .find('span[class="nodeLabel"]')
.should("be.visible") .should("be.visible")
.then($span => { .then(($span) => {
const dbId1 = $span.text(); const dbId1 = $span.text();
cy.log("DBBB", dbId1); cy.log("DBBB", dbId1);
@ -28,30 +28,17 @@ context("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-existingDatabase"]').check();
.find('input[data-test="addCollection-existingDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-existingDatabase"]').type(dbId1);
.find('input[data-test="addCollection-existingDatabase"]')
.type(dbId1);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -12,7 +12,7 @@ context.skip("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -21,50 +21,31 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body)
.find(".createNewDatabaseOrUseExisting") .find(".createNewDatabaseOrUseExisting")
.should("have.length", 2) .should("have.length", 2)
.and(input => { .and((input) => {
expect(input.get(0).textContent, "first item").contains("Create new"); expect(input.get(0).textContent, "first item").contains("Create new");
expect(input.get(1).textContent, "second item").contains("Use existing"); expect(input.get(1).textContent, "second item").contains("Use existing");
}); });
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);
@ -84,7 +65,7 @@ context.skip("Mongo API Test", () => {
const collectionIdTitle = `Add Collection`; const collectionIdTitle = `Add Collection`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -93,42 +74,23 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').uncheck();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.uncheck();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[id="tab2"]').check({ force: true });
.find('input[id="tab2"]')
.check({ force: true });
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);
@ -147,7 +109,7 @@ context.skip("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -156,38 +118,21 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').uncheck();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.uncheck();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[id="tab1"]').check({ force: true });
.find('input[id="tab1"]')
.check({ force: true });
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -25,7 +25,7 @@ context("SQL API Test", () => {
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString();
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -34,38 +34,21 @@ context("SQL API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check();
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check();
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId);
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey);
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body) cy.wrap($body).find("#submitBtnAddCollection").click();
.find("#submitBtnAddCollection")
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -22,7 +22,7 @@ context("Table API Test", () => {
it("Create a new table in Table API", () => { it("Create a new table in Table API", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => { cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@ -31,22 +31,13 @@ context("Table API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body) cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]');
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId);
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body) cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400");
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body) cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click();
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@ -29,7 +29,7 @@ context("Emulator - createDatabase", () => {
cy.get(".createNewDatabaseOrUseExisting") cy.get(".createNewDatabaseOrUseExisting")
.should("have.length", 2) .should("have.length", 2)
.and(input => { .and((input) => {
expect(input.get(0).textContent, "first item").contains("Create new"); expect(input.get(0).textContent, "first item").contains("Create new");
expect(input.get(1).textContent, "second item").contains("Use existing"); expect(input.get(1).textContent, "second item").contains("Use existing");
}); });

View File

@ -38,27 +38,15 @@ context("Emulator - Create database -> container -> item", () => {
cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk"); cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk");
cy.get('input[name="createCollection"]').click(); cy.get('input[name="createCollection"]').click();
cy.get(".dataResourceTree").should("contain", databaseId); cy.get(".dataResourceTree").should("contain", databaseId);
cy.get(".dataResourceTree") cy.get(".dataResourceTree").contains(databaseId).click();
.contains(databaseId)
.click();
cy.get(".dataResourceTree").should("contain", collectionId); cy.get(".dataResourceTree").should("contain", collectionId);
cy.get(".dataResourceTree") cy.get(".dataResourceTree").contains(collectionId).click();
.contains(collectionId) cy.get(".dataResourceTree").contains("Items").click();
.click(); cy.get(".dataResourceTree").contains("Items").click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".commandBarContainer") cy.get(".commandBarContainer").contains("New Item").click();
.contains("New Item")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".commandBarContainer") cy.get(".commandBarContainer").contains("Save").click();
.contains("Save")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".documentsGridHeaderContainer").should("contain", "replace_with_new_document_id"); cy.get(".documentsGridHeaderContainer").should("contain", "replace_with_new_document_id");
}); });

View File

@ -14,25 +14,18 @@ context("Emulator - deleteCollection", () => {
}); });
it("Delete a collection", () => { it("Delete a collection", () => {
cy.get(".databaseId") cy.get(".databaseId").last().click();
.last()
.click();
cy.get(".collectionList") cy.get(".collectionList")
.last() .last()
.then($id => { .then(($id) => {
const collectionId = $id.text(); const collectionId = $id.text();
cy.get('span[data-test="collectionEllipsisMenu"]').should("exist"); cy.get('span[data-test="collectionEllipsisMenu"]').should("exist");
cy.get('span[data-test="collectionEllipsisMenu"]') cy.get('span[data-test="collectionEllipsisMenu"]').invoke("show").last().click();
.invoke("show")
.last()
.click();
cy.get('div[data-test="collectionContextMenu"]') cy.get('div[data-test="collectionContextMenu"]').contains("Delete Container").click({ force: true });
.contains("Delete Container")
.click({ force: true });
cy.get('input[data-test="confirmCollectionId"]').type(collectionId.trim()); cy.get('input[data-test="confirmCollectionId"]').type(collectionId.trim());

View File

@ -22,10 +22,10 @@ context("Emulator - deleteDatabase", () => {
url: "https://localhost:8081/_explorer/authorization/post/dbs/", url: "https://localhost:8081/_explorer/authorization/post/dbs/",
headers: { headers: {
"x-ms-date": date, "x-ms-date": date,
authorization: "-" authorization: "-",
} },
}) })
.then(response => { .then((response) => {
authToken = response.body.Token; // Getting auth token for collection creation authToken = response.body.Token; // Getting auth token for collection creation
return new Cypress.Promise((resolve, reject) => { return new Cypress.Promise((resolve, reject) => {
return resolve(); return resolve();
@ -38,12 +38,12 @@ context("Emulator - deleteDatabase", () => {
headers: { headers: {
"x-ms-date": date, "x-ms-date": date,
authorization: authToken, authorization: authToken,
"x-ms-version": "2018-12-31" "x-ms-version": "2018-12-31",
}, },
body: { body: {
id: dbId id: dbId,
} },
}).then(response => { }).then((response) => {
cy.log("Response", response); cy.log("Response", response);
db_rid = response.body._rid; db_rid = response.body._rid;
return new Cypress.Promise((resolve, reject) => { return new Cypress.Promise((resolve, reject) => {
@ -59,19 +59,14 @@ context("Emulator - deleteDatabase", () => {
cy.get(".databaseId") cy.get(".databaseId")
.last() .last()
.then($id => { .then(($id) => {
const dbId = $id.text(); const dbId = $id.text();
cy.get('span[data-test="databaseEllipsisMenu"]').should("exist"); cy.get('span[data-test="databaseEllipsisMenu"]').should("exist");
cy.get('span[data-test="databaseEllipsisMenu"]') cy.get('span[data-test="databaseEllipsisMenu"]').invoke("show").last().click();
.invoke("show")
.last()
.click();
cy.get('div[data-test="databaseContextMenu"]') cy.get('div[data-test="databaseContextMenu"]').contains("Delete Database").click({ force: true });
.contains("Delete Database")
.click({ force: true });
cy.get('input[data-test="confirmDatabaseId"]').type(dbId.trim()); cy.get('input[data-test="confirmDatabaseId"]').type(dbId.trim());

View File

@ -21,29 +21,25 @@ context("New Notebook smoke test", () => {
cy.contains("New Notebook").click(); cy.contains("New Notebook").click();
// Check tab name // Check tab name
cy.get("li.tabList .tabNavText").should($span => { cy.get("li.tabList .tabNavText").should(($span) => {
const text = $span.text(); const text = $span.text();
expect(text).to.match(/^Untitled.*\.ipynb$/); expect(text).to.match(/^Untitled.*\.ipynb$/);
}); });
// Wait for python3 | idle status // Wait for python3 | idle status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*idle$/); expect(text).to.match(/^python3.*idle$/);
}); });
// Click on a cell // Click on a cell
cy.get(".cell-container") cy.get(".cell-container").as("cellContainer").click();
.as("cellContainer")
.click();
// Type in some code // Type in some code
cy.get("@cellContainer").type("2+4"); cy.get("@cellContainer").type("2+4");
// Execute // Execute
cy.get('[data-test="Run"]') cy.get('[data-test="Run"]').first().click();
.first()
.click();
// Verify results // Verify results
cy.get("@cellContainer").within(() => { cy.get("@cellContainer").within(() => {
@ -51,39 +47,29 @@ context("New Notebook smoke test", () => {
}); });
// Restart kernel // Restart kernel
cy.get('[data-test="Run"] button') cy.get('[data-test="Run"] button').eq(-1).click();
.eq(-1) cy.get("li").contains("Restart Kernel").click();
.click();
cy.get("li")
.contains("Restart Kernel")
.click();
// Wait for python3 | restarting status // Wait for python3 | restarting status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*restarting$/); expect(text).to.match(/^python3.*restarting$/);
}); });
// Wait for python3 | idle status // Wait for python3 | idle status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*idle$/); expect(text).to.match(/^python3.*idle$/);
}); });
// Click on a cell // Click on a cell
cy.get(".cell-container") cy.get(".cell-container").as("cellContainer").find(".input").as("codeInput").click();
.as("cellContainer")
.find(".input")
.as("codeInput")
.click();
// Type in some code // Type in some code
cy.get("@codeInput").type("{backspace}{backspace}{backspace}4+5"); cy.get("@codeInput").type("{backspace}{backspace}{backspace}4+5");
// Execute // Execute
cy.get('[data-test="Run"]') cy.get('[data-test="Run"]').first().click();
.first()
.click();
// Verify results // Verify results
cy.get("@cellContainer").within(() => { cy.get("@cellContainer").within(() => {

View File

@ -11,15 +11,11 @@ context("Resource tree notebook file manipulation", () => {
}; };
const clickContextMenuAndSelectOption = (nodeLabel, option) => { const clickContextMenuAndSelectOption = (nodeLabel, option) => {
cy.get(`.treeNodeHeader[data-test="${nodeLabel}"]`) cy.get(`.treeNodeHeader[data-test="${nodeLabel}"]`).find("button.treeMenuEllipsis").click();
.find("button.treeMenuEllipsis") cy.get('[data-test="treeComponentMenuItemContainer"]').contains(option).click();
.click();
cy.get('[data-test="treeComponentMenuItemContainer"]')
.contains(option)
.click();
}; };
const createFolder = folder => { const createFolder = (folder) => {
clickContextMenuAndSelectOption("My Notebooks/", "New Directory"); clickContextMenuAndSelectOption("My Notebooks/", "New Directory");
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
@ -28,11 +24,9 @@ context("Resource tree notebook file manipulation", () => {
}); });
}; };
const deleteItem = nodeName => { const deleteItem = (nodeName) => {
clickContextMenuAndSelectOption(`${nodeName}`, "Delete"); clickContextMenuAndSelectOption(`${nodeName}`, "Delete");
cy.get(".ms-Dialog-main") cy.get(".ms-Dialog-main").contains("Delete").click();
.contains("Delete")
.click();
}; };
beforeEach(() => { beforeEach(() => {
@ -56,9 +50,7 @@ context("Resource tree notebook file manipulation", () => {
// Rename // Rename
clickContextMenuAndSelectOption(`${folder}/`, "Rename"); clickContextMenuAndSelectOption(`${folder}/`, "Rename");
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
cy.get('input[name="collectionIdConfirmation"]') cy.get('input[name="collectionIdConfirmation"]').clear().type(renamedFolder);
.clear()
.type(renamedFolder);
cy.get("form").submit(); cy.get("form").submit();
}); });
cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("exist"); cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("exist");
@ -75,16 +67,12 @@ context("Resource tree notebook file manipulation", () => {
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook"); clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
// Verify tab is open // Verify tab is open
cy.get(".tabList") cy.get(".tabList").contains(newNotebookName).should("exist");
.contains(newNotebookName)
.should("exist");
// Close tab // Close tab
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`) cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`).find(".cancelButton").click();
.find(".cancelButton")
.click();
// When running from command line, closing the tab is too fast // When running from command line, closing the tab is too fast
cy.get("body").then($body => { cy.get("body").then(($body) => {
if ($body.find(".ms-Dialog-main").length) { if ($body.find(".ms-Dialog-main").length) {
// For some reason, this does not work // For some reason, this does not work
// cy.get(".ms-Dialog-main").contains("Close").click(); // cy.get(".ms-Dialog-main").contains("Close").click();
@ -100,14 +88,10 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]') cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Delete").click();
.contains("Delete")
.click();
// Confirm // Confirm
cy.get(".ms-Dialog-main") cy.get(".ms-Dialog-main").contains("Delete").click();
.contains("Delete")
.click();
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist"); cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist");
deleteItem(`${folder}/`); deleteItem(`${folder}/`);
@ -121,10 +105,8 @@ context("Resource tree notebook file manipulation", () => {
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook"); clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
// Close tab // Close tab
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`) cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`).find(".cancelButton").click();
.find(".cancelButton") cy.get("body").then(($body) => {
.click();
cy.get("body").then($body => {
if ($body.find(".ms-Dialog-main").length) { if ($body.find(".ms-Dialog-main").length) {
// For some reason, this does not work // For some reason, this does not work
// cy.get(".ms-Dialog-main").contains("Close").click(); // cy.get(".ms-Dialog-main").contains("Close").click();
@ -140,14 +122,10 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]') cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Rename").click();
.contains("Rename")
.click();
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
cy.get('input[name="collectionIdConfirmation"]') cy.get('input[name="collectionIdConfirmation"]').clear().type(renamedNotebookName);
.clear()
.type(renamedNotebookName);
cy.get("form").submit(); cy.get("form").submit();
}); });
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist"); cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist");
@ -157,14 +135,10 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${renamedNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${renamedNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]') cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Delete").click();
.contains("Delete")
.click();
// Confirm // Confirm
cy.get(".ms-Dialog-main") cy.get(".ms-Dialog-main").contains("Delete").click();
.contains("Delete")
.click();
// Give it time to settle // Give it time to settle
cy.wait(1000); cy.wait(1000);
deleteItem(`${folder}/`); deleteItem(`${folder}/`);

View File

@ -4,6 +4,6 @@ module.exports = {
launch: { launch: {
headless: isCI, headless: isCI,
slowMo: isCI ? null : 20, slowMo: isCI ? null : 20,
defaultViewport: null defaultViewport: null,
} },
}; };

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
preset: "jest-puppeteer", preset: "jest-puppeteer",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"], testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"] setupFiles: ["dotenv/config"],
}; };

View File

@ -42,8 +42,8 @@ module.exports = {
branches: 18, branches: 18,
functions: 22, functions: 22,
lines: 28, lines: 28,
statements: 27 statements: 27,
} },
}, },
// Make calling deprecated APIs throw helpful error messages // Make calling deprecated APIs throw helpful error messages
@ -76,7 +76,7 @@ module.exports = {
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes "office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
"^dnd-core$": "dnd-core/dist/cjs", "^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs" "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
}, },
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@ -164,11 +164,11 @@ module.exports = {
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
transform: { transform: {
"^.+\\.html?$": "html-loader-jest", "^.+\\.html?$": "html-loader-jest",
"^.+\\.[t|j]sx?$": "babel-jest" "^.+\\.[t|j]sx?$": "babel-jest",
}, },
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: ["/node_modules/", "/externals/"] transformIgnorePatterns: ["/node_modules/", "/externals/"],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined, // unmockedModulePathPatterns: undefined,

6
package-lock.json generated
View File

@ -20463,9 +20463,9 @@
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
}, },
"prettier": { "prettier": {
"version": "1.19.1", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
"dev": true "dev": true
}, },
"pretty-error": { "pretty-error": {

View File

@ -146,7 +146,7 @@
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"prettier": "1.19.1", "prettier": "2.0.5",
"puppeteer": "4.0.0", "puppeteer": "4.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"rimraf": "3.0.0", "rimraf": "3.0.0",

View File

@ -1,13 +1,13 @@
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
export class DefaultApi implements ViewModels.CosmosDbApi { export class DefaultApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => { public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return false; return false;
}; };
} }
export class CassandraApi implements ViewModels.CosmosDbApi { export class CassandraApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => { public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return database.id() === "system"; return database.id() === "system";
}; };
} }

View File

@ -1,6 +1,6 @@
export enum AuthType { export enum AuthType {
AAD = "aad", AAD = "aad",
EncryptedToken = "encryptedtoken", EncryptedToken = "encryptedtoken",
MasterKey = "masterkey", MasterKey = "masterkey",
ResourceToken = "resourcetoken" ResourceToken = "resourcetoken",
} }

View File

@ -1,26 +1,26 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ReactBindingHandler from "./ReactBindingHandler"; import * as ReactBindingHandler from "./ReactBindingHandler";
interface RestorePoint { interface RestorePoint {
readonly element: JQuery; readonly element: JQuery;
readonly width: number; readonly width: number;
} }
export class BindingHandlersRegisterer { export class BindingHandlersRegisterer {
public static registerBindingHandlers() { public static registerBindingHandlers() {
ko.bindingHandlers.setTemplateReady = { ko.bindingHandlers.setTemplateReady = {
init( init(
element: any, element: any,
wrappedValueAccessor: () => any, wrappedValueAccessor: () => any,
allBindings?: ko.AllBindings, allBindings?: ko.AllBindings,
viewModel?: any, viewModel?: any,
bindingContext?: ko.BindingContext bindingContext?: ko.BindingContext
) { ) {
const value = ko.unwrap(wrappedValueAccessor()); const value = ko.unwrap(wrappedValueAccessor());
bindingContext.$data.isTemplateReady(value); bindingContext.$data.isTemplateReady(value);
} },
} as ko.BindingHandler; } as ko.BindingHandler;
ReactBindingHandler.Registerer.register(); ReactBindingHandler.Registerer.register();
} }
} }

View File

@ -42,7 +42,7 @@ export class Registerer {
// Initial rendering at mount point // Initial rendering at mount point
ReactDOM.render(adapter.renderComponent(), element); ReactDOM.render(adapter.renderComponent(), element);
} },
} as ko.BindingHandler; } as ko.BindingHandler;
} }
} }

View File

@ -40,7 +40,7 @@ export class ArrayHashMap<T> {
public forEach(key: string, iteratorFct: (value: T) => void) { public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key); const values = this.store.get(key);
if (values) { if (values) {
values.forEach(value => iteratorFct(value)); values.forEach((value) => iteratorFct(value));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ describe("tokenProvider", () => {
resourceId: "", resourceId: "",
resourceType: "dbs" as ResourceType, resourceType: "dbs" as ResourceType,
headers: {}, headers: {},
getAuthorizationTokenUsingMasterKey: () => "" getAuthorizationTokenUsingMasterKey: () => "",
}; };
beforeEach(() => { beforeEach(() => {
@ -17,7 +17,7 @@ describe("tokenProvider", () => {
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}; };
}); });
}); });
@ -45,7 +45,7 @@ describe("getTokenFromAuthService", () => {
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}; };
}); });
}); });
@ -86,8 +86,8 @@ describe("endpoint", () => {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo",
} },
}); });
expect(endpoint()).toEqual("bar"); expect(endpoint()).toEqual("bar");
}); });

View File

@ -63,13 +63,13 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-encrypted-auth-token": _accessToken "x-ms-encrypted-auth-token": _accessToken,
}, },
body: JSON.stringify({ body: JSON.stringify({
verb, verb,
resourceType, resourceType,
resourceId resourceId,
}) }),
}); });
//TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json() //TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json()
const result = JSON.parse(await response.json()); const result = JSON.parse(await response.json());
@ -93,9 +93,9 @@ export const CosmosClient = {
key: _masterKey, key: _masterKey,
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false enableEndpointDiscovery: false,
}, },
userAgentSuffix: "Azure Portal" userAgentSuffix: "Azure Portal",
}; };
// In development we proxy requests to the backend via webpack. This is removed in production bundles. // In development we proxy requests to the backend via webpack. This is removed in production bundles.
@ -176,5 +176,5 @@ export const CosmosClient = {
_client = null; _client = null;
_resourceToken = value; _resourceToken = value;
return value; return value;
} },
}; };

View File

@ -1,13 +1,13 @@
import { getCommonQueryOptions } from "./DataAccessUtilityBase"; import { getCommonQueryOptions } from "./DataAccessUtilityBase";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
describe("getCommonQueryOptions", () => { describe("getCommonQueryOptions", () => {
it("builds the correct default options objects", () => { it("builds the correct default options objects", () => {
expect(getCommonQueryOptions({})).toMatchSnapshot(); expect(getCommonQueryOptions({})).toMatchSnapshot();
}); });
it("reads from localStorage", () => { it("reads from localStorage", () => {
LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, 37); LocalStorageUtility.setEntryNumber(StorageKey.ActualItemPerPage, 37);
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, 17); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, 17);
expect(getCommonQueryOptions({})).toMatchSnapshot(); expect(getCommonQueryOptions({})).toMatchSnapshot();
}); });
}); });

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +1,94 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
export default class EditableUtility { export default class EditableUtility {
public static observable<T>(initialValue?: T): ViewModels.Editable<T> { public static observable<T>(initialValue?: T): ViewModels.Editable<T> {
var observable: ViewModels.Editable<T> = <ViewModels.Editable<T>>ko.observable<T>(initialValue); var observable: ViewModels.Editable<T> = <ViewModels.Editable<T>>ko.observable<T>(initialValue);
observable.edits = ko.observableArray<T>([initialValue]); observable.edits = ko.observableArray<T>([initialValue]);
observable.validations = ko.observableArray<(value: T) => boolean>([]); observable.validations = ko.observableArray<(value: T) => boolean>([]);
observable.setBaseline = (baseline: T) => { observable.setBaseline = (baseline: T) => {
observable(baseline); observable(baseline);
observable.edits([baseline]); observable.edits([baseline]);
}; };
observable.getEditableCurrentValue = ko.computed<T>(() => { observable.getEditableCurrentValue = ko.computed<T>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length === 0) { if (edits.length === 0) {
return undefined; return undefined;
} }
return edits[edits.length - 1]; return edits[edits.length - 1];
}); });
observable.getEditableOriginalValue = ko.computed<T>(() => { observable.getEditableOriginalValue = ko.computed<T>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length === 0) { if (edits.length === 0) {
return undefined; return undefined;
} }
return edits[0]; return edits[0];
}); });
observable.editableIsDirty = ko.computed<boolean>(() => { observable.editableIsDirty = ko.computed<boolean>(() => {
const edits = (observable.edits && observable.edits()) || []; const edits = (observable.edits && observable.edits()) || [];
if (edits.length <= 1) { if (edits.length <= 1) {
return false; return false;
} }
let current: any = observable.getEditableCurrentValue(); let current: any = observable.getEditableCurrentValue();
let original: any = observable.getEditableOriginalValue(); let original: any = observable.getEditableOriginalValue();
switch (typeof current) { switch (typeof current) {
case "string": case "string":
case "undefined": case "undefined":
case "number": case "number":
case "boolean": case "boolean":
current = current && current.toString(); current = current && current.toString();
break; break;
default: default:
current = JSON.stringify(current); current = JSON.stringify(current);
break; break;
} }
switch (typeof original) { switch (typeof original) {
case "string": case "string":
case "undefined": case "undefined":
case "number": case "number":
case "boolean": case "boolean":
original = original && original.toString(); original = original && original.toString();
break; break;
default: default:
original = JSON.stringify(original); original = JSON.stringify(original);
break; break;
} }
if (current !== original) { if (current !== original) {
return true; return true;
} }
return false; return false;
}); });
observable.subscribe(edit => { observable.subscribe((edit) => {
var edits = observable.edits && observable.edits(); var edits = observable.edits && observable.edits();
if (!edits) { if (!edits) {
return; return;
} }
edits.push(edit); edits.push(edit);
observable.edits(edits); observable.edits(edits);
}); });
observable.editableIsValid = ko.observable<boolean>(true); observable.editableIsValid = ko.observable<boolean>(true);
observable.subscribe(value => { observable.subscribe((value) => {
const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || []; const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || [];
const isValid = validations.every(validate => validate(value)); const isValid = validations.every((validate) => validate(value));
observable.editableIsValid(isValid); observable.editableIsValid(isValid);
}); });
return observable; return observable;
} }
} }

View File

@ -1,48 +1,48 @@
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { StringUtils } from "../Utils/StringUtils"; import { StringUtils } from "../Utils/StringUtils";
export default class EnvironmentUtility { export default class EnvironmentUtility {
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string { public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
const defaultEnvironment: string = "default"; const defaultEnvironment: string = "default";
const defaultLocation: string = "default"; const defaultLocation: string = "default";
let environment: string = serverId; let environment: string = serverId;
const endpointType: Constants.MongoBackendEndpointType = const endpointType: Constants.MongoBackendEndpointType =
Constants.MongoBackend.endpointsByEnvironment[environment] || Constants.MongoBackend.endpointsByEnvironment[environment] ||
Constants.MongoBackend.endpointsByEnvironment[defaultEnvironment]; Constants.MongoBackend.endpointsByEnvironment[defaultEnvironment];
if (endpointType === Constants.MongoBackendEndpointType.local) { if (endpointType === Constants.MongoBackendEndpointType.local) {
return `${extensionEndpoint}${Constants.MongoBackend.localhostEndpoint}`; return `${extensionEndpoint}${Constants.MongoBackend.localhostEndpoint}`;
} }
const normalizedLocation = EnvironmentUtility.normalizeRegionName(location); const normalizedLocation = EnvironmentUtility.normalizeRegionName(location);
return ( return (
Constants.MongoBackend.endpointsByRegion[normalizedLocation] || Constants.MongoBackend.endpointsByRegion[normalizedLocation] ||
Constants.MongoBackend.endpointsByRegion[defaultLocation] Constants.MongoBackend.endpointsByRegion[defaultLocation]
); );
} }
public static isAadUser(): boolean { public static isAadUser(): boolean {
return window.authType === AuthType.AAD; return window.authType === AuthType.AAD;
} }
public static getCassandraBackendEndpoint(explorer: ViewModels.Explorer): string { public static getCassandraBackendEndpoint(explorer: ViewModels.Explorer): string {
const defaultLocation: string = "default"; const defaultLocation: string = "default";
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location); const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
return ( return (
Constants.CassandraBackend.endpointsByRegion[location] || Constants.CassandraBackend.endpointsByRegion[location] ||
Constants.CassandraBackend.endpointsByRegion[defaultLocation] Constants.CassandraBackend.endpointsByRegion[defaultLocation]
); );
} }
public static normalizeArmEndpointUri(uri: string): string { public static normalizeArmEndpointUri(uri: string): string {
if (uri && uri.slice(-1) !== "/") { if (uri && uri.slice(-1) !== "/") {
return `${uri}/`; return `${uri}/`;
} }
return uri; return uri;
} }
private static normalizeRegionName(region: string): string { private static normalizeRegionName(region: string): string {
return region && StringUtils.stripSpacesFromString(region.toLocaleLowerCase()); return region && StringUtils.stripSpacesFromString(region.toLocaleLowerCase());
} }
} }

View File

@ -11,7 +11,7 @@ describe("Error Parser Utility", () => {
err.code = 400; err.code = 400;
err.body = { err.body = {
code: "BadRequest", code: "BadRequest",
message message,
}; };
err.headers = {}; err.headers = {};
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220"; err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";

View File

@ -26,7 +26,7 @@ function _parse(err: any): DataModels.ErrorDataModel[] {
normalizedErrors.push(err); normalizedErrors.push(err);
} else { } else {
const innerErrors: any[] = _getInnerErrors(err.message); const innerErrors: any[] = _getInnerErrors(err.message);
normalizedErrors = innerErrors.map(innerError => normalizedErrors = innerErrors.map((innerError) =>
typeof innerError === "string" ? { message: innerError } : innerError typeof innerError === "string" ? { message: innerError } : innerError
); );
} }

View File

@ -1,25 +1,25 @@
import * as HeadersUtility from "./HeadersUtility"; import * as HeadersUtility from "./HeadersUtility";
import { ExplorerSettings } from "../Shared/ExplorerSettings"; import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
describe("Headers Utility", () => { describe("Headers Utility", () => {
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => { describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
beforeEach(() => { beforeEach(() => {
ExplorerSettings.createDefaultSettings(); ExplorerSettings.createDefaultSettings();
}); });
it("should return true by default", () => { it("should return true by default", () => {
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true);
}); });
it("should return false if the enable cross partition key feed option is false", () => { it("should return false if the enable cross partition key feed option is false", () => {
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "false"); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "false");
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(false); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(false);
}); });
it("should return true if the enable cross partition key feed option is true", () => { it("should return true if the enable cross partition key feed option is true", () => {
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true"); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, "true");
expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true); expect(HeadersUtility.shouldEnableCrossPartitionKey()).toBe(true);
}); });
}); });
}); });

View File

@ -1,28 +1,28 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
// x-ms-resource-quota: databases = 100; collections = 5000; users = 500000; permissions = 2000000; // x-ms-resource-quota: databases = 100; collections = 5000; users = 500000; permissions = 2000000;
export function getQuota(responseHeaders: any): any { export function getQuota(responseHeaders: any): any {
return responseHeaders && responseHeaders[Constants.HttpHeaders.resourceQuota] return responseHeaders && responseHeaders[Constants.HttpHeaders.resourceQuota]
? parseStringIntoObject(responseHeaders[Constants.HttpHeaders.resourceQuota]) ? parseStringIntoObject(responseHeaders[Constants.HttpHeaders.resourceQuota])
: null; : null;
} }
export function shouldEnableCrossPartitionKey(): boolean { export function shouldEnableCrossPartitionKey(): boolean {
return LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"; return LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true";
} }
function parseStringIntoObject(resourceString: string) { function parseStringIntoObject(resourceString: string) {
var entityObject: any = {}; var entityObject: any = {};
if (resourceString) { if (resourceString) {
var entitiesArray: string[] = resourceString.split(";"); var entitiesArray: string[] = resourceString.split(";");
for (var i: any = 0; i < entitiesArray.length; i++) { for (var i: any = 0; i < entitiesArray.length; i++) {
var entity: string[] = entitiesArray[i].split("="); var entity: string[] = entitiesArray[i].split("=");
entityObject[entity[0]] = entity[1]; entityObject[entity[0]] = entity[1];
} }
} }
return entityObject; return entityObject;
} }

View File

@ -11,8 +11,8 @@ describe("nextPage", () => {
queryMetrics: {}, queryMetrics: {},
requestCharge: 1, requestCharge: 1,
headers: {}, headers: {},
activityId: "foo" activityId: "foo",
}) }),
}; };
expect(await nextPage(fakeIterator, 10)).toMatchSnapshot(); expect(await nextPage(fakeIterator, 10)).toMatchSnapshot();

View File

@ -14,7 +14,7 @@ export interface MinimalQueryIterator {
// Pick<QueryIterator<any>, "fetchNext">; // Pick<QueryIterator<any>, "fetchNext">;
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> { export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
return documentsIterator.fetchNext().then(response => { return documentsIterator.fetchNext().then((response) => {
const documents = response.resources; const documents = response.resources;
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any const headers = (response as any).headers || {}; // TODO this is a private key. Remove any
const itemCount = (documents && documents.length) || 0; const itemCount = (documents && documents.length) || 0;
@ -26,7 +26,7 @@ export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex
lastItemIndex: Number(firstItemIndex) + Number(itemCount), lastItemIndex: Number(firstItemIndex) + Number(itemCount),
headers, headers,
activityId: response.activityId, activityId: response.activityId,
requestCharge: response.requestCharge requestCharge: response.requestCharge,
}; };
}); });
} }

View File

@ -1,46 +1,46 @@
import { LogEntryLevel } from "../Contracts/Diagnostics"; import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler"; import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
describe("Logger", () => { describe("Logger", () => {
let sendMessageSpy: jasmine.Spy; let sendMessageSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
sendMessageSpy = spyOn(MessageHandler, "sendMessage"); sendMessageSpy = spyOn(MessageHandler, "sendMessage");
}); });
afterEach(() => { afterEach(() => {
sendMessageSpy = null; sendMessageSpy = null;
}); });
it("should log info messages", () => { it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB"); Logger.logInfo("Test info", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0]; const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo); expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Verbose); expect(spyArgs.data).toContain(LogEntryLevel.Verbose);
expect(spyArgs.data).toContain("DocDB"); expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test info"); expect(spyArgs.data).toContain("Test info");
}); });
it("should log error messages", () => { it("should log error messages", () => {
Logger.logError("Test error", "DocDB"); Logger.logError("Test error", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0]; const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo); expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Error); expect(spyArgs.data).toContain(LogEntryLevel.Error);
expect(spyArgs.data).toContain("DocDB"); expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test error"); expect(spyArgs.data).toContain("Test error");
}); });
it("should log warnings", () => { it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB"); Logger.logWarning("Test warning", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0]; const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo); expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Warning); expect(spyArgs.data).toContain(LogEntryLevel.Warning);
expect(spyArgs.data).toContain("DocDB"); expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test warning"); expect(spyArgs.data).toContain("Test warning");
}); });
}); });

View File

@ -35,7 +35,7 @@ export function logError(message: string | Error, area: string, code?: number):
function _logEntry(entry: Diagnostics.LogEntry): void { function _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.LogInfo, type: MessageTypes.LogInfo,
data: JSON.stringify(entry) data: JSON.stringify(entry),
}); });
const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => { const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => {
@ -66,6 +66,6 @@ function _generateLogEntry(
level: level, level: level,
message: message, message: message,
area: area, area: area,
code: code code: code,
}; };
} }

View File

@ -1,65 +1,65 @@
import Q from "q"; import Q from "q";
import { CachedDataPromise, MessageHandler } from "./MessageHandler"; import { CachedDataPromise, MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
class MockMessageHandler extends MessageHandler { class MockMessageHandler extends MessageHandler {
public static addToMap(key: string, value: CachedDataPromise<any>): void { public static addToMap(key: string, value: CachedDataPromise<any>): void {
MessageHandler.RequestMap[key] = value; MessageHandler.RequestMap[key] = value;
} }
public static mapContainsKey(key: string): boolean { public static mapContainsKey(key: string): boolean {
return MessageHandler.RequestMap[key] != null; return MessageHandler.RequestMap[key] != null;
} }
public static clearAllEntries(): void { public static clearAllEntries(): void {
MessageHandler.RequestMap = {}; MessageHandler.RequestMap = {};
} }
public static runGarbageCollector(): void { public static runGarbageCollector(): void {
MessageHandler.runGarbageCollector(); MessageHandler.runGarbageCollector();
} }
} }
describe("Message Handler", () => { describe("Message Handler", () => {
beforeEach(() => { beforeEach(() => {
MockMessageHandler.clearAllEntries(); MockMessageHandler.clearAllEntries();
}); });
xit("should send cached data message", (done: any) => { xit("should send cached data message", (done: any) => {
const testValidationCallback = (e: MessageEvent) => { const testValidationCallback = (e: MessageEvent) => {
expect(e.data.data).toEqual( expect(e.data.data).toEqual(
jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] }) jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] })
); );
e.currentTarget.removeEventListener(e.type, testValidationCallback); e.currentTarget.removeEventListener(e.type, testValidationCallback);
done(); done();
}; };
window.parent.addEventListener("message", testValidationCallback); window.parent.addEventListener("message", testValidationCallback);
MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]); MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]);
}); });
it("should handle cached message", () => { it("should handle cached message", () => {
let mockPromise: CachedDataPromise<any> = { let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>(),
}; };
let mockMessage = { message: { id: "123", data: "{}" } }; let mockMessage = { message: { id: "123", data: "{}" } };
MockMessageHandler.addToMap(mockPromise.id, mockPromise); MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MockMessageHandler.handleCachedDataMessage(mockMessage); MockMessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true); expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
}); });
it("should delete fulfilled promises on running the garbage collector", () => { it("should delete fulfilled promises on running the garbage collector", () => {
let mockPromise: CachedDataPromise<any> = { let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>() deferred: Q.defer<any>(),
}; };
MockMessageHandler.addToMap(mockPromise.id, mockPromise); MockMessageHandler.addToMap(mockPromise.id, mockPromise);
mockPromise.deferred.reject("some error"); mockPromise.deferred.reject("some error");
MockMessageHandler.runGarbageCollector(); MockMessageHandler.runGarbageCollector();
expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false); expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false);
}); });
}); });

View File

@ -1,85 +1,85 @@
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import Q from "q"; import Q from "q";
import * as _ from "underscore"; import * as _ from "underscore";
import * as Constants from "./Constants"; import * as Constants from "./Constants";
export interface CachedDataPromise<T> { export interface CachedDataPromise<T> {
deferred: Q.Deferred<T>; deferred: Q.Deferred<T>;
startTime: Date; startTime: Date;
id: string; id: string;
} }
/** /**
* For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill, * For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill,
* so we define our own custom implementation of the ES6 Map to work around it. * so we define our own custom implementation of the ES6 Map to work around it.
*/ */
type Map = { [key: string]: CachedDataPromise<any> }; type Map = { [key: string]: CachedDataPromise<any> };
export class MessageHandler { export class MessageHandler {
protected static RequestMap: Map = {}; protected static RequestMap: Map = {};
public static handleCachedDataMessage(message: any): void { public static handleCachedDataMessage(message: any): void {
const messageContent = message && message.message; const messageContent = message && message.message;
if ( if (
message == null || message == null ||
messageContent == null || messageContent == null ||
messageContent.id == null || messageContent.id == null ||
!MessageHandler.RequestMap[messageContent.id] !MessageHandler.RequestMap[messageContent.id]
) { ) {
return; return;
} }
const cachedDataPromise = MessageHandler.RequestMap[messageContent.id]; const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
if (messageContent.error != null) { if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error); cachedDataPromise.deferred.reject(messageContent.error);
} else { } else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data)); cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
} }
MessageHandler.runGarbageCollector(); MessageHandler.runGarbageCollector();
} }
public static sendCachedDataMessage<TResponseDataModel>( public static sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes, messageType: MessageTypes,
params: Object[], params: Object[],
timeoutInMs?: number timeoutInMs?: number
): Q.Promise<TResponseDataModel> { ): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = { let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(), deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(), startTime: new Date(),
id: _.uniqueId() id: _.uniqueId(),
}; };
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise; MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id }); MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
//TODO: Use telemetry to measure optimal time to resolve/reject promises //TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout( return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs, timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal" "Timed out while waiting for response from portal"
); );
} }
public static sendMessage(data: any): void { public static sendMessage(data: any): void {
if (MessageHandler.canSendMessage()) { if (MessageHandler.canSendMessage()) {
window.parent.postMessage( window.parent.postMessage(
{ {
signature: "pcIframe", signature: "pcIframe",
data: data data: data,
}, },
window.document.referrer window.document.referrer
); );
} }
} }
public static canSendMessage(): boolean { public static canSendMessage(): boolean {
return window.parent !== window; return window.parent !== window;
} }
protected static runGarbageCollector() { protected static runGarbageCollector() {
Object.keys(MessageHandler.RequestMap).forEach((key: string) => { Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise; const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) { if (promise.isFulfilled() || promise.isRejected()) {
delete MessageHandler.RequestMap[key]; delete MessageHandler.RequestMap[key];
} }
}); });
} }
} }

View File

@ -4,7 +4,7 @@ import {
getEndpoint, getEndpoint,
queryDocuments, queryDocuments,
readDocument, readDocument,
updateDocument updateDocument,
} from "./MongoProxyClient"; } from "./MongoProxyClient";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels"; import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
@ -20,7 +20,7 @@ const fetchMock = () => {
ok: true, ok: true,
text: () => "{}", text: () => "{}",
json: () => "{}", json: () => "{}",
headers: new Map() headers: new Map(),
}); });
}; };
@ -33,8 +33,8 @@ const collection = {
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1 version: 1,
} },
} as Collection; } as Collection;
const documentId = ({ const documentId = ({
@ -44,8 +44,8 @@ const documentId = ({
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1 version: 1,
} },
} as unknown) as DocumentId; } as unknown) as DocumentId;
const databaseAccount = { const databaseAccount = {
@ -58,8 +58,8 @@ const databaseAccount = {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo" cassandraEndpoint: "foo",
} },
}; };
describe("MongoProxyClient", () => { describe("MongoProxyClient", () => {
@ -69,7 +69,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@ -100,7 +100,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@ -131,7 +131,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@ -162,7 +162,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@ -193,7 +193,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@ -225,7 +225,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "" serverId: () => "",
} as any; } as any;
}); });
@ -259,7 +259,7 @@ describe("MongoProxyClient", () => {
sid: "a2", sid: "a2",
rg: "c1", rg: "c1",
dba: "main", dba: "main",
is: false is: false,
}; };
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" }); _createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
@ -268,8 +268,8 @@ describe("MongoProxyClient", () => {
{ {
properties: { properties: {
options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
resource: { id: "abc-collection" } resource: { id: "abc-collection" },
} },
} }
); );
}); });
@ -285,7 +285,7 @@ describe("MongoProxyClient", () => {
rg: "c1", rg: "c1",
dba: "main", dba: "main",
is: false, is: false,
offerThroughput: 400 offerThroughput: 400,
}; };
_createMongoCollectionWithARM("management.azure.com", properties, undefined); _createMongoCollectionWithARM("management.azure.com", properties, undefined);
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
@ -294,8 +294,8 @@ describe("MongoProxyClient", () => {
{ {
properties: { properties: {
options: { throughput: "400" }, options: { throughput: "400" },
resource: { id: "abc-collection" } resource: { id: "abc-collection" },
} },
} }
); );
}); });

View File

@ -20,7 +20,7 @@ import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClie
const defaultHeaders = { const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(), [HttpHeaders.apiType]: ApiType.MongoDB.toString(),
[CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100", [CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100",
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15" [CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15",
}; };
function authHeaders(): any { function authHeaders(): any {
@ -35,7 +35,7 @@ export function queryIterator(databaseId: string, collection: Collection, query:
let continuationToken: string; let continuationToken: string;
return { return {
fetchNext: () => { fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then(response => { return queryDocuments(databaseId, collection, false, query).then((response) => {
continuationToken = response.continuationToken; continuationToken = response.continuationToken;
const headers = {} as any; const headers = {} as any;
response.headers.forEach((value: any, key: any) => { response.headers.forEach((value: any, key: any) => {
@ -46,10 +46,10 @@ export function queryIterator(databaseId: string, collection: Collection, query:
headers, headers,
requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge], requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge],
activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId], activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId],
hasMoreResults: !!continuationToken hasMoreResults: !!continuationToken,
}; };
}); });
} },
}; };
} }
@ -78,7 +78,9 @@ export function queryDocuments(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : "" collection && collection.partitionKey && !collection.partitionKey.systemKey
? collection.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount) || ""; const endpoint = getEndpoint(databaseAccount) || "";
@ -91,7 +93,7 @@ export function queryDocuments(
[CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true",
[CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true",
[CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true",
[HttpHeaders.contentType]: "application/query+json" [HttpHeaders.contentType]: "application/query+json",
}; };
if (continuationToken) { if (continuationToken) {
@ -104,14 +106,14 @@ export function queryDocuments(
.fetch(`${endpoint}${path}?${queryString.stringify(params)}`, { .fetch(`${endpoint}${path}?${queryString.stringify(params)}`, {
method: "POST", method: "POST",
body: JSON.stringify({ query }), body: JSON.stringify({ query }),
headers headers,
}) })
.then(async response => { .then(async (response) => {
if (response.ok) { if (response.ok) {
return { return {
continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation), continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation),
documents: (await response.json()).Documents as DataModels.DocumentId[], documents: (await response.json()).Documents as DataModels.DocumentId[],
headers: response.headers headers: response.headers,
}; };
} }
return errorHandling(response, "querying documents", params); return errorHandling(response, "querying documents", params);
@ -138,7 +140,9 @@ export function readDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@ -150,10 +154,10 @@ export function readDocument(
...authHeaders(), ...authHeaders(),
[CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent( [CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent(
JSON.stringify(documentId.partitionKeyHeader()) JSON.stringify(documentId.partitionKeyHeader())
) ),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -178,7 +182,7 @@ export function createDocument(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "" pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@ -189,10 +193,10 @@ export function createDocument(
body: JSON.stringify(documentContent), body: JSON.stringify(documentContent),
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders() ...authHeaders(),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -221,7 +225,9 @@ export function updateDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@ -233,10 +239,10 @@ export function updateDocument(
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()) [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -264,7 +270,9 @@ export function deleteDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : "" documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@ -275,10 +283,10 @@ export function deleteDocument(
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()) [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
} },
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return undefined; return undefined;
} }
@ -311,7 +319,7 @@ export function createMongoCollectionWithProxy(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
isAutoPilot: false isAutoPilot: false,
}; };
if (autopilotOptions) { if (autopilotOptions) {
@ -329,11 +337,11 @@ export function createMongoCollectionWithProxy(
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json" [HttpHeaders.contentType]: "application/json",
} },
} }
) )
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return undefined; return undefined;
} }
@ -368,7 +376,7 @@ export function createMongoCollectionWithARM(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
analyticalStorageTtl analyticalStorageTtl,
}; };
if (createDatabase) { if (createDatabase) {
@ -424,10 +432,10 @@ export async function _createMongoCollectionWithARM(
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = { const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: { properties: {
resource: { resource: {
id: params.coll id: params.coll,
}, },
options: {} options: {},
} },
}; };
if (params.is) { if (params.is) {

View File

@ -1,168 +1,168 @@
/* Copyright 2013 10gen Inc. /* Copyright 2013 10gen Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export default class MongoUtility { export default class MongoUtility {
public static tojson = function(x: any, indent: string, nolint: boolean) { public static tojson = function (x: any, indent: string, nolint: boolean) {
if (x === null || x === undefined) { if (x === null || x === undefined) {
return String(x); return String(x);
} }
indent = indent || ""; indent = indent || "";
switch (typeof x) { switch (typeof x) {
case "string": case "string":
var out = new Array(x.length + 1); var out = new Array(x.length + 1);
out[0] = '"'; out[0] = '"';
for (var i = 0; i < x.length; i++) { for (var i = 0; i < x.length; i++) {
if (x[i] === '"') { if (x[i] === '"') {
out[out.length] = '\\"'; out[out.length] = '\\"';
} else if (x[i] === "\\") { } else if (x[i] === "\\") {
out[out.length] = "\\\\"; out[out.length] = "\\\\";
} else if (x[i] === "\b") { } else if (x[i] === "\b") {
out[out.length] = "\\b"; out[out.length] = "\\b";
} else if (x[i] === "\f") { } else if (x[i] === "\f") {
out[out.length] = "\\f"; out[out.length] = "\\f";
} else if (x[i] === "\n") { } else if (x[i] === "\n") {
out[out.length] = "\\n"; out[out.length] = "\\n";
} else if (x[i] === "\r") { } else if (x[i] === "\r") {
out[out.length] = "\\r"; out[out.length] = "\\r";
} else if (x[i] === "\t") { } else if (x[i] === "\t") {
out[out.length] = "\\t"; out[out.length] = "\\t";
} else { } else {
var code = x.charCodeAt(i); var code = x.charCodeAt(i);
if (code < 0x20) { if (code < 0x20) {
out[out.length] = (code < 0x10 ? "\\u000" : "\\u00") + code.toString(16); out[out.length] = (code < 0x10 ? "\\u000" : "\\u00") + code.toString(16);
} else { } else {
out[out.length] = x[i]; out[out.length] = x[i];
} }
} }
} }
return out.join("") + '"'; return out.join("") + '"';
case "number": case "number":
/* falls through */ /* falls through */
case "boolean": case "boolean":
return "" + x; return "" + x;
case "object": case "object":
var func = $.isArray(x) ? MongoUtility.tojsonArray : MongoUtility.tojsonObject; var func = $.isArray(x) ? MongoUtility.tojsonArray : MongoUtility.tojsonObject;
var s = func(x, indent, nolint); var s = func(x, indent, nolint);
if ( if (
(nolint === null || nolint === undefined || nolint === true) && (nolint === null || nolint === undefined || nolint === true) &&
s.length < 80 && s.length < 80 &&
(indent === null || indent.length === 0) (indent === null || indent.length === 0)
) { ) {
s = s.replace(/[\t\r\n]+/gm, " "); s = s.replace(/[\t\r\n]+/gm, " ");
} }
return s; return s;
case "function": case "function":
return x.toString(); return x.toString();
default: default:
throw new Error("tojson can't handle type " + typeof x); throw new Error("tojson can't handle type " + typeof x);
} }
}; };
private static tojsonObject = function(x: any, indent: string, nolint: boolean) { private static tojsonObject = function (x: any, indent: string, nolint: boolean) {
var lineEnding = nolint ? " " : "\n"; var lineEnding = nolint ? " " : "\n";
var tabSpace = nolint ? "" : "\t"; var tabSpace = nolint ? "" : "\t";
indent = indent || ""; indent = indent || "";
if (typeof x.tojson === "function" && x.tojson !== MongoUtility.tojson) { if (typeof x.tojson === "function" && x.tojson !== MongoUtility.tojson) {
return x.tojson(indent, nolint); return x.tojson(indent, nolint);
} }
if (x.constructor && typeof x.constructor.tojson === "function" && x.constructor.tojson !== MongoUtility.tojson) { if (x.constructor && typeof x.constructor.tojson === "function" && x.constructor.tojson !== MongoUtility.tojson) {
return x.constructor.tojson(x, indent, nolint); return x.constructor.tojson(x, indent, nolint);
} }
if (MongoUtility.hasDefinedProperty(x, "toString") && !$.isArray(x)) { if (MongoUtility.hasDefinedProperty(x, "toString") && !$.isArray(x)) {
return x.toString(); return x.toString();
} }
if (x instanceof Error) { if (x instanceof Error) {
return x.toString(); return x.toString();
} }
if (MongoUtility.isObjectId(x)) { if (MongoUtility.isObjectId(x)) {
return 'ObjectId("' + x.$oid + '")'; return 'ObjectId("' + x.$oid + '")';
} }
// push one level of indent // push one level of indent
indent += tabSpace; indent += tabSpace;
var s = "{"; var s = "{";
var pairs = []; var pairs = [];
for (var k in x) { for (var k in x) {
if (x.hasOwnProperty(k)) { if (x.hasOwnProperty(k)) {
var val = x[k]; var val = x[k];
var pair = '"' + k + '" : ' + MongoUtility.tojson(val, indent, nolint); var pair = '"' + k + '" : ' + MongoUtility.tojson(val, indent, nolint);
if (k === "_id") { if (k === "_id") {
pairs.unshift(pair); pairs.unshift(pair);
} else { } else {
pairs.push(pair); pairs.push(pair);
} }
} }
} }
// Add proper line endings, indents, and commas to each line // Add proper line endings, indents, and commas to each line
s += $.map(pairs, function(pair) { s += $.map(pairs, function (pair) {
return lineEnding + indent + pair; return lineEnding + indent + pair;
}).join(","); }).join(",");
s += lineEnding; s += lineEnding;
// pop one level of indent // pop one level of indent
indent = indent.substring(1); indent = indent.substring(1);
return s + indent + "}"; return s + indent + "}";
}; };
private static tojsonArray = function(a: any, indent: string, nolint: boolean) { private static tojsonArray = function (a: any, indent: string, nolint: boolean) {
if (a.length === 0) { if (a.length === 0) {
return "[ ]"; return "[ ]";
} }
var lineEnding = nolint ? " " : "\n"; var lineEnding = nolint ? " " : "\n";
if (!indent || nolint) { if (!indent || nolint) {
indent = ""; indent = "";
} }
var s = "[" + lineEnding; var s = "[" + lineEnding;
indent += "\t"; indent += "\t";
for (var i = 0; i < a.length; i++) { for (var i = 0; i < a.length; i++) {
s += indent + MongoUtility.tojson(a[i], indent, nolint); s += indent + MongoUtility.tojson(a[i], indent, nolint);
if (i < a.length - 1) { if (i < a.length - 1) {
s += "," + lineEnding; s += "," + lineEnding;
} }
} }
if (a.length === 0) { if (a.length === 0) {
s += indent; s += indent;
} }
indent = indent.substring(1); indent = indent.substring(1);
s += lineEnding + indent + "]"; s += lineEnding + indent + "]";
return s; return s;
}; };
private static hasDefinedProperty = function(obj: any, prop: string): boolean { private static hasDefinedProperty = function (obj: any, prop: string): boolean {
if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) { if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) {
return false; return false;
} else if (obj.hasOwnProperty(prop)) { } else if (obj.hasOwnProperty(prop)) {
return true; return true;
} else { } else {
return MongoUtility.hasDefinedProperty(Object.getPrototypeOf(obj), prop); return MongoUtility.hasDefinedProperty(Object.getPrototypeOf(obj), prop);
} }
}; };
private static isObjectId(obj: any): boolean { private static isObjectId(obj: any): boolean {
var keys = Object.keys(obj); var keys = Object.keys(obj);
return keys.length === 1 && keys[0] === "$oid" && typeof obj.$oid === "string" && /^[0-9a-f]{24}$/.test(obj.$oid); return keys.length === 1 && keys[0] === "$oid" && typeof obj.$oid === "string" && /^[0-9a-f]{24}$/.test(obj.$oid);
} }
} }

View File

@ -1,47 +1,47 @@
import "jquery"; import "jquery";
import * as Q from "q"; import * as Q from "q";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils"; import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { CosmosClient } from "./CosmosClient"; import { CosmosClient } from "./CosmosClient";
export class NotificationsClientBase implements ViewModels.NotificationsClient { export class NotificationsClientBase implements ViewModels.NotificationsClient {
private _extensionEndpoint: string; private _extensionEndpoint: string;
private _notificationsApiSuffix: string; private _notificationsApiSuffix: string;
protected constructor(notificationsApiSuffix: string) { protected constructor(notificationsApiSuffix: string) {
this._notificationsApiSuffix = notificationsApiSuffix; this._notificationsApiSuffix = notificationsApiSuffix;
} }
public fetchNotifications(): Q.Promise<DataModels.Notification[]> { public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>(); const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount(); const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const subscriptionId: string = CosmosClient.subscriptionId(); const subscriptionId: string = CosmosClient.subscriptionId();
const resourceGroup: string = CosmosClient.resourceGroup(); const resourceGroup: string = CosmosClient.resourceGroup();
const url: string = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`; const url: string = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers: any = {}; const headers: any = {};
headers[authorizationHeader.header] = authorizationHeader.token; headers[authorizationHeader.header] = authorizationHeader.token;
$.ajax({ $.ajax({
url: url, url: url,
type: "GET", type: "GET",
headers: headers, headers: headers,
cache: false cache: false,
}).then( }).then(
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => { (notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
deferred.resolve(notifications); deferred.resolve(notifications);
}, },
(xhr: JQueryXHR<any>, textStatus: string, error: any) => { (xhr: JQueryXHR<any>, textStatus: string, error: any) => {
deferred.reject(xhr.responseText); deferred.reject(xhr.responseText);
} }
); );
return deferred.promise; return deferred.promise;
} }
public setExtensionEndpoint(extensionEndpoint: string): void { public setExtensionEndpoint(extensionEndpoint: string): void {
this._extensionEndpoint = extensionEndpoint; this._extensionEndpoint = extensionEndpoint;
} }
} }

View File

@ -1,286 +1,286 @@
import * as _ from "underscore"; import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId"; import DocumentId from "../Explorer/Tree/DocumentId";
import * as ErrorParserUtility from "./ErrorParserUtility"; import * as ErrorParserUtility from "./ErrorParserUtility";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants"; import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "./CosmosClient"; import { CosmosClient } from "./CosmosClient";
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger"; import * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import { QueryUtils } from "../Utils/QueryUtils";
export class QueriesClient implements ViewModels.QueriesClient { export class QueriesClient implements ViewModels.QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`], paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: BackendDefaults.partitionKeyKind, kind: BackendDefaults.partitionKeyKind,
version: BackendDefaults.partitionKeyVersion version: BackendDefaults.partitionKeyVersion,
}; };
private static readonly FetchQuery: string = "SELECT * FROM c"; private static readonly FetchQuery: string = "SELECT * FROM c";
private static readonly FetchMongoQuery: string = "{}"; private static readonly FetchMongoQuery: string = "{}";
public constructor(private container: ViewModels.Explorer) {} public constructor(private container: ViewModels.Explorer) {}
public async setupQueriesCollection(): Promise<DataModels.Collection> { public async setupQueriesCollection(): Promise<DataModels.Collection> {
const queriesCollection: ViewModels.Collection = this.findQueriesCollection(); const queriesCollection: ViewModels.Collection = this.findQueriesCollection();
if (queriesCollection) { if (queriesCollection) {
return Promise.resolve(queriesCollection.rawDataModel); return Promise.resolve(queriesCollection.rawDataModel);
} }
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
"Setting up account for saving queries" "Setting up account for saving queries"
); );
return this.container.documentClientUtility return this.container.documentClientUtility
.getOrCreateDatabaseAndCollection({ .getOrCreateDatabaseAndCollection({
collectionId: SavedQueries.CollectionName, collectionId: SavedQueries.CollectionName,
databaseId: SavedQueries.DatabaseName, databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput, offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined databaseLevelThroughput: undefined,
}) })
.then( .then(
(collection: DataModels.Collection) => { (collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info, ConsoleDataType.Info,
"Successfully set up account for saving queries" "Successfully set up account for saving queries"
); );
return Promise.resolve(collection); return Promise.resolve(collection);
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = JSON.stringify(error); const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to set up account for saving queries: ${stringifiedError}` `Failed to set up account for saving queries: ${stringifiedError}`
); );
Logger.logError(stringifiedError, "setupQueriesCollection"); Logger.logError(stringifiedError, "setupQueriesCollection");
return Promise.reject(stringifiedError); return Promise.reject(stringifiedError);
} }
) )
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
} }
public async saveQuery(query: DataModels.Query): Promise<void> { public async saveQuery(query: DataModels.Query): Promise<void> {
const queriesCollection = this.findQueriesCollection(); const queriesCollection = this.findQueriesCollection();
if (!queriesCollection) { if (!queriesCollection) {
const errorMessage: string = "Account not set up to perform saved query operations"; const errorMessage: string = "Account not set up to perform saved query operations";
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to save query ${query.queryName}: ${errorMessage}` `Failed to save query ${query.queryName}: ${errorMessage}`
); );
return Promise.reject(errorMessage); return Promise.reject(errorMessage);
} }
try { try {
this.validateQuery(query); this.validateQuery(query);
} catch (error) { } catch (error) {
const errorMessage: string = "Invalid query specified"; const errorMessage: string = "Invalid query specified";
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to save query ${query.queryName}: ${errorMessage}` `Failed to save query ${query.queryName}: ${errorMessage}`
); );
return Promise.reject(errorMessage); return Promise.reject(errorMessage);
} }
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Saving query ${query.queryName}` `Saving query ${query.queryName}`
); );
query.id = query.queryName; query.id = query.queryName;
return this.container.documentClientUtility return this.container.documentClientUtility
.createDocument(queriesCollection, query) .createDocument(queriesCollection, query)
.then( .then(
(savedQuery: DataModels.Query) => { (savedQuery: DataModels.Query) => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info, ConsoleDataType.Info,
`Successfully saved query ${query.queryName}` `Successfully saved query ${query.queryName}`
); );
return Promise.resolve(); return Promise.resolve();
}, },
(error: any) => { (error: any) => {
let errorMessage: string; let errorMessage: string;
const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0]; const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
if (parsedError.code === HttpStatusCodes.Conflict.toString()) { if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
errorMessage = `Query ${query.queryName} already exists`; errorMessage = `Query ${query.queryName} already exists`;
} else { } else {
errorMessage = parsedError.message; errorMessage = parsedError.message;
} }
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to save query ${query.queryName}: ${errorMessage}` `Failed to save query ${query.queryName}: ${errorMessage}`
); );
Logger.logError(JSON.stringify(parsedError), "saveQuery"); Logger.logError(JSON.stringify(parsedError), "saveQuery");
return Promise.reject(errorMessage); return Promise.reject(errorMessage);
} }
) )
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
} }
public async getQueries(): Promise<DataModels.Query[]> { public async getQueries(): Promise<DataModels.Query[]> {
const queriesCollection = this.findQueriesCollection(); const queriesCollection = this.findQueriesCollection();
if (!queriesCollection) { if (!queriesCollection) {
const errorMessage: string = "Account not set up to perform saved query operations"; const errorMessage: string = "Account not set up to perform saved query operations";
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${errorMessage}` `Failed to fetch saved queries: ${errorMessage}`
); );
return Promise.reject(errorMessage); return Promise.reject(errorMessage);
} }
const options: any = { enableCrossPartitionQuery: true }; const options: any = { enableCrossPartitionQuery: true };
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries"); const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
return this.container.documentClientUtility return this.container.documentClientUtility
.queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options) .queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then( .then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => { (queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> => const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
this.container.documentClientUtility.queryDocumentsPage( this.container.documentClientUtility.queryDocumentsPage(
queriesCollection.id(), queriesCollection.id(),
queryIterator, queryIterator,
firstItemIndex, firstItemIndex,
options options
); );
return QueryUtils.queryAllPages(fetchQueries).then( return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => { (results: ViewModels.QueryResults) => {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => { let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
if (!document) { if (!document) {
return undefined; return undefined;
} }
const { id, resourceId, query, queryName } = document; const { id, resourceId, query, queryName } = document;
const parsedQuery: DataModels.Query = { const parsedQuery: DataModels.Query = {
resourceId: resourceId, resourceId: resourceId,
queryName: queryName, queryName: queryName,
query: query, query: query,
id: id id: id,
}; };
try { try {
this.validateQuery(parsedQuery); this.validateQuery(parsedQuery);
return parsedQuery; return parsedQuery;
} catch (error) { } catch (error) {
return undefined; return undefined;
} }
}); });
queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery); queries = _.reject(queries, (parsedQuery: DataModels.Query) => !parsedQuery);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries"); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully fetched saved queries");
return Promise.resolve(queries); return Promise.resolve(queries);
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = JSON.stringify(error); const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}` `Failed to fetch saved queries: ${stringifiedError}`
); );
Logger.logError(stringifiedError, "getSavedQueries"); Logger.logError(stringifiedError, "getSavedQueries");
return Promise.reject(stringifiedError); return Promise.reject(stringifiedError);
} }
); );
}, },
(error: any) => { (error: any) => {
// should never get into this state but we handle this regardless // should never get into this state but we handle this regardless
const stringifiedError: string = JSON.stringify(error); const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}` `Failed to fetch saved queries: ${stringifiedError}`
); );
Logger.logError(stringifiedError, "getSavedQueries"); Logger.logError(stringifiedError, "getSavedQueries");
return Promise.reject(stringifiedError); return Promise.reject(stringifiedError);
} }
) )
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
} }
public async deleteQuery(query: DataModels.Query): Promise<void> { public async deleteQuery(query: DataModels.Query): Promise<void> {
const queriesCollection = this.findQueriesCollection(); const queriesCollection = this.findQueriesCollection();
if (!queriesCollection) { if (!queriesCollection) {
const errorMessage: string = "Account not set up to perform saved query operations"; const errorMessage: string = "Account not set up to perform saved query operations";
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to fetch saved queries: ${errorMessage}` `Failed to fetch saved queries: ${errorMessage}`
); );
return Promise.reject(errorMessage); return Promise.reject(errorMessage);
} }
try { try {
this.validateQuery(query); this.validateQuery(query);
} catch (error) { } catch (error) {
const errorMessage: string = "Invalid query specified"; const errorMessage: string = "Invalid query specified";
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to delete query ${query.queryName}: ${errorMessage}` `Failed to delete query ${query.queryName}: ${errorMessage}`
); );
} }
const id = NotificationConsoleUtils.logConsoleMessage( const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress, ConsoleDataType.InProgress,
`Deleting query ${query.queryName}` `Deleting query ${query.queryName}`
); );
query.id = query.queryName; query.id = query.queryName;
const documentId: ViewModels.DocumentId = new DocumentId( const documentId: ViewModels.DocumentId = new DocumentId(
{ {
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id" partitionKeyProperty: "id",
} as ViewModels.DocumentsTab, } as ViewModels.DocumentsTab,
query, query,
query.queryName query.queryName
); // TODO: Remove DocumentId's dependency on DocumentsTab ); // TODO: Remove DocumentId's dependency on DocumentsTab
const options: any = { partitionKey: query.resourceId }; const options: any = { partitionKey: query.resourceId };
return this.container.documentClientUtility return this.container.documentClientUtility
.deleteDocument(queriesCollection, documentId) .deleteDocument(queriesCollection, documentId)
.then( .then(
() => { () => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info, ConsoleDataType.Info,
`Successfully deleted query ${query.queryName}` `Successfully deleted query ${query.queryName}`
); );
return Promise.resolve(); return Promise.resolve();
}, },
(error: any) => { (error: any) => {
const stringifiedError: string = JSON.stringify(error); const stringifiedError: string = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Failed to delete query ${query.queryName}: ${stringifiedError}` `Failed to delete query ${query.queryName}: ${stringifiedError}`
); );
Logger.logError(stringifiedError, "deleteQuery"); Logger.logError(stringifiedError, "deleteQuery");
return Promise.reject(stringifiedError); return Promise.reject(stringifiedError);
} }
) )
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
} }
public getResourceId(): string { public getResourceId(): string {
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount(); const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const databaseAccountName: string = (databaseAccount && databaseAccount.name) || ""; const databaseAccountName: string = (databaseAccount && databaseAccount.name) || "";
const subscriptionId: string = CosmosClient.subscriptionId() || ""; const subscriptionId: string = CosmosClient.subscriptionId() || "";
const resourceGroup: string = CosmosClient.resourceGroup() || ""; const resourceGroup: string = CosmosClient.resourceGroup() || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`; return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
} }
private findQueriesCollection(): ViewModels.Collection { private findQueriesCollection(): ViewModels.Collection {
const queriesDatabase: ViewModels.Database = _.find( const queriesDatabase: ViewModels.Database = _.find(
this.container.databases(), this.container.databases(),
(database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName (database: ViewModels.Database) => database.id() === SavedQueries.DatabaseName
); );
if (!queriesDatabase) { if (!queriesDatabase) {
return undefined; return undefined;
} }
return _.find( return _.find(
queriesDatabase.collections(), queriesDatabase.collections(),
(collection: ViewModels.Collection) => collection.id() === SavedQueries.CollectionName (collection: ViewModels.Collection) => collection.id() === SavedQueries.CollectionName
); );
} }
private validateQuery(query: DataModels.Query): void { private validateQuery(query: DataModels.Query): void {
if (!query || query.queryName == null || query.query == null || query.resourceId == null) { if (!query || query.queryName == null || query.query == null || query.resourceId == null) {
throw new Error("Invalid query specified"); throw new Error("Invalid query specified");
} }
} }
private fetchQueriesQuery(): string { private fetchQueriesQuery(): string {
if (this.container.isPreferredApiMongoDB()) { if (this.container.isPreferredApiMongoDB()) {
return QueriesClient.FetchMongoQuery; return QueriesClient.FetchMongoQuery;
} }
return QueriesClient.FetchQuery; return QueriesClient.FetchQuery;
} }
} }

View File

@ -1,108 +1,106 @@
import * as ko from "knockout"; import * as ko from "knockout";
import { SplitterMetrics } from "./Constants"; import { SplitterMetrics } from "./Constants";
export enum SplitterDirection { export enum SplitterDirection {
Horizontal = "horizontal", Horizontal = "horizontal",
Vertical = "vertical" Vertical = "vertical",
} }
export interface SplitterBounds { export interface SplitterBounds {
max: number; max: number;
min: number; min: number;
} }
export interface SplitterOptions { export interface SplitterOptions {
splitterId: string; splitterId: string;
leftId: string; leftId: string;
bounds: SplitterBounds; bounds: SplitterBounds;
direction: SplitterDirection; direction: SplitterDirection;
} }
export class Splitter { export class Splitter {
public splitterId: string; public splitterId: string;
public leftSideId: string; public leftSideId: string;
public splitter: HTMLElement; public splitter: HTMLElement;
public leftSide: HTMLElement; public leftSide: HTMLElement;
public lastX: number; public lastX: number;
public lastWidth: number; public lastWidth: number;
private isCollapsed: ko.Observable<boolean>; private isCollapsed: ko.Observable<boolean>;
private bounds: SplitterBounds; private bounds: SplitterBounds;
private direction: SplitterDirection; private direction: SplitterDirection;
constructor(options: SplitterOptions) { constructor(options: SplitterOptions) {
this.splitterId = options.splitterId; this.splitterId = options.splitterId;
this.leftSideId = options.leftId; this.leftSideId = options.leftId;
this.isCollapsed = ko.observable<boolean>(false); this.isCollapsed = ko.observable<boolean>(false);
this.bounds = options.bounds; this.bounds = options.bounds;
this.direction = options.direction; this.direction = options.direction;
this.initialize(); this.initialize();
} }
public initialize() { public initialize() {
this.splitter = document.getElementById(this.splitterId); this.splitter = document.getElementById(this.splitterId);
this.leftSide = document.getElementById(this.leftSideId); this.leftSide = document.getElementById(this.leftSideId);
const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical; const isVerticalSplitter: boolean = this.direction === SplitterDirection.Vertical;
const splitterOptions: JQueryUI.ResizableOptions = { const splitterOptions: JQueryUI.ResizableOptions = {
animate: true, animate: true,
animateDuration: "fast", animateDuration: "fast",
start: this.onResizeStart, start: this.onResizeStart,
stop: this.onResizeStop stop: this.onResizeStop,
}; };
if (isVerticalSplitter) { if (isVerticalSplitter) {
$(this.leftSide).css("width", this.bounds.min); $(this.leftSide).css("width", this.bounds.min);
$(this.splitter).css("height", "100%"); $(this.splitter).css("height", "100%");
splitterOptions.maxWidth = this.bounds.max; splitterOptions.maxWidth = this.bounds.max;
splitterOptions.minWidth = this.bounds.min; splitterOptions.minWidth = this.bounds.min;
splitterOptions.handles = { e: "#" + this.splitterId }; splitterOptions.handles = { e: "#" + this.splitterId };
} else { } else {
$(this.leftSide).css("height", this.bounds.min); $(this.leftSide).css("height", this.bounds.min);
$(this.splitter).css("width", "100%"); $(this.splitter).css("width", "100%");
splitterOptions.maxHeight = this.bounds.max; splitterOptions.maxHeight = this.bounds.max;
splitterOptions.minHeight = this.bounds.min; splitterOptions.minHeight = this.bounds.min;
splitterOptions.handles = { s: "#" + this.splitterId }; splitterOptions.handles = { s: "#" + this.splitterId };
} }
$(this.leftSide).resizable(splitterOptions); $(this.leftSide).resizable(splitterOptions);
} }
private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStart: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
if (this.direction === SplitterDirection.Vertical) { if (this.direction === SplitterDirection.Vertical) {
$(".ui-resizable-helper").height("100%"); $(".ui-resizable-helper").height("100%");
} else { } else {
$(".ui-resizable-helper").width("100%"); $(".ui-resizable-helper").width("100%");
} }
$("iframe").css("pointer-events", "none"); $("iframe").css("pointer-events", "none");
}; };
private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => { private onResizeStop: JQueryUI.ResizableEvent = (e: Event, ui: JQueryUI.ResizableUIParams) => {
$("iframe").css("pointer-events", "auto"); $("iframe").css("pointer-events", "auto");
}; };
public collapseLeft() { public collapseLeft() {
this.lastX = $(this.splitter).position().left; this.lastX = $(this.splitter).position().left;
this.lastWidth = $(this.leftSide).width(); this.lastWidth = $(this.leftSide).width();
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft); $(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(this.leftSide).css("width", ""); $(this.leftSide).css("width", "");
$(this.leftSide) $(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible
.resizable("option", "disabled", true) $(this.splitter).removeClass("ui-resizable-e");
.removeClass("ui-resizable-disabled"); // remove class so splitter is visible this.isCollapsed(true);
$(this.splitter).removeClass("ui-resizable-e"); }
this.isCollapsed(true);
} public expandLeft() {
$(this.splitter).addClass("ui-resizable-e");
public expandLeft() { $(this.leftSide).css("width", this.lastWidth);
$(this.splitter).addClass("ui-resizable-e"); $(this.splitter).css("left", this.lastX);
$(this.leftSide).css("width", this.lastWidth); $(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing
$(this.splitter).css("left", this.lastX); $(this.leftSide).resizable("enable");
$(this.splitter).css("left", ""); // this ensures the splitter's position is not fixed and enables movement during resizing this.isCollapsed(false);
$(this.leftSide).resizable("enable"); }
this.isCollapsed(false); }
}
}

View File

@ -32,8 +32,8 @@ export default class UrlUtility {
type: type, type: type,
objectBody: { objectBody: {
id: id, id: id,
self: resourcePath self: resourcePath,
} },
}; };
return result; return result;

View File

@ -1,7 +1,7 @@
export enum Platform { export enum Platform {
Portal = "Portal", Portal = "Portal",
Hosted = "Hosted", Hosted = "Hosted",
Emulator = "Emulator" Emulator = "Emulator",
} }
interface Config { interface Config {
@ -45,7 +45,7 @@ let config: Config = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com", JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86" //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")] AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86", //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")]
}; };
// Injected for local develpment. These will be removed in the production bundle by webpack // Injected for local develpment. These will be removed in the production bundle by webpack

View File

@ -7,7 +7,7 @@ export enum TabKind {
TableEntities, TableEntities,
Graph, Graph,
SQLQuery, SQLQuery,
ScaleSettings ScaleSettings,
} }
/** /**
@ -20,7 +20,7 @@ export enum PaneKind {
DeleteDatabase, DeleteDatabase,
GlobalSettings, GlobalSettings,
AdHocAccess, AdHocAccess,
SwitchDirectory SwitchDirectory,
} }
/** /**
@ -79,5 +79,5 @@ export enum ActionType {
OpenCollectionTab, OpenCollectionTab,
OpenPane, OpenPane,
TransmitCachedData, TransmitCachedData,
OpenSampleNotebook OpenSampleNotebook,
} }

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ export enum LogEntryLevel {
/** /**
* Error level. * Error level.
*/ */
Error = 2 Error = 2,
} }
/** /**
* Schema of a log entry. * Schema of a log entry.

View File

@ -1,38 +1,38 @@
import * as Versions from "./Versions"; import * as Versions from "./Versions";
import * as ActionContracts from "./ActionContracts"; import * as ActionContracts from "./ActionContracts";
import * as Diagnostics from "./Diagnostics"; import * as Diagnostics from "./Diagnostics";
/** /**
* Messaging types used with Data Explorer <-> Portal communication * Messaging types used with Data Explorer <-> Portal communication
* and Hosted <-> Explorer communication * and Hosted <-> Explorer communication
*/ */
export enum MessageTypes { export enum MessageTypes {
TelemetryInfo, TelemetryInfo,
LogInfo, LogInfo,
RefreshResources, RefreshResources,
AllDatabases, AllDatabases,
CollectionsForDatabase, CollectionsForDatabase,
RefreshOffers, RefreshOffers,
AllOffers, AllOffers,
UpdateLocationHash, UpdateLocationHash,
SingleOffer, SingleOffer,
RefreshOffer, RefreshOffer,
UpdateAccountName, UpdateAccountName,
ForbiddenError, ForbiddenError,
AadSignIn, AadSignIn,
GetAccessAadRequest, GetAccessAadRequest,
GetAccessAadResponse, GetAccessAadResponse,
UpdateAccountSwitch, UpdateAccountSwitch,
UpdateDirectoryControl, UpdateDirectoryControl,
SwitchAccount, SwitchAccount,
SendNotification, SendNotification,
ClearNotification, ClearNotification,
ExplorerClickEvent, ExplorerClickEvent,
LoadingStatus, LoadingStatus,
GetArcadiaToken, GetArcadiaToken,
CreateWorkspace, CreateWorkspace,
CreateSparkPool, CreateSparkPool,
RefreshDatabaseAccount RefreshDatabaseAccount,
} }
export { Versions, ActionContracts, Diagnostics }; export { Versions, ActionContracts, Diagnostics };

View File

@ -1,4 +1,4 @@
/** /**
* Data Explorer version {major.minor.patch} * Data Explorer version {major.minor.patch}
*/ */
export const DataExplorer: string = "1.0.1"; export const DataExplorer: string = "1.0.1";

File diff suppressed because it is too large Load Diff

View File

@ -6,19 +6,19 @@ describe("The Heatmap Control", () => {
const dataPoints = { const dataPoints = {
"1": { "1": {
"2019-06-19T00:59:10Z": { "2019-06-19T00:59:10Z": {
"Normalized Throughput": 0.35 "Normalized Throughput": 0.35,
}, },
"2019-06-19T00:48:10Z": { "2019-06-19T00:48:10Z": {
"Normalized Throughput": 0.25 "Normalized Throughput": 0.25,
} },
} },
}; };
const chartCaptions = { const chartCaptions = {
chartTitle: "chart title", chartTitle: "chart title",
yAxisTitle: "YAxisTitle", yAxisTitle: "YAxisTitle",
tooltipText: "Tooltip text", tooltipText: "Tooltip text",
timeWindow: 123456789 timeWindow: 123456789,
}; };
let heatmap: Heatmap; let heatmap: Heatmap;
@ -75,12 +75,12 @@ describe("The Heatmap Control", () => {
if (dayjs().utcOffset()) { if (dayjs().utcOffset()) {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([ expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([
"2019-06-19T00:48:10Z", "2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z" "2019-06-19T00:59:10Z",
]); ]);
} else { } else {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([ expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([
"2019-06-19T00:48:10Z", "2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z" "2019-06-19T00:59:10Z",
]); ]);
} }
}); });
@ -106,9 +106,9 @@ describe("iframe rendering when there is no data", () => {
data: { data: {
chartData: {}, chartData: {},
chartSettings: {}, chartSettings: {},
theme: 4 theme: 4,
} },
} },
}; };
const divElement: string = `<div id="${Heatmap.elementId}"></div>`; const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
@ -126,9 +126,9 @@ describe("iframe rendering when there is no data", () => {
data: { data: {
chartData: {}, chartData: {},
chartSettings: {}, chartSettings: {},
theme: 2 theme: 2,
} },
} },
}; };
const divElement: string = `<div id="${Heatmap.elementId}"></div>`; const divElement: string = `<div id="${Heatmap.elementId}"></div>`;

View File

@ -9,7 +9,7 @@ import {
HeatmapData, HeatmapData,
LayoutSettings, LayoutSettings,
PartitionTimeStampToData, PartitionTimeStampToData,
PortalTheme PortalTheme,
} from "./HeatmapDatatypes"; } from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { MessageHandler } from "../../Common/MessageHandler"; import { MessageHandler } from "../../Common/MessageHandler";
@ -43,7 +43,7 @@ export class Heatmap {
return { return {
family: StyleConstants.DataExplorerFont, family: StyleConstants.DataExplorerFont,
size, size,
color color,
}; };
} }
@ -73,7 +73,7 @@ export class Heatmap {
return 0; return 0;
} }
} }
}) }),
}; };
// go thru all rows and create 2d matrix for heatmap... // go thru all rows and create 2d matrix for heatmap...
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
@ -115,7 +115,7 @@ export class Heatmap {
[0.7, "#E46612"], [0.7, "#E46612"],
[0.8, "#E64914"], [0.8, "#E64914"],
[0.9, "#B80016"], [0.9, "#B80016"],
[1.0, "#B80016"] [1.0, "#B80016"],
], ],
name: "", name: "",
hovertemplate: this._heatmapCaptions.tooltipText, hovertemplate: this._heatmapCaptions.tooltipText,
@ -123,11 +123,11 @@ export class Heatmap {
thickness: 15, thickness: 15,
outlinewidth: 0, outlinewidth: 0,
tickcolor: StyleConstants.BaseDark, tickcolor: StyleConstants.BaseDark,
tickfont: this._getFontStyles(10, this._defaultFontColor) tickfont: this._getFontStyles(10, this._defaultFontColor),
}, },
y: this._chartData.yAxisPoints, y: this._chartData.yAxisPoints,
x: this._chartData.xAxisPoints x: this._chartData.xAxisPoints,
} },
]; ];
} }
@ -138,7 +138,7 @@ export class Heatmap {
r: 10, r: 10,
b: 35, b: 35,
t: 30, t: 30,
pad: 0 pad: 0,
}, },
paper_bgcolor: "transparent", paper_bgcolor: "transparent",
plot_bgcolor: "transparent", plot_bgcolor: "transparent",
@ -154,7 +154,7 @@ export class Heatmap {
autotick: true, autotick: true,
fixedrange: true, fixedrange: true,
ticks: "", ticks: "",
showticklabels: false showticklabels: false,
}, },
xaxis: { xaxis: {
fixedrange: true, fixedrange: true,
@ -167,13 +167,13 @@ export class Heatmap {
autotick: true, autotick: true,
tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e", tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e",
showticklabels: true, showticklabels: true,
tickfont: this._getFontStyles(10) tickfont: this._getFontStyles(10),
}, },
title: { title: {
text: this._heatmapCaptions.chartTitle, text: this._heatmapCaptions.chartTitle,
x: 0.01, x: 0.01,
font: this._getFontStyles(13, this._defaultFontColor) font: this._getFontStyles(13, this._defaultFontColor),
} },
}; };
} }
@ -181,7 +181,7 @@ export class Heatmap {
return { return {
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings /* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
responsive: true,*/ responsive: true,*/
displayModeBar: false displayModeBar: false,
}; };
} }

View File

@ -8,7 +8,7 @@ export enum PortalTheme {
blue = 1, blue = 1,
azure, azure,
light, light,
dark dark,
} }
export interface HeatmapData { export interface HeatmapData {

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,34 @@
/* Type definitions for code-runner's jquery-typeahead v2.8.0 /* Type definitions for code-runner's jquery-typeahead v2.8.0
* https://github.com/running-coder/jquery-typeahead * https://github.com/running-coder/jquery-typeahead
* *
* There is no DefinitelyTyped support for this library, yet, so we only define here what we use. * There is no DefinitelyTyped support for this library, yet, so we only define here what we use.
* https://github.com/running-coder/jquery-typeahead/issues/156 * https://github.com/running-coder/jquery-typeahead/issues/156
* TODO: Replace this minimum definition by the official one when it comes out. * TODO: Replace this minimum definition by the official one when it comes out.
*/ */
/// <reference path="jquery.d.ts" /> /// <reference path="jquery.d.ts" />
interface JQueryTypeaheadParam { interface JQueryTypeaheadParam {
input: string; input: string;
order?: string; order?: string;
source: any; source: any;
callback?: any; callback?: any;
minLength?: number; minLength?: number;
searchOnFocus?: boolean; searchOnFocus?: boolean;
template?: string | { (query: string, item: any): string }; template?: string | { (query: string, item: any): string };
dynamic?: boolean; dynamic?: boolean;
mustSelectItem?: boolean; mustSelectItem?: boolean;
} }
/** /**
* For use with: $.typeahead() * For use with: $.typeahead()
*/ */
interface JQueryStatic { interface JQueryStatic {
typeahead(arg: JQueryTypeaheadParam): void; typeahead(arg: JQueryTypeaheadParam): void;
} }
/** /**
* For use with $('').typehead() * For use with $('').typehead()
*/ */
// interface JQuery { // interface JQuery {
// typeahead(arg: JQueryTypeaheadParam): void; // typeahead(arg: JQueryTypeaheadParam): void;
// } // }

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,42 @@
// Type definitions for jQuery contextMenu 1.7.0 // Type definitions for jQuery contextMenu 1.7.0
// Project: http://medialize.github.com/jQuery-contextMenu/ // Project: http://medialize.github.com/jQuery-contextMenu/
// Definitions by: Natan Vivo <https://github.com/nvivo/> // Definitions by: Natan Vivo <https://github.com/nvivo/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="jquery.d.ts" /> /// <reference path="jquery.d.ts" />
interface JQueryContextMenuOptions { interface JQueryContextMenuOptions {
selector: string; selector: string;
appendTo?: string; appendTo?: string;
trigger?: string; trigger?: string;
autoHide?: boolean; autoHide?: boolean;
delay?: number; delay?: number;
determinePosition?: (menu: JQuery) => void; determinePosition?: (menu: JQuery) => void;
position?: (opt: JQuery, x: number, y: number) => void; position?: (opt: JQuery, x: number, y: number) => void;
positionSubmenu?: (menu: JQuery) => void; positionSubmenu?: (menu: JQuery) => void;
zIndex?: number; zIndex?: number;
animation?: { animation?: {
duration?: number; duration?: number;
show?: string; show?: string;
hide?: string; hide?: string;
}; };
events?: { events?: {
show?: () => void; show?: () => void;
hide?: () => void; hide?: () => void;
}; };
callback?: (key: any, options: any) => any; callback?: (key: any, options: any) => any;
items?: any; items?: any;
build?: (triggerElement: JQuery, e: Event) => any; build?: (triggerElement: JQuery, e: Event) => any;
reposition?: boolean; reposition?: boolean;
className?: string; className?: string;
itemClickEvent?: string; itemClickEvent?: string;
} }
interface JQueryStatic { interface JQueryStatic {
contextMenu(options?: JQueryContextMenuOptions): JQuery; contextMenu(options?: JQueryContextMenuOptions): JQuery;
contextMenu(type: string, selector?: any): JQuery; contextMenu(type: string, selector?: any): JQuery;
} }
interface JQuery { interface JQuery {
contextMenu(options?: any): JQuery; contextMenu(options?: any): JQuery;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,142 +1,142 @@
jest.mock("monaco-editor"); jest.mock("monaco-editor");
import * as ko from "knockout"; import * as ko from "knockout";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
describe("Component Registerer", () => { describe("Component Registerer", () => {
it("should register input-typeahead component", () => { it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true); expect(ko.components.isRegistered("input-typeahead")).toBe(true);
}); });
it("should register new-vertex-form component", () => { it("should register new-vertex-form component", () => {
expect(ko.components.isRegistered("new-vertex-form")).toBe(true); expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
}); });
it("should register error-display component", () => { it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true); expect(ko.components.isRegistered("error-display")).toBe(true);
}); });
it("should register graph-style component", () => { it("should register graph-style component", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true); expect(ko.components.isRegistered("graph-style")).toBe(true);
}); });
it("should register collapsible-panel component", () => { it("should register collapsible-panel component", () => {
expect(ko.components.isRegistered("collapsible-panel")).toBe(true); expect(ko.components.isRegistered("collapsible-panel")).toBe(true);
}); });
it("should register json-editor component", () => { it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true); expect(ko.components.isRegistered("json-editor")).toBe(true);
}); });
it("should register documents-tab component", () => { it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true); expect(ko.components.isRegistered("documents-tab")).toBe(true);
}); });
it("should register stored-procedure-tab component", () => { it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true); expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
}); });
it("should register trigger-tab component", () => { it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true); expect(ko.components.isRegistered("trigger-tab")).toBe(true);
}); });
it("should register user-defined-function-tab component", () => { it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true); expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
}); });
it("should register settings-tab component", () => { it("should register settings-tab component", () => {
expect(ko.components.isRegistered("settings-tab")).toBe(true); expect(ko.components.isRegistered("settings-tab")).toBe(true);
}); });
it("should register query-tab component", () => { it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true); expect(ko.components.isRegistered("query-tab")).toBe(true);
}); });
it("should register tables-query-tab component", () => { it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true); expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
}); });
it("should register graph-tab component", () => { it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true); expect(ko.components.isRegistered("graph-tab")).toBe(true);
}); });
it("should register notebookv2-tab component", () => { it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true); expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
}); });
it("should register terminal-tab component", () => { it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true); expect(ko.components.isRegistered("terminal-tab")).toBe(true);
}); });
it("should register spark-master-tab component", () => { it("should register spark-master-tab component", () => {
expect(ko.components.isRegistered("spark-master-tab")).toBe(true); expect(ko.components.isRegistered("spark-master-tab")).toBe(true);
}); });
it("should register mongo-shell-tab component", () => { it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true); expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
}); });
it("should registeradd-collection-pane component", () => { it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true); expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
}); });
it("should register delete-collection-confirmation-pane component", () => { it("should register delete-collection-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true); expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
}); });
it("should register delete-database-confirmation-pane component", () => { it("should register delete-database-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-database-confirmation-pane")).toBe(true); expect(ko.components.isRegistered("delete-database-confirmation-pane")).toBe(true);
}); });
it("should register save-query-pane component", () => { it("should register save-query-pane component", () => {
expect(ko.components.isRegistered("save-query-pane")).toBe(true); expect(ko.components.isRegistered("save-query-pane")).toBe(true);
}); });
it("should register browse-queries-pane component", () => { it("should register browse-queries-pane component", () => {
expect(ko.components.isRegistered("browse-queries-pane")).toBe(true); expect(ko.components.isRegistered("browse-queries-pane")).toBe(true);
}); });
it("should register graph-new-vertex-pane component", () => { it("should register graph-new-vertex-pane component", () => {
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true); expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
}); });
it("should register graph-styling-pane component", () => { it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true); expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
}); });
it("should register upload-file-pane component", () => { it("should register upload-file-pane component", () => {
expect(ko.components.isRegistered("upload-file-pane")).toBe(true); expect(ko.components.isRegistered("upload-file-pane")).toBe(true);
}); });
it("should register string-input-pane component", () => { it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true); expect(ko.components.isRegistered("string-input-pane")).toBe(true);
}); });
it("should register setup-notebooks-pane component", () => { it("should register setup-notebooks-pane component", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true); expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
}); });
it("should register setup-spark-cluster-pane component", () => { it("should register setup-spark-cluster-pane component", () => {
expect(ko.components.isRegistered("setup-spark-cluster-pane")).toBe(true); expect(ko.components.isRegistered("setup-spark-cluster-pane")).toBe(true);
}); });
it("should register manage-spark-cluster-pane component", () => { it("should register manage-spark-cluster-pane component", () => {
expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true); expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true);
}); });
it("should register dynamic-list component", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });
it("should register throughput-input component", () => { it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true); expect(ko.components.isRegistered("throughput-input")).toBe(true);
}); });
it("should register library-manage-pane component", () => { it("should register library-manage-pane component", () => {
expect(ko.components.isRegistered("library-manage-pane")).toBe(true); expect(ko.components.isRegistered("library-manage-pane")).toBe(true);
}); });
it("should register cluster-library-pane component", () => { it("should register cluster-library-pane component", () => {
expect(ko.components.isRegistered("cluster-library-pane")).toBe(true); expect(ko.components.isRegistered("cluster-library-pane")).toBe(true);
}); });
}); });

View File

@ -1,83 +1,83 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents"; import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent"; import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent"; import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent"; import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead"; import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent"; import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar"; import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
ko.components.register("toolbar", new ToolbarComponent()); ko.components.register("toolbar", new ToolbarComponent());
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent); ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent()); ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent()); ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent); ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
// Collection Tabs // Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab()); ko.components.register("documents-tab", new TabComponents.DocumentsTab());
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab()); ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab()); ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
ko.components.register("trigger-tab", new TabComponents.TriggerTab()); ko.components.register("trigger-tab", new TabComponents.TriggerTab());
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab()); ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
ko.components.register("settings-tab", new TabComponents.SettingsTab()); ko.components.register("settings-tab", new TabComponents.SettingsTab());
ko.components.register("query-tab", new TabComponents.QueryTab()); ko.components.register("query-tab", new TabComponents.QueryTab());
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab()); ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab()); ko.components.register("graph-tab", new TabComponents.GraphTab());
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab()); ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab()); ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab()); ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab()); ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab()); ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab()); ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab()); ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
// Database Tabs // Database Tabs
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab()); ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
// Panes // Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register( ko.components.register(
"delete-collection-confirmation-pane", "delete-collection-confirmation-pane",
new PaneComponents.DeleteCollectionConfirmationPaneComponent() new PaneComponents.DeleteCollectionConfirmationPaneComponent()
); );
ko.components.register( ko.components.register(
"delete-database-confirmation-pane", "delete-database-confirmation-pane",
new PaneComponents.DeleteDatabaseConfirmationPaneComponent() new PaneComponents.DeleteDatabaseConfirmationPaneComponent()
); );
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent()); ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent()); ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent()); ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent()); ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent()); ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent());
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent()); ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent()); ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent()); ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent());
ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent()); ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent());
ko.components.register("renew-adhoc-access-pane", new PaneComponents.RenewAdHocAccessPane()); ko.components.register("renew-adhoc-access-pane", new PaneComponents.RenewAdHocAccessPane());
ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent()); ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent()); ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent()); ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());
ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent()); ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent());
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent()); ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent()); ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent()); ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("setup-spark-cluster-pane", new PaneComponents.SetupSparkClusterPaneComponent()); ko.components.register("setup-spark-cluster-pane", new PaneComponents.SetupSparkClusterPaneComponent());
ko.components.register("manage-spark-cluster-pane", new PaneComponents.ManageSparkClusterPaneComponent()); ko.components.register("manage-spark-cluster-pane", new PaneComponents.ManageSparkClusterPaneComponent());
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent()); ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent()); ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@ -32,13 +32,13 @@ export class ResourceTreeContextMenuButtonFactory {
const newCollectionMenuItem: TreeNodeMenuItem = { const newCollectionMenuItem: TreeNodeMenuItem = {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(), onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText() label: container.addCollectionText(),
}; };
const deleteDatabaseMenuItem = { const deleteDatabaseMenuItem = {
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: () => container.deleteDatabaseConfirmationPane.open(), onClick: () => container.deleteDatabaseConfirmationPane.open(),
label: container.deleteDatabaseText() label: container.deleteDatabaseText(),
}; };
return [newCollectionMenuItem, deleteDatabaseMenuItem]; return [newCollectionMenuItem, deleteDatabaseMenuItem];
} }
@ -52,7 +52,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query" label: "New SQL Query",
}); });
} }
@ -60,7 +60,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query" label: "New Query",
}); });
items.push({ items.push({
@ -69,7 +69,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick(); selectedCollection && selectedCollection.onNewMongoShellClick();
}, },
label: "New Shell" label: "New Shell",
}); });
} }
@ -80,7 +80,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
}, },
label: "New Stored Procedure" label: "New Stored Procedure",
}); });
items.push({ items.push({
@ -89,7 +89,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
}, },
label: "New UDF" label: "New UDF",
}); });
items.push({ items.push({
@ -98,7 +98,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
}, },
label: "New Trigger" label: "New Trigger",
}); });
} }
@ -108,7 +108,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null); selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
}, },
label: container.deleteCollectionText() label: container.deleteCollectionText(),
}); });
return items; return items;
@ -126,8 +126,8 @@ export class ResourceTreeContextMenuButtonFactory {
{ {
iconSrc: DeleteSprocIcon, iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(), onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure" label: "Delete Store Procedure",
} },
]; ];
} }
@ -143,8 +143,8 @@ export class ResourceTreeContextMenuButtonFactory {
{ {
iconSrc: DeleteTriggerIcon, iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(), onClick: () => trigger.delete(),
label: "Delete Trigger" label: "Delete Trigger",
} },
]; ];
} }
@ -160,8 +160,8 @@ export class ResourceTreeContextMenuButtonFactory {
{ {
iconSrc: DeleteUDFIcon, iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(), onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function" label: "Delete User Defined Function",
} },
]; ];
} }
} }

View File

@ -31,7 +31,7 @@ export class AccessibleElement extends React.Component<AccessibleElementProps> {
...elementProps, ...elementProps,
onKeyPress: this.onKeyPress, onKeyPress: this.onKeyPress,
onClick: this.props.onActivated, onClick: this.props.onActivated,
tabIndex tabIndex,
}); });
} }
} }

View File

@ -38,7 +38,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
super(props); super(props);
this.isExpanded = props.isExpanded; this.isExpanded = props.isExpanded;
this.state = { this.state = {
isExpanded: true isExpanded: true,
}; };
} }
@ -46,7 +46,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
if (this.props.isExpanded !== this.isExpanded) { if (this.props.isExpanded !== this.isExpanded) {
this.isExpanded = this.props.isExpanded; this.isExpanded = this.props.isExpanded;
this.setState({ this.setState({
isExpanded: this.props.isExpanded isExpanded: this.props.isExpanded,
}); });
} }
} }

View File

@ -16,7 +16,7 @@ const createBlankProps = (): AccountSwitchComponentProps => {
subscriptions: [], subscriptions: [],
selectedSubscriptionId: null, selectedSubscriptionId: null,
isLoadingSubscriptions: false, isLoadingSubscriptions: false,
onSubscriptionChange: jest.fn() onSubscriptionChange: jest.fn(),
}; };
}; };
@ -28,7 +28,7 @@ const createBlankAccount = (): DatabaseAccount => {
properties: null, properties: null,
location: "", location: "",
tags: null, tags: null,
type: "" type: "",
}; };
}; };
@ -40,7 +40,7 @@ const createBlankSubscription = (): Subscription => {
state: "", state: "",
subscriptionPolicies: null, subscriptionPolicies: null,
tenantId: "", tenantId: "",
uniqueDisplayName: "" uniqueDisplayName: "",
}; };
}; };

View File

@ -34,13 +34,13 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
items: [ items: [
{ {
key: "switchSubscription", key: "switchSubscription",
onRender: this._renderSubscriptionDropdown.bind(this) onRender: this._renderSubscriptionDropdown.bind(this),
}, },
{ {
key: "switchAccount", key: "switchAccount",
onRender: this._renderAccountDropDown.bind(this) onRender: this._renderAccountDropDown.bind(this),
} },
] ],
}; };
const buttonStyles: IButtonStyles = { const buttonStyles: IButtonStyles = {
@ -51,27 +51,27 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
paddingLeft: 10, paddingLeft: 10,
marginRight: 5, marginRight: 5,
backgroundColor: StyleConstants.BaseDark, backgroundColor: StyleConstants.BaseDark,
color: StyleConstants.BaseLight color: StyleConstants.BaseLight,
}, },
rootHovered: { rootHovered: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight color: StyleConstants.BaseLight,
}, },
rootFocused: { rootFocused: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight color: StyleConstants.BaseLight,
}, },
rootPressed: { rootPressed: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight color: StyleConstants.BaseLight,
}, },
rootExpanded: { rootExpanded: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight color: StyleConstants.BaseLight,
}, },
textContainer: { textContainer: {
flexGrow: "initial" flexGrow: "initial",
} },
}; };
const buttonProps: IButtonProps = { const buttonProps: IButtonProps = {
@ -79,7 +79,7 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
menuProps: menuProps, menuProps: menuProps,
styles: buttonStyles, styles: buttonStyles,
className: "accountSwitchButton", className: "accountSwitchButton",
id: "accountSwitchButton" id: "accountSwitchButton",
}; };
return <DefaultButton {...buttonProps} />; return <DefaultButton {...buttonProps} />;
@ -87,11 +87,11 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
private _renderSubscriptionDropdown(): JSX.Element { private _renderSubscriptionDropdown(): JSX.Element {
const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props; const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props;
const options: IDropdownOption[] = subscriptions.map(sub => { const options: IDropdownOption[] = subscriptions.map((sub) => {
return { return {
key: sub.subscriptionId, key: sub.subscriptionId,
text: sub.displayName, text: sub.displayName,
data: sub data: sub,
}; };
}); });
@ -109,8 +109,8 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
defaultSelectedKey: selectedSubscriptionId, defaultSelectedKey: selectedSubscriptionId,
placeholder: placeHolderText, placeholder: placeHolderText,
styles: { styles: {
callout: "accountSwitchSubscriptionDropdownMenu" callout: "accountSwitchSubscriptionDropdownMenu",
} },
}; };
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;
@ -126,11 +126,11 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
private _renderAccountDropDown(): JSX.Element { private _renderAccountDropDown(): JSX.Element {
const { accounts, selectedAccountName, isLoadingAccounts } = this.props; const { accounts, selectedAccountName, isLoadingAccounts } = this.props;
const options: IDropdownOption[] = accounts.map(account => { const options: IDropdownOption[] = accounts.map((account) => {
return { return {
key: account.name, key: account.name,
text: account.name, text: account.name,
data: account data: account,
}; };
}); });
// Fabric UI will also try to select the first non-disabled option from dropdown. // Fabric UI will also try to select the first non-disabled option from dropdown.
@ -138,7 +138,7 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
options.unshift({ options.unshift({
key: "select from list", key: "select from list",
text: "Select Cosmos DB account from list", text: "Select Cosmos DB account from list",
data: undefined data: undefined,
}); });
const placeHolderText = isLoadingAccounts const placeHolderText = isLoadingAccounts
@ -155,8 +155,8 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
defaultSelectedKey: selectedAccountName, defaultSelectedKey: selectedAccountName,
placeholder: placeHolderText, placeholder: placeHolderText,
styles: { styles: {
callout: "accountSwitchAccountDropdownMenu" callout: "accountSwitchAccountDropdownMenu",
} },
}; };
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;

View File

@ -4,7 +4,7 @@ import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button"
import { import {
IContextualMenuItem, IContextualMenuItem,
IContextualMenuProps, IContextualMenuProps,
ContextualMenuItemType ContextualMenuItemType,
} from "office-ui-fabric-react/lib/ContextualMenu"; } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
@ -33,7 +33,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
constructor(props: ArcadiaMenuPickerProps) { constructor(props: ArcadiaMenuPickerProps) {
super(props); super(props);
this.state = { this.state = {
selectedSparkPool: props.selectedSparkPool selectedSparkPool: props.selectedSparkPool,
}; };
} }
@ -44,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
try { try {
this.props.onSparkPoolSelect(e, item); this.props.onSparkPoolSelect(e, item);
this.setState({ this.setState({
selectedSparkPool: item.text selectedSparkPool: item.text,
}); });
} catch (error) { } catch (error) {
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked"); Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
@ -68,28 +68,28 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
public render() { public render() {
const { workspaces } = this.props; const { workspaces } = this.props;
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map(workspace => { let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => {
let sparkPoolsMenuProps: IContextualMenuProps = { let sparkPoolsMenuProps: IContextualMenuProps = {
items: workspace.sparkPools.map( items: workspace.sparkPools.map(
(sparkpool): IContextualMenuItem => ({ (sparkpool): IContextualMenuItem => ({
key: sparkpool.id, key: sparkpool.id,
text: sparkpool.name, text: sparkpool.name,
onClick: this._onSparkPoolClicked onClick: this._onSparkPoolClicked,
}) })
) ),
}; };
if (!sparkPoolsMenuProps.items.length) { if (!sparkPoolsMenuProps.items.length) {
sparkPoolsMenuProps.items.push({ sparkPoolsMenuProps.items.push({
key: workspace.id, key: workspace.id,
text: "Create new spark pool", text: "Create new spark pool",
onClick: this._onCreateNewSparkPoolClicked onClick: this._onCreateNewSparkPoolClicked,
}); });
} }
return { return {
key: workspace.id, key: workspace.id,
text: workspace.name, text: workspace.name,
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps,
}; };
}); });
@ -97,7 +97,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
workspaceMenuItems.push({ workspaceMenuItems.push({
key: "create_workspace", key: "create_workspace",
text: "Create new workspace", text: "Create new workspace",
onClick: this._onCreateNewWorkspaceClicked onClick: this._onCreateNewWorkspaceClicked,
}); });
} }
@ -106,29 +106,29 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
backgroundColor: "transparent", backgroundColor: "transparent",
margin: "auto 5px", margin: "auto 5px",
padding: "0", padding: "0",
border: "0" border: "0",
}, },
rootHovered: { rootHovered: {
backgroundColor: "transparent" backgroundColor: "transparent",
}, },
rootChecked: { rootChecked: {
backgroundColor: "transparent" backgroundColor: "transparent",
}, },
rootFocused: { rootFocused: {
backgroundColor: "transparent" backgroundColor: "transparent",
}, },
rootExpanded: { rootExpanded: {
backgroundColor: "transparent" backgroundColor: "transparent",
}, },
flexContainer: { flexContainer: {
height: "30px", height: "30px",
border: "1px solid #a6a6a6", border: "1px solid #a6a6a6",
padding: "0 8px" padding: "0 8px",
}, },
label: { label: {
fontWeight: "400", fontWeight: "400",
fontSize: "12px" fontSize: "12px",
} },
}; };
return ( return (
@ -137,7 +137,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
persistMenu={true} persistMenu={true}
className="arcadia-menu-picker" className="arcadia-menu-picker"
menuProps={{ menuProps={{
items: workspaceMenuItems items: workspaceMenuItems,
}} }}
styles={dropdownStyle} styles={dropdownStyle}
/> />

View File

@ -1,56 +1,56 @@
import * as ko from "knockout"; import * as ko from "knockout";
import template from "./collapsible-panel-component.html"; import template from "./collapsible-panel-component.html";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
*/ */
export class CollapsiblePanelComponent { export class CollapsiblePanelComponent {
constructor() { constructor() {
return { return {
viewModel: CollapsiblePanelViewModel, viewModel: CollapsiblePanelViewModel,
template template,
}; };
} }
} }
/** /**
* Parameters for this component * Parameters for this component
*/ */
interface CollapsiblePanelParams { interface CollapsiblePanelParams {
collapsedTitle: ko.Observable<string>; collapsedTitle: ko.Observable<string>;
expandedTitle: ko.Observable<string>; expandedTitle: ko.Observable<string>;
isCollapsed?: ko.Observable<boolean>; isCollapsed?: ko.Observable<boolean>;
collapseToLeft?: boolean; collapseToLeft?: boolean;
} }
/** /**
* Collapsible panel: * Collapsible panel:
* Contains a header with [>] button to collapse and an title ("expandedTitle"). * Contains a header with [>] button to collapse and an title ("expandedTitle").
* Collapsing the panel: * Collapsing the panel:
* - shrinks width to narrow amount * - shrinks width to narrow amount
* - hides children * - hides children
* - shows [<] * - shows [<]
* - shows vertical title ("collapsedTitle") * - shows vertical title ("collapsedTitle")
* - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter) * - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter)
* *
* How to use in your markup: * How to use in your markup:
* <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }"> * <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }">
* <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) --> * <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) -->
* </collapsible-panel> * </collapsible-panel>
* *
* Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component. * Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component.
* Use the optional "collapseToLeft" parameter to collapse to the left. * Use the optional "collapseToLeft" parameter to collapse to the left.
*/ */
class CollapsiblePanelViewModel { class CollapsiblePanelViewModel {
private params: CollapsiblePanelParams; private params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>; private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) { public constructor(params: CollapsiblePanelParams) {
this.params = params; this.params = params;
this.isCollapsed = params.isCollapsed || ko.observable(false); this.isCollapsed = params.isCollapsed || ko.observable(false);
} }
private toggleCollapse(): void { private toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed()); this.isCollapsed(!this.isCollapsed());
} }
} }

View File

@ -1,44 +1,44 @@
<div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }"> <div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }">
<div class="panelHeader" data-bind="visible: !isCollapsed()"> <div class="panelHeader" data-bind="visible: !isCollapsed()">
<span <span
class="collapsedIconContainer collapseExpandButton" class="collapsedIconContainer collapseExpandButton"
data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }" data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }"
> >
<img <img
class="collapsedIcon imgVerticalAlignment" class="collapsedIcon imgVerticalAlignment"
src="/imgarrowlefticon.svg" src="/imgarrowlefticon.svg"
alt="Collapse" alt="Collapse"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }" data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
/> />
</span> </span>
<span <span
class="expandedTitle" class="expandedTitle"
data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }" data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }"
></span> ></span>
</div> </div>
<div class="collapsibleNav nav" data-bind="visible:isCollapsed"> <div class="collapsibleNav nav" data-bind="visible:isCollapsed">
<ul class="nav"> <ul class="nav">
<li class="collapsedBtn collapseExpandButton"> <li class="collapsedBtn collapseExpandButton">
<span class="collapsedIconContainer" data-bind="click: toggleCollapse"> <span class="collapsedIconContainer" data-bind="click: toggleCollapse">
<img <img
class="collapsedIcon" class="collapsedIcon"
src="/imgarrowlefticon.svg" src="/imgarrowlefticon.svg"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }" data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
alt="Expand" alt="Expand"
/> />
</span> </span>
<span class="rotatedInner" data-bind="click: toggleCollapse"> <span class="rotatedInner" data-bind="click: toggleCollapse">
<span data-bind="text: params.collapsedTitle"></span> <span data-bind="text: params.collapsedTitle"></span>
</span> </span>
</li> </li>
</ul> </ul>
</div> </div>
<div class="panelContent" data-bind="visible:!isCollapsed()"> <div class="panelContent" data-bind="visible:!isCollapsed()">
<!-- ko with:$parent --> <!-- ko with:$parent -->
<!-- ko template: { nodes: $componentTemplateNodes } --> <!-- ko template: { nodes: $componentTemplateNodes } -->
<!-- /ko --> <!-- /ko -->
<!-- /ko --> <!-- /ko -->
</div> </div>
</div> </div>

View File

@ -149,9 +149,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean { private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) { if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide(); $(this.dropdownElt).hide();
$(this.dropdownElt) $(this.dropdownElt).show().focus();
.show()
.focus();
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
@ -187,7 +185,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
} }
this.props.onCommandClick(e); this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel commandButtonClicked: this.props.commandButtonLabel,
}); });
} }

View File

@ -1,94 +1,94 @@
import * as React from "react"; import * as React from "react";
import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog"; import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
multiline: boolean; multiline: boolean;
autoAdjustHeight: boolean; autoAdjustHeight: boolean;
rows: number; rows: number;
onChange: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void; onChange: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
defaultValue?: string; defaultValue?: string;
} }
export interface LinkProps { export interface LinkProps {
linkText: string; linkText: string;
linkUrl: string; linkUrl: string;
} }
export interface DialogProps { export interface DialogProps {
title: string; title: string;
subText: string; subText: string;
isModal: boolean; isModal: boolean;
visible: boolean; visible: boolean;
textFieldProps?: TextFieldProps; textFieldProps?: TextFieldProps;
linkProps?: LinkProps; linkProps?: LinkProps;
primaryButtonText: string; primaryButtonText: string;
secondaryButtonText: string; secondaryButtonText: string;
onPrimaryButtonClick: () => void; onPrimaryButtonClick: () => void;
onSecondaryButtonClick: () => void; onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean; primaryButtonDisabled?: boolean;
type?: DialogType; type?: DialogType;
} }
const DIALOG_MIN_WIDTH = "400px"; const DIALOG_MIN_WIDTH = "400px";
const DIALOG_MAX_WIDTH = "600px"; const DIALOG_MAX_WIDTH = "600px";
const DIALOG_TITLE_FONT_SIZE = "17px"; const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400; const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px"; const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export class DialogComponent extends React.Component<DialogProps, {}> { export class DialogComponent extends React.Component<DialogProps, {}> {
constructor(props: DialogProps) { constructor(props: DialogProps) {
super(props); super(props);
} }
public render(): JSX.Element { public render(): JSX.Element {
const dialogProps: IDialogProps = { const dialogProps: IDialogProps = {
hidden: !this.props.visible, hidden: !this.props.visible,
dialogContentProps: { dialogContentProps: {
type: this.props.type || DialogType.normal, type: this.props.type || DialogType.normal,
title: this.props.title, title: this.props.title,
subText: this.props.subText, subText: this.props.subText,
styles: { styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE } subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
}, },
showCloseButton: false showCloseButton: false,
}, },
modalProps: { isBlocking: this.props.isModal }, modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH, minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH maxWidth: DIALOG_MAX_WIDTH,
}; };
const textFieldProps: ITextFieldProps = this.props.textFieldProps; const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps; const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = { const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText, text: this.props.primaryButtonText,
disabled: this.props.primaryButtonDisabled || false, disabled: this.props.primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick onClick: this.props.onPrimaryButtonClick,
}; };
const secondaryButtonProps: IButtonProps = const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick this.props.secondaryButtonText && this.props.onSecondaryButtonClick
? { ? {
text: this.props.secondaryButtonText, text: this.props.secondaryButtonText,
onClick: this.props.onSecondaryButtonClick onClick: this.props.onSecondaryButtonClick,
} }
: undefined; : undefined;
return ( return (
<Dialog {...dialogProps}> <Dialog {...dialogProps}>
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
<Link href={linkProps.linkUrl} target="_blank"> <Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} {linkProps.linkText}
</Link> </Link>
)} )}
<DialogFooter> <DialogFooter>
<PrimaryButton {...primaryButtonProps} /> <PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />} {secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter> </DialogFooter>
</Dialog> </Dialog>
); );
} }
} }

View File

@ -1,16 +1,16 @@
/** /**
* This adapter is responsible to render the Dialog React component * This adapter is responsible to render the Dialog React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate * If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent. * and update any knockout observables passed from the parent.
*/ */
import * as React from "react"; import * as React from "react";
import { DialogComponent, DialogProps } from "./DialogComponent"; import { DialogComponent, DialogProps } from "./DialogComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DialogComponentAdapter implements ReactAdapter { export class DialogComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<DialogProps>; public parameters: ko.Observable<DialogProps>;
public renderComponent(): JSX.Element { public renderComponent(): JSX.Element {
return <DialogComponent {...this.parameters()} />; return <DialogComponent {...this.parameters()} />;
} }
} }

View File

@ -9,7 +9,7 @@ export class DiffEditorComponent {
constructor() { constructor() {
return { return {
viewModel: DiffEditorViewModel, viewModel: DiffEditorViewModel,
template template,
}; };
} }
} }
@ -103,7 +103,7 @@ export class DiffEditorViewModel {
lineNumbers: this.params.lineNumbers || "off", lineNumbers: this.params.lineNumbers || "off",
fontSize: 12, fontSize: 12,
ariaLabel: this.params.ariaLabel, ariaLabel: this.params.ariaLabel,
theme: this.params.theme theme: this.params.theme,
}; };
if (this.params.renderSideBySide !== undefined) { if (this.params.renderSideBySide !== undefined) {
@ -120,7 +120,7 @@ export class DiffEditorViewModel {
); );
diffEditor.setModel({ diffEditor.setModel({
original: originalModel, original: originalModel,
modified: modifiedModel modified: modifiedModel,
}); });
createCallback(diffEditor); createCallback(diffEditor);
@ -147,7 +147,7 @@ export class DiffEditorViewModel {
this.observer.observe(document.body, { this.observer.observe(document.body, {
attributes: true, attributes: true,
subtree: true, subtree: true,
childList: true childList: true,
}); });
this.editor.focus(); this.editor.focus();
} }

View File

@ -7,7 +7,7 @@ const createBlankProps = (): DefaultDirectoryDropdownProps => {
return { return {
defaultDirectoryId: "", defaultDirectoryId: "",
directories: [], directories: [],
onDefaultDirectoryChange: jest.fn() onDefaultDirectoryChange: jest.fn(),
}; };
}; };
@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: "", displayName: "",
domains: [], domains: [],
id: "", id: "",
tenantId: "" tenantId: "",
}; };
}; };
@ -90,27 +90,15 @@ describe("test function", () => {
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />); const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
wrapper wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true); expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
.find("button.ms-Dropdown-item")
.at(1)
.simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled(); expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled(); expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
wrapper wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true); expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
.find("button.ms-Dropdown-item")
.at(0)
.simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled(); expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled(); expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
}); });

View File

@ -19,13 +19,13 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
public render(): JSX.Element { public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = { const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey, key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory" text: "Sign in to your last visited directory",
}; };
const directoryOptions: Array<IDropdownOption> = this.props.directories.map( const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
(dirc): IDropdownOption => { (dirc): IDropdownOption => {
return { return {
key: dirc.tenantId, key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})` text: `${dirc.displayName}(${dirc.tenantId})`,
}; };
} }
); );
@ -35,7 +35,7 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
options: dropDownOptions, options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key, defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange, onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown" className: "defaultDirectoryDropdown",
}; };
return <Dropdown {...dropDownProps} />; return <Dropdown {...dropDownProps} />;
@ -56,12 +56,12 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
countryCode: undefined, countryCode: undefined,
displayName: undefined, displayName: undefined,
domains: [], domains: [],
id: undefined id: undefined,
}); });
return; return;
} }
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === option.key); const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) { if (!selectedDirectory) {
return; return;
} }

View File

@ -7,7 +7,7 @@ const createBlankProps = (): DirectoryListProps => {
return { return {
selectedDirectoryId: undefined, selectedDirectoryId: undefined,
directories: [], directories: [],
onNewDirectorySelected: jest.fn() onNewDirectorySelected: jest.fn(),
}; };
}; };
@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: undefined, displayName: undefined,
domains: [], domains: [],
id: undefined, id: undefined,
tenantId: undefined tenantId: undefined,
}; };
}; };

View File

@ -28,7 +28,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
super(props); super(props);
this.state = { this.state = {
filterText: "" filterText: "",
}; };
} }
@ -38,12 +38,12 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
const filteredItems = const filteredItems =
originalItems && originalItems.length && filterText originalItems && originalItems.length && filterText
? originalItems.filter( ? originalItems.filter(
directory => (directory) =>
directory.displayName && directory.displayName &&
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0 directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
) )
: originalItems; : originalItems;
const filteredItemsSelected = filteredItems.map(t => { const filteredItemsSelected = filteredItems.map((t) => {
let tenant: ListTenant = t; let tenant: ListTenant = t;
tenant.selected = t.tenantId === selectedDirectoryId; tenant.selected = t.tenantId === selectedDirectoryId;
return tenant; return tenant;
@ -53,7 +53,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
className: "directoryListFilterTextBox", className: "directoryListFilterTextBox",
placeholder: "Filter by directory name", placeholder: "Filter by directory name",
onChange: this._onFilterChanged, onChange: this._onFilterChanged,
ariaLabel: "Directory filter text box" ariaLabel: "Directory filter text box",
}; };
// TODO: add magnify glass to search bar with onRenderSuffix // TODO: add magnify glass to search bar with onRenderSuffix
@ -69,7 +69,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => { private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
this.setState({ this.setState({
filterText: text filterText: text,
}); });
}; };
@ -84,19 +84,19 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
height: "auto", height: "auto",
borderBottom: "1px solid #ccc", borderBottom: "1px solid #ccc",
padding: "1px 0", padding: "1px 0",
width: "100%" width: "100%",
}, },
rootDisabled: { rootDisabled: {
backgroundColor: "#f1f1f8" backgroundColor: "#f1f1f8",
}, },
rootHovered: { rootHovered: {
backgroundColor: "rgba(85,179,255,.1)" backgroundColor: "rgba(85,179,255,.1)",
}, },
flexContainer: { flexContainer: {
height: "auto", height: "auto",
justifyContent: "flex-start" justifyContent: "flex-start",
} },
} },
}; };
return ( return (
@ -115,7 +115,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
} }
const buttonElement = e.currentTarget; const buttonElement = e.currentTarget;
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent; const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === selectedDirectoryId); const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
this.props.onNewDirectorySelected(selectedDirectory); this.props.onNewDirectorySelected(selectedDirectory);
}; };

View File

@ -25,7 +25,7 @@ describe("Dynamic List Component", () => {
placeholder: placeholder, placeholder: placeholder,
listItems: items, listItems: items,
buttonText: mockButton, buttonText: mockButton,
ariaLabel: mockAriaLabel ariaLabel: mockAriaLabel,
}; };
} }

View File

@ -112,5 +112,5 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
*/ */
export const DynamicListComponent = { export const DynamicListComponent = {
viewModel: DynamicListViewModel, viewModel: DynamicListViewModel,
template template,
}; };

View File

@ -1,63 +1,63 @@
import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent"; import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent";
import template from "./editor-component.html"; import template from "./editor-component.html";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service"; import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
*/ */
export class EditorComponent { export class EditorComponent {
constructor() { constructor() {
return { return {
viewModel: EditorViewModel, viewModel: EditorViewModel,
template template,
}; };
} }
} }
/** /**
* Parameters for this component * Parameters for this component
*/ */
export interface EditorParams extends JsonEditorParams { export interface EditorParams extends JsonEditorParams {
contentType: string; contentType: string;
} }
/** /**
* This is a generic editor component that builds on top of the pre-existing JsonEditorComponent. * This is a generic editor component that builds on top of the pre-existing JsonEditorComponent.
*/ */
// TODO: Ideally, JsonEditorViewModel should extend EditorViewModel and not the other way around // TODO: Ideally, JsonEditorViewModel should extend EditorViewModel and not the other way around
class EditorViewModel extends JsonEditorViewModel { class EditorViewModel extends JsonEditorViewModel {
public params: EditorParams; public params: EditorParams;
private static providerRegistered: string[] = []; private static providerRegistered: string[] = [];
public constructor(params: EditorParams) { public constructor(params: EditorParams) {
super(params); super(params);
this.params = params; this.params = params;
super.createEditor.bind(this); super.createEditor.bind(this);
/** /**
* setTimeout is needed as creating the edtior manipulates the dom directly and expects * setTimeout is needed as creating the edtior manipulates the dom directly and expects
* Knockout to have completed all of the initial bindings for the component * Knockout to have completed all of the initial bindings for the component
*/ */
this.params.content() != null && this.params.content() != null &&
setTimeout(() => { setTimeout(() => {
this.createEditor(this.params.content(), this.configureEditor.bind(this)); this.createEditor(this.params.content(), this.configureEditor.bind(this));
}); });
} }
protected getEditorLanguage(): string { protected getEditorLanguage(): string {
return this.params.contentType; return this.params.contentType;
} }
protected registerCompletionItemProvider() { protected registerCompletionItemProvider() {
let sqlCompletionItemProvider = new SqlCompletionItemProvider(); let sqlCompletionItemProvider = new SqlCompletionItemProvider();
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) { if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider); monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider);
EditorViewModel.providerRegistered.push("sql"); EditorViewModel.providerRegistered.push("sql");
} }
} }
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> { protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return ErrorMarkProvider.getErrorMark(input); return ErrorMarkProvider.getErrorMark(input);
} }
} }

View File

@ -70,7 +70,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
fontSize: 12, fontSize: 12,
ariaLabel: this.props.ariaLabel, ariaLabel: this.props.ariaLabel,
theme: this.props.theme, theme: this.props.theme,
automaticLayout: true automaticLayout: true,
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";

View File

@ -1,30 +1,30 @@
import template from "./error-display-component.html"; import template from "./error-display-component.html";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
* This component displays an error as designed in: * This component displays an error as designed in:
* https://microsoft.sharepoint.com/teams/DPX/Modern/DocDB/_layouts/15/WopiFrame.aspx?sourcedoc={66864d4a-f925-4cbe-9eb4-79f8d191a115}&action=edit&wd=target%28DocumentDB%20emulator%2Eone%7CE617D0A7-F77C-4968-B75A-1451049F4FEA%2FError%20notification%7CAA1E4BC9-4D72-472C-B40C-2437FA217226%2F%29 * https://microsoft.sharepoint.com/teams/DPX/Modern/DocDB/_layouts/15/WopiFrame.aspx?sourcedoc={66864d4a-f925-4cbe-9eb4-79f8d191a115}&action=edit&wd=target%28DocumentDB%20emulator%2Eone%7CE617D0A7-F77C-4968-B75A-1451049F4FEA%2FError%20notification%7CAA1E4BC9-4D72-472C-B40C-2437FA217226%2F%29
* TODO: support "More details" * TODO: support "More details"
*/ */
export class ErrorDisplayComponent { export class ErrorDisplayComponent {
constructor() { constructor() {
return { return {
viewModel: ErrorDisplayViewModel, viewModel: ErrorDisplayViewModel,
template template,
}; };
} }
} }
/** /**
* Parameters for this component * Parameters for this component
*/ */
interface ErrorDisplayParams { interface ErrorDisplayParams {
errorMsg: ko.Observable<string>; // Primary message errorMsg: ko.Observable<string>; // Primary message
} }
class ErrorDisplayViewModel { class ErrorDisplayViewModel {
private params: ErrorDisplayParams; private params: ErrorDisplayParams;
public constructor(params: ErrorDisplayParams) { public constructor(params: ErrorDisplayParams) {
this.params = params; this.params = params;
} }
} }

View File

@ -1,6 +1,6 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()"> <div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent"> <div class="warningErrorContent">
<span><img src="/error_red.svg" alt="Error"/></span> <span><img src="/error_red.svg" alt="Error" /></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span> <span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div> </div>
</div> </div>

View File

@ -15,23 +15,23 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
const baseUrlOptions = [ const baseUrlOptions = [
{ key: "https://localhost:1234/explorer.html", text: "localhost:1234" }, { key: "https://localhost:1234/explorer.html", text: "localhost:1234" },
{ key: "https://cosmos.azure.com/explorer.html", text: "cosmos.azure.com" }, { key: "https://cosmos.azure.com/explorer.html", text: "cosmos.azure.com" },
{ key: "https://portal.azure.com", text: "portal" } { key: "https://portal.azure.com", text: "portal" },
]; ];
const platformOptions = [ const platformOptions = [
{ key: "Hosted", text: "Hosted" }, { key: "Hosted", text: "Hosted" },
{ key: "Portal", text: "Portal" }, { key: "Portal", text: "Portal" },
{ key: "Emulator", text: "Emulator" }, { key: "Emulator", text: "Emulator" },
{ key: "", text: "None" } { key: "", text: "None" },
]; ];
// React hooks to keep state // React hooks to keep state
const [baseUrl, setBaseUrl] = React.useState<IDropdownOption>( const [baseUrl, setBaseUrl] = React.useState<IDropdownOption>(
baseUrlOptions.find(o => o.key === window.location.origin + window.location.pathname) || baseUrlOptions[0] baseUrlOptions.find((o) => o.key === window.location.origin + window.location.pathname) || baseUrlOptions[0]
); );
const [platform, setPlatform] = React.useState<IDropdownOption>( const [platform, setPlatform] = React.useState<IDropdownOption>(
urlParams.has("platform") urlParams.has("platform")
? platformOptions.find(o => o.key === urlParams.get("platform")) || platformOptions[0] ? platformOptions.find((o) => o.key === urlParams.get("platform")) || platformOptions[0]
: platformOptions[0] : platformOptions[0]
); );
@ -54,7 +54,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{ {
key: "feature.enablefixedcollectionwithsharedthroughput", key: "feature.enablefixedcollectionwithsharedthroughput",
label: "Enable fixed collection with shared throughput", label: "Enable fixed collection with shared throughput",
value: "true" value: "true",
}, },
{ key: "feature.ttl90days", label: "TTL 90 days", value: "true" }, { key: "feature.ttl90days", label: "TTL 90 days", value: "true" },
{ key: "feature.enablenotebooks", label: "Enable notebooks", value: "true" }, { key: "feature.enablenotebooks", label: "Enable notebooks", value: "true" },
@ -62,10 +62,10 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
key: "feature.customportal", key: "feature.customportal",
label: "Force Production portal (portal only)", label: "Force Production portal (portal only)",
value: "false", value: "false",
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com" disabled: (): boolean => baseUrl.key !== "https://portal.azure.com",
}, },
{ key: "feature.enablespark", label: "Enable Synapse", value: "true" }, { key: "feature.enablespark", label: "Enable Synapse", value: "true" },
{ key: "feature.enableautopilotv2", label: "Enable Auto-pilot V2", value: "true" } { key: "feature.enableautopilotv2", label: "Enable Auto-pilot V2", value: "true" },
]; ];
const stringFeatures: { const stringFeatures: {
@ -84,23 +84,23 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
key: "dataExplorerSource", key: "dataExplorerSource",
label: "Data Explorer Source (portal only)", label: "Data Explorer Source (portal only)",
placeholder: "https://localhost:1234/explorer.html", placeholder: "https://localhost:1234/explorer.html",
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com" disabled: (): boolean => baseUrl.key !== "https://portal.azure.com",
}, },
{ key: "feature.livyendpoint", label: "Livy endpoint", placeholder: "" } { key: "feature.livyendpoint", label: "Livy endpoint", placeholder: "" },
]; ];
booleanFeatures.forEach( booleanFeatures.forEach(
f => (f.reactState = React.useState<boolean>(urlParams.has(f.key) ? urlParams.get(f.key) === "true" : false)) (f) => (f.reactState = React.useState<boolean>(urlParams.has(f.key) ? urlParams.get(f.key) === "true" : false))
); );
stringFeatures.forEach( stringFeatures.forEach(
f => (f.reactState = React.useState<string>(urlParams.has(f.key) ? urlParams.get(f.key) : undefined)) (f) => (f.reactState = React.useState<string>(urlParams.has(f.key) ? urlParams.get(f.key) : undefined))
); );
const buildUrl = (): string => { const buildUrl = (): string => {
const fragments = (platform.key === "" ? [] : [`platform=${platform.key}`]) const fragments = (platform.key === "" ? [] : [`platform=${platform.key}`])
.concat(booleanFeatures.map(f => (f.reactState[0] ? `${f.key}=${f.value}` : ""))) .concat(booleanFeatures.map((f) => (f.reactState[0] ? `${f.key}=${f.value}` : "")))
.concat(stringFeatures.map(f => (f.reactState[0] ? `${f.key}=${encodeURIComponent(f.reactState[0])}` : ""))) .concat(stringFeatures.map((f) => (f.reactState[0] ? `${f.key}=${encodeURIComponent(f.reactState[0])}` : "")))
.filter(v => v && v.length > 0); .filter((v) => v && v.length > 0);
const paramString = fragments.length < 1 ? "" : `?${fragments.join("&")}`; const paramString = fragments.length < 1 ? "" : `?${fragments.join("&")}`;
return `${baseUrl.key}${paramString}`; return `${baseUrl.key}${paramString}`;
@ -115,38 +115,38 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
}; };
booleanFeatures.forEach( booleanFeatures.forEach(
f => (f) =>
(f.onChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean): void => { (f.onChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean): void => {
f.reactState[1](checked); f.reactState[1](checked);
}) })
); );
stringFeatures.forEach( stringFeatures.forEach(
f => (f) =>
(f.onChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => { (f.onChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
f.reactState[1](newValue); f.reactState[1](newValue);
}) })
); );
const onNotebookShortcut = (): void => { const onNotebookShortcut = (): void => {
booleanFeatures.find(f => f.key === "feature.enablenotebooks").reactState[1](true); booleanFeatures.find((f) => f.key === "feature.enablenotebooks").reactState[1](true);
stringFeatures stringFeatures
.find(f => f.key === "feature.notebookserverurl") .find((f) => f.key === "feature.notebookserverurl")
.reactState[1]("https://localhost:10001/12345/notebook/"); .reactState[1]("https://localhost:10001/12345/notebook/");
stringFeatures.find(f => f.key === "feature.notebookservertoken").reactState[1]("token"); stringFeatures.find((f) => f.key === "feature.notebookservertoken").reactState[1]("token");
stringFeatures.find(f => f.key === "feature.notebookbasepath").reactState[1]("./notebooks"); stringFeatures.find((f) => f.key === "feature.notebookbasepath").reactState[1]("./notebooks");
setPlatform(platformOptions.find(o => o.key === "Hosted")); setPlatform(platformOptions.find((o) => o.key === "Hosted"));
}; };
const onPortalLocalDEShortcut = (): void => { const onPortalLocalDEShortcut = (): void => {
setBaseUrl(baseUrlOptions.find(o => o.key === "https://portal.azure.com")); setBaseUrl(baseUrlOptions.find((o) => o.key === "https://portal.azure.com"));
setPlatform(platformOptions.find(o => o.key === "Portal")); setPlatform(platformOptions.find((o) => o.key === "Portal"));
stringFeatures.find(f => f.key === "dataExplorerSource").reactState[1]("https://localhost:1234/explorer.html"); stringFeatures.find((f) => f.key === "dataExplorerSource").reactState[1]("https://localhost:1234/explorer.html");
}; };
const onReset = (): void => { const onReset = (): void => {
booleanFeatures.forEach(f => f.reactState[1](false)); booleanFeatures.forEach((f) => f.reactState[1](false));
stringFeatures.forEach(f => f.reactState[1]("")); stringFeatures.forEach((f) => f.reactState[1](""));
}; };
const stackTokens = { childrenGap: 10 }; const stackTokens = { childrenGap: 10 };
@ -165,7 +165,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
const anchorOptions = { const anchorOptions = {
href: buildUrl(), href: buildUrl(),
target: "_blank", target: "_blank",
rel: "noopener" rel: "noopener",
}; };
return ( return (
@ -197,7 +197,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
</Stack> </Stack>
<Stack horizontal> <Stack horizontal>
<Stack className="checkboxRow" horizontalAlign="space-between"> <Stack className="checkboxRow" horizontalAlign="space-between">
{leftBooleanFeatures.map(f => ( {leftBooleanFeatures.map((f) => (
<Checkbox <Checkbox
key={f.key} key={f.key}
label={f.label} label={f.label}
@ -208,7 +208,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
))} ))}
</Stack> </Stack>
<Stack className="checkboxRow" horizontalAlign="space-between"> <Stack className="checkboxRow" horizontalAlign="space-between">
{rightBooleanFeatures.map(f => ( {rightBooleanFeatures.map((f) => (
<Checkbox <Checkbox
key={f.key} key={f.key}
label={f.label} label={f.label}
@ -221,7 +221,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
</Stack> </Stack>
<Stack horizontal tokens={stackTokens}> <Stack horizontal tokens={stackTokens}>
<Stack horizontalAlign="space-between"> <Stack horizontalAlign="space-between">
{leftStringFeatures.map(f => ( {leftStringFeatures.map((f) => (
<TextField <TextField
key={f.key} key={f.key}
value={f.reactState[0]} value={f.reactState[0]}
@ -234,7 +234,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
))} ))}
</Stack> </Stack>
<Stack horizontalAlign="space-between"> <Stack horizontalAlign="space-between">
{rightStringFeatures.map(f => ( {rightStringFeatures.map((f) => (
<TextField <TextField
key={f.key} key={f.key}
value={f.reactState[0]} value={f.reactState[0]}

View File

@ -20,7 +20,7 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
container: { container: {
display: "flex", display: "flex",
flexFlow: "column nowrap", flexFlow: "column nowrap",
alignItems: "stretch" alignItems: "stretch",
}, },
header: [ header: [
// tslint:disable-next-line:deprecation // tslint:disable-next-line:deprecation
@ -32,16 +32,16 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
fontWeight: FontWeights.semibold, fontWeight: FontWeights.semibold,
padding: "12px 12px 14px 24px" padding: "12px 12px 14px 24px",
} },
], ],
body: { body: {
flex: "4 4 auto", flex: "4 4 auto",
overflowY: "hidden", overflowY: "hidden",
marginBottom: 40, marginBottom: 40,
height: "100%", height: "100%",
display: "flex" display: "flex",
} },
}); });
const iconButtonStyles = { const iconButtonStyles = {
@ -49,11 +49,11 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
color: theme.palette.neutralPrimary, color: theme.palette.neutralPrimary,
marginLeft: "auto", marginLeft: "auto",
marginTop: "4px", marginTop: "4px",
marginRight: "2px" marginRight: "2px",
}, },
rootHovered: { rootHovered: {
color: theme.palette.neutralDark color: theme.palette.neutralDark,
} },
}; };
const cancelIcon: IIconProps = { iconName: "Cancel" }; const cancelIcon: IIconProps = { iconName: "Cancel" };
const hideModal = (): void => showModal(false); const hideModal = (): void => showModal(false);

View File

@ -1,132 +1,132 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react"; import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent"; import { RepoListItem } from "./GitHubReposComponent";
import { ChildrenMargin } from "./GitHubStyleConstants"; import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils"; import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient"; import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility"; import UrlUtility from "../../../Common/UrlUtility";
export interface AddRepoComponentProps { export interface AddRepoComponentProps {
container: ViewModels.Explorer; container: ViewModels.Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>; getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void; pinRepo: (item: RepoListItem) => void;
} }
interface AddRepoComponentState { interface AddRepoComponentState {
textFieldValue: string; textFieldValue: string;
textFieldErrorMessage: string; textFieldErrorMessage: string;
} }
export class AddRepoComponent extends React.Component<AddRepoComponentProps, AddRepoComponentState> { export class AddRepoComponent extends React.Component<AddRepoComponentProps, AddRepoComponentState> {
private static readonly DescriptionText = private static readonly DescriptionText =
"Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: "; "Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: ";
private static readonly ButtonText = "Add"; private static readonly ButtonText = "Add";
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch"; private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
private static readonly TextFieldErrorMessage = "Invalid url"; private static readonly TextFieldErrorMessage = "Invalid url";
private static readonly DefaultBranchName = "master"; private static readonly DefaultBranchName = "master";
constructor(props: AddRepoComponentProps) { constructor(props: AddRepoComponentProps) {
super(props); super(props);
this.state = { this.state = {
textFieldValue: "", textFieldValue: "",
textFieldErrorMessage: undefined textFieldErrorMessage: undefined,
}; };
} }
public render(): JSX.Element { public render(): JSX.Element {
const textFieldProps: ITextFieldProps = { const textFieldProps: ITextFieldProps = {
placeholder: AddRepoComponent.TextFieldPlaceholder, placeholder: AddRepoComponent.TextFieldPlaceholder,
autoFocus: true, autoFocus: true,
value: this.state.textFieldValue, value: this.state.textFieldValue,
errorMessage: this.state.textFieldErrorMessage, errorMessage: this.state.textFieldErrorMessage,
onChange: this.onTextFieldChange onChange: this.onTextFieldChange,
}; };
const buttonProps: IButtonProps = { const buttonProps: IButtonProps = {
text: AddRepoComponent.ButtonText, text: AddRepoComponent.ButtonText,
ariaLabel: AddRepoComponent.ButtonText, ariaLabel: AddRepoComponent.ButtonText,
onClick: this.onAddRepoButtonClick onClick: this.onAddRepoButtonClick,
}; };
return ( return (
<> <>
<p style={{ marginBottom: ChildrenMargin }}>{AddRepoComponent.DescriptionText}</p> <p style={{ marginBottom: ChildrenMargin }}>{AddRepoComponent.DescriptionText}</p>
<TextField {...textFieldProps} /> <TextField {...textFieldProps} />
<DefaultButton style={{ marginTop: ChildrenMargin }} {...buttonProps} /> <DefaultButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</> </>
); );
} }
private onTextFieldChange = ( private onTextFieldChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string newValue?: string
): void => { ): void => {
this.setState({ this.setState({
textFieldValue: newValue || "", textFieldValue: newValue || "",
textFieldErrorMessage: undefined textFieldErrorMessage: undefined,
}); });
}; };
private onAddRepoButtonClick = async (): Promise<void> => { private onAddRepoButtonClick = async (): Promise<void> => {
const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, { const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name, databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(), defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook dataExplorerArea: Constants.Areas.Notebook,
}); });
let enteredUrl = this.state.textFieldValue; let enteredUrl = this.state.textFieldValue;
if (enteredUrl.indexOf("/tree/") === -1) { if (enteredUrl.indexOf("/tree/") === -1) {
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`); enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
} }
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl); const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
if (repoInfo) { if (repoInfo) {
this.setState({ this.setState({
textFieldValue: "", textFieldValue: "",
textFieldErrorMessage: undefined textFieldErrorMessage: undefined,
}); });
const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo); const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo);
if (repo) { if (repo) {
const item: RepoListItem = { const item: RepoListItem = {
key: GitHubUtils.toRepoFullName(repo.owner, repo.name), key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
repo, repo,
branches: [ branches: [
{ {
name: repoInfo.branch name: repoInfo.branch,
} },
] ],
}; };
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.NotebooksGitHubManualRepoAdd, Action.NotebooksGitHubManualRepoAdd,
{ {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name, databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(), defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook dataExplorerArea: Constants.Areas.Notebook,
}, },
startKey startKey
); );
return this.props.pinRepo(item); return this.props.pinRepo(item);
} }
} }
this.setState({ this.setState({
textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage,
}); });
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
Action.NotebooksGitHubManualRepoAdd, Action.NotebooksGitHubManualRepoAdd,
{ {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name, databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(), defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook, dataExplorerArea: Constants.Areas.Notebook,
error: AddRepoComponent.TextFieldErrorMessage error: AddRepoComponent.TextFieldErrorMessage,
}, },
startKey startKey
); );
}; };
} }

View File

@ -1,91 +1,91 @@
import { import {
ChoiceGroup, ChoiceGroup,
IButtonProps, IButtonProps,
IChoiceGroupProps, IChoiceGroupProps,
PrimaryButton, PrimaryButton,
IChoiceGroupOption IChoiceGroupOption,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { ChildrenMargin } from "./GitHubStyleConstants"; import { ChildrenMargin } from "./GitHubStyleConstants";
export interface AuthorizeAccessComponentProps { export interface AuthorizeAccessComponentProps {
scope: string; scope: string;
authorizeAccess: (scope: string) => void; authorizeAccess: (scope: string) => void;
} }
export interface AuthorizeAccessComponentState { export interface AuthorizeAccessComponentState {
scope: string; scope: string;
} }
export class AuthorizeAccessComponent extends React.Component< export class AuthorizeAccessComponent extends React.Component<
AuthorizeAccessComponentProps, AuthorizeAccessComponentProps,
AuthorizeAccessComponentState AuthorizeAccessComponentState
> { > {
// Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos. // Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos.
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ // https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
public static readonly Scopes = { public static readonly Scopes = {
Public: { Public: {
key: "public_repo", key: "public_repo",
text: "Public repos only" text: "Public repos only",
}, },
PublicAndPrivate: { PublicAndPrivate: {
key: "repo", key: "repo",
text: "Public and private repos" text: "Public and private repos",
} },
}; };
private static readonly DescriptionPara1 = private static readonly DescriptionPara1 =
"Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer."; "Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer.";
private static readonly DescriptionPara2 = private static readonly DescriptionPara2 =
"Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: "; "Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: ";
private static readonly AuthorizeButtonText = "Authorize access"; private static readonly AuthorizeButtonText = "Authorize access";
private onChoiceGroupChange = (event: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void => private onChoiceGroupChange = (event: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void =>
this.setState({ this.setState({
scope: option.key scope: option.key,
}); });
private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope); private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope);
constructor(props: AuthorizeAccessComponentProps) { constructor(props: AuthorizeAccessComponentProps) {
super(props); super(props);
this.state = { this.state = {
scope: this.props.scope scope: this.props.scope,
}; };
} }
public render(): JSX.Element { public render(): JSX.Element {
const choiceGroupProps: IChoiceGroupProps = { const choiceGroupProps: IChoiceGroupProps = {
options: [ options: [
{ {
key: AuthorizeAccessComponent.Scopes.Public.key, key: AuthorizeAccessComponent.Scopes.Public.key,
text: AuthorizeAccessComponent.Scopes.Public.text, text: AuthorizeAccessComponent.Scopes.Public.text,
ariaLabel: AuthorizeAccessComponent.Scopes.Public.text ariaLabel: AuthorizeAccessComponent.Scopes.Public.text,
}, },
{ {
key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key, key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key,
text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text, text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
} },
], ],
selectedKey: this.state.scope, selectedKey: this.state.scope,
onChange: this.onChoiceGroupChange onChange: this.onChoiceGroupChange,
}; };
const buttonProps: IButtonProps = { const buttonProps: IButtonProps = {
text: AuthorizeAccessComponent.AuthorizeButtonText, text: AuthorizeAccessComponent.AuthorizeButtonText,
ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText, ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText,
onClick: this.onButtonClick onClick: this.onButtonClick,
}; };
return ( return (
<> <>
<p>{AuthorizeAccessComponent.DescriptionPara1}</p> <p>{AuthorizeAccessComponent.DescriptionPara1}</p>
<p style={{ marginTop: ChildrenMargin }}>{AuthorizeAccessComponent.DescriptionPara2}</p> <p style={{ marginTop: ChildrenMargin }}>{AuthorizeAccessComponent.DescriptionPara2}</p>
<ChoiceGroup style={{ marginTop: ChildrenMargin }} {...choiceGroupProps} /> <ChoiceGroup style={{ marginTop: ChildrenMargin }} {...choiceGroupProps} />
<PrimaryButton style={{ marginTop: ChildrenMargin }} {...buttonProps} /> <PrimaryButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</> </>
); );
} }
} }

View File

@ -1,85 +1,85 @@
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react"; import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient"; import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent"; import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent"; import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants"; import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants";
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent"; import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
export interface GitHubReposComponentProps { export interface GitHubReposComponentProps {
showAuthorizeAccess: boolean; showAuthorizeAccess: boolean;
authorizeAccessProps: AuthorizeAccessComponentProps; authorizeAccessProps: AuthorizeAccessComponentProps;
reposListProps: ReposListComponentProps; reposListProps: ReposListComponentProps;
addRepoProps: AddRepoComponentProps; addRepoProps: AddRepoComponentProps;
resetConnection: () => void; resetConnection: () => void;
onOkClick: () => void; onOkClick: () => void;
onCancelClick: () => void; onCancelClick: () => void;
} }
export interface RepoListItem { export interface RepoListItem {
key: string; key: string;
repo: IGitHubRepo; repo: IGitHubRepo;
branches: IGitHubBranch[]; branches: IGitHubBranch[];
} }
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> { export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub"; public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings"; public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription = private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace."; "Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings."; private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
private static readonly OKButtonText = "OK"; private static readonly OKButtonText = "OK";
private static readonly CancelButtonText = "Cancel"; private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element { public render(): JSX.Element {
const header: JSX.Element = ( const header: JSX.Element = (
<p> <p>
{this.props.showAuthorizeAccess {this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle ? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle} : GitHubReposComponent.ManageGitHubRepoTitle}
</p> </p>
); );
const content: JSX.Element = this.props.showAuthorizeAccess ? ( const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} /> <AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : ( ) : (
<> <>
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p> <p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
<Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}> <Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}>
{GitHubReposComponent.ManageGitHubRepoResetConnection} {GitHubReposComponent.ManageGitHubRepoResetConnection}
</Link> </Link>
<ReposListComponent {...this.props.reposListProps} /> <ReposListComponent {...this.props.reposListProps} />
</> </>
); );
const okProps: IButtonProps = { const okProps: IButtonProps = {
text: GitHubReposComponent.OKButtonText, text: GitHubReposComponent.OKButtonText,
ariaLabel: GitHubReposComponent.OKButtonText, ariaLabel: GitHubReposComponent.OKButtonText,
onClick: this.props.onOkClick onClick: this.props.onOkClick,
}; };
const cancelProps: IButtonProps = { const cancelProps: IButtonProps = {
text: GitHubReposComponent.CancelButtonText, text: GitHubReposComponent.CancelButtonText,
ariaLabel: GitHubReposComponent.CancelButtonText, ariaLabel: GitHubReposComponent.CancelButtonText,
onClick: this.props.onCancelClick onClick: this.props.onCancelClick,
}; };
return ( return (
<> <>
<div className={"firstdivbg headerline"}>{header}</div> <div className={"firstdivbg headerline"}>{header}</div>
<div className={"paneMainContent"}>{content}</div> <div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && ( {!this.props.showAuthorizeAccess && (
<> <>
<div className={"paneFooter"} style={ContentFooterStyle}> <div className={"paneFooter"} style={ContentFooterStyle}>
<AddRepoComponent {...this.props.addRepoProps} /> <AddRepoComponent {...this.props.addRepoProps} />
</div> </div>
<div className={"paneFooter"} style={ButtonsFooterStyle}> <div className={"paneFooter"} style={ButtonsFooterStyle}>
<PrimaryButton {...okProps} /> <PrimaryButton {...okProps} />
<DefaultButton style={{ marginLeft: ChildrenMargin }} {...cancelProps} /> <DefaultButton style={{ marginLeft: ChildrenMargin }} {...cancelProps} />
</div> </div>
</> </>
)} )}
</> </>
); );
} }
} }

View File

@ -1,20 +1,20 @@
import ko from "knockout"; import ko from "knockout";
import * as React from "react"; import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent"; import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
export class GitHubReposComponentAdapter implements ReactAdapter { export class GitHubReposComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
constructor(private props: GitHubReposComponentProps) { constructor(private props: GitHubReposComponentProps) {
this.parameters = ko.observable<number>(Date.now()); this.parameters = ko.observable<number>(Date.now());
} }
public renderComponent(): JSX.Element { public renderComponent(): JSX.Element {
return <GitHubReposComponent {...this.props} />; return <GitHubReposComponent {...this.props} />;
} }
public triggerRender(): void { public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now())); window.requestAnimationFrame(() => this.parameters(Date.now()));
} }
} }

View File

@ -1,58 +1,58 @@
import { import {
IStyleFunctionOrObject, IStyleFunctionOrObject,
ICheckboxStyleProps, ICheckboxStyleProps,
ICheckboxStyles, ICheckboxStyles,
IDropdownStyles, IDropdownStyles,
IDropdownStyleProps IDropdownStyleProps,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
export const ButtonsFooterStyle: React.CSSProperties = { export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14, padding: 14,
height: "auto" height: "auto",
}; };
export const ContentFooterStyle: React.CSSProperties = { export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px", padding: "10px 24px 10px 24px",
height: "auto" height: "auto",
}; };
export const ChildrenMargin = 10; export const ChildrenMargin = 10;
export const FontSize = 12; export const FontSize = 12;
export const ReposListCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = { export const ReposListCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: { label: {
margin: 0, margin: 0,
padding: "2 0 2 0" padding: "2 0 2 0",
}, },
text: { text: {
fontSize: FontSize fontSize: FontSize,
} },
}; };
export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = { export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: { label: {
margin: 0, margin: 0,
padding: 0, padding: 0,
fontSize: FontSize fontSize: FontSize,
}, },
root: { root: {
padding: 0 padding: 0,
}, },
text: { text: {
fontSize: FontSize fontSize: FontSize,
} },
}; };
export const BranchesDropdownStyles: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles> = { export const BranchesDropdownStyles: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles> = {
title: { title: {
fontSize: FontSize fontSize: FontSize,
} },
}; };
export const BranchesDropdownOptionContainerStyle: React.CSSProperties = { export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8 padding: 8,
}; };
export const ReposListRepoColumnMinWidth = 192; export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116; export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200; export const BranchesDropdownWidth = 200;

View File

@ -1,304 +1,304 @@
import { import {
Checkbox, Checkbox,
DetailsList, DetailsList,
DetailsRow, DetailsRow,
Dropdown, Dropdown,
ICheckboxProps, ICheckboxProps,
IDetailsFooterProps, IDetailsFooterProps,
IDetailsListProps, IDetailsListProps,
IDetailsRowBaseProps, IDetailsRowBaseProps,
IDropdown, IDropdown,
IDropdownOption, IDropdownOption,
IDropdownProps, IDropdownProps,
ILinkProps, ILinkProps,
ISelectableDroppableTextProps, ISelectableDroppableTextProps,
Link, Link,
ResponsiveMode, ResponsiveMode,
SelectionMode, SelectionMode,
Text Text,
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient"; import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils"; import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { RepoListItem } from "./GitHubReposComponent"; import { RepoListItem } from "./GitHubReposComponent";
import { import {
BranchesDropdownCheckboxStyles, BranchesDropdownCheckboxStyles,
BranchesDropdownOptionContainerStyle, BranchesDropdownOptionContainerStyle,
ReposListCheckboxStyles, ReposListCheckboxStyles,
ReposListRepoColumnMinWidth, ReposListRepoColumnMinWidth,
ReposListBranchesColumnWidth, ReposListBranchesColumnWidth,
BranchesDropdownWidth, BranchesDropdownWidth,
BranchesDropdownStyles BranchesDropdownStyles,
} from "./GitHubStyleConstants"; } from "./GitHubStyleConstants";
export interface ReposListComponentProps { export interface ReposListComponentProps {
branchesProps: Record<string, BranchesProps>; // key'd by repo key branchesProps: Record<string, BranchesProps>; // key'd by repo key
pinnedReposProps: PinnedReposProps; pinnedReposProps: PinnedReposProps;
unpinnedReposProps: UnpinnedReposProps; unpinnedReposProps: UnpinnedReposProps;
pinRepo: (repo: RepoListItem) => void; pinRepo: (repo: RepoListItem) => void;
unpinRepo: (repo: RepoListItem) => void; unpinRepo: (repo: RepoListItem) => void;
} }
export interface BranchesProps { export interface BranchesProps {
branches: IGitHubBranch[]; branches: IGitHubBranch[];
lastPageInfo?: IGitHubPageInfo; lastPageInfo?: IGitHubPageInfo;
hasMore: boolean; hasMore: boolean;
isLoading: boolean; isLoading: boolean;
loadMore: () => void; loadMore: () => void;
} }
export interface PinnedReposProps { export interface PinnedReposProps {
repos: RepoListItem[]; repos: RepoListItem[];
} }
export interface UnpinnedReposProps { export interface UnpinnedReposProps {
repos: RepoListItem[]; repos: RepoListItem[];
hasMore: boolean; hasMore: boolean;
isLoading: boolean; isLoading: boolean;
loadMore: () => void; loadMore: () => void;
} }
export class ReposListComponent extends React.Component<ReposListComponentProps> { export class ReposListComponent extends React.Component<ReposListComponentProps> {
private static readonly PinnedReposColumnName = "Pinned repos"; private static readonly PinnedReposColumnName = "Pinned repos";
private static readonly UnpinnedReposColumnName = "Unpinned repos"; private static readonly UnpinnedReposColumnName = "Unpinned repos";
private static readonly BranchesColumnName = "Branches"; private static readonly BranchesColumnName = "Branches";
private static readonly LoadingText = "Loading..."; private static readonly LoadingText = "Loading...";
private static readonly LoadMoreText = "Load more"; private static readonly LoadMoreText = "Load more";
private static readonly DefaultBranchName = "master"; private static readonly DefaultBranchName = "master";
private static readonly FooterIndex = -1; private static readonly FooterIndex = -1;
public render(): JSX.Element { public render(): JSX.Element {
const pinnedReposListProps: IDetailsListProps = { const pinnedReposListProps: IDetailsListProps = {
styles: { styles: {
contentWrapper: { contentWrapper: {
height: this.props.pinnedReposProps.repos.length ? undefined : 0 height: this.props.pinnedReposProps.repos.length ? undefined : 0,
} },
}, },
items: this.props.pinnedReposProps.repos, items: this.props.pinnedReposProps.repos,
getKey: ReposListComponent.getKey, getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none, selectionMode: SelectionMode.none,
compact: true, compact: true,
columns: [ columns: [
{ {
key: ReposListComponent.PinnedReposColumnName, key: ReposListComponent.PinnedReposColumnName,
name: ReposListComponent.PinnedReposColumnName, name: ReposListComponent.PinnedReposColumnName,
ariaLabel: ReposListComponent.PinnedReposColumnName, ariaLabel: ReposListComponent.PinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth, minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderPinnedReposColumnItem onRender: this.onRenderPinnedReposColumnItem,
}, },
{ {
key: ReposListComponent.BranchesColumnName, key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName, name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName, ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth, minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth, maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderPinnedReposBranchesColumnItem onRender: this.onRenderPinnedReposBranchesColumnItem,
} },
], ],
onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter,
}; };
const unpinnedReposListProps: IDetailsListProps = { const unpinnedReposListProps: IDetailsListProps = {
items: this.props.unpinnedReposProps.repos, items: this.props.unpinnedReposProps.repos,
getKey: ReposListComponent.getKey, getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none, selectionMode: SelectionMode.none,
compact: true, compact: true,
columns: [ columns: [
{ {
key: ReposListComponent.UnpinnedReposColumnName, key: ReposListComponent.UnpinnedReposColumnName,
name: ReposListComponent.UnpinnedReposColumnName, name: ReposListComponent.UnpinnedReposColumnName,
ariaLabel: ReposListComponent.UnpinnedReposColumnName, ariaLabel: ReposListComponent.UnpinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth, minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderUnpinnedReposColumnItem onRender: this.onRenderUnpinnedReposColumnItem,
}, },
{ {
key: ReposListComponent.BranchesColumnName, key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName, name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName, ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth, minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth, maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderUnpinnedReposBranchesColumnItem onRender: this.onRenderUnpinnedReposBranchesColumnItem,
} },
], ],
onRenderDetailsFooter: onRenderDetailsFooter:
this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore
? this.onRenderReposFooter ? this.onRenderReposFooter
: undefined : undefined,
}; };
return ( return (
<> <>
<DetailsList {...pinnedReposListProps} /> <DetailsList {...pinnedReposListProps} />
<DetailsList {...unpinnedReposListProps} /> <DetailsList {...unpinnedReposListProps} />
</> </>
); );
} }
private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => { private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) { if (index === ReposListComponent.FooterIndex) {
return <Text>None</Text>; return <Text>None</Text>;
} }
const checkboxProps: ICheckboxProps = { const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)), ...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles, styles: ReposListCheckboxStyles,
defaultChecked: true, defaultChecked: true,
onChange: () => this.props.unpinRepo(item) onChange: () => this.props.unpinRepo(item),
}; };
return <Checkbox {...checkboxProps} />; return <Checkbox {...checkboxProps} />;
}; };
private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => { private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) { if (index === ReposListComponent.FooterIndex) {
return <></>; return <></>;
} }
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]; const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
const options: IDropdownOption[] = branchesProps.branches.map(branch => ({ const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
key: branch.name, key: branch.name,
text: branch.name, text: branch.name,
data: item, data: item,
disabled: item.branches.length === 1 && branch.name === item.branches[0].name, disabled: item.branches.length === 1 && branch.name === item.branches[0].name,
selected: item.branches.findIndex(element => element.name === branch.name) !== -1 selected: item.branches.findIndex((element) => element.name === branch.name) !== -1,
})); }));
if (branchesProps.hasMore || branchesProps.isLoading) { if (branchesProps.hasMore || branchesProps.isLoading) {
const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText; const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText;
options.push({ options.push({
key: text, key: text,
text, text,
data: item, data: item,
index: ReposListComponent.FooterIndex index: ReposListComponent.FooterIndex,
}); });
} }
const dropdownProps: IDropdownProps = { const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles, styles: BranchesDropdownStyles,
dropdownWidth: BranchesDropdownWidth, dropdownWidth: BranchesDropdownWidth,
responsiveMode: ResponsiveMode.large, responsiveMode: ResponsiveMode.large,
options, options,
onRenderList: this.onRenderBranchesDropdownList onRenderList: this.onRenderBranchesDropdownList,
}; };
if (item.branches.length === 1) { if (item.branches.length === 1) {
dropdownProps.placeholder = item.branches[0].name; dropdownProps.placeholder = item.branches[0].name;
} else if (item.branches.length > 1) { } else if (item.branches.length > 1) {
dropdownProps.placeholder = `${item.branches.length} branches`; dropdownProps.placeholder = `${item.branches.length} branches`;
} }
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;
}; };
private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => { private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) { if (index === ReposListComponent.FooterIndex) {
return <></>; return <></>;
} }
const dropdownProps: IDropdownProps = { const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles, styles: BranchesDropdownStyles,
options: [], options: [],
placeholder: ReposListComponent.DefaultBranchName, placeholder: ReposListComponent.DefaultBranchName,
disabled: true disabled: true,
}; };
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;
}; };
private onRenderBranchesDropdownList = ( private onRenderBranchesDropdownList = (
props: ISelectableDroppableTextProps<IDropdown, HTMLDivElement> props: ISelectableDroppableTextProps<IDropdown, HTMLDivElement>
): JSX.Element => { ): JSX.Element => {
const renderedList: JSX.Element[] = []; const renderedList: JSX.Element[] = [];
props.options.forEach((option: IDropdownOption) => { props.options.forEach((option: IDropdownOption) => {
const item = ( const item = (
<div key={option.key} style={BranchesDropdownOptionContainerStyle}> <div key={option.key} style={BranchesDropdownOptionContainerStyle}>
{this.onRenderPinnedReposBranchesDropdownOption(option)} {this.onRenderPinnedReposBranchesDropdownOption(option)}
</div> </div>
); );
renderedList.push(item); renderedList.push(item);
}); });
return <>{renderedList}</>; return <>{renderedList}</>;
}; };
private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element { private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element {
const item: RepoListItem = option.data; const item: RepoListItem = option.data;
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)]; const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
if (option.index === ReposListComponent.FooterIndex) { if (option.index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = { const linkProps: ILinkProps = {
disabled: branchesProps.isLoading, disabled: branchesProps.isLoading,
onClick: branchesProps.loadMore onClick: branchesProps.loadMore,
}; };
return <Link {...linkProps}>{option.text}</Link>; return <Link {...linkProps}>{option.text}</Link>;
} }
const checkboxProps: ICheckboxProps = { const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(option.text), ...ReposListComponent.getCheckboxPropsForLabel(option.text),
styles: BranchesDropdownCheckboxStyles, styles: BranchesDropdownCheckboxStyles,
defaultChecked: option.selected, defaultChecked: option.selected,
disabled: option.disabled, disabled: option.disabled,
onChange: (event, checked) => { onChange: (event, checked) => {
const repoListItem = { ...item }; const repoListItem = { ...item };
const branch: IGitHubBranch = { name: option.text }; const branch: IGitHubBranch = { name: option.text };
repoListItem.branches = repoListItem.branches.filter(element => element.name !== branch.name); repoListItem.branches = repoListItem.branches.filter((element) => element.name !== branch.name);
if (checked) { if (checked) {
repoListItem.branches.push(branch); repoListItem.branches.push(branch);
} }
this.props.pinRepo(repoListItem); this.props.pinRepo(repoListItem);
} },
}; };
return <Checkbox {...checkboxProps} />; return <Checkbox {...checkboxProps} />;
} }
private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => { private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) { if (index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = { const linkProps: ILinkProps = {
disabled: this.props.unpinnedReposProps.isLoading, disabled: this.props.unpinnedReposProps.isLoading,
onClick: this.props.unpinnedReposProps.loadMore onClick: this.props.unpinnedReposProps.loadMore,
}; };
const linkText = this.props.unpinnedReposProps.isLoading const linkText = this.props.unpinnedReposProps.isLoading
? ReposListComponent.LoadingText ? ReposListComponent.LoadingText
: ReposListComponent.LoadMoreText; : ReposListComponent.LoadMoreText;
return <Link {...linkProps}>{linkText}</Link>; return <Link {...linkProps}>{linkText}</Link>;
} }
const checkboxProps: ICheckboxProps = { const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)), ...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles, styles: ReposListCheckboxStyles,
onChange: () => { onChange: () => {
const repoListItem = { ...item }; const repoListItem = { ...item };
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }]; repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
this.props.pinRepo(repoListItem); this.props.pinRepo(repoListItem);
} },
}; };
return <Checkbox {...checkboxProps} />; return <Checkbox {...checkboxProps} />;
}; };
private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => { private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => {
const props: IDetailsRowBaseProps = { const props: IDetailsRowBaseProps = {
...detailsFooterProps, ...detailsFooterProps,
item: {}, item: {},
itemIndex: ReposListComponent.FooterIndex itemIndex: ReposListComponent.FooterIndex,
}; };
return <DetailsRow {...props} />; return <DetailsRow {...props} />;
}; };
private static getCheckboxPropsForLabel(label: string): ICheckboxProps { private static getCheckboxPropsForLabel(label: string): ICheckboxProps {
return { return {
label, label,
title: label, title: label,
ariaLabel: label ariaLabel: label,
}; };
} }
private static getKey(item: RepoListItem): string { private static getKey(item: RepoListItem): string {
return item.key; return item.key;
} }
} }

View File

@ -1,189 +1,189 @@
/** /**
* How to use this component: * How to use this component:
* *
* In your html markup, use: * In your html markup, use:
* <input-typeahead params="{ * <input-typeahead params="{
choices:choices, choices:choices,
selection:selection, selection:selection,
inputValue:inputValue, inputValue:inputValue,
placeholder:'Enter source', placeholder:'Enter source',
typeaheadOverrideOptions:typeaheadOverrideOptions typeaheadOverrideOptions:typeaheadOverrideOptions
}"></input-typeahead> }"></input-typeahead>
* The parameters are documented below. * The parameters are documented below.
* *
* Notes: * Notes:
* - dynamic:true by default, this allows choices to change after initialization. * - dynamic:true by default, this allows choices to change after initialization.
* To turn it off, use: * To turn it off, use:
* typeaheadOverrideOptions: { dynamic:false } * typeaheadOverrideOptions: { dynamic:false }
* *
*/ */
import "jquery-typeahead"; import "jquery-typeahead";
import template from "./input-typeahead.html"; import template from "./input-typeahead.html";
/** /**
* Helper class for ko component registration * Helper class for ko component registration
*/ */
export class InputTypeaheadComponent { export class InputTypeaheadComponent {
constructor() { constructor() {
return { return {
viewModel: InputTypeaheadViewModel, viewModel: InputTypeaheadViewModel,
template template,
}; };
} }
} }
export interface Item { export interface Item {
caption: string; caption: string;
value: any; value: any;
} }
/** /**
* Parameters for this component * Parameters for this component
*/ */
interface InputTypeaheadParams { interface InputTypeaheadParams {
/** /**
* List of choices available in the dropdown. * List of choices available in the dropdown.
*/ */
choices: ko.ObservableArray<Item>; choices: ko.ObservableArray<Item>;
/** /**
* Gets updated when user clicks on the choice in the dropdown * Gets updated when user clicks on the choice in the dropdown
*/ */
selection?: ko.Observable<Item>; selection?: ko.Observable<Item>;
/** /**
* The current string value of <input> * The current string value of <input>
*/ */
inputValue?: ko.Observable<string>; inputValue?: ko.Observable<string>;
/** /**
* Define what text you want as the input placeholder * Define what text you want as the input placeholder
*/ */
placeholder: string; placeholder: string;
/** /**
* Override default jquery-typeahead options * Override default jquery-typeahead options
* WARNING: do not override input, source or callback to avoid breaking the components behavior. * WARNING: do not override input, source or callback to avoid breaking the components behavior.
*/ */
typeaheadOverrideOptions?: any; typeaheadOverrideOptions?: any;
/** /**
* This function gets called when pressing ENTER on the input box * This function gets called when pressing ENTER on the input box
*/ */
submitFct?: (inputValue: string, selection: Item) => void; submitFct?: (inputValue: string, selection: Item) => void;
/** /**
* Typehead comes with a Search button that we normally remove. * Typehead comes with a Search button that we normally remove.
* If you want to use it, turn this on * If you want to use it, turn this on
*/ */
showSearchButton?: boolean; showSearchButton?: boolean;
} }
interface OnClickItem { interface OnClickItem {
matchedKey: string; matchedKey: string;
value: any; value: any;
caption: string; caption: string;
group: string; group: string;
} }
interface Cache { interface Cache {
inputValue: string; inputValue: string;
selection: Item; selection: Item;
} }
class InputTypeaheadViewModel { class InputTypeaheadViewModel {
private static instanceCount = 0; // Generate unique id for each component's typeahead instance private static instanceCount = 0; // Generate unique id for each component's typeahead instance
private instanceNumber: number; private instanceNumber: number;
private params: InputTypeaheadParams; private params: InputTypeaheadParams;
private cache: Cache; private cache: Cache;
private inputValue: string; private inputValue: string;
private selection: Item; private selection: Item;
public constructor(params: InputTypeaheadParams) { public constructor(params: InputTypeaheadParams) {
this.instanceNumber = InputTypeaheadViewModel.instanceCount++; this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
this.params = params; this.params = params;
this.params.choices.subscribe(this.initializeTypeahead.bind(this)); this.params.choices.subscribe(this.initializeTypeahead.bind(this));
this.cache = { this.cache = {
inputValue: null, inputValue: null,
selection: null selection: null,
}; };
} }
/** /**
* Must execute once ko is rendered, so that it can find the input element by id * Must execute once ko is rendered, so that it can find the input element by id
*/ */
private initializeTypeahead() { private initializeTypeahead() {
let params = this.params; let params = this.params;
let cache = this.cache; let cache = this.cache;
let options: any = { let options: any = {
input: `#${this.getComponentId()}`, //'.input-typeahead', input: `#${this.getComponentId()}`, //'.input-typeahead',
order: "asc", order: "asc",
minLength: 0, minLength: 0,
searchOnFocus: true, searchOnFocus: true,
source: { source: {
display: "caption", display: "caption",
data: () => { data: () => {
return this.params.choices(); return this.params.choices();
} },
}, },
callback: { callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => { onClick: (node: any, a: any, item: OnClickItem, event: any) => {
cache.selection = item; cache.selection = item;
if (params.selection) { if (params.selection) {
params.selection(item); params.selection(item);
} }
}, },
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) { onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
cache.inputValue = query; cache.inputValue = query;
if (params.inputValue) { if (params.inputValue) {
params.inputValue(query); params.inputValue(query);
} }
} },
}, },
template: (query: string, item: any) => { template: (query: string, item: any) => {
// Don't display id if caption *IS* the id // Don't display id if caption *IS* the id
return item.caption === item.value return item.caption === item.value
? "<span>{{caption}}</span>" ? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>"; : "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
}, },
dynamic: true dynamic: true,
}; };
// Override options // Override options
if (params.typeaheadOverrideOptions) { if (params.typeaheadOverrideOptions) {
for (let p in params.typeaheadOverrideOptions) { for (let p in params.typeaheadOverrideOptions) {
options[p] = params.typeaheadOverrideOptions[p]; options[p] = params.typeaheadOverrideOptions[p];
} }
} }
$.typeahead(options); $.typeahead(options);
} }
/** /**
* Get this component id * Get this component id
* @return unique id per instance * @return unique id per instance
*/ */
private getComponentId(): string { private getComponentId(): string {
return `input-typeahead${this.instanceNumber}`; return `input-typeahead${this.instanceNumber}`;
} }
/** /**
* Executed once ko is done rendering bindings * Executed once ko is done rendering bindings
* Use ko's "template: afterRender" callback to do that without actually using any template. * Use ko's "template: afterRender" callback to do that without actually using any template.
* Another way is to call it within setTimeout() in constructor. * Another way is to call it within setTimeout() in constructor.
*/ */
private afterRender(): void { private afterRender(): void {
this.initializeTypeahead(); this.initializeTypeahead();
} }
private submit(): void { private submit(): void {
if (this.params.submitFct) { if (this.params.submitFct) {
this.params.submitFct(this.cache.inputValue, this.cache.selection); this.params.submitFct(this.cache.inputValue, this.cache.selection);
} }
} }
} }

View File

@ -8,10 +8,10 @@ describe("inputTypeahead", () => {
const props: InputTypeaheadComponentProps = { const props: InputTypeaheadComponentProps = {
choices: [ choices: [
{ caption: "item1", value: "value1" }, { caption: "item1", value: "value1" },
{ caption: "item2", value: "value2" } { caption: "item2", value: "value2" },
], ],
placeholder: "placeholder", placeholder: "placeholder",
useTextarea: false useTextarea: false,
}; };
const wrapper = shallow(<InputTypeaheadComponent {...props} />); const wrapper = shallow(<InputTypeaheadComponent {...props} />);
@ -22,10 +22,10 @@ describe("inputTypeahead", () => {
const props: InputTypeaheadComponentProps = { const props: InputTypeaheadComponentProps = {
choices: [ choices: [
{ caption: "item1", value: "value1" }, { caption: "item1", value: "value1" },
{ caption: "item2", value: "value2" } { caption: "item2", value: "value2" },
], ],
placeholder: "placeholder", placeholder: "placeholder",
useTextarea: true useTextarea: true,
}; };
const wrapper = shallow(<InputTypeaheadComponent {...props} />); const wrapper = shallow(<InputTypeaheadComponent {...props} />);

Some files were not shown because too many files have changed in this diff Show More