mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-01-01 15:22:08 +00:00
Compare commits
44 Commits
codescan
...
users/lang
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
572d573fdd | ||
|
|
37c64c4a4d | ||
|
|
fc5ffeb7ca | ||
|
|
f39b6accb1 | ||
|
|
64601693b7 | ||
|
|
0c80c45e22 | ||
|
|
84b6075ee8 | ||
|
|
d880723be9 | ||
|
|
4ce9dcc024 | ||
|
|
addcfedd5e | ||
|
|
a133134b8b | ||
|
|
79dec6a8a8 | ||
|
|
53a8cea95e | ||
|
|
5f1f7a8266 | ||
|
|
a009a8ba5f | ||
|
|
3e782527d0 | ||
|
|
e6ca1d25c9 | ||
|
|
473f722dcc | ||
|
|
5741802c25 | ||
|
|
e2e58f73b1 | ||
|
|
79769e9689 | ||
|
|
542abf4d3a | ||
|
|
4bee46ce55 | ||
|
|
fe58722002 | ||
|
|
94ff6b3e81 | ||
|
|
b4219e2994 | ||
|
|
294270b6aa | ||
|
|
e4bab1de4b | ||
|
|
703ceacd3f | ||
|
|
734df3dd18 | ||
|
|
1e19f02fd7 | ||
|
|
24b5b754ca | ||
|
|
e09730d782 | ||
|
|
09a95fded4 | ||
|
|
7ffa18a190 | ||
|
|
30353c26f3 | ||
|
|
34d8704071 | ||
|
|
23714831bd | ||
|
|
9a5d46b6e0 | ||
|
|
b9245101bc | ||
|
|
274deb13be | ||
|
|
9d50577800 | ||
|
|
bd00e5eb9b | ||
|
|
821f665e78 |
@@ -1,7 +1,10 @@
|
|||||||
# These options are only needed when if running end to end tests locally
|
|
||||||
PORTAL_RUNNER_USERNAME=
|
PORTAL_RUNNER_USERNAME=
|
||||||
PORTAL_RUNNER_PASSWORD=
|
PORTAL_RUNNER_PASSWORD=
|
||||||
PORTAL_RUNNER_SUBSCRIPTION=
|
PORTAL_RUNNER_SUBSCRIPTION=
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP=
|
PORTAL_RUNNER_RESOURCE_GROUP=
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
PORTAL_RUNNER_DATABASE_ACCOUNT=
|
||||||
PORTAL_RUNNER_CONNECTION_STRING=
|
PORTAL_RUNNER_CONNECTION_STRING=
|
||||||
|
CASSANDRA_CONNECTION_STRING=
|
||||||
|
MONGO_CONNECTION_STRING=
|
||||||
|
TABLES_CONNECTION_STRING=
|
||||||
|
DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
|
||||||
@@ -15,8 +15,6 @@ src/Common/DeleteFeedback.ts
|
|||||||
src/Common/DocumentClientUtilityBase.ts
|
src/Common/DocumentClientUtilityBase.ts
|
||||||
src/Common/EditableUtility.ts
|
src/Common/EditableUtility.ts
|
||||||
src/Common/EnvironmentUtility.ts
|
src/Common/EnvironmentUtility.ts
|
||||||
src/Common/ErrorParserUtility.test.ts
|
|
||||||
src/Common/ErrorParserUtility.ts
|
|
||||||
src/Common/HashMap.test.ts
|
src/Common/HashMap.test.ts
|
||||||
src/Common/HashMap.ts
|
src/Common/HashMap.ts
|
||||||
src/Common/HeadersUtility.test.ts
|
src/Common/HeadersUtility.test.ts
|
||||||
@@ -398,19 +396,5 @@ src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
|
|||||||
src/GalleryViewer/Cards/GalleryCardComponent.tsx
|
src/GalleryViewer/Cards/GalleryCardComponent.tsx
|
||||||
src/GalleryViewer/GalleryViewer.tsx
|
src/GalleryViewer/GalleryViewer.tsx
|
||||||
src/GalleryViewer/GalleryViewerComponent.tsx
|
src/GalleryViewer/GalleryViewerComponent.tsx
|
||||||
cypress/integration/dataexplorer/CASSANDRA/addCollection.spec.ts
|
|
||||||
cypress/integration/dataexplorer/GRAPH/addCollection.spec.ts
|
|
||||||
cypress/integration/dataexplorer/ci-tests/addCollectionPane.spec.ts
|
|
||||||
cypress/integration/dataexplorer/ci-tests/createDatabase.spec.ts
|
|
||||||
cypress/integration/dataexplorer/ci-tests/deleteCollection.spec.ts
|
|
||||||
cypress/integration/dataexplorer/ci-tests/deleteDatabase.spec.ts
|
|
||||||
cypress/integration/dataexplorer/MONGO/addCollection.spec.ts
|
|
||||||
cypress/integration/dataexplorer/MONGO/addCollectionAutopilot.spec.ts
|
|
||||||
cypress/integration/dataexplorer/MONGO/addCollectionExistingDatabase.spec.ts
|
|
||||||
cypress/integration/dataexplorer/MONGO/provisionDatabaseThroughput.spec.ts
|
|
||||||
cypress/integration/dataexplorer/SQL/addCollection.spec.ts
|
|
||||||
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
|
|
||||||
cypress/integration/notebook/newNotebook.spec.ts
|
|
||||||
cypress/integration/notebook/resourceTree.spec.ts
|
|
||||||
__mocks__/monaco-editor.ts
|
__mocks__/monaco-editor.ts
|
||||||
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx
|
||||||
@@ -42,6 +42,13 @@ module.exports = {
|
|||||||
"no-null/no-null": "error",
|
"no-null/no-null": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
eqeqeq: "error"
|
eqeqeq: "error",
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
|
||||||
|
message: "Do not use JSON.stringify(error). It will print '{}'"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
39
.github/workflows/ci.yml
vendored
39
.github/workflows/ci.yml
vendored
@@ -79,32 +79,31 @@ jobs:
|
|||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
name: "End To End Tests | Emulator | SQL"
|
name: "End To End Emulator Tests"
|
||||||
needs: [lint, format, compile, unittest]
|
needs: [lint, format, compile, unittest]
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: southpolesteve/cosmos-emulator-github-action@v1
|
|
||||||
- name: Use Node.js 12.x
|
- name: Use Node.js 12.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 12.x
|
||||||
- name: Restore Cypress Binary Cache
|
- uses: southpolesteve/cosmos-emulator-github-action@v1
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.cache/Cypress
|
|
||||||
key: ${{ runner.os }}-cypress-binary-cache
|
|
||||||
- name: End to End Tests
|
- name: End to End Tests
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm start &
|
npm start &
|
||||||
npm ci --prefix ./cypress
|
npm run wait-for-server
|
||||||
npm run test:ci --prefix ./cypress -- --spec ./integration/dataexplorer/ci-tests/createDatabase.spec.ts
|
npx jest -c ./jest.config.e2e.js --detectOpenHandles sql
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
|
||||||
|
PLATFORM: "Emulator"
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: failed-*
|
||||||
accessibility:
|
accessibility:
|
||||||
name: "Accessibility | Hosted"
|
name: "Accessibility | Hosted"
|
||||||
needs: [lint, format, compile, unittest]
|
needs: [lint, format, compile, unittest]
|
||||||
@@ -123,13 +122,13 @@ jobs:
|
|||||||
sudo sysctl -p
|
sudo sysctl -p
|
||||||
npm ci
|
npm ci
|
||||||
npm start &
|
npm start &
|
||||||
npx wait-on -i 5000 https-get://0.0.0.0:1234/
|
npx wait-on -i 5000 https-get://0.0.0.0:1234/
|
||||||
node utils/accesibilityCheck.js
|
node utils/accesibilityCheck.js
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
endtoendpuppeteer:
|
endtoendhosted:
|
||||||
name: "End to end puppeteer tests"
|
name: "End to End Hosted Tests"
|
||||||
needs: [lint, format, compile, unittest]
|
needs: [lint, format, compile, unittest]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -138,7 +137,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12.x
|
node-version: 12.x
|
||||||
- name: End to End Puppeteer Tests
|
- name: End to End Hosted Tests
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm start &
|
npm start &
|
||||||
@@ -150,10 +149,16 @@ jobs:
|
|||||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||||
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
||||||
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
||||||
|
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
||||||
|
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: failed-*
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
@@ -177,7 +182,7 @@ jobs:
|
|||||||
nugetmpac:
|
nugetmpac:
|
||||||
name: Publish Nuget MPAC
|
name: Publish Nuget MPAC
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
|
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendhosted]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,9 +9,6 @@ pkg/DataExplorer/*
|
|||||||
test/out/*
|
test/out/*
|
||||||
workers/**/*.js
|
workers/**/*.js
|
||||||
*.trx
|
*.trx
|
||||||
cypress/videos
|
|
||||||
cypress/screenshots
|
|
||||||
cypress/fixtures
|
|
||||||
notebookapp/*
|
notebookapp/*
|
||||||
Contracts/*
|
Contracts/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -33,7 +33,7 @@ To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin t
|
|||||||
|
|
||||||
### Emulator Development
|
### Emulator Development
|
||||||
|
|
||||||
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows enironment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
|
||||||
|
|
||||||
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ The Cosmos Portal that consumes this repo is not currently open source. If you h
|
|||||||
You can however load a local running instance of data explorer in the production portal.
|
You can however load a local running instance of data explorer in the production portal.
|
||||||
|
|
||||||
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
|
||||||
2. Whitelist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
|
||||||
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
|
||||||
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
|
||||||
|
|
||||||
@@ -76,24 +76,17 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
|
|||||||
|
|
||||||
#### End to End CI Tests
|
#### End to End CI Tests
|
||||||
|
|
||||||
[Cypress](https://www.cypress.io/) is used for end to end tests and are contained in `cypress/`. Currently, it operates as sub project with its own typescript config and dependencies. It also only operates against the emulator. To run cypress tests:
|
Jest and Puppeteer are used for end to end browser based tests and are contained in `test/`. To run these tests locally:
|
||||||
|
|
||||||
1. Ensure the emulator is running
|
1. Copy .env.example to .env
|
||||||
2. Start cosmos explorer in emulator mode: `PLATFORM=Emulator npm run watch`
|
2. Update the values in .env including your local data explorer endpoint (ask a teammate/codeowner for help with .env values)
|
||||||
3. Move into `cypress/` folder: `cd cypress`
|
3. Make sure all packages are installed `npm install`
|
||||||
4. Install dependencies: `npm install`
|
4. Run the server `npm run start` and wait for it to start
|
||||||
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
|
5. Run `npm run test:e2e`
|
||||||
|
|
||||||
#### End to End Production Runners
|
|
||||||
|
|
||||||
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
|
|
||||||
|
|
||||||
1. Copy .env.example to .env and fill in all variables
|
|
||||||
2. Run `npm run test:e2e`
|
|
||||||
|
|
||||||
### Releasing
|
### Releasing
|
||||||
|
|
||||||
We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Add steps that build, run tests, deploy, and more:
|
# Add steps that build, run tests, deploy, and more:
|
||||||
# https://aka.ms/yaml
|
# https://aka.ms/yaml
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "ubuntu-latest"
|
vmImage: "ubuntu-latest"
|
||||||
|
|
||||||
|
|||||||
4
cypress/.gitignore
vendored
4
cypress/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
cypress.env.json
|
|
||||||
cypress/report
|
|
||||||
cypress/screenshots
|
|
||||||
cypress/videos
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// Cleans up old databases from previous test runs
|
|
||||||
const { CosmosClient } = require("@azure/cosmos");
|
|
||||||
|
|
||||||
// TODO: Add support for other API connection strings
|
|
||||||
const mongoRegex = RegExp("mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com");
|
|
||||||
|
|
||||||
async function cleanup() {
|
|
||||||
const connectionString = process.env.CYPRESS_CONNECTION_STRING;
|
|
||||||
if (!connectionString) {
|
|
||||||
throw new Error("Connection string not provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
let client;
|
|
||||||
switch (true) {
|
|
||||||
case connectionString.includes("mongodb://"): {
|
|
||||||
const [, key, accountName] = connectionString.match(mongoRegex);
|
|
||||||
client = new CosmosClient({
|
|
||||||
key,
|
|
||||||
endpoint: `https://${accountName}.documents.azure.com:443/`
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// TODO: Add support for other API connection strings
|
|
||||||
default:
|
|
||||||
client = new CosmosClient(connectionString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await client.databases.readAll().fetchAll();
|
|
||||||
return Promise.all(
|
|
||||||
response.resources.map(async db => {
|
|
||||||
const dbTimestamp = new Date(db._ts * 1000);
|
|
||||||
const twentyMinutesAgo = new Date(Date.now() - 1000 * 60 * 20);
|
|
||||||
if (dbTimestamp < twentyMinutesAgo) {
|
|
||||||
await client.database(db.id).delete();
|
|
||||||
console.log(`DELETED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
|
||||||
} else {
|
|
||||||
console.log(`SKIPPED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
.then(() => {
|
|
||||||
process.exit(0);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"integrationFolder": "./integration",
|
|
||||||
"pluginsFile": false,
|
|
||||||
"fixturesFolder": false,
|
|
||||||
"supportFile": "./support/index.js",
|
|
||||||
"defaultCommandTimeout": 90000,
|
|
||||||
"chromeWebSecurity": false,
|
|
||||||
"reporter": "mochawesome",
|
|
||||||
"reporterOptions": {
|
|
||||||
"reportDir": "cypress/report",
|
|
||||||
"json": true,
|
|
||||||
"overwrite": false,
|
|
||||||
"html": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Cassandra API Test - createDatabase", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString(connectionString.constants.cassandra);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new table in Cassandra API", () => {
|
|
||||||
const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const tableId = `TableId112`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Table"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[id="keyspace-id"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.type(keyspaceId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[class="textfontclr"]')
|
|
||||||
.type(tableId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('data-test="addCollection-createCollection"')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", tableId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
// 1. Click on "New Graph" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Graph API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString(connectionString.constants.graph);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new graph in Graph API", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const graphId = `TestGraph${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const partitionKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Graph"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(graphId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(partitionKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", graphId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// // 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Mongo API Test - createDatabase", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new collection in Mongo API", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find("#submitBtnAddCollection")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Mongo API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip("Create a new collection in Mongo API - Autopilot", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="throughputModeContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.and(input => {
|
|
||||||
expect(input.get(0).textContent, "first item").contains("Autopilot (preview)");
|
|
||||||
expect(input.get(1).textContent, "second item").contains("Manual");
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[id="newContainer-databaseThroughput-autoPilotRadio"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('select[name="autoPilotTiers"]')
|
|
||||||
// .eq(1).should('contain', '4,000 RU/s');
|
|
||||||
// // .select('4,000 RU/s').should('have.value', '1');
|
|
||||||
|
|
||||||
.find('option[value="2"]')
|
|
||||||
.then($element => $element.get(1).setAttribute("selected", "selected"));
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Mongo API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip("Create a new collection in existing database in Mongo API", () => {
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('span[class="nodeLabel"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.then($span => {
|
|
||||||
const dbId1 = $span.text();
|
|
||||||
cy.log("DBBB", dbId1);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-existingDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-existingDatabase"]')
|
|
||||||
.type(dbId1);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context.skip("Mongo API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new collection in Mongo API - Provision database throughput", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find(".createNewDatabaseOrUseExisting")
|
|
||||||
.should("have.length", 2)
|
|
||||||
.and(input => {
|
|
||||||
expect(input.get(0).textContent, "first item").contains("Create new");
|
|
||||||
expect(input.get(1).textContent, "second item").contains("Use existing");
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new collection - without provision database throughput", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionIdTitle = `Add Collection`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.uncheck();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[id="tab2"]')
|
|
||||||
.check({ force: true });
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new collection - without provision database throughput Fixed Storage Capacity", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Collection"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.uncheck();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[id="tab1"]')
|
|
||||||
.check({ force: true });
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId)
|
|
||||||
.click()
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("SQL API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new container in SQL API", () => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
connectionString.loginUsingConnectionString();
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Container"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createNewDatabase"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
|
|
||||||
.check();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-newDatabaseId"]')
|
|
||||||
.type(dbId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-partitionKeyValue"]')
|
|
||||||
.type(sharedKey);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find("#submitBtnAddCollection")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", dbId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
const connectionString = require("../../../utilities/connectionString");
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Table API Test", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
connectionString.loginUsingConnectionString(connectionString.constants.table);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new table in Table API", () => {
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="commandBarContainer"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('button[data-test="New Table"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[class="contextual-pane-in"]')
|
|
||||||
.should("be.visible")
|
|
||||||
.find('span[id="containerTitle"]');
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-collectionId"]')
|
|
||||||
.type(collectionId);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="databaseThroughputValue"]')
|
|
||||||
.should("have.value", "400");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('input[data-test="addCollection-createCollection"]')
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10000);
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find('div[data-test="resourceTreeId"]')
|
|
||||||
.should("exist")
|
|
||||||
.find('div[class="treeComponent dataResourceTree"]')
|
|
||||||
.should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// 1. Click on "New Container" on the command bar.
|
|
||||||
// 2. Pane with the title "Add Container" should appear on the right side of the screen
|
|
||||||
// 3. It includes an input box for the database Id.
|
|
||||||
// 4. It includes a checkbox called "Create now".
|
|
||||||
// 5. When the checkbox is marked, enter new database id.
|
|
||||||
// 3. Create a database WITH "Provision throughput" checked.
|
|
||||||
// 4. Enter minimum throughput value of 400.
|
|
||||||
// 5. Enter container id to the container id text box.
|
|
||||||
// 6. Enter partition key to the partition key text box.
|
|
||||||
// 7. Click "OK" to create a new container.
|
|
||||||
// 8. Verify the new container is created along with the database id and should appead in the Data Explorer list in the left side of the screen.
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Emulator - createDatabase", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("http://localhost:1234/explorer.html");
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionIdTitle = `Add Collection`;
|
|
||||||
const partitionKey = `PartitionKey${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
|
|
||||||
it("Create a new collection", () => {
|
|
||||||
cy.contains("New Container").click();
|
|
||||||
|
|
||||||
// cy.contains(collectionIdTitle);
|
|
||||||
|
|
||||||
cy.get(".createNewDatabaseOrUseExisting")
|
|
||||||
.should("have.length", 2)
|
|
||||||
.and(input => {
|
|
||||||
expect(input.get(0).textContent, "first item").contains("Create new");
|
|
||||||
expect(input.get(1).textContent, "second item").contains("Use existing");
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.get('input[data-test="addCollection-createNewDatabase"]').check();
|
|
||||||
|
|
||||||
cy.get('input[data-test="addCollection-newDatabaseId"]').type(dbId);
|
|
||||||
|
|
||||||
cy.get('input[data-test="addCollection-collectionId"]').type(collectionId);
|
|
||||||
|
|
||||||
cy.get('input[data-test="databaseThroughputValue"]').should("have.value", "400");
|
|
||||||
|
|
||||||
cy.get('input[data-test="addCollection-partitionKeyValue"]').type(partitionKey);
|
|
||||||
|
|
||||||
cy.get('input[data-test="addCollection-createCollection"]').click();
|
|
||||||
|
|
||||||
cy.get('div[data-test="resourceTreeId"]').should("exist");
|
|
||||||
|
|
||||||
cy.get('div[data-test="resourceTree-collectionsTree"]').should("contain", dbId);
|
|
||||||
|
|
||||||
cy.get('div[data-test="databaseList"]').should("contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// 1. Click on "New Database" on the command bar
|
|
||||||
// 2. a Pane with the title "Add Database" should appear on the right side of the screen
|
|
||||||
// i. It includes an input box for the database Id.
|
|
||||||
// ii. It includes a checkbox called "Provision throughput".
|
|
||||||
// iii. Whe the checkbox is marked, a new input with a throughput control let's you customize RU at the database level
|
|
||||||
// 3. Create a database WITHOUT "Provision throughput" checked.
|
|
||||||
// 4. It should appear in the Data Explorer list.
|
|
||||||
// 5. Repeat steps 1-3 but create a database WITH "Provision throughput" with the default RU value.
|
|
||||||
// 6. It should appear in the Data Explorer list.
|
|
||||||
// 7. If expanded, it should have the list item called "Scale", that once clicked, it should show the "Scale" tab.
|
|
||||||
// 8. Inside that tab, a throughput control will let you change the RU value within the permited range.
|
|
||||||
// 9. If you change the value, it should enable the "Save" button.
|
|
||||||
// 10. Click "Save" and verify that the process completes without error.
|
|
||||||
// 11. Close the tab and reopen it and verify that the input contains the last saved value.%
|
|
||||||
|
|
||||||
const crypto = require("crypto");
|
|
||||||
const client = require("../../../utilities/cosmosClient");
|
|
||||||
const randomString = crypto.randomBytes(2).toString("hex");
|
|
||||||
const databaseId = `TestDB-${randomString}`;
|
|
||||||
const collectionId = `TestColl-${randomString}`;
|
|
||||||
|
|
||||||
context("Emulator - Create database -> container -> item", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const { resources } = await client.databases.readAll().fetchAll();
|
|
||||||
for (const database of resources) {
|
|
||||||
await client.database(database.id).delete();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("creates a new database", () => {
|
|
||||||
cy.visit("https://0.0.0.0:1234/explorer.html?platform=Emulator");
|
|
||||||
cy.contains("New Container").click();
|
|
||||||
cy.get("[data-test=addCollection-newDatabaseId]").click();
|
|
||||||
cy.get("[data-test=addCollection-newDatabaseId]").type(databaseId);
|
|
||||||
cy.get("[data-test=addCollection-collectionId]").click();
|
|
||||||
cy.get("[data-test=addCollection-collectionId]").type(collectionId);
|
|
||||||
cy.get("[data-test=addCollection-partitionKeyValue]").click();
|
|
||||||
cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk");
|
|
||||||
cy.get('input[name="createCollection"]').click();
|
|
||||||
cy.get(".dataResourceTree").should("contain", databaseId);
|
|
||||||
cy.get(".dataResourceTree")
|
|
||||||
.contains(databaseId)
|
|
||||||
.click();
|
|
||||||
cy.get(".dataResourceTree").should("contain", collectionId);
|
|
||||||
cy.get(".dataResourceTree")
|
|
||||||
.contains(collectionId)
|
|
||||||
.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.get(".commandBarContainer")
|
|
||||||
.contains("New Item")
|
|
||||||
.click();
|
|
||||||
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
|
|
||||||
cy.get(".commandBarContainer")
|
|
||||||
.contains("Save")
|
|
||||||
.click();
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// 1. Click last database in the resource tree
|
|
||||||
// 2. Click the last collection within the database
|
|
||||||
// 3. Select the context menu within the collection
|
|
||||||
// 4. Select "Delete Container" option in the dropdown
|
|
||||||
// 5. On Selection, Delete Container pane opens on the right side
|
|
||||||
// 6. Enter the same collection id that is to be deleted and click ok
|
|
||||||
// 7. Now, the resource tree refreshes, the deleted collection should not appear under the database
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Emulator - deleteCollection", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit("http://localhost:1234/explorer.html");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Delete a collection", () => {
|
|
||||||
cy.get(".databaseId")
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get(".collectionList")
|
|
||||||
.last()
|
|
||||||
.then($id => {
|
|
||||||
const collectionId = $id.text();
|
|
||||||
|
|
||||||
cy.get('span[data-test="collectionEllipsisMenu"]').should("exist");
|
|
||||||
|
|
||||||
cy.get('span[data-test="collectionEllipsisMenu"]')
|
|
||||||
.invoke("show")
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get('div[data-test="collectionContextMenu"]')
|
|
||||||
.contains("Delete Container")
|
|
||||||
.click({ force: true });
|
|
||||||
|
|
||||||
cy.get('input[data-test="confirmCollectionId"]').type(collectionId.trim());
|
|
||||||
|
|
||||||
cy.get('input[data-test="deleteCollection"]').click();
|
|
||||||
|
|
||||||
cy.get('div[data-test="databaseList"]').should("not.contain", collectionId);
|
|
||||||
|
|
||||||
cy.get('div[data-test="databaseMenu"]').should("not.contain", collectionId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
// 1. Click last database in the resource tree
|
|
||||||
// 2. Select the context menu within the database
|
|
||||||
// 4. Select "Delete Database" option in the dropdown
|
|
||||||
// 5. On Selection, Delete Database pane opens on the right side
|
|
||||||
// 6. Enter the same database id that is to be deleted and click ok
|
|
||||||
// 7. Now, the resource tree refreshes, the deleted database should not appear in the resource tree
|
|
||||||
|
|
||||||
let crypt = require("crypto");
|
|
||||||
|
|
||||||
context("Emulator - deleteDatabase", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
|
||||||
let db_rid = "";
|
|
||||||
const date = new Date().toUTCString();
|
|
||||||
let authToken = "";
|
|
||||||
cy.visit("http://localhost:1234/explorer.html");
|
|
||||||
|
|
||||||
// Creating auth token for collection creation
|
|
||||||
cy.request({
|
|
||||||
method: "GET",
|
|
||||||
url: "https://localhost:8081/_explorer/authorization/post/dbs/",
|
|
||||||
headers: {
|
|
||||||
"x-ms-date": date,
|
|
||||||
authorization: "-"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
authToken = response.body.Token; // Getting auth token for collection creation
|
|
||||||
return new Cypress.Promise((resolve, reject) => {
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
cy.request({
|
|
||||||
method: "POST",
|
|
||||||
url: "https://localhost:8081/dbs",
|
|
||||||
headers: {
|
|
||||||
"x-ms-date": date,
|
|
||||||
authorization: authToken,
|
|
||||||
"x-ms-version": "2018-12-31"
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
id: dbId
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
cy.log("Response", response);
|
|
||||||
db_rid = response.body._rid;
|
|
||||||
return new Cypress.Promise((resolve, reject) => {
|
|
||||||
cy.log("Rid", db_rid);
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Delete a database", () => {
|
|
||||||
cy.get('span[data-test="refreshTree"]').click();
|
|
||||||
|
|
||||||
cy.get(".databaseId")
|
|
||||||
.last()
|
|
||||||
.then($id => {
|
|
||||||
const dbId = $id.text();
|
|
||||||
|
|
||||||
cy.get('span[data-test="databaseEllipsisMenu"]').should("exist");
|
|
||||||
|
|
||||||
cy.get('span[data-test="databaseEllipsisMenu"]')
|
|
||||||
.invoke("show")
|
|
||||||
.last()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get('div[data-test="databaseContextMenu"]')
|
|
||||||
.contains("Delete Database")
|
|
||||||
.click({ force: true });
|
|
||||||
|
|
||||||
cy.get('input[data-test="confirmDatabaseId"]').type(dbId.trim());
|
|
||||||
|
|
||||||
cy.get('input[data-test="deleteDatabase"]').click();
|
|
||||||
|
|
||||||
cy.get('div[data-test="databaseList"]').should("not.contain", dbId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Notebook end-to-end tests
|
|
||||||
This describes how to run the tests locally
|
|
||||||
|
|
||||||
## Stand up a local notebook container instance:
|
|
||||||
Instructions on how to build and run the container [here](https://microsoft.sharepoint.com/teams/DocDB/_layouts/OneNote.aspx?id=%2Fteams%2FDocDB%2FSiteAssets%2FDocDB%20Team%20Notebook&wd=target%28Tools%20_%20SDK%2FPortal%2FDevelopment.one%7CF800BE8E-1E31-48FE-90D7-EF698EF88112%2FHow%20to%20build%20notebook%20service%7C4BAA153B-422C-41E2-B997-F3FCE02CD743%2F%29)
|
|
||||||
|
|
||||||
## Run a local data explorer
|
|
||||||
Instructions are in [`DataExplorer/README.md`](https://msdata.visualstudio.com/CosmosDB/_git/cosmosdb-dataexplorer?path=%2FProduct%2FPortal%2FDataExplorer%2FREADME.md&_a=preview).
|
|
||||||
|
|
||||||
Make sure you can run Data Explorer locally from the web browser.
|
|
||||||
|
|
||||||
## Run cypress tests
|
|
||||||
1. Edit the URL for your DataExplorer in the `.spec.ts` file
|
|
||||||
2. Run the test:
|
|
||||||
```bash
|
|
||||||
cd DataExplorer/cypress
|
|
||||||
npm i
|
|
||||||
npm t -- --spec 'integration/notebook/newNotebook.spec.ts'
|
|
||||||
```
|
|
||||||
|
|
||||||
To run in Debug mode:
|
|
||||||
```
|
|
||||||
npm run test:debug
|
|
||||||
```
|
|
||||||
This opens Cypress UI
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
* The tests are recorded in the `videos` folder.
|
|
||||||
* Cypress does not support hover: workarounds [here](https://docs.cypress.io/api/commands/hover.html#Workarounds).
|
|
||||||
|
|
||||||
|
|
||||||
## References
|
|
||||||
* [Cypress API](https://docs.cypress.io/api/api/table-of-contents.html)
|
|
||||||
* [Cypress cookbook](https://docs.cypress.io/faq/questions/using-cypress-faq.html#How-do-I-get-an-element%E2%80%99s-text-contents)
|
|
||||||
* [Cypress best practices](https://docs.cypress.io/guides/references/best-practices.html#Selecting-Elements)
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// THIS ADDS A NEW NOTEBOOK TO YOUR NOTEBOOKS
|
|
||||||
context("New Notebook smoke test", () => {
|
|
||||||
const timeout = 15000; // in ms
|
|
||||||
const explorerUrl =
|
|
||||||
"https://localhost:1234/explorer.html?feature.notebookserverurl=https%3A%2F%2Flocalhost%3A10001%2F12345%2Fnotebook&feature.notebookServerToken=token&feature.enablenotebooks=true";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for UI to be ready
|
|
||||||
*/
|
|
||||||
const waitForReady = () => {
|
|
||||||
cy.get(".splashScreenContainer", { timeout }).should("be.visible");
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit(explorerUrl);
|
|
||||||
waitForReady();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a new notebook and run some code", () => {
|
|
||||||
// Create new notebook
|
|
||||||
cy.contains("New Notebook").click();
|
|
||||||
|
|
||||||
// Check tab name
|
|
||||||
cy.get("li.tabList .tabNavText").should($span => {
|
|
||||||
const text = $span.text();
|
|
||||||
expect(text).to.match(/^Untitled.*\.ipynb$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for python3 | idle status
|
|
||||||
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
|
|
||||||
const text = $p.text();
|
|
||||||
expect(text).to.match(/^python3.*idle$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click on a cell
|
|
||||||
cy.get(".cell-container")
|
|
||||||
.as("cellContainer")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Type in some code
|
|
||||||
cy.get("@cellContainer").type("2+4");
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
cy.get('[data-test="Run"]')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Verify results
|
|
||||||
cy.get("@cellContainer").within(() => {
|
|
||||||
cy.get("pre code span").should("contain", "6");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restart kernel
|
|
||||||
cy.get('[data-test="Run"] button')
|
|
||||||
.eq(-1)
|
|
||||||
.click();
|
|
||||||
cy.get("li")
|
|
||||||
.contains("Restart Kernel")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Wait for python3 | restarting status
|
|
||||||
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
|
|
||||||
const text = $p.text();
|
|
||||||
expect(text).to.match(/^python3.*restarting$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for python3 | idle status
|
|
||||||
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
|
|
||||||
const text = $p.text();
|
|
||||||
expect(text).to.match(/^python3.*idle$/);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click on a cell
|
|
||||||
cy.get(".cell-container")
|
|
||||||
.as("cellContainer")
|
|
||||||
.find(".input")
|
|
||||||
.as("codeInput")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Type in some code
|
|
||||||
cy.get("@codeInput").type("{backspace}{backspace}{backspace}4+5");
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
cy.get('[data-test="Run"]')
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Verify results
|
|
||||||
cy.get("@cellContainer").within(() => {
|
|
||||||
cy.get("pre code span").should("contain", "9");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
context("Resource tree notebook file manipulation", () => {
|
|
||||||
const timeout = 15000; // in ms
|
|
||||||
const explorerUrl =
|
|
||||||
"https://localhost:1234/explorer.html?feature.notebookserverurl=https%3A%2F%2Flocalhost%3A10001%2F12345%2Fnotebook&feature.notebookServerToken=token&feature.enablenotebooks=true";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for UI to be ready
|
|
||||||
*/
|
|
||||||
const waitForReady = () => {
|
|
||||||
cy.get(".splashScreenContainer", { timeout }).should("be.visible");
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickContextMenuAndSelectOption = (nodeLabel, option) => {
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${nodeLabel}"]`)
|
|
||||||
.find("button.treeMenuEllipsis")
|
|
||||||
.click();
|
|
||||||
cy.get('[data-test="treeComponentMenuItemContainer"]')
|
|
||||||
.contains(option)
|
|
||||||
.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
const createFolder = folder => {
|
|
||||||
clickContextMenuAndSelectOption("My Notebooks/", "New Directory");
|
|
||||||
|
|
||||||
cy.get("#stringInputPane").within(() => {
|
|
||||||
cy.get('input[name="collectionIdConfirmation"]').type(folder);
|
|
||||||
cy.get("form").submit();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteItem = nodeName => {
|
|
||||||
clickContextMenuAndSelectOption(`${nodeName}`, "Delete");
|
|
||||||
cy.get(".ms-Dialog-main")
|
|
||||||
.contains("Delete")
|
|
||||||
.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.visit(explorerUrl);
|
|
||||||
waitForReady();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create and remove a directory", () => {
|
|
||||||
const folder = "e2etest_folder1";
|
|
||||||
createFolder(folder);
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${folder}/"]`).should("exist");
|
|
||||||
deleteItem(`${folder}/`);
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${folder}/"]`).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create and rename a directory", () => {
|
|
||||||
const folder = "e2etest_folder2";
|
|
||||||
const renamedFolder = "e2etest_folder2_renamed";
|
|
||||||
createFolder(folder);
|
|
||||||
|
|
||||||
// Rename
|
|
||||||
clickContextMenuAndSelectOption(`${folder}/`, "Rename");
|
|
||||||
cy.get("#stringInputPane").within(() => {
|
|
||||||
cy.get('input[name="collectionIdConfirmation"]')
|
|
||||||
.clear()
|
|
||||||
.type(renamedFolder);
|
|
||||||
cy.get("form").submit();
|
|
||||||
});
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("exist");
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${folder}/"]`).should("not.exist");
|
|
||||||
|
|
||||||
deleteItem(`${renamedFolder}/`);
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("not.exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create a notebook inside a directory", () => {
|
|
||||||
const folder = "e2etest_folder3";
|
|
||||||
const newNotebookName = "Untitled.ipynb";
|
|
||||||
createFolder(folder);
|
|
||||||
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
|
|
||||||
|
|
||||||
// Verify tab is open
|
|
||||||
cy.get(".tabList")
|
|
||||||
.contains(newNotebookName)
|
|
||||||
.should("exist");
|
|
||||||
|
|
||||||
// Close tab
|
|
||||||
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`)
|
|
||||||
.find(".cancelButton")
|
|
||||||
.click();
|
|
||||||
// When running from command line, closing the tab is too fast
|
|
||||||
cy.get("body").then($body => {
|
|
||||||
if ($body.find(".ms-Dialog-main").length) {
|
|
||||||
// For some reason, this does not work
|
|
||||||
// cy.get(".ms-Dialog-main").contains("Close").click();
|
|
||||||
cy.get(".ms-Dialog-main .ms-Button--primary").click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expand folder node
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${folder}/"]`).click();
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("exist");
|
|
||||||
|
|
||||||
// Delete notebook
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
|
|
||||||
.find("button.treeMenuEllipsis")
|
|
||||||
.click();
|
|
||||||
cy.get('[data-test="treeComponentMenuItemContainer"]')
|
|
||||||
.contains("Delete")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Confirm
|
|
||||||
cy.get(".ms-Dialog-main")
|
|
||||||
.contains("Delete")
|
|
||||||
.click();
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist");
|
|
||||||
|
|
||||||
deleteItem(`${folder}/`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Create and rename a notebook inside a directory", () => {
|
|
||||||
const folder = "e2etest_folder4";
|
|
||||||
const newNotebookName = "Untitled.ipynb";
|
|
||||||
const renamedNotebookName = "mynotebook.ipynb";
|
|
||||||
createFolder(folder);
|
|
||||||
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
|
|
||||||
|
|
||||||
// Close tab
|
|
||||||
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`)
|
|
||||||
.find(".cancelButton")
|
|
||||||
.click();
|
|
||||||
cy.get("body").then($body => {
|
|
||||||
if ($body.find(".ms-Dialog-main").length) {
|
|
||||||
// For some reason, this does not work
|
|
||||||
// cy.get(".ms-Dialog-main").contains("Close").click();
|
|
||||||
cy.get(".ms-Dialog-main .ms-Button--primary").click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Expand folder node
|
|
||||||
cy.get(`.treeNodeHeader[data-test="${folder}/"]`).click();
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("exist");
|
|
||||||
|
|
||||||
// Rename notebook
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
|
|
||||||
.find("button.treeMenuEllipsis")
|
|
||||||
.click();
|
|
||||||
cy.get('[data-test="treeComponentMenuItemContainer"]')
|
|
||||||
.contains("Rename")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.get("#stringInputPane").within(() => {
|
|
||||||
cy.get('input[name="collectionIdConfirmation"]')
|
|
||||||
.clear()
|
|
||||||
.type(renamedNotebookName);
|
|
||||||
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="${renamedNotebookName}"]`).should("exist");
|
|
||||||
|
|
||||||
// Delete notebook
|
|
||||||
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${renamedNotebookName}"]`)
|
|
||||||
.find("button.treeMenuEllipsis")
|
|
||||||
.click();
|
|
||||||
cy.get('[data-test="treeComponentMenuItemContainer"]')
|
|
||||||
.contains("Delete")
|
|
||||||
.click();
|
|
||||||
|
|
||||||
// Confirm
|
|
||||||
cy.get(".ms-Dialog-main")
|
|
||||||
.contains("Delete")
|
|
||||||
.click();
|
|
||||||
// Give it time to settle
|
|
||||||
cy.wait(1000);
|
|
||||||
deleteItem(`${folder}/`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
3066
cypress/package-lock.json
generated
3066
cypress/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cosmos-explorer-cypress",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "cypress run",
|
|
||||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
|
||||||
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"",
|
|
||||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless",
|
|
||||||
"test:debug": "cypress open"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"cypress": "^4.8.0",
|
|
||||||
"mocha": "^7.0.1",
|
|
||||||
"mochawesome": "^4.1.0",
|
|
||||||
"mochawesome-merge": "^4.0.1",
|
|
||||||
"mochawesome-report-generator": "^4.1.0",
|
|
||||||
"typescript": "3.4.3",
|
|
||||||
"wait-on": "^4.0.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@microsoft/applicationinsights-web": "^2.5.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
let appInsightsLib = require("@microsoft/applicationinsights-web");
|
|
||||||
|
|
||||||
const appInsights = new appInsightsLib.ApplicationInsights({
|
|
||||||
config: {
|
|
||||||
instrumentationKey: "fe61c39f-7d32-4488-a191-b13621965315"
|
|
||||||
/* ...Other Configuration Options... */
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
appInsights.loadAppInsights();
|
|
||||||
|
|
||||||
Cypress.on("fail", (error, runnable) => {
|
|
||||||
// App Insights will record the fail tests for Create Collection
|
|
||||||
let message = JSON.stringify(runnable.title);
|
|
||||||
appInsights.trackTrace({
|
|
||||||
message: `${message}`,
|
|
||||||
properties: {
|
|
||||||
passed: false,
|
|
||||||
error: error
|
|
||||||
}
|
|
||||||
});
|
|
||||||
throw error; // throw error to have test still fail
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["es5", "dom", "es6"],
|
|
||||||
"types": ["cypress", "node"]
|
|
||||||
},
|
|
||||||
"include": ["**/*.ts", "**/*.spec.ts"]
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
loginUsingConnectionString: function() {
|
|
||||||
const prodUrl = Cypress.env("TEST_ENDPOINT") || "https://localhost:1234/hostedExplorer.html";
|
|
||||||
const timeout = 15000;
|
|
||||||
|
|
||||||
cy.visit(prodUrl);
|
|
||||||
cy.get('iframe[id="explorerMenu"]').should("be.visible");
|
|
||||||
|
|
||||||
cy.get("iframe").then($element => {
|
|
||||||
const $body = $element.contents().find("body");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find("#connectExplorer")
|
|
||||||
.should("exist")
|
|
||||||
.find("div[class='connectExplorer']")
|
|
||||||
.should("exist")
|
|
||||||
.find("p[class='welcomeText']")
|
|
||||||
.should("exist");
|
|
||||||
|
|
||||||
cy.wrap($body.find("div > p.switchConnectTypeText"))
|
|
||||||
.should("exist")
|
|
||||||
.last()
|
|
||||||
.click({ force: true });
|
|
||||||
|
|
||||||
const secret = Cypress.env("CONNECTION_STRING");
|
|
||||||
|
|
||||||
cy.wrap($body)
|
|
||||||
.find("input[class='inputToken']")
|
|
||||||
.should("exist")
|
|
||||||
.type(secret, {
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wrap($body.find("input[value='Connect']"), { timeout })
|
|
||||||
.first()
|
|
||||||
.click({ force: true });
|
|
||||||
|
|
||||||
cy.wait(15000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const { CosmosClient } = require("@azure/cosmos");
|
|
||||||
|
|
||||||
module.exports = new CosmosClient({
|
|
||||||
endpoint: "https://0.0.0.0:8081",
|
|
||||||
key: "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
|
|
||||||
});
|
|
||||||
@@ -3023,3 +3023,7 @@ settings-pane {
|
|||||||
.infoBoxContent a {
|
.infoBoxContent a {
|
||||||
color: @AccentMediumHigh
|
color: @AccentMediumHigh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapsibleSection :hover{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
158
package-lock.json
generated
158
package-lock.json
generated
@@ -76,7 +76,6 @@
|
|||||||
"version": "7.9.0",
|
"version": "7.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
|
||||||
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
"integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.8.3",
|
"@babel/code-frame": "^7.8.3",
|
||||||
"@babel/generator": "^7.9.0",
|
"@babel/generator": "^7.9.0",
|
||||||
@@ -99,8 +98,7 @@
|
|||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2805,12 +2803,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nteract/monaco-editor": {
|
"@nteract/monaco-editor": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/monaco-editor/-/monaco-editor-3.2.2.tgz",
|
||||||
"integrity": "sha512-PGEUvy/GTBMECy4RUfh4wxO7GfA9YDBSV3hGt8MyrVz/GxUDtjB7FqrYS0ZhmVQPYl8hnV2i48F3YlypC+xIXA==",
|
"integrity": "sha512-51Pxt6v6qaAlbDY0BgEydk/Jxuu93t+uB8Geg3vJfE6VDphTEakB0wocBIfvcTKVV55Lx53/rTSp6QHqtaHiGg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@nteract/core": "^14.0.0",
|
"@nteract/core": "^14.0.0",
|
||||||
"@nteract/messaging": "^7.0.10",
|
"@nteract/messaging": "^7.0.12",
|
||||||
|
"lodash.debounce": "^4.0.6",
|
||||||
"monaco-editor": "0.18.1",
|
"monaco-editor": "0.18.1",
|
||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
},
|
},
|
||||||
@@ -2859,6 +2858,40 @@
|
|||||||
"rxjs": "^6.3.3"
|
"rxjs": "^6.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nteract/messaging": {
|
||||||
|
"version": "7.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/messaging/-/messaging-7.0.12.tgz",
|
||||||
|
"integrity": "sha512-5z2Ffd1hj7AsGBJTAoqJshLlUZ+ISJBjiZAdNDjb70PNEv0x8UOMk/di80RI3WBLK5MKxSJkGXfs4jfzfdW6bA==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/types": "^7.1.2",
|
||||||
|
"@types/uuid": "^8.0.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nteract/commutable": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/commutable/-/commutable-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-Z6aUtIZN0CKUMJwbZjUUqaaBhT6P0RiEG5nHso+oG/FOXF20Qv+hf/TyvYhw9SXQVmmacaMk4zj0iOID20pIng==",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@nteract/types": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nteract/types/-/types-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-I/1TvaUC/m9I/LFk1HemsOUqB0eNdagu0KRLA1YEtChPh9pk5F9flglA7m5+0/j31gLXBISj5+6tL8ikA8BxOQ==",
|
||||||
|
"requires": {
|
||||||
|
"@nteract/commutable": "^7.3.4",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"rxjs": "^6.6.0",
|
||||||
|
"uuid": "^8.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nteract/mythic-notifications": {
|
"@nteract/mythic-notifications": {
|
||||||
"version": "0.1.9",
|
"version": "0.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@nteract/mythic-notifications/-/mythic-notifications-0.1.9.tgz",
|
||||||
@@ -7203,11 +7236,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create-react-class": {
|
"create-react-class": {
|
||||||
"version": "15.6.3",
|
"version": "15.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.7.0.tgz",
|
||||||
"integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
|
"integrity": "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "^0.8.9",
|
|
||||||
"loose-envify": "^1.3.1",
|
"loose-envify": "^1.3.1",
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
@@ -8504,24 +8536,6 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"requires": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": {
|
|
||||||
"version": "0.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
|
||||||
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
|
||||||
"requires": {
|
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@@ -9667,27 +9681,6 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fbjs": {
|
|
||||||
"version": "0.8.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
|
||||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
|
||||||
"requires": {
|
|
||||||
"core-js": "^1.0.0",
|
|
||||||
"isomorphic-fetch": "^2.1.1",
|
|
||||||
"loose-envify": "^1.0.0",
|
|
||||||
"object-assign": "^4.1.0",
|
|
||||||
"promise": "^7.1.1",
|
|
||||||
"setimmediate": "^1.0.5",
|
|
||||||
"ua-parser-js": "^0.7.18"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": {
|
|
||||||
"version": "1.2.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
|
|
||||||
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fd-slicer": {
|
"fd-slicer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
@@ -11888,26 +11881,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||||
},
|
},
|
||||||
"isomorphic-fetch": {
|
|
||||||
"version": "2.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
|
||||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^1.0.1",
|
|
||||||
"whatwg-fetch": ">=0.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"node-fetch": {
|
|
||||||
"version": "1.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
|
||||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
|
||||||
"requires": {
|
|
||||||
"encoding": "^0.1.11",
|
|
||||||
"is-stream": "^1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"isstream": {
|
"isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
@@ -13406,36 +13379,6 @@
|
|||||||
"micromatch": "^3.1.10",
|
"micromatch": "^3.1.10",
|
||||||
"pretty-format": "^24.9.0",
|
"pretty-format": "^24.9.0",
|
||||||
"realpath-native": "^1.1.0"
|
"realpath-native": "^1.1.0"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/core": {
|
|
||||||
"version": "7.11.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz",
|
|
||||||
"integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/code-frame": "^7.10.4",
|
|
||||||
"@babel/generator": "^7.11.6",
|
|
||||||
"@babel/helper-module-transforms": "^7.11.0",
|
|
||||||
"@babel/helpers": "^7.10.4",
|
|
||||||
"@babel/parser": "^7.11.5",
|
|
||||||
"@babel/template": "^7.10.4",
|
|
||||||
"@babel/traverse": "^7.11.5",
|
|
||||||
"@babel/types": "^7.11.5",
|
|
||||||
"convert-source-map": "^1.7.0",
|
|
||||||
"debug": "^4.1.0",
|
|
||||||
"gensync": "^1.0.0-beta.1",
|
|
||||||
"json5": "^2.1.2",
|
|
||||||
"lodash": "^4.17.19",
|
|
||||||
"resolve": "^1.3.2",
|
|
||||||
"semver": "^5.4.1",
|
|
||||||
"source-map": "^0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"source-map": {
|
|
||||||
"version": "0.5.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-dev-server": {
|
"jest-dev-server": {
|
||||||
@@ -14514,6 +14457,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||||
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
"integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
|
||||||
},
|
},
|
||||||
|
"lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||||
|
},
|
||||||
"lodash.defaults": {
|
"lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
@@ -16619,6 +16567,8 @@
|
|||||||
"version": "7.3.1",
|
"version": "7.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"asap": "~2.0.3"
|
"asap": "~2.0.3"
|
||||||
}
|
}
|
||||||
@@ -18246,7 +18196,8 @@
|
|||||||
"setimmediate": {
|
"setimmediate": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"setprototypeof": {
|
"setprototypeof": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -20006,11 +19957,6 @@
|
|||||||
"free-style": "3.1.0"
|
"free-style": "3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
|
||||||
"version": "0.7.22",
|
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
|
|
||||||
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
|
|
||||||
},
|
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.4.10",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.4.0",
|
"@nteract/markdown": "4.4.0",
|
||||||
"@nteract/monaco-editor": "3.2.0",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "3.0.9",
|
"@nteract/outputs": "3.0.9",
|
||||||
"@nteract/presentational-components": "3.0.7",
|
"@nteract/presentational-components": "3.0.7",
|
||||||
@@ -194,8 +194,8 @@
|
|||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
|
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
|
||||||
"compile:strict": "tsc -p ./tsconfig.strict.json",
|
"compile:strict": "tsc -p ./tsconfig.strict.json",
|
||||||
"format": "prettier --write \"{src,cypress,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
"format": "prettier --write \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
||||||
"format:check": "prettier --check \"{src,cypress,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
||||||
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
|
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
|
||||||
"build:contracts": "npm run compile:contracts",
|
"build:contracts": "npm run compile:contracts",
|
||||||
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
|
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { AutopilotTier } from "../Contracts/DataModels";
|
|
||||||
import { HashMap } from "./HashMap";
|
import { HashMap } from "./HashMap";
|
||||||
|
|
||||||
export class AuthorizationEndpoints {
|
export class AuthorizationEndpoints {
|
||||||
@@ -124,15 +123,17 @@ export class Features {
|
|||||||
public static readonly notebookBasePath = "notebookbasepath";
|
public static readonly notebookBasePath = "notebookbasepath";
|
||||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
|
||||||
public static readonly ttl90Days = "ttl90days";
|
public static readonly ttl90Days = "ttl90days";
|
||||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||||
|
public static readonly enableSchema = "enableschema";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||||
|
public static readonly showMinRUSurvey = "showminrusurvey";
|
||||||
}
|
}
|
||||||
|
|
||||||
// flight names returned from the portal are always lowercase
|
// flight names returned from the portal are always lowercase
|
||||||
export class Flights {
|
export class Flights {
|
||||||
public static readonly SettingsV2 = "settingsv2";
|
public static readonly SettingsV2 = "settingsv2";
|
||||||
|
public static readonly MongoIndexEditor = "mongoindexeditor";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AfecFeatures {
|
export class AfecFeatures {
|
||||||
@@ -261,7 +262,6 @@ export class HttpHeaders {
|
|||||||
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
||||||
public static autoPilotThroughput = "autoscaleSettings";
|
public static autoPilotThroughput = "autoscaleSettings";
|
||||||
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
|
||||||
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
|
|
||||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||||
@@ -406,54 +406,6 @@ export enum ConflictOperationType {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AutoPilot {
|
|
||||||
public static tier1Text: string = "4,000 RU/s";
|
|
||||||
public static tier2Text: string = "20,000 RU/s";
|
|
||||||
public static tier3Text: string = "100,000 RU/s";
|
|
||||||
public static tier4Text: string = "500,000 RU/s";
|
|
||||||
|
|
||||||
public static tierText = {
|
|
||||||
[AutopilotTier.Tier1]: "Tier 1",
|
|
||||||
[AutopilotTier.Tier2]: "Tier 2",
|
|
||||||
[AutopilotTier.Tier3]: "Tier 3",
|
|
||||||
[AutopilotTier.Tier4]: "Tier 4"
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMaxRus = {
|
|
||||||
[AutopilotTier.Tier1]: 2000,
|
|
||||||
[AutopilotTier.Tier2]: 20000,
|
|
||||||
[AutopilotTier.Tier3]: 100000,
|
|
||||||
[AutopilotTier.Tier4]: 500000
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierMinRus = {
|
|
||||||
[AutopilotTier.Tier1]: 0,
|
|
||||||
[AutopilotTier.Tier2]: 0,
|
|
||||||
[AutopilotTier.Tier3]: 0,
|
|
||||||
[AutopilotTier.Tier4]: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
public static tierStorageInGB = {
|
|
||||||
[AutopilotTier.Tier1]: 50,
|
|
||||||
[AutopilotTier.Tier2]: 200,
|
|
||||||
[AutopilotTier.Tier3]: 1000,
|
|
||||||
[AutopilotTier.Tier4]: 5000
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerVersions {
|
|
||||||
public static readonly v_1_0_0: string = "1.0.0";
|
|
||||||
public static readonly v_1_0_1: string = "1.0.1";
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DataExplorerFeatures {
|
|
||||||
public static offerCache: string = "OfferCache";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DataExplorerFeaturesVersions: any = {
|
|
||||||
OfferCache: DataExplorerVersions.v_1_0_1
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EmulatorMasterKey =
|
export const EmulatorMasterKey =
|
||||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ describe("endpoint", () => {
|
|||||||
|
|
||||||
describe("requestPlugin", () => {
|
describe("requestPlugin", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
delete window.dataExplorerPlatform;
|
|
||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { configContext, Platform } from "../ConfigContext";
|
||||||
|
import { getErrorMessage } from "./ErrorHandlingUtils";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
@@ -69,7 +70,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
const result = JSON.parse(await response.json());
|
const result = JSON.parse(await response.json());
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`);
|
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,22 +168,6 @@ export function deleteConflict(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshOffers, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedResources(options?: any): Q.Promise<void> {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage(MessageTypes.RefreshResources, []);
|
|
||||||
} else {
|
|
||||||
return Q();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryConflicts(
|
export function queryConflicts(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
containerId: string,
|
containerId: string,
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import ConflictId from "../Explorer/Tree/ConflictId";
|
import ConflictId from "../Explorer/Tree/ConflictId";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as Constants from "./Constants";
|
import * as Constants from "./Constants";
|
||||||
import { sendNotificationForError } from "./dataAccess/sendNotificationForError";
|
|
||||||
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
import * as DataAccessUtilityBase from "./DataAccessUtilityBase";
|
||||||
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
|
||||||
import * as Logger from "./Logger";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
|
|
||||||
// TODO: Log all promise resolutions and errors with verbosity levels
|
// TODO: Log all promise resolutions and errors with verbosity levels
|
||||||
export function queryDocuments(
|
export function queryDocuments(
|
||||||
@@ -59,13 +56,11 @@ export function executeStoredProcedure(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(
|
||||||
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}: ${JSON.stringify(
|
error,
|
||||||
error
|
"ExecuteStoredProcedure",
|
||||||
)}`
|
`Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
|
||||||
);
|
);
|
||||||
Logger.logError(JSON.stringify(error), "ExecuteStoredProcedure", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -93,9 +88,7 @@ export function queryDocumentsPage(
|
|||||||
deferred.resolve(result);
|
deferred.resolve(result);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to query ${entityName} for container ${resourceName}: ${JSON.stringify(error)}`);
|
handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
|
||||||
Logger.logError(JSON.stringify(error), "QueryDocumentsPage", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -116,9 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
|
|||||||
deferred.resolve(document);
|
deferred.resolve(document);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to read ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "ReadDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -144,9 +135,7 @@ export function updateDocument(
|
|||||||
deferred.resolve(updatedDocument);
|
deferred.resolve(updatedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Failed to update ${entityName} ${documentId.id()}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "UpdateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -168,11 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
|
|||||||
deferred.resolve(savedDocument);
|
deferred.resolve(savedDocument);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(
|
handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
|
||||||
`Error while creating new ${entityName} for container ${collection.id()}:\n ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(error), "CreateDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -194,9 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting ${entityName} ${documentId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteDocument", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -222,9 +205,7 @@ export function deleteConflict(
|
|||||||
deferred.resolve(response);
|
deferred.resolve(response);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
logConsoleError(`Error while deleting conflict ${conflictId.id()}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
|
||||||
Logger.logError(JSON.stringify(error), "DeleteConflict", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -234,11 +215,3 @@ export function deleteConflict(
|
|||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function refreshCachedResources(options: any = {}): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedResources(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshCachedOffers(): Q.Promise<void> {
|
|
||||||
return DataAccessUtilityBase.refreshCachedOffers();
|
|
||||||
}
|
|
||||||
|
|||||||
56
src/Common/ErrorHandlingUtils.ts
Normal file
56
src/Common/ErrorHandlingUtils.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { ARMError } from "../Utils/arm/request";
|
||||||
|
import { HttpStatusCodes } from "./Constants";
|
||||||
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
|
import { SubscriptionType } from "../Contracts/SubscriptionType";
|
||||||
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { logError } from "./Logger";
|
||||||
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
|
export const handleError = (error: string | ARMError | Error, area: string, consoleErrorPrefix?: string): void => {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
const errorCode = error instanceof ARMError ? error.code : undefined;
|
||||||
|
|
||||||
|
// logs error to data explorer console
|
||||||
|
const consoleErrorMessage = consoleErrorPrefix ? `${consoleErrorPrefix}:\n ${errorMessage}` : errorMessage;
|
||||||
|
logConsoleError(consoleErrorMessage);
|
||||||
|
|
||||||
|
// logs error to both app insight and kusto
|
||||||
|
logError(errorMessage, area, errorCode);
|
||||||
|
|
||||||
|
// checks for errors caused by firewall and sends them to portal to handle
|
||||||
|
sendNotificationForError(errorMessage, errorCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorMessage = (error: string | Error): string => {
|
||||||
|
const errorMessage = typeof error === "string" ? error : error.message;
|
||||||
|
return replaceKnownError(errorMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorStack = (error: string | Error): string => {
|
||||||
|
return typeof error === "string" ? undefined : error.stack;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
|
||||||
|
if (errorCode === HttpStatusCodes.Forbidden) {
|
||||||
|
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMessage({
|
||||||
|
type: MessageTypes.ForbiddenError,
|
||||||
|
reason: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceKnownError = (errorMessage: string): string => {
|
||||||
|
if (
|
||||||
|
window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
|
||||||
|
errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
|
||||||
|
) {
|
||||||
|
return "Database throughput is not supported for internal subscriptions.";
|
||||||
|
} else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) {
|
||||||
|
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
};
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
|
||||||
|
|
||||||
describe("Error Parser Utility", () => {
|
|
||||||
describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
|
|
||||||
it("should parse a backend error correctly", () => {
|
|
||||||
// A fake error matching what is thrown by the SDK on a bad collection create request
|
|
||||||
const innerMessage =
|
|
||||||
"The partition key component definition path '/asdwqr31 @#$#$WRadf' could not be accepted, failed near position '10'. Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
|
||||||
const message = `Message: {\"Errors\":[\"${innerMessage}\"]}\r\nActivityId: 97b2e684-7505-4921-85f6-2513b9b28220, Request URI: /apps/89fdcf25-2a0b-4d2a-aab6-e161e565b26f/services/54911149-7bb1-4e7d-a1fa-22c8b36a4bb9/partitions/cc2a7a04-5f5a-4709-bcf7-8509b264963f/replicas/132304018743619218p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.10.0`;
|
|
||||||
const err = new Error(message) as any;
|
|
||||||
err.code = 400;
|
|
||||||
err.body = {
|
|
||||||
code: "BadRequest",
|
|
||||||
message
|
|
||||||
};
|
|
||||||
err.headers = {};
|
|
||||||
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";
|
|
||||||
|
|
||||||
const parsedError = ErrorParserUtility.parse(err);
|
|
||||||
expect(parsedError.length).toBe(1);
|
|
||||||
expect(parsedError[0].message).toBe(innerMessage);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
|
|
||||||
export function replaceKnownError(err: string): string {
|
|
||||||
if (
|
|
||||||
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
|
|
||||||
err.indexOf("SharedOffer is Disabled for your account") >= 0
|
|
||||||
) {
|
|
||||||
return "Database throughput is not supported for internal subscriptions.";
|
|
||||||
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
|
|
||||||
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
try {
|
|
||||||
return _parse(err);
|
|
||||||
} catch (e) {
|
|
||||||
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parse(err: any): DataModels.ErrorDataModel[] {
|
|
||||||
var normalizedErrors: DataModels.ErrorDataModel[] = [];
|
|
||||||
if (err.message && !err.code) {
|
|
||||||
normalizedErrors.push(err);
|
|
||||||
} else {
|
|
||||||
const innerErrors: any[] = _getInnerErrors(err.message);
|
|
||||||
normalizedErrors = innerErrors.map(innerError =>
|
|
||||||
typeof innerError === "string" ? { message: innerError } : innerError
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getInnerErrors(message: string): any[] {
|
|
||||||
/*
|
|
||||||
The backend error message has an inner-message which is a stringified object.
|
|
||||||
|
|
||||||
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
|
|
||||||
Example:
|
|
||||||
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
|
|
||||||
For non-SQL errors the "Errors" propery is an array of string.
|
|
||||||
Example:
|
|
||||||
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
|
|
||||||
*/
|
|
||||||
|
|
||||||
let innerMessage: any = null;
|
|
||||||
|
|
||||||
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
|
|
||||||
try {
|
|
||||||
// Multi-Partition error flavor
|
|
||||||
const regExp = /^(.*)ActivityId: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
} catch (e) {
|
|
||||||
// Single-partition error flavor
|
|
||||||
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
|
|
||||||
const regString = regExp.exec(singleLineMessage);
|
|
||||||
const innerMessageString = regString[1];
|
|
||||||
innerMessage = JSON.parse(innerMessageString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
|
|
||||||
}
|
|
||||||
@@ -21,14 +21,8 @@ export function logWarning(message: string, area: string, code?: number): void {
|
|||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logError(message: string | Error, area: string, code?: number): void {
|
export function logError(errorMessage: string, area: string, code?: number | string): void {
|
||||||
let logMessage: string;
|
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
|
||||||
if (typeof message === "string") {
|
|
||||||
logMessage = message;
|
|
||||||
} else {
|
|
||||||
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
|
|
||||||
}
|
|
||||||
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
|
|
||||||
return _logEntry(entry);
|
return _logEntry(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +53,7 @@ function _generateLogEntry(
|
|||||||
level: Diagnostics.LogEntryLevel,
|
level: Diagnostics.LogEntryLevel,
|
||||||
message: string,
|
message: string,
|
||||||
area: string,
|
area: string,
|
||||||
code?: number
|
code?: number | string
|
||||||
): Diagnostics.LogEntry {
|
): Diagnostics.LogEntry {
|
||||||
return {
|
return {
|
||||||
timestamp: new Date().getUTCSeconds(),
|
timestamp: new Date().getUTCSeconds(),
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
|
||||||
import { createCollection } from "./dataAccess/createCollection";
|
import { createCollection } from "./dataAccess/createCollection";
|
||||||
import * as ErrorParserUtility from "./ErrorParserUtility";
|
import { handleError } from "./ErrorHandlingUtils";
|
||||||
import * as Logger from "./Logger";
|
|
||||||
|
|
||||||
export class QueriesClient {
|
export class QueriesClient {
|
||||||
private static readonly PartitionKey: DataModels.PartitionKey = {
|
private static readonly PartitionKey: DataModels.PartitionKey = {
|
||||||
@@ -53,13 +52,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(collection);
|
return Promise.resolve(collection);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to set up account for saving queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "setupQueriesCollection");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -102,19 +96,11 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
let errorMessage: string;
|
if (error.code === HttpStatusCodes.Conflict.toString()) {
|
||||||
const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
|
error = `Query ${query.queryName} already exists`;
|
||||||
if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
|
|
||||||
errorMessage = `Query ${query.queryName} already exists`;
|
|
||||||
} else {
|
|
||||||
errorMessage = parsedError.message;
|
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
|
||||||
ConsoleDataType.Error,
|
return Promise.reject(error);
|
||||||
`Failed to save query ${query.queryName}: ${errorMessage}`
|
|
||||||
);
|
|
||||||
Logger.logError(JSON.stringify(parsedError), "saveQuery");
|
|
||||||
return Promise.reject(errorMessage);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -163,25 +149,15 @@ export class QueriesClient {
|
|||||||
return Promise.resolve(queries);
|
return Promise.resolve(queries);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
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);
|
handleError(error, "getSavedQueries", "Failed to fetch saved queries");
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to fetch saved queries: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "getSavedQueries");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
@@ -232,13 +208,8 @@ export class QueriesClient {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const stringifiedError: string = JSON.stringify(error);
|
handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
return Promise.reject(error);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete query ${query.queryName}: ${stringifiedError}`
|
|
||||||
);
|
|
||||||
Logger.logError(stringifiedError, "deleteQuery");
|
|
||||||
return Promise.reject(stringifiedError);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ErrorParserUtility from "../ErrorParserUtility";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
|
||||||
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
|
||||||
@@ -23,21 +22,19 @@ import {
|
|||||||
getGremlinGraph
|
getGremlinGraph
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { createDatabase } from "./createDatabase";
|
import { createDatabase } from "./createDatabase";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
let collection: DataModels.Collection;
|
|
||||||
const clearMessage = logConsoleProgress(
|
const clearMessage = logConsoleProgress(
|
||||||
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
let collection: DataModels.Collection;
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
||||||
if (params.createNewDatabase) {
|
if (params.createNewDatabase) {
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
@@ -54,18 +51,15 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
} else {
|
} else {
|
||||||
collection = await createCollectionWithSDK(params);
|
collection = await createCollectionWithSDK(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
||||||
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const sanitizedError = ErrorParserUtility.replaceKnownError(JSON.stringify(error));
|
handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
|
||||||
logConsoleError(`Error while creating container ${params.collectionId}:\n ${sanitizedError}`);
|
|
||||||
logError(JSON.stringify(error), "CreateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created container ${params.collectionId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
clearMessage();
|
|
||||||
return collection;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
|
||||||
|
|||||||
@@ -24,36 +24,28 @@ import {
|
|||||||
createUpdateGremlinDatabase,
|
createUpdateGremlinDatabase,
|
||||||
getGremlinDatabase
|
getGremlinDatabase
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
let database: DataModels.Database;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
||||||
throw new Error("Creating database resources is not allowed for tables accounts");
|
throw new Error("Creating database resources is not allowed for tables accounts");
|
||||||
}
|
}
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
|
||||||
database = await createDatabaseWithARM(params);
|
? createDatabaseWithARM(params)
|
||||||
} else {
|
: createDatabaseWithSDK(params));
|
||||||
database = await createDatabaseWithSDK(params);
|
|
||||||
}
|
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
||||||
|
return database;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating database ${params.databaseId}:\n ${error.message}`);
|
handleError(error, "CreateDatabase", `Error while creating database ${params.databaseId}`);
|
||||||
logError(JSON.stringify(error), "CreateDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
clearMessage();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully created database ${params.databaseId}`);
|
|
||||||
await refreshCachedResources();
|
|
||||||
await refreshCachedOffers();
|
|
||||||
clearMessage();
|
|
||||||
return database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
|
||||||
|
|||||||
@@ -1,28 +1,78 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createStoredProcedure(
|
export async function createStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let createdStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.create(storedProcedure);
|
.scripts.storedProcedures.create(storedProcedure);
|
||||||
createdStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "CreateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdStoredProcedure;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,73 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createTrigger(
|
export async function createTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition & Resource> {
|
): Promise<TriggerDefinition & Resource> {
|
||||||
let createdTrigger: TriggerDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.create(trigger);
|
.scripts.triggers.create(trigger);
|
||||||
createdTrigger = response.resource;
|
return response.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "CreateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdTrigger;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,82 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function createUserDefinedFunction(
|
export async function createUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let createdUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
throw new Error(
|
||||||
|
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "NotFound") {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
.scripts.userDefinedFunctions.create(userDefinedFunction);
|
||||||
createdUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while creating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "CreateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"CreateUserupdateUserDefinedFunction",
|
||||||
|
`Error while creating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return createdUserDefinedFunction;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteCollection", () => {
|
describe("deleteCollection", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteCollection", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-0
|
|||||||
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
|
|
||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
@@ -23,15 +21,13 @@ export async function deleteCollection(databaseId: string, collectionId: string)
|
|||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "DeleteCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted container ${collectionId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { AuthType } from "../../AuthType";
|
|||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
|
||||||
describe("deleteDatabase", () => {
|
describe("deleteDatabase", () => {
|
||||||
@@ -18,7 +17,6 @@ describe("deleteDatabase", () => {
|
|||||||
} as DatabaseAccount,
|
} as DatabaseAccount,
|
||||||
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
defaultExperience: DefaultAccountExperienceType.DocumentDB
|
||||||
});
|
});
|
||||||
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should call ARM if logged in with AAD", async () => {
|
it("should call ARM if logged in with AAD", async () => {
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/s
|
|||||||
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function deleteDatabase(databaseId: string): Promise<void> {
|
export async function deleteDatabase(databaseId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
|
||||||
@@ -25,15 +23,13 @@ export async function deleteDatabase(databaseId: string): Promise<void> {
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.delete();
|
.delete();
|
||||||
}
|
}
|
||||||
|
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteDatabase", `Error while deleting database ${databaseId}`);
|
||||||
logError(JSON.stringify(error), "DeleteDatabase", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
logConsoleInfo(`Successfully deleted database ${databaseId}`);
|
|
||||||
clearMessage();
|
|
||||||
await refreshCachedResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteStoredProcedure(
|
export async function deleteStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
@@ -10,17 +13,30 @@ export async function deleteStoredProcedure(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.storedProcedure(storedProcedureId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedureId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.storedProcedure(storedProcedureId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting stored procedure ${storedProcedureId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);
|
||||||
logError(JSON.stringify(error), "DeleteStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.trigger(triggerId)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
triggerId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.trigger(triggerId)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting trigger ${triggerId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);
|
||||||
logError(JSON.stringify(error), "DeleteTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
|
||||||
try {
|
try {
|
||||||
await client()
|
if (
|
||||||
.database(databaseId)
|
window.authType === AuthType.AAD &&
|
||||||
.container(collectionId)
|
!userContext.useSDKOperations &&
|
||||||
.scripts.userDefinedFunction(id)
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
.delete();
|
) {
|
||||||
|
await deleteSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.scripts.userDefinedFunction(id)
|
||||||
|
.delete();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while deleting user defined function ${id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);
|
||||||
logError(JSON.stringify(error), "DeleteUserDefinedFunction", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
28
src/Common/dataAccess/getIndexTransformationProgress.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import * as Constants from "../Constants";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise<number> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let indexTransformationPercentage: number;
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.container(collectionId)
|
||||||
|
.read({ populateQuotaInfo: true });
|
||||||
|
|
||||||
|
indexTransformationPercentage = parseInt(
|
||||||
|
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return indexTransformationPercentage;
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
|
||||||
let collection: DataModels.Collection;
|
let collection: DataModels.Collection;
|
||||||
@@ -14,9 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
|
|||||||
.read();
|
.read();
|
||||||
collection = response.resource as DataModels.Collection;
|
collection = response.resource as DataModels.Collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying container ${collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType
|
|||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readCollectionOffer = async (
|
export const readCollectionOffer = async (
|
||||||
@@ -57,9 +56,7 @@ export const readCollectionOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for collection ${params.collectionId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollectionOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import { ContainerDefinition, Resource } from "@azure/cosmos";
|
|||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
|
|
||||||
interface ResourceWithStatistics {
|
interface ResourceWithStatistics {
|
||||||
statistics: DataModels.Statistic[];
|
statistics: DataModels.Statistic[];
|
||||||
@@ -38,9 +37,7 @@ export const readCollectionQuotaInfo = async (
|
|||||||
|
|
||||||
return quota;
|
return quota;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying quota info for container ${collection.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
|
||||||
logError(JSON.stringify(error), "ReadCollectionQuotaInfo", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
let collections: DataModels.Collection[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
@@ -22,22 +20,20 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
collections = await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
} else {
|
|
||||||
const sdkResponse = await client()
|
|
||||||
.database(databaseId)
|
|
||||||
.containers.readAll()
|
|
||||||
.fetchAll();
|
|
||||||
collections = sdkResponse.resources as DataModels.Collection[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sdkResponse = await client()
|
||||||
|
.database(databaseId)
|
||||||
|
.containers.readAll()
|
||||||
|
.fetchAll();
|
||||||
|
return sdkResponse.resources as DataModels.Collection[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying containers for database ${databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadCollections", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return collections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-
|
|||||||
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { readOffers } from "./readOffers";
|
import { readOffers } from "./readOffers";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export const readDatabaseOffer = async (
|
export const readDatabaseOffer = async (
|
||||||
@@ -48,9 +47,7 @@ export const readDatabaseOffer = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying offer for database ${params.databaseId}:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabaseOffer", `Error while querying offer for database ${params.databaseId}`);
|
||||||
logError(JSON.stringify(error), "ReadDatabaseOffer", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -2,24 +2,23 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readDatabases(): Promise<DataModels.Database[]> {
|
export async function readDatabases(): Promise<DataModels.Database[]> {
|
||||||
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
|
|
||||||
return [{ id: "TablesDB" } as DataModels.Database];
|
|
||||||
}
|
|
||||||
|
|
||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
try {
|
try {
|
||||||
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
const sdkResponse = await client()
|
const sdkResponse = await client()
|
||||||
@@ -28,9 +27,7 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
databases = sdkResponse.resources as DataModels.Database[];
|
databases = sdkResponse.resources as DataModels.Database[];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while querying databases:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadDatabases", `Error while querying databases`);
|
||||||
logError(JSON.stringify(error), "ReadDatabases", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
30
src/Common/dataAccess/readMongoDBCollection.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
|
||||||
|
export async function readMongoDBCollectionThroughRP(
|
||||||
|
databaseId: string,
|
||||||
|
collectionId: string
|
||||||
|
): Promise<MongoDBCollectionResource> {
|
||||||
|
if (window.authType !== AuthType.AAD) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let collection: MongoDBCollectionResource;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
|
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
|
||||||
|
try {
|
||||||
|
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
|
collection = response.properties.resource;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
clearMessage();
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
@@ -1,26 +1,10 @@
|
|||||||
import { Offer } from "../../Contracts/DataModels";
|
import { Offer } from "../../Contracts/DataModels";
|
||||||
import { ClientDefaults } from "../Constants";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
import { Platform, configContext } from "../../ConfigContext";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleProgress, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
|
||||||
import { sendCachedDataMessage } from "../MessageHandler";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
|
||||||
|
|
||||||
export const readOffers = async (): Promise<Offer[]> => {
|
export const readOffers = async (): Promise<Offer[]> => {
|
||||||
const clearMessage = logConsoleProgress(`Querying offers`);
|
const clearMessage = logConsoleProgress(`Querying offers`);
|
||||||
try {
|
|
||||||
if (configContext.platform === Platform.Portal) {
|
|
||||||
return sendCachedDataMessage<Offer[]>(MessageTypes.AllOffers, [
|
|
||||||
userContext.databaseAccount.id,
|
|
||||||
ClientDefaults.portalCacheTimeoutMs
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// If error getting cached Offers, continue on and read via SDK
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await client()
|
const response = await client()
|
||||||
@@ -29,13 +13,11 @@ export const readOffers = async (): Promise<Offer[]> => {
|
|||||||
return response?.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
|
||||||
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
if (getErrorMessage(error)?.includes("Reading or replacing offers is not supported for serverless accounts")) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleError(`Error while querying offers:\n ${JSON.stringify(error)}`);
|
handleError(error, "ReadOffers", `Error while querying offers`);
|
||||||
logError(JSON.stringify(error), "ReadOffers", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readStoredProcedures(
|
export async function readStoredProcedures(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
): Promise<(StoredProcedureDefinition & Resource)[]> {
|
||||||
let sprocs: (StoredProcedureDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlStoredProcedures(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedures.readAll()
|
.scripts.storedProcedures.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
sprocs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query stored procedures for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadStoredProcedures", `Failed to query stored procedures for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadStoredProcedures", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return sprocs;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,43 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
import { Resource, TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export async function readTriggers(
|
export async function readTriggers(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(TriggerDefinition & Resource)[]> {
|
): Promise<(TriggerDefinition & Resource)[]> {
|
||||||
let triggers: (TriggerDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlTriggers(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.triggers.readAll()
|
.scripts.triggers.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
triggers = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query triggers for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "ReadTriggers", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return triggers;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,47 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function readUserDefinedFunctions(
|
export async function readUserDefinedFunctions(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string
|
collectionId: string
|
||||||
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
|
||||||
let udfs: (UserDefinedFunctionDefinition & Resource)[];
|
|
||||||
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const rpResponse = await listSqlUserDefinedFunctions(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId
|
||||||
|
);
|
||||||
|
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunctions.readAll()
|
.scripts.userDefinedFunctions.readAll()
|
||||||
.fetchAll();
|
.fetchAll();
|
||||||
udfs = response.resources;
|
return response?.resources;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to query user defined functions for container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "ReadUserDefinedFunctions", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"ReadUserDefinedFunctions",
|
||||||
|
`Failed to query user defined functions for container ${collectionId}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
clearMessage();
|
|
||||||
return udfs;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import * as Constants from "../Constants";
|
|
||||||
import { sendMessage } from "../MessageHandler";
|
|
||||||
import { MessageTypes } from "../../Contracts/ExplorerContracts";
|
|
||||||
|
|
||||||
interface CosmosError {
|
|
||||||
code: number;
|
|
||||||
message?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendNotificationForError(error: CosmosError): void {
|
|
||||||
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
|
|
||||||
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendMessage({
|
|
||||||
type: MessageTypes.ForbiddenError,
|
|
||||||
reason: error && error.message ? error.message : error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,10 @@ import { Collection } from "../../Contracts/DataModels";
|
|||||||
import { ContainerDefinition } from "@azure/cosmos";
|
import { ContainerDefinition } from "@azure/cosmos";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import {
|
import {
|
||||||
|
CreateUpdateOptions,
|
||||||
ExtendedResourceProperties,
|
ExtendedResourceProperties,
|
||||||
|
MongoDBCollectionCreateUpdateParameters,
|
||||||
|
MongoDBCollectionResource,
|
||||||
SqlContainerCreateUpdateParameters,
|
SqlContainerCreateUpdateParameters,
|
||||||
SqlContainerResource
|
SqlContainerResource
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
@@ -23,10 +26,8 @@ import {
|
|||||||
getGremlinGraph
|
getGremlinGraph
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateCollection(
|
export async function updateCollection(
|
||||||
@@ -51,16 +52,14 @@ export async function updateCollection(
|
|||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.replace(newCollection as ContainerDefinition, options);
|
.replace(newCollection as ContainerDefinition, options);
|
||||||
|
|
||||||
collection = sdkResponse.resource as Collection;
|
collection = sdkResponse.resource as Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
logConsoleInfo(`Successfully updated container ${collectionId}`);
|
||||||
await refreshCachedResources();
|
|
||||||
return collection;
|
return collection;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Failed to update container ${collectionId}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateCollection", `Failed to update container ${collectionId}`);
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -80,15 +79,6 @@ async function updateCollectionWithARM(
|
|||||||
switch (defaultExperience) {
|
switch (defaultExperience) {
|
||||||
case DefaultAccountExperienceType.DocumentDB:
|
case DefaultAccountExperienceType.DocumentDB:
|
||||||
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.MongoDB:
|
|
||||||
return updateMongoDBCollection(
|
|
||||||
databaseId,
|
|
||||||
collectionId,
|
|
||||||
subscriptionId,
|
|
||||||
resourceGroup,
|
|
||||||
accountName,
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
case DefaultAccountExperienceType.Cassandra:
|
case DefaultAccountExperienceType.Cassandra:
|
||||||
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.Graph:
|
case DefaultAccountExperienceType.Graph:
|
||||||
@@ -125,26 +115,35 @@ async function updateSqlContainer(
|
|||||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateMongoDBCollection(
|
export async function updateMongoDBCollectionThroughRP(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
subscriptionId: string,
|
newCollection: MongoDBCollectionResource,
|
||||||
resourceGroup: string,
|
updateOptions?: CreateUpdateOptions
|
||||||
accountName: string,
|
): Promise<MongoDBCollectionResource> {
|
||||||
newCollection: Collection
|
const subscriptionId = userContext.subscriptionId;
|
||||||
): Promise<Collection> {
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
const accountName = userContext.databaseAccount.name;
|
||||||
|
|
||||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: newCollection,
|
||||||
|
options: updateOptions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateResponse = await createUpdateMongoDBCollection(
|
const updateResponse = await createUpdateMongoDBCollection(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
accountName,
|
accountName,
|
||||||
databaseId,
|
databaseId,
|
||||||
collectionId,
|
collectionId,
|
||||||
getResponse as SqlContainerCreateUpdateParameters
|
updateParams
|
||||||
);
|
);
|
||||||
return updateResponse && (updateResponse.properties.resource as Collection);
|
|
||||||
|
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import { OfferDefinition } from "@azure/cosmos";
|
|||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
import { logError } from "../Logger";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { readCollectionOffer } from "./readCollectionOffer";
|
import { readCollectionOffer } from "./readCollectionOffer";
|
||||||
import { readDatabaseOffer } from "./readDatabaseOffer";
|
import { readDatabaseOffer } from "./readDatabaseOffer";
|
||||||
import { refreshCachedOffers, refreshCachedResources } from "../DataAccessUtilityBase";
|
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
|
||||||
import {
|
import {
|
||||||
updateSqlDatabaseThroughput,
|
updateSqlDatabaseThroughput,
|
||||||
migrateSqlDatabaseToAutoscale,
|
migrateSqlDatabaseToAutoscale,
|
||||||
@@ -71,14 +69,10 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
} else {
|
} else {
|
||||||
updatedOffer = await updateOfferWithSDK(params);
|
updatedOffer = await updateOfferWithSDK(params);
|
||||||
}
|
}
|
||||||
await refreshCachedOffers();
|
|
||||||
await refreshCachedResources();
|
|
||||||
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
|
||||||
return updatedOffer;
|
return updatedOffer;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error updating offer for ${offerResourceText}: ${JSON.stringify(error)}`);
|
handleError(error, "UpdateCollection", `Error updating offer for ${offerResourceText}`);
|
||||||
logError(JSON.stringify(error), "UpdateCollection", error.code);
|
|
||||||
sendNotificationForError(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
clearMessage();
|
clearMessage();
|
||||||
@@ -406,10 +400,14 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
|
|
||||||
const options: RequestOptions = {};
|
const options: RequestOptions = {};
|
||||||
if (params.migrateToAutoPilot) {
|
if (params.migrateToAutoPilot) {
|
||||||
options.initialHeaders[HttpHeaders.migrateOfferToAutopilot] = "true";
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToAutopilot]: "true"
|
||||||
|
};
|
||||||
delete newOffer.content.offerAutopilotSettings;
|
delete newOffer.content.offerAutopilotSettings;
|
||||||
} else if (params.migrateToManual) {
|
} else if (params.migrateToManual) {
|
||||||
options.initialHeaders[HttpHeaders.migrateOfferToManualThroughput] = "true";
|
options.initialHeaders = {
|
||||||
|
[HttpHeaders.migrateOfferToManualThroughput]: "true"
|
||||||
|
};
|
||||||
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Platform, configContext } from "../../ConfigContext";
|
import { Platform, configContext } from "../../ConfigContext";
|
||||||
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
|
||||||
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
|
||||||
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { HttpHeaders } from "../Constants";
|
import { HttpHeaders } from "../Constants";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
interface UpdateOfferThroughputRequest {
|
interface UpdateOfferThroughputRequest {
|
||||||
subscriptionId: string;
|
subscriptionId: string;
|
||||||
@@ -44,8 +45,13 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
|
|||||||
clearMessage();
|
clearMessage();
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
|
handleError(
|
||||||
|
error,
|
||||||
|
"updateOfferThroughputBeyondLimit",
|
||||||
|
`Failed to request an increase in throughput for ${request.throughput}`
|
||||||
|
);
|
||||||
clearMessage();
|
clearMessage();
|
||||||
throw new Error(error.message);
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,72 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlStoredProcedureCreateUpdateParameters,
|
||||||
|
SqlStoredProcedureResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlStoredProcedure,
|
||||||
|
getSqlStoredProcedure
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateStoredProcedure(
|
export async function updateStoredProcedure(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
storedProcedure: StoredProcedureDefinition
|
storedProcedure: StoredProcedureDefinition
|
||||||
): Promise<StoredProcedureDefinition & Resource> {
|
): Promise<StoredProcedureDefinition & Resource> {
|
||||||
let updatedStoredProcedure: StoredProcedureDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: storedProcedure as SqlStoredProcedureResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlStoredProcedure(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
storedProcedure.id,
|
||||||
|
createSprocParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.storedProcedure(storedProcedure.id)
|
.scripts.storedProcedure(storedProcedure.id)
|
||||||
.replace(storedProcedure);
|
.replace(storedProcedure);
|
||||||
updatedStoredProcedure = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating stored procedure ${storedProcedure.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateStoredProcedure", `Error while updating stored procedure ${storedProcedure.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateStoredProcedure", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedStoredProcedure;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,69 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import {
|
||||||
|
SqlTriggerCreateUpdateParameters,
|
||||||
|
SqlTriggerResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { TriggerDefinition } from "@azure/cosmos";
|
import { TriggerDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateTrigger(
|
export async function updateTrigger(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
trigger: TriggerDefinition
|
trigger: TriggerDefinition
|
||||||
): Promise<TriggerDefinition> {
|
): Promise<TriggerDefinition> {
|
||||||
let updatedTrigger: TriggerDefinition;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: trigger as SqlTriggerResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlTrigger(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
trigger.id,
|
||||||
|
createTriggerParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.trigger(trigger.id)
|
.scripts.trigger(trigger.id)
|
||||||
.replace(trigger);
|
.replace(trigger);
|
||||||
updatedTrigger = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating trigger ${trigger.id}:\n ${JSON.stringify(error)}`);
|
handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);
|
||||||
logError(JSON.stringify(error), "UpdateTrigger", error.code);
|
throw error;
|
||||||
sendNotificationForError(error);
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedTrigger;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,76 @@
|
|||||||
|
import { AuthType } from "../../AuthType";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import {
|
||||||
|
SqlUserDefinedFunctionCreateUpdateParameters,
|
||||||
|
SqlUserDefinedFunctionResource
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { client } from "../CosmosClient";
|
import { client } from "../CosmosClient";
|
||||||
import { logError } from "../Logger";
|
import {
|
||||||
import { sendNotificationForError } from "./sendNotificationForError";
|
createUpdateSqlUserDefinedFunction,
|
||||||
|
getSqlUserDefinedFunction
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
|
||||||
export async function updateUserDefinedFunction(
|
export async function updateUserDefinedFunction(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
userDefinedFunction: UserDefinedFunctionDefinition
|
userDefinedFunction: UserDefinedFunctionDefinition
|
||||||
): Promise<UserDefinedFunctionDefinition & Resource> {
|
): Promise<UserDefinedFunctionDefinition & Resource> {
|
||||||
let updatedUserDefinedFunction: UserDefinedFunctionDefinition & Resource;
|
|
||||||
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
|
||||||
try {
|
try {
|
||||||
|
if (
|
||||||
|
window.authType === AuthType.AAD &&
|
||||||
|
!userContext.useSDKOperations &&
|
||||||
|
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
|
||||||
|
) {
|
||||||
|
const getResponse = await getSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (getResponse?.properties?.resource) {
|
||||||
|
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
|
||||||
|
properties: {
|
||||||
|
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rpResponse = await createUpdateSqlUserDefinedFunction(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
userDefinedFunction.id,
|
||||||
|
createUDFParams
|
||||||
|
);
|
||||||
|
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await client()
|
const response = await client()
|
||||||
.database(databaseId)
|
.database(databaseId)
|
||||||
.container(collectionId)
|
.container(collectionId)
|
||||||
.scripts.userDefinedFunction(userDefinedFunction.id)
|
.scripts.userDefinedFunction(userDefinedFunction.id)
|
||||||
.replace(userDefinedFunction);
|
.replace(userDefinedFunction);
|
||||||
updatedUserDefinedFunction = response.resource;
|
return response?.resource;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(`Error while updating user defined function ${userDefinedFunction.id}:\n ${JSON.stringify(error)}`);
|
handleError(
|
||||||
logError(JSON.stringify(error), "UpdateUserupdateUserDefinedFunction", error.code);
|
error,
|
||||||
sendNotificationForError(error);
|
"UpdateUserupdateUserDefinedFunction",
|
||||||
|
`Error while updating user defined function ${userDefinedFunction.id}`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage();
|
|
||||||
return updatedUserDefinedFunction;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,38 @@ export interface Resource {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IType {
|
||||||
|
name: string;
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDataField {
|
||||||
|
dataType: IType;
|
||||||
|
hasNulls: boolean;
|
||||||
|
isArray: boolean;
|
||||||
|
schemaType: IType;
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
maxRepetitionLevel: number;
|
||||||
|
maxDefinitionLevel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISchema {
|
||||||
|
id: string;
|
||||||
|
accountName: string;
|
||||||
|
resource: string;
|
||||||
|
fields: IDataField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISchemaRequest {
|
||||||
|
id: string;
|
||||||
|
subscriptionId: string;
|
||||||
|
resourceGroup: string;
|
||||||
|
accountName: string;
|
||||||
|
resource: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Collection extends Resource {
|
export interface Collection extends Resource {
|
||||||
defaultTtl?: number;
|
defaultTtl?: number;
|
||||||
indexingPolicy?: IndexingPolicy;
|
indexingPolicy?: IndexingPolicy;
|
||||||
@@ -98,6 +130,8 @@ export interface Collection extends Resource {
|
|||||||
changeFeedPolicy?: ChangeFeedPolicy;
|
changeFeedPolicy?: ChangeFeedPolicy;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
geospatialConfig?: GeospatialConfig;
|
geospatialConfig?: GeospatialConfig;
|
||||||
|
schema?: ISchema;
|
||||||
|
requestSchema?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Database extends Resource {
|
export interface Database extends Resource {
|
||||||
@@ -216,18 +250,6 @@ export interface UniqueKey {
|
|||||||
paths: string[];
|
paths: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returned by DocumentDb client proxy
|
|
||||||
// Inner errors in BackendErrorDataModel when error is in SQL syntax
|
|
||||||
export interface ErrorDataModel {
|
|
||||||
message: string;
|
|
||||||
severity?: string;
|
|
||||||
location?: {
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
};
|
|
||||||
code?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateDatabaseAndCollectionRequest {
|
export interface CreateDatabaseAndCollectionRequest {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -239,21 +261,12 @@ export interface CreateDatabaseAndCollectionRequest {
|
|||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
autoPilot?: AutoPilotCreationSettings;
|
autoPilot?: AutoPilotCreationSettings;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
hasAutoPilotV2FeatureFlag?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotCreationSettings {
|
export interface AutoPilotCreationSettings {
|
||||||
autopilotTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AutopilotTier {
|
|
||||||
Tier1 = 1,
|
|
||||||
Tier2 = 2,
|
|
||||||
Tier3 = 3,
|
|
||||||
Tier4 = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
id: string;
|
id: string;
|
||||||
resourceId: string;
|
resourceId: string;
|
||||||
@@ -262,9 +275,7 @@ export interface Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AutoPilotOfferSettings {
|
export interface AutoPilotOfferSettings {
|
||||||
tier?: AutopilotTier;
|
|
||||||
maximumTierThroughput?: number;
|
maximumTierThroughput?: number;
|
||||||
targetTier?: AutopilotTier;
|
|
||||||
maxThroughput?: number;
|
maxThroughput?: number;
|
||||||
targetMaxThroughput?: number;
|
targetMaxThroughput?: number;
|
||||||
}
|
}
|
||||||
@@ -491,7 +502,6 @@ export interface MongoParameters extends RpParameters {
|
|||||||
rid?: string;
|
rid?: string;
|
||||||
rtype?: string;
|
rtype?: string;
|
||||||
isAutoPilot?: Boolean;
|
isAutoPilot?: Boolean;
|
||||||
autoPilotTier?: string;
|
|
||||||
autoPilotThroughput?: string;
|
autoPilotThroughput?: string;
|
||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export interface LogEntry {
|
|||||||
/**
|
/**
|
||||||
* The message code.
|
* The message code.
|
||||||
*/
|
*/
|
||||||
code?: number;
|
code?: number | string;
|
||||||
/**
|
/**
|
||||||
* Any additional data to be logged.
|
* Any additional data to be logged.
|
||||||
*/
|
*/
|
||||||
|
|||||||
7
src/Contracts/SubscriptionType.ts
Normal file
7
src/Contracts/SubscriptionType.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum SubscriptionType {
|
||||||
|
Benefits,
|
||||||
|
EA,
|
||||||
|
Free,
|
||||||
|
Internal,
|
||||||
|
PAYG
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import Trigger from "../Explorer/Tree/Trigger";
|
|||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import { UploadDetails } from "../workers/upload/definitions";
|
import { UploadDetails } from "../workers/upload/definitions";
|
||||||
import * as DataModels from "./DataModels";
|
import * as DataModels from "./DataModels";
|
||||||
|
import { SubscriptionType } from "./SubscriptionType";
|
||||||
|
|
||||||
export interface TokenProvider {
|
export interface TokenProvider {
|
||||||
getAuthHeader(): Promise<Headers>;
|
getAuthHeader(): Promise<Headers>;
|
||||||
@@ -115,6 +116,8 @@ export interface CollectionBase extends TreeNode {
|
|||||||
export interface Collection extends CollectionBase {
|
export interface Collection extends CollectionBase {
|
||||||
defaultTtl: ko.Observable<number>;
|
defaultTtl: ko.Observable<number>;
|
||||||
analyticalStorageTtl: ko.Observable<number>;
|
analyticalStorageTtl: ko.Observable<number>;
|
||||||
|
schema?: DataModels.ISchema;
|
||||||
|
requestSchema?: () => void;
|
||||||
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
||||||
@@ -289,6 +292,10 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SettingsTabV2Options extends TabOptions {
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConflictsTabOptions extends TabOptions {
|
export interface ConflictsTabOptions extends TabOptions {
|
||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
conflictIds: ko.ObservableArray<ConflictId>;
|
conflictIds: ko.ObservableArray<ConflictId>;
|
||||||
@@ -354,6 +361,7 @@ export enum CollectionTabKind {
|
|||||||
SparkMasterTab = 16,
|
SparkMasterTab = 16,
|
||||||
Gallery = 17,
|
Gallery = 17,
|
||||||
NotebookViewer = 18,
|
NotebookViewer = 18,
|
||||||
|
Schema = 19,
|
||||||
SettingsV2 = 19
|
SettingsV2 = 19
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,14 +416,6 @@ export interface ThroughputDefaults {
|
|||||||
shared: number;
|
shared: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SubscriptionType {
|
|
||||||
Benefits,
|
|
||||||
EA,
|
|
||||||
Free,
|
|
||||||
Internal,
|
|
||||||
PAYG
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MonacoEditorSettings {
|
export class MonacoEditorSettings {
|
||||||
public readonly language: string;
|
public readonly language: string;
|
||||||
public readonly readOnly: boolean;
|
public readonly readOnly: boolean;
|
||||||
|
|||||||
@@ -123,8 +123,4 @@ describe("Component Registerer", () => {
|
|||||||
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", () => {
|
|
||||||
expect(ko.components.isRegistered("throughput-input")).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
|||||||
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 { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
||||||
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
|
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
|
|
||||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||||
@@ -23,12 +22,11 @@ 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-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
ko.components.register("tabs-manager", TabsManagerKOComponent());
|
||||||
|
|
||||||
// Collection Tabs
|
// Collection Tabs
|
||||||
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
|
ko.components.register("documents-tab", new TabComponents.MongoDocumentsTabV2());
|
||||||
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());
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
|
||||||
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
|
||||||
import {
|
import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||||
IContextualMenuItem,
|
|
||||||
IContextualMenuProps,
|
|
||||||
ContextualMenuItemType
|
|
||||||
} from "office-ui-fabric-react/lib/ContextualMenu";
|
|
||||||
import * as Logger from "../../../Common/Logger";
|
import * as Logger from "../../../Common/Logger";
|
||||||
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface ArcadiaMenuPickerProps {
|
export interface ArcadiaMenuPickerProps {
|
||||||
selectText?: string;
|
selectText?: string;
|
||||||
@@ -47,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
|
|||||||
selectedSparkPool: item.text
|
selectedSparkPool: item.text
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
|
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
|
||||||
|
|
||||||
|
describe("CollapsibleSectionComponent", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
const props: CollapsibleSectionProps = {
|
||||||
|
title: "Sample title"
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Icon, Label, Stack } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
|
|
||||||
|
export interface CollapsibleSectionProps {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollapsibleSectionState {
|
||||||
|
isExpanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
|
||||||
|
constructor(props: CollapsibleSectionProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isExpanded: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleCollapsed = (): void => {
|
||||||
|
this.setState({ isExpanded: !this.state.isExpanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
|
||||||
|
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
|
||||||
|
<Label>{this.props.title}</Label>
|
||||||
|
</Stack>
|
||||||
|
{this.state.isExpanded && this.props.children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CollapsibleSectionComponent renders 1`] = `
|
||||||
|
<Fragment>
|
||||||
|
<Stack
|
||||||
|
className="collapsibleSection"
|
||||||
|
horizontal={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
tokens={
|
||||||
|
Object {
|
||||||
|
"childrenGap": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<StyledIconBase
|
||||||
|
iconName="ChevronDown"
|
||||||
|
styles={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"paddingTop": 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<StyledLabelBase>
|
||||||
|
Sample title
|
||||||
|
</StyledLabelBase>
|
||||||
|
</Stack>
|
||||||
|
</Fragment>
|
||||||
|
`;
|
||||||
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { StringUtils } from "../../../Utils/StringUtils";
|
import { StringUtils } from "../../../Utils/StringUtils";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { TerminalQueryParams } from "../../../Common/Constants";
|
import { TerminalQueryParams } from "../../../Common/Constants";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookTerminalComponentProps {
|
export interface NotebookTerminalComponentProps {
|
||||||
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
notebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo;
|
||||||
@@ -71,9 +69,10 @@ export class NotebookTerminalComponent extends React.Component<NotebookTerminalC
|
|||||||
params: Map<string, string>
|
params: Map<string, string>
|
||||||
): string {
|
): string {
|
||||||
if (!serverInfo.notebookServerEndpoint) {
|
if (!serverInfo.notebookServerEndpoint) {
|
||||||
const error = "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.";
|
handleError(
|
||||||
Logger.logError(error, "NotebookTerminalComponent/createNotebookAppSrc");
|
"Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
"NotebookTerminalComponent/createNotebookAppSrc"
|
||||||
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ describe("GalleryCardComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
showDownload: true,
|
showDownload: true,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { JunoClient } from "../../../Juno/JunoClient";
|
import { JunoClient } from "../../../Juno/JunoClient";
|
||||||
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface CodeOfConductComponentProps {
|
export interface CodeOfConductComponentProps {
|
||||||
junoClient: JunoClient;
|
junoClient: JunoClient;
|
||||||
@@ -45,9 +44,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
|
|||||||
|
|
||||||
this.props.onAcceptCodeOfConduct(response.data);
|
this.props.onAcceptCodeOfConduct(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to accept code of conduct: ${error}`;
|
handleError(error, "CodeOfConductComponent/acceptCodeOfConduct", "Failed to accept code of conduct");
|
||||||
Logger.logError(message, "CodeOfConductComponent/acceptCodeOfConduct");
|
|
||||||
logConsoleError(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FocusZone,
|
FocusZone,
|
||||||
|
FontWeights,
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
IPageSpecification,
|
IPageSpecification,
|
||||||
IPivotItemProps,
|
IPivotItemProps,
|
||||||
@@ -11,14 +12,12 @@ import {
|
|||||||
Pivot,
|
Pivot,
|
||||||
PivotItem,
|
PivotItem,
|
||||||
SearchBox,
|
SearchBox,
|
||||||
Stack
|
Stack,
|
||||||
|
Text
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as Logger from "../../../Common/Logger";
|
|
||||||
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
import { IGalleryItem, JunoClient, IJunoResponse, IPublicGalleryData } from "../../../Juno/JunoClient";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
import "./GalleryViewerComponent.less";
|
import "./GalleryViewerComponent.less";
|
||||||
@@ -26,6 +25,7 @@ import { HttpStatusCodes } from "../../../Common/Constants";
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface GalleryViewerComponentProps {
|
export interface GalleryViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -151,7 +151,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
// explicitly checking if isCodeOfConductAccepted is not false, as it is initially undefined.
|
||||||
// Displaying code of conduct component on gallery load should not be the default behavior.
|
// Displaying code of conduct component on gallery load should not be the default behavior.
|
||||||
if (this.state.isCodeOfConductAccepted !== false) {
|
if (this.state.isCodeOfConductAccepted !== false) {
|
||||||
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
|
tabs.push(this.createPublishedNotebooksTab(GalleryTab.Published, this.state.publishedNotebooks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +197,59 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
private createTab(tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo {
|
||||||
return {
|
return {
|
||||||
tab,
|
tab,
|
||||||
content: this.createTabContent(data)
|
content: this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPublishedNotebooksTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
|
||||||
|
return {
|
||||||
|
tab,
|
||||||
|
content: this.createPublishedNotebooksTabContent(data)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksTabContent = (data: IGalleryItem[]): JSX.Element => {
|
||||||
|
const { published, underReview, removed } = GalleryUtils.filterPublishedNotebooks(data);
|
||||||
|
const content = (
|
||||||
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
|
{published?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
undefined,
|
||||||
|
"You have successfully published the following notebook(s) to public gallery and shared with other Azure Cosmos DB users.",
|
||||||
|
this.createCardsTabContent(published)
|
||||||
|
)}
|
||||||
|
{underReview?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Under Review",
|
||||||
|
"Content of a notebook you published is currently being scanned for illegal content. It will not be available to public gallery until the review is completed (may take a few days)",
|
||||||
|
this.createCardsTabContent(underReview)
|
||||||
|
)}
|
||||||
|
{removed?.length > 0 &&
|
||||||
|
this.createPublishedNotebooksSectionContent(
|
||||||
|
"Removed",
|
||||||
|
"These notebooks were found to contain illegal content and has been taken down.",
|
||||||
|
this.createPolicyViolationsListContent(removed)
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.createSearchBarHeader(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
private createPublishedNotebooksSectionContent = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
content: JSX.Element
|
||||||
|
): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 5 }}>
|
||||||
|
{title && <Text styles={{ root: { fontWeight: FontWeights.semibold } }}>{title}</Text>}
|
||||||
|
{description && <Text>{description}</Text>}
|
||||||
|
{content}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
private createPublicGalleryTabContent(data: IGalleryItem[], acceptedCodeOfConduct: boolean): JSX.Element {
|
||||||
return acceptedCodeOfConduct === false ? (
|
return acceptedCodeOfConduct === false ? (
|
||||||
<CodeOfConductComponent
|
<CodeOfConductComponent
|
||||||
@@ -210,11 +259,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
this.createTabContent(data)
|
this.createSearchBarHeader(this.createCardsTabContent(data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createTabContent(data: IGalleryItem[]): JSX.Element {
|
private createSearchBarHeader(content: JSX.Element): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
|
||||||
@@ -233,7 +282,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{data && this.createCardsTabContent(data)}
|
<Stack.Item>{content}</Stack.Item>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -251,6 +300,25 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createPolicyViolationsListContent(data: IGalleryItem[]): JSX.Element {
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Policy violations</th>
|
||||||
|
</tr>
|
||||||
|
{data.map(item => (
|
||||||
|
<tr key={`policy-violations-tr-${item.id}`}>
|
||||||
|
<td>{item.name}</td>
|
||||||
|
<td>{item.policyViolations.join(", ")}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
private loadTabContent(tab: GalleryTab, searchText: string, sortBy: SortBy, offline: boolean): void {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case GalleryTab.OfficialSamples:
|
case GalleryTab.OfficialSamples:
|
||||||
@@ -284,9 +352,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.sampleNotebooks = response.data;
|
this.sampleNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load sample notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadSampleNotebooks", "Failed to load sample notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadSampleNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,9 +378,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
throw new Error(`Received HTTP ${response.status} when loading public notebooks`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load public notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublicNotebooks", "Failed to load public notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublicNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,9 +398,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.favoriteNotebooks = response.data;
|
this.favoriteNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load favorite notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadFavoriteNotebooks", "Failed to load favorite notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadFavoriteNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,9 +424,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
|
|
||||||
this.publishedNotebooks = response.data;
|
this.publishedNotebooks = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = `Failed to load published notebooks: ${error}`;
|
handleError(error, "GalleryViewerComponent/loadPublishedNotebooks", "Failed to load published notebooks");
|
||||||
Logger.logError(message, "GalleryViewerComponent/loadPublishedNotebooks");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class InfoComponent extends React.Component<InfoComponentProps> {
|
|||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
{this.getInfoPanel("KnowledgeArticle", "Microsoft Terms of Use", CodeOfConductEndpoints.termsOfUse)}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
{this.props.onReportAbuseClick !== undefined && (
|
{this.props.onReportAbuseClick && (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
{this.getInfoPanel("ReportHacked", "Report Abuse", undefined, () => this.props.onReportAbuseClick())}
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
|
|||||||
@@ -81,6 +81,21 @@ exports[`GalleryViewerComponent renders 1`] = `
|
|||||||
<InfoComponent />
|
<InfoComponent />
|
||||||
</StackItem>
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<StackItem>
|
||||||
|
<FocusZone
|
||||||
|
direction={2}
|
||||||
|
isCircularNavigation={false}
|
||||||
|
shouldRaiseClicks={true}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
getPageSpecification={[Function]}
|
||||||
|
onRenderCell={[Function]}
|
||||||
|
renderedWindowsAhead={3}
|
||||||
|
renderedWindowsBehind={2}
|
||||||
|
startIndex={0}
|
||||||
|
/>
|
||||||
|
</FocusZone>
|
||||||
|
</StackItem>
|
||||||
</Stack>
|
</Stack>
|
||||||
</PivotItem>
|
</PivotItem>
|
||||||
</StyledPivotBase>
|
</StyledPivotBase>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
@@ -48,7 +50,9 @@ describe("NotebookMetadataComponent", () => {
|
|||||||
downloads: 0,
|
downloads: 0,
|
||||||
favorites: 0,
|
favorites: 0,
|
||||||
views: 0,
|
views: 0,
|
||||||
newCellId: undefined
|
newCellId: undefined,
|
||||||
|
policyViolations: undefined,
|
||||||
|
pendingScanJobIds: undefined
|
||||||
},
|
},
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
downloadButtonText: "Download",
|
downloadButtonText: "Download",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import Explorer from "../../Explorer";
|
|||||||
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
import { NotebookV4 } from "@nteract/commutable/lib/v4";
|
||||||
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
|
||||||
import { DialogHost } from "../../../Utils/GalleryUtils";
|
import { DialogHost } from "../../../Utils/GalleryUtils";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface NotebookViewerComponentProps {
|
export interface NotebookViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -100,9 +101,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({ showProgressBar: false });
|
this.setState({ showProgressBar: false });
|
||||||
const message = `Failed to load notebook content: ${error}`;
|
handleError(error, "NotebookViewerComponent/loadNotebookContent", "Failed to load notebook content");
|
||||||
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso
|
|||||||
|
|
||||||
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export interface QueriesGridComponentProps {
|
export interface QueriesGridComponentProps {
|
||||||
queriesClient: QueriesClient;
|
queriesClient: QueriesClient;
|
||||||
@@ -244,7 +245,9 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
|
|||||||
databaseAccountName: container && container.databaseAccount().name,
|
databaseAccountName: container && container.databaseAccount().name,
|
||||||
defaultExperience: container && container.defaultExperience(),
|
defaultExperience: container && container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
paneTitle: container && container.browseQueriesPane.title()
|
paneTitle: container && container.browseQueriesPane.title(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import * as DataModels from "../../../Contracts/DataModels";
|
|||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import { TtlType, isDirty } from "./SettingsUtils";
|
import { TtlType, isDirty } from "./SettingsUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
|
||||||
|
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
|
||||||
|
}));
|
||||||
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
||||||
updateCollection: jest.fn().mockReturnValue({
|
updateCollection: jest.fn().mockReturnValue({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -18,9 +21,17 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
changeFeedPolicy: undefined,
|
changeFeedPolicy: undefined,
|
||||||
analyticalStorageTtl: undefined,
|
analyticalStorageTtl: undefined,
|
||||||
geospatialConfig: undefined
|
geospatialConfig: undefined
|
||||||
} as DataModels.Collection)
|
} as DataModels.Collection),
|
||||||
|
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
||||||
|
id: undefined,
|
||||||
|
shardKey: undefined,
|
||||||
|
indexes: [],
|
||||||
|
analyticalStorageTtl: undefined
|
||||||
|
} as MongoDBCollectionResource)
|
||||||
}));
|
}));
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
|
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import Q from "q";
|
||||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
|
||||||
}));
|
}));
|
||||||
@@ -35,7 +46,10 @@ describe("SettingsComponent", () => {
|
|||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: undefined
|
onUpdateTabsButtons: undefined,
|
||||||
|
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
|
||||||
|
return;
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -188,13 +202,17 @@ describe("SettingsComponent", () => {
|
|||||||
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
expect(settingsComponentInstance.isOfferReplacePending()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("save calls updateCollection and updateOffer", async () => {
|
it("save calls updateCollection, updateMongoDBCollectionThroughRP and updateOffer", async () => {
|
||||||
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true });
|
wrapper.setState({ isSubSettingsSaveable: true, isScaleSaveable: true, isMongoIndexingPolicySaveable: true });
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||||
|
settingsComponentInstance.mongoDBCollectionResource = {
|
||||||
|
id: "id"
|
||||||
|
};
|
||||||
await settingsComponentInstance.onSaveClick();
|
await settingsComponentInstance.onSaveClick();
|
||||||
expect(updateCollection).toBeCalled();
|
expect(updateCollection).toBeCalled();
|
||||||
|
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
||||||
expect(updateOffer).toBeCalled();
|
expect(updateOffer).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,17 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
|
|||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit";
|
||||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
import SettingsTab from "../../Tabs/SettingsTabV2";
|
||||||
import { throughputUnit } from "./SettingsRenderUtils";
|
import { throughputUnit } from "./SettingsRenderUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||||
|
import {
|
||||||
|
MongoIndexingPolicyComponent,
|
||||||
|
MongoIndexingPolicyComponentProps
|
||||||
|
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
|
||||||
import {
|
import {
|
||||||
getMaxRUs,
|
getMaxRUs,
|
||||||
hasDatabaseSharedThroughput,
|
hasDatabaseSharedThroughput,
|
||||||
@@ -27,8 +31,11 @@ import {
|
|||||||
SettingsV2TabTypes,
|
SettingsV2TabTypes,
|
||||||
getTabTitle,
|
getTabTitle,
|
||||||
isDirty,
|
isDirty,
|
||||||
|
AddMongoIndexProps,
|
||||||
|
MongoIndexTypes,
|
||||||
parseConflictResolutionMode,
|
parseConflictResolutionMode,
|
||||||
parseConflictResolutionProcedure
|
parseConflictResolutionProcedure,
|
||||||
|
getMongoNotification
|
||||||
} from "./SettingsUtils";
|
} from "./SettingsUtils";
|
||||||
import {
|
import {
|
||||||
ConflictResolutionComponent,
|
ConflictResolutionComponent,
|
||||||
@@ -38,6 +45,10 @@ import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubCo
|
|||||||
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
|
||||||
import "./SettingsComponent.less";
|
import "./SettingsComponent.less";
|
||||||
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
|
||||||
|
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
|
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||||
|
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
interface SettingsV2TabInfo {
|
interface SettingsV2TabInfo {
|
||||||
tab: SettingsV2TabTypes;
|
tab: SettingsV2TabTypes;
|
||||||
@@ -84,6 +95,13 @@ export interface SettingsComponentState {
|
|||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
isIndexingPolicyDirty: boolean;
|
isIndexingPolicyDirty: boolean;
|
||||||
|
|
||||||
|
isMongoIndexingPolicySaveable: boolean;
|
||||||
|
isMongoIndexingPolicyDiscardable: boolean;
|
||||||
|
currentMongoIndexes: MongoIndex[];
|
||||||
|
indexesToDrop: number[];
|
||||||
|
indexesToAdd: AddMongoIndexProps[];
|
||||||
|
indexTransformationProgress: number;
|
||||||
|
|
||||||
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
|
||||||
conflictResolutionPolicyPath: string;
|
conflictResolutionPolicyPath: string;
|
||||||
@@ -98,17 +116,16 @@ export interface SettingsComponentState {
|
|||||||
|
|
||||||
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
|
||||||
private static readonly sixMonthsInSeconds = 15768000;
|
private static readonly sixMonthsInSeconds = 15768000;
|
||||||
|
private saveSettingsButton: ButtonV2;
|
||||||
|
private discardSettingsChangesButton: ButtonV2;
|
||||||
|
|
||||||
public saveSettingsButton: ButtonV2;
|
private isAnalyticalStorageEnabled: boolean;
|
||||||
public discardSettingsChangesButton: ButtonV2;
|
|
||||||
|
|
||||||
public isAnalyticalStorageEnabled: boolean;
|
|
||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
|
||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -158,6 +175,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
shouldDiscardIndexingPolicy: false,
|
shouldDiscardIndexingPolicy: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: undefined,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
|
||||||
conflictResolutionPolicyMode: undefined,
|
conflictResolutionPolicyMode: undefined,
|
||||||
conflictResolutionPolicyModeBaseline: undefined,
|
conflictResolutionPolicyModeBaseline: undefined,
|
||||||
conflictResolutionPolicyPath: undefined,
|
conflictResolutionPolicyPath: undefined,
|
||||||
@@ -186,6 +210,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
this.refreshIndexTransformationProgress();
|
||||||
|
this.loadMongoIndexes();
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
@@ -199,6 +225,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadMongoIndexes = async (): Promise<void> => {
|
||||||
|
if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent() &&
|
||||||
|
this.container.databaseAccount()
|
||||||
|
) {
|
||||||
|
this.mongoDBCollectionResource = await readMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.mongoDBCollectionResource) {
|
||||||
|
this.setState({
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public refreshIndexTransformationProgress = async (): Promise<void> => {
|
||||||
|
const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
|
||||||
|
this.setState({ indexTransformationProgress: currentProgress });
|
||||||
|
};
|
||||||
|
|
||||||
public isSaveSettingsButtonEnabled = (): boolean => {
|
public isSaveSettingsButtonEnabled = (): boolean => {
|
||||||
if (this.isOfferReplacePending()) {
|
if (this.isOfferReplacePending()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -208,7 +259,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,7 +269,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isScaleDiscardable ||
|
this.state.isScaleDiscardable ||
|
||||||
this.state.isSubSettingsDiscardable ||
|
this.state.isSubSettingsDiscardable ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty
|
this.state.isConflictResolutionDirty ||
|
||||||
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -293,6 +346,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||||
newCollection.defaultTtl = defaultTtl;
|
newCollection.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||||
@@ -328,6 +382,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
|
||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
|
|
||||||
|
if (wasIndexingPolicyModified) {
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
@@ -336,6 +395,58 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
|
try {
|
||||||
|
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||||
|
const newMongoCollection: MongoDBCollectionResource = {
|
||||||
|
...this.mongoDBCollectionResource,
|
||||||
|
indexes: newMongoIndexes
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id(),
|
||||||
|
newMongoCollection
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
this.setState({
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||||
|
});
|
||||||
|
traceSuccess(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.isScaleSaveable) {
|
if (this.state.isScaleSaveable) {
|
||||||
const newThroughput = this.state.throughput;
|
const newThroughput = this.state.throughput;
|
||||||
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
const newOffer: DataModels.Offer = { ...this.collection.offer() };
|
||||||
@@ -392,33 +503,18 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
throughput: newThroughput,
|
throughput: newThroughput,
|
||||||
offerIsRUPerMinuteThroughputEnabled: false
|
offerIsRUPerMinuteThroughputEnabled: false
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||||
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
this.collection.offer().content.offerThroughput = originalThroughputValue;
|
||||||
this.setState({
|
this.setState({
|
||||||
throughput: originalThroughputValue,
|
isScaleSaveable: false,
|
||||||
throughputBaseline: originalThroughputValue,
|
isScaleDiscardable: false,
|
||||||
initialNotification: {
|
throughput: originalThroughputValue,
|
||||||
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
throughputBaseline: originalThroughputValue,
|
||||||
} as DataModels.Notification
|
initialNotification: {
|
||||||
});
|
description: `Throughput update for ${newThroughput} ${throughputUnit}`
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
} as DataModels.Notification
|
||||||
} catch (error) {
|
});
|
||||||
traceFailure(
|
|
||||||
Action.SettingsV2Updated,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
|
||||||
databaseName: this.collection && this.collection.databaseId,
|
|
||||||
collectionName: this.collection && this.collection.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
databaseId: this.collection.databaseId,
|
databaseId: this.collection.databaseId,
|
||||||
@@ -457,23 +553,29 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
Action.SettingsV2Updated,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle()
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
} catch (reason) {
|
} catch (error) {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.props.settingsTab.isExecutionError(true);
|
this.props.settingsTab.isExecutionError(true);
|
||||||
console.error(reason);
|
console.error(error);
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.SettingsV2Updated,
|
Action.SettingsV2Updated,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
defaultExperience: this.container.defaultExperience(),
|
defaultExperience: this.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.props.settingsTab.tabTitle()
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error)
|
||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
@@ -492,6 +594,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||||
|
indexesToAdd: [],
|
||||||
|
indexesToDrop: [],
|
||||||
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyModeBaseline,
|
||||||
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
conflictResolutionPolicyPath: this.state.conflictResolutionPolicyPathBaseline,
|
||||||
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedureBaseline,
|
||||||
@@ -506,10 +610,23 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
isConflictResolutionDirty: false
|
isConflictResolutionDirty: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getMongoIndexesToSave = (): MongoIndex[] => {
|
||||||
|
let finalIndexes: MongoIndex[] = [];
|
||||||
|
this.state.currentMongoIndexes?.map((mongoIndex: MongoIndex, index: number) => {
|
||||||
|
if (!this.state.indexesToDrop.includes(index)) {
|
||||||
|
finalIndexes.push(mongoIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
finalIndexes = finalIndexes.concat(this.state.indexesToAdd.map((m: AddMongoIndexProps) => m.mongoIndex));
|
||||||
|
return finalIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
private onScaleSaveableChange = (isScaleSaveable: boolean): void =>
|
||||||
this.setState({ isScaleSaveable: isScaleSaveable });
|
this.setState({ isScaleSaveable: isScaleSaveable });
|
||||||
|
|
||||||
@@ -539,6 +656,36 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onIndexDrop = (index: number): void => this.setState({ indexesToDrop: [...this.state.indexesToDrop, index] });
|
||||||
|
|
||||||
|
private onRevertIndexDrop = (index: number): void => {
|
||||||
|
const indexesToDrop = [...this.state.indexesToDrop];
|
||||||
|
indexesToDrop.splice(index, 1);
|
||||||
|
this.setState({ indexesToDrop });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onRevertIndexAdd = (index: number): void => {
|
||||||
|
const indexesToAdd = [...this.state.indexesToAdd];
|
||||||
|
indexesToAdd.splice(index, 1);
|
||||||
|
this.setState({ indexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onIndexAddOrChange = (index: number, description: string, type: MongoIndexTypes): void => {
|
||||||
|
const newIndexesToAdd = [...this.state.indexesToAdd];
|
||||||
|
const notification = getMongoNotification(description, type);
|
||||||
|
const newMongoIndexWithType: AddMongoIndexProps = {
|
||||||
|
mongoIndex: { key: { keys: [description] } } as MongoIndex,
|
||||||
|
type: type,
|
||||||
|
notification: notification
|
||||||
|
};
|
||||||
|
if (index === newIndexesToAdd.length) {
|
||||||
|
newIndexesToAdd.push(newMongoIndexWithType);
|
||||||
|
} else {
|
||||||
|
newIndexesToAdd[index] = newMongoIndexWithType;
|
||||||
|
}
|
||||||
|
this.setState({ indexesToAdd: newIndexesToAdd });
|
||||||
|
};
|
||||||
|
|
||||||
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
|
||||||
this.setState({ conflictResolutionPolicyMode: newMode });
|
this.setState({ conflictResolutionPolicyMode: newMode });
|
||||||
|
|
||||||
@@ -577,6 +724,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicySaveableChange = (isMongoIndexingPolicySaveable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicySaveable });
|
||||||
|
|
||||||
|
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
|
||||||
|
this.setState({ isMongoIndexingPolicyDiscardable });
|
||||||
|
|
||||||
public getAnalyticalStorageTtl = (): number => {
|
public getAnalyticalStorageTtl = (): number => {
|
||||||
if (this.isAnalyticalStorageEnabled) {
|
if (this.isAnalyticalStorageEnabled) {
|
||||||
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
if (this.state.analyticalStorageTtlSelection === TtlType.On) {
|
||||||
@@ -747,7 +900,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
autoPilotTiersList: this.autoPilotTiersList,
|
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
throughput: this.state.throughput,
|
throughput: this.state.throughput,
|
||||||
throughputBaseline: this.state.throughputBaseline,
|
throughputBaseline: this.state.throughputBaseline,
|
||||||
@@ -796,9 +948,25 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
|
||||||
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
|
||||||
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
|
||||||
|
mongoIndexes: this.state.currentMongoIndexes,
|
||||||
|
onIndexDrop: this.onIndexDrop,
|
||||||
|
indexesToDrop: this.state.indexesToDrop,
|
||||||
|
onRevertIndexDrop: this.onRevertIndexDrop,
|
||||||
|
indexesToAdd: this.state.indexesToAdd,
|
||||||
|
onRevertIndexAdd: this.onRevertIndexAdd,
|
||||||
|
onIndexAddOrChange: this.onIndexAddOrChange,
|
||||||
|
indexTransformationProgress: this.state.indexTransformationProgress,
|
||||||
|
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
|
||||||
|
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
|
||||||
|
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
|
||||||
|
};
|
||||||
|
|
||||||
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
@@ -815,7 +983,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs: SettingsV2TabInfo[] = [];
|
const tabs: SettingsV2TabInfo[] = [];
|
||||||
if (!hasDatabaseSharedThroughput(this.collection)) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ScaleTab,
|
tab: SettingsV2TabTypes.ScaleTab,
|
||||||
content: <ScaleComponent {...scaleComponentProps} />
|
content: <ScaleComponent {...scaleComponentProps} />
|
||||||
@@ -832,6 +1000,15 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
this.container.isMongoIndexEditorEnabled() &&
|
||||||
|
this.container.isPreferredApiMongoDB() &&
|
||||||
|
this.container.isEnableMongoCapabilityPresent()
|
||||||
|
) {
|
||||||
|
tabs.push({
|
||||||
|
tab: SettingsV2TabTypes.IndexingPolicyTab,
|
||||||
|
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasConflictResolution()) {
|
if (this.hasConflictResolution()) {
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
|||||||
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
import { SettingsComponent, SettingsComponentProps } from "./SettingsComponent";
|
||||||
|
|
||||||
export class SettingsComponentAdapter implements ReactAdapter {
|
export class SettingsComponentAdapter implements ReactAdapter {
|
||||||
public parameters: ko.Observable<number>;
|
public parameters: ko.Computed<boolean>;
|
||||||
|
|
||||||
constructor(private props: SettingsComponentProps) {
|
constructor(private props: SettingsComponentProps) {}
|
||||||
this.parameters = ko.observable<number>(Date.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
public renderComponent(): JSX.Element {
|
||||||
return <SettingsComponent {...this.props} />;
|
return this.parameters() ? <SettingsComponent {...this.props} /> : <></>;
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getEstimatedAutoscaleSpendElement,
|
getEstimatedAutoscaleSpendElement,
|
||||||
manualToAutoscaleDisclaimerElement,
|
manualToAutoscaleDisclaimerElement,
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
indexingPolicyTTLWarningMessage,
|
indexingPolicynUnsavedWarningMessage,
|
||||||
updateThroughputBeyondLimitWarningMessage,
|
updateThroughputBeyondLimitWarningMessage,
|
||||||
updateThroughputDelayedApplyWarningMessage,
|
updateThroughputDelayedApplyWarningMessage,
|
||||||
getThroughputApplyDelayedMessage,
|
getThroughputApplyDelayedMessage,
|
||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
getToolTipContainer,
|
getToolTipContainer,
|
||||||
conflictResolutionCustomToolTip,
|
conflictResolutionCustomToolTip,
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
conflictResolutionLwwTooltip
|
conflictResolutionLwwTooltip,
|
||||||
|
mongoIndexingPolicyDisclaimer,
|
||||||
|
mongoIndexingPolicyAADError,
|
||||||
|
mongoIndexTransformationRefreshingMessage,
|
||||||
|
renderMongoIndexTransformationRefreshMessage
|
||||||
} from "./SettingsRenderUtils";
|
} from "./SettingsRenderUtils";
|
||||||
|
|
||||||
class SettingsRenderUtilsTestComponent extends React.Component {
|
class SettingsRenderUtilsTestComponent extends React.Component {
|
||||||
@@ -33,7 +37,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
|
|
||||||
{manualToAutoscaleDisclaimerElement}
|
{manualToAutoscaleDisclaimerElement}
|
||||||
{ttlWarning}
|
{ttlWarning}
|
||||||
{indexingPolicyTTLWarningMessage}
|
{indexingPolicynUnsavedWarningMessage}
|
||||||
{updateThroughputBeyondLimitWarningMessage}
|
{updateThroughputBeyondLimitWarningMessage}
|
||||||
{updateThroughputDelayedApplyWarningMessage}
|
{updateThroughputDelayedApplyWarningMessage}
|
||||||
|
|
||||||
@@ -45,6 +49,16 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
|||||||
{conflictResolutionLwwTooltip}
|
{conflictResolutionLwwTooltip}
|
||||||
{conflictResolutionCustomToolTip}
|
{conflictResolutionCustomToolTip}
|
||||||
{changeFeedPolicyToolTip}
|
{changeFeedPolicyToolTip}
|
||||||
|
|
||||||
|
{mongoIndexingPolicyDisclaimer}
|
||||||
|
{mongoIndexingPolicyAADError}
|
||||||
|
{mongoIndexTransformationRefreshingMessage}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(0, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
|
{renderMongoIndexTransformationRefreshMessage(90, () => {
|
||||||
|
return;
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,24 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
IMessageBarStyles,
|
IMessageBarStyles,
|
||||||
ITextStyles
|
ITextStyles,
|
||||||
|
IDetailsRowStyles,
|
||||||
|
IStackStyles,
|
||||||
|
IIconStyles,
|
||||||
|
IDetailsListStyles,
|
||||||
|
IDropdownStyles,
|
||||||
|
ISeparatorStyles,
|
||||||
|
MessageBar,
|
||||||
|
MessageBarType,
|
||||||
|
Stack,
|
||||||
|
Spinner,
|
||||||
|
SpinnerSize
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
import { isDirtyTypes, isDirty } from "./SettingsUtils";
|
||||||
|
|
||||||
const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 12 } };
|
||||||
|
|
||||||
export const spendAckCheckBoxStyle: ICheckboxStyles = {
|
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
|
||||||
label: {
|
label: {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
padding: "2 0 2 0"
|
padding: "2 0 2 0"
|
||||||
@@ -45,6 +56,20 @@ export const titleAndInputStackProps: Partial<IStackProps> = {
|
|||||||
tokens: { childrenGap: 5 }
|
tokens: { childrenGap: 5 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mongoWarningStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
|
||||||
|
|
||||||
|
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 5 }
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexStackProps: Partial<IStackProps> = {
|
||||||
|
tokens: { childrenGap: 10 }
|
||||||
|
};
|
||||||
|
|
||||||
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
|
||||||
tokens: { childrenGap: 10 }
|
tokens: { childrenGap: 10 }
|
||||||
};
|
};
|
||||||
@@ -53,6 +78,54 @@ export const toolTipLabelStackTokens: IStackTokens = {
|
|||||||
childrenGap: 6
|
childrenGap: 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const accordionStackTokens: IStackTokens = {
|
||||||
|
childrenGap: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addMongoIndexSubElementsTokens: IStackTokens = {
|
||||||
|
childrenGap: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
|
||||||
|
|
||||||
|
export const mediumWidthStackStyles: IStackStyles = { root: { width: 600 } };
|
||||||
|
|
||||||
|
export const shortWidthTextFieldStyles: Partial<ITextFieldStyles> = { root: { paddingLeft: 10, width: 210 } };
|
||||||
|
|
||||||
|
export const shortWidthDropDownStyles: Partial<IDropdownStyles> = { dropdown: { paddingleft: 10, width: 202 } };
|
||||||
|
|
||||||
|
export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
":hover": {
|
||||||
|
background: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
|
||||||
|
root: {
|
||||||
|
selectors: {
|
||||||
|
".ms-FocusZone": {
|
||||||
|
paddingTop: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const separatorStyles: Partial<ISeparatorStyles> = {
|
||||||
|
root: [
|
||||||
|
{
|
||||||
|
selectors: {
|
||||||
|
"::before": {
|
||||||
|
background: StyleConstants.BaseMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
export const messageBarStyles: Partial<IMessageBarStyles> = { root: { marginTop: "5px" } };
|
||||||
|
|
||||||
export const throughputUnit = "RU/s";
|
export const throughputUnit = "RU/s";
|
||||||
@@ -172,15 +245,9 @@ export const ttlWarning: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const indexingPolicyTTLWarningMessage: JSX.Element = (
|
export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
|
||||||
<Text styles={infoAndToolTipTextStyle}>
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and
|
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
|
||||||
the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
|
|
||||||
more information see,{" "}
|
|
||||||
<Link target="_blank" href="https://aka.ms/cosmosdb/modify-index-policy">
|
|
||||||
Modifying Indexing Policies
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -313,6 +380,56 @@ export const changeFeedPolicyToolTip: JSX.Element = (
|
|||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyDisclaimer: JSX.Element = (
|
||||||
|
<Text>
|
||||||
|
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
|
||||||
|
<Link href="https://docs.microsoft.com/azure/cosmos-db/mongodb-indexing#index-types" target="_blank">
|
||||||
|
{` Compound indexes `}
|
||||||
|
</Link>
|
||||||
|
are only used for sorting query results. If you need to add a compound index, you can create one using the Mongo
|
||||||
|
shell.
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexingPolicyAADError: JSX.Element = (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
<Text>
|
||||||
|
To use the indexing policy editor, please login to the
|
||||||
|
<Link target="_blank" href="https://portal.azure.com">
|
||||||
|
{"azure portal."}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
|
||||||
|
<Stack horizontal {...mongoWarningStackProps}>
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>Refreshing index transformation progress</Text>
|
||||||
|
<Spinner size={SpinnerSize.small} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const renderMongoIndexTransformationRefreshMessage = (
|
||||||
|
progress: number,
|
||||||
|
performRefresh: () => void
|
||||||
|
): JSX.Element => {
|
||||||
|
if (progress === 0) {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{"You can make more indexing changes once the current index transformation is complete. "}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check if it has completed."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text styles={infoAndToolTipTextStyle}>
|
||||||
|
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
|
||||||
|
<Link onClick={performRefresh}>{"Refresh to check the progress."}</Link>
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<ITextFieldStyles> => ({
|
||||||
fieldGroup: {
|
fieldGroup: {
|
||||||
height: 25,
|
height: 25,
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
|
|||||||
},
|
},
|
||||||
onIndexingPolicyDirtyChange: () => {
|
onIndexingPolicyDirtyChange: () => {
|
||||||
return;
|
return;
|
||||||
}
|
},
|
||||||
|
indexTransformationProgress: undefined,
|
||||||
|
refreshIndexTransformationProgress: () => new Promise(jest.fn())
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user