mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-23 19:01:28 +00:00
Compare commits
4 Commits
add-dp-rba
...
offer_bug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe7b645cd | ||
|
|
6894020faa | ||
|
|
dfcb771939 | ||
|
|
6925fa8e4e |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -8,6 +8,9 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
jobs:
|
jobs:
|
||||||
codemetrics:
|
codemetrics:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -134,7 +137,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -145,11 +148,18 @@ jobs:
|
|||||||
- ./test/mongo/container.spec.ts
|
- ./test/mongo/container.spec.ts
|
||||||
- ./test/mongo/container32.spec.ts
|
- ./test/mongo/container32.spec.ts
|
||||||
- ./test/selfServe/selfServeExample.spec.ts
|
- ./test/selfServe/selfServeExample.spec.ts
|
||||||
# - ./test/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
|
|
||||||
- ./test/sql/resourceToken.spec.ts
|
- ./test/sql/resourceToken.spec.ts
|
||||||
- ./test/tables/container.spec.ts
|
- ./test/tables/container.spec.ts
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: "Az CLI login"
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
15
.github/workflows/cleanup.yml
vendored
15
.github/workflows/cleanup.yml
vendored
@@ -9,6 +9,10 @@ on:
|
|||||||
# Once every hour
|
# Once every hour
|
||||||
- cron: "0 15 * * *"
|
- cron: "0 15 * * *"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
jobs:
|
jobs:
|
||||||
# This workflow contains a single job called "build"
|
# This workflow contains a single job called "build"
|
||||||
@@ -16,10 +20,17 @@ jobs:
|
|||||||
name: "Cleanup Test Database Accounts"
|
name: "Cleanup Test Database Accounts"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: "Az CLI login"
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -690,9 +690,16 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useMongoProxyEndpoint(api: string): boolean {
|
function useMongoProxyEndpoint(api: string): boolean {
|
||||||
const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac];
|
const activeMongoProxyEndpoints: string[] = [
|
||||||
|
MongoProxyEndpoints.Development,
|
||||||
|
MongoProxyEndpoints.Mpac,
|
||||||
|
MongoProxyEndpoints.Prod,
|
||||||
|
];
|
||||||
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
if (
|
||||||
|
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development &&
|
||||||
|
userContext.databaseAccount.properties.ipRules?.length > 0
|
||||||
|
) {
|
||||||
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,13 +54,17 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
|
|||||||
const existingContent = this.editor.getModel().getValue();
|
const existingContent = this.editor.getModel().getValue();
|
||||||
|
|
||||||
if (this.props.content !== existingContent) {
|
if (this.props.content !== existingContent) {
|
||||||
this.editor.pushUndoStop();
|
if (this.props.isReadOnly) {
|
||||||
this.editor.executeEdits("", [
|
this.editor.setValue(this.props.content);
|
||||||
{
|
} else {
|
||||||
range: this.editor.getModel().getFullModelRange(),
|
this.editor.pushUndoStop();
|
||||||
text: this.props.content,
|
this.editor.executeEdits("", [
|
||||||
},
|
{
|
||||||
]);
|
range: this.editor.getModel().getFullModelRange(),
|
||||||
|
text: this.props.content,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export interface MongoNotificationMessage {
|
|||||||
|
|
||||||
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
|
||||||
const database: ViewModels.Database = collection.getDatabase();
|
const database: ViewModels.Database = collection.getDatabase();
|
||||||
|
console.log(database?.isDatabaseShared(), collection.offer());
|
||||||
return database?.isDatabaseShared() && !collection.offer();
|
return database?.isDatabaseShared() && !collection.offer();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,11 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
|
|||||||
// Only call when expand has actually changed
|
// Only call when expand has actually changed
|
||||||
if (this.state.isExpanded !== prevState.isExpanded) {
|
if (this.state.isExpanded !== prevState.isExpanded) {
|
||||||
if (this.state.isExpanded) {
|
if (this.state.isExpanded) {
|
||||||
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent.callbackDelayMS);
|
console.log("IN HERE");
|
||||||
|
this.props.node.onExpanded &&
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this.props.node.onExpanded();
|
||||||
|
}, TreeNodeComponent.callbackDelayMS);
|
||||||
} else {
|
} else {
|
||||||
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS);
|
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,7 +336,8 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
directionalHint={4}
|
directionalHint={4}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
|
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without
|
||||||
|
impacting the performance of transactional workloads."
|
||||||
className="panelInfoIcon"
|
className="panelInfoIcon"
|
||||||
iconName="Info"
|
iconName="Info"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -486,4 +487,4 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
isButtonDisabled={false}
|
isButtonDisabled={false}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as ko from "knockout";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
@@ -19,7 +20,6 @@ import Explorer from "../Explorer";
|
|||||||
import * as TableConstants from "./Constants";
|
import * as TableConstants from "./Constants";
|
||||||
import * as Entities from "./Entities";
|
import * as Entities from "./Entities";
|
||||||
import * as TableEntityProcessor from "./TableEntityProcessor";
|
import * as TableEntityProcessor from "./TableEntityProcessor";
|
||||||
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
|
||||||
|
|
||||||
export interface CassandraTableKeys {
|
export interface CassandraTableKeys {
|
||||||
partitionKeys: CassandraTableKey[];
|
partitionKeys: CassandraTableKey[];
|
||||||
@@ -732,9 +732,16 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private useCassandraProxyEndpoint(api: string): boolean {
|
private useCassandraProxyEndpoint(api: string): boolean {
|
||||||
const activeCassandraProxyEndpoints: string[] = [CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac];
|
const activeCassandraProxyEndpoints: string[] = [
|
||||||
|
CassandraProxyEndpoints.Development,
|
||||||
|
CassandraProxyEndpoints.Mpac,
|
||||||
|
CassandraProxyEndpoints.Prod,
|
||||||
|
];
|
||||||
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
|
||||||
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
|
if (
|
||||||
|
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development &&
|
||||||
|
userContext.databaseAccount.properties.ipRules?.length > 0
|
||||||
|
) {
|
||||||
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ export const QueryResultSection: React.FC<QueryResultProps> = ({
|
|||||||
: JSON.stringify(queryResults.documents, undefined, 4)
|
: JSON.stringify(queryResults.documents, undefined, 4)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
console.log("QUERY RESULT: ", queryResultsString);
|
||||||
|
|
||||||
const onErrorDetailsClick = (): boolean => {
|
const onErrorDetailsClick = (): boolean => {
|
||||||
useNotificationConsole.getState().expandConsole();
|
useNotificationConsole.getState().expandConsole();
|
||||||
|
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ export default class QueryTabComponent extends React.Component<IQueryTabComponen
|
|||||||
firstItemIndex,
|
firstItemIndex,
|
||||||
queryDocuments,
|
queryDocuments,
|
||||||
);
|
);
|
||||||
|
console.log("SETTING QUERY RESULTS", queryResults);
|
||||||
this.setState({ queryResults, error: "" });
|
this.setState({ queryResults, error: "" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.props.tabsBaseInstance.isExecutionError(true);
|
this.props.tabsBaseInstance.isExecutionError(true);
|
||||||
|
|||||||
@@ -324,7 +324,12 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
|
|
||||||
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
||||||
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
||||||
if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) {
|
if (
|
||||||
|
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) ||
|
||||||
|
(userContext.apiType === "Cassandra" &&
|
||||||
|
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
||||||
|
ipRules?.length
|
||||||
|
) {
|
||||||
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
||||||
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
||||||
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
||||||
|
|||||||
@@ -264,12 +264,16 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public expandCollection(): void {
|
public async expandCollection(): Promise<void> {
|
||||||
if (this.isCollectionExpanded()) {
|
if (this.isCollectionExpanded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||||
|
throughputCap && throughputCap !== -1 ? await useDatabases.getState().loadAllOffers() : await this.loadOffer();
|
||||||
|
console.log("LOADED OFFERS", this.offer());
|
||||||
this.isCollectionExpanded(true);
|
this.isCollectionExpanded(true);
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
||||||
description: "Collection node",
|
description: "Collection node",
|
||||||
|
|
||||||
@@ -576,8 +580,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
public onSettingsClick = async (): Promise<void> => {
|
public onSettingsClick = async (): Promise<void> => {
|
||||||
useSelectedNode.getState().setSelectedNode(this);
|
useSelectedNode.getState().setSelectedNode(this);
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
// const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||||
throughputCap && throughputCap !== -1 ? await useDatabases.getState().loadAllOffers() : await this.loadOffer();
|
// throughputCap && throughputCap !== -1 ? await useDatabases.getState().loadAllOffers() : await this.loadOffer();
|
||||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
||||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||||
description: "Settings node",
|
description: "Settings node",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react";
|
||||||
|
import { hasDatabaseSharedThroughput } from "Explorer/Controls/Settings/SettingsUtils";
|
||||||
import { SampleDataTree } from "Explorer/Tree/SampleDataTree";
|
import { SampleDataTree } from "Explorer/Tree/SampleDataTree";
|
||||||
import { getItemName } from "Utils/APITypeUtils";
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
@@ -548,9 +549,11 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
id = database.isDatabaseShared() ? "sampleSettings" : "sampleScaleSettings";
|
id = database.isDatabaseShared() ? "sampleSettings" : "sampleScaleSettings";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("RUNNING HERE", database.id(), database.offer(), collection.id(), collection.offer());
|
||||||
|
|
||||||
children.push({
|
children.push({
|
||||||
id,
|
id,
|
||||||
label: database.isDatabaseShared() || isServerlessAccount() ? "Settings" : "Scale & Settings",
|
label: hasDatabaseSharedThroughput(collection) || isServerlessAccount() ? "Settings" : "Scale & Settings",
|
||||||
onClick: collection.onSettingsClick.bind(collection),
|
onClick: collection.onSettingsClick.bind(collection),
|
||||||
isSelected: () =>
|
isSelected: () =>
|
||||||
useSelectedNode
|
useSelectedNode
|
||||||
@@ -598,6 +601,7 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
|
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(container, collection),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// Rewritten version of expandCollapseCollection
|
// Rewritten version of expandCollapseCollection
|
||||||
|
console.log("CLICKED onClick");
|
||||||
useSelectedNode.getState().setSelectedNode(collection);
|
useSelectedNode.getState().setSelectedNode(collection);
|
||||||
useCommandBar.getState().setContextButtons([]);
|
useCommandBar.getState().setContextButtons([]);
|
||||||
refreshActiveTab(
|
refreshActiveTab(
|
||||||
@@ -605,13 +609,18 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ container }: Resourc
|
|||||||
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId,
|
tab.collection?.id() === collection.id() && tab.collection.databaseId === collection.databaseId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onExpanded: () => {
|
onExpanded: async () => {
|
||||||
|
console.log("CLICKED onExpanded");
|
||||||
|
await collection.expandCollection();
|
||||||
if (showScriptNodes) {
|
if (showScriptNodes) {
|
||||||
collection.loadStoredProcedures();
|
collection.loadStoredProcedures();
|
||||||
collection.loadUserDefinedFunctions();
|
collection.loadUserDefinedFunctions();
|
||||||
collection.loadTriggers();
|
collection.loadTriggers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onCollapsed: () => {
|
||||||
|
collection.collapseCollection();
|
||||||
|
},
|
||||||
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
isSelected: () => useSelectedNode.getState().isDataNodeSelected(collection.databaseId, collection.id()),
|
||||||
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
onContextMenuOpen: () => useSelectedNode.getState().setSelectedNode(collection),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("Cassandra keyspace and table CRUD", async () => {
|
test("Cassandra keyspace and table CRUD", async () => {
|
||||||
const keyspaceId = generateUniqueName("keyspace");
|
const keyspaceId = generateUniqueName("keyspace");
|
||||||
const tableId = generateUniqueName("table");
|
const tableId = generateUniqueName("table");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner&token=${token}`);
|
||||||
await page.waitForSelector("iframe");
|
await page.waitForSelector("iframe");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Graph CRUD", async () => {
|
test("Graph CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and graph
|
// Create new database and graph
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Mongo CRUD", async () => {
|
test("Mongo CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(240000);
|
jest.setTimeout(240000);
|
||||||
|
|
||||||
test("Mongo CRUD", async () => {
|
test("Mongo CRUD", async () => {
|
||||||
const databaseId = generateDatabaseNameWithTimestamp();
|
const databaseId = generateDatabaseNameWithTimestamp();
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
import { getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
|
|
||||||
test("Self Serve", async () => {
|
test("Self Serve", async () => {
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
|
|
||||||
|
await page.goto(`https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html&token=${token}`);
|
||||||
const handle = await page.waitForSelector("iframe");
|
const handle = await page.waitForSelector("iframe");
|
||||||
const frame = await handle.contentFrame();
|
const frame = await handle.contentFrame();
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("SQL CRUD", async () => {
|
test("SQL CRUD", async () => {
|
||||||
const databaseId = generateUniqueName("db");
|
const databaseId = generateUniqueName("db");
|
||||||
const containerId = generateUniqueName("container");
|
const containerId = generateUniqueName("container");
|
||||||
|
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await explorer.click('[data-test="New Container"]');
|
await explorer.click('[data-test="New Container"]');
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
|
||||||
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
import { CosmosClient, PermissionMode } from "@azure/cosmos";
|
||||||
import * as msRestNodeAuth from "@azure/ms-rest-nodeauth";
|
|
||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentials } from "../utils/shared";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
|
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? "";
|
||||||
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
|
|
||||||
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
|
||||||
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
|
||||||
const resourceGroupName = "runners";
|
const resourceGroupName = "runners";
|
||||||
|
|
||||||
test("Resource token", async () => {
|
test("Resource token", async () => {
|
||||||
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
const credentials = await getAzureCLICredentials();
|
||||||
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||||
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
|
const account = await armClient.databaseAccounts.get(resourceGroupName, "portal-sql-runner-west-us");
|
||||||
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
|
const keys = await armClient.databaseAccounts.listKeys(resourceGroupName, "portal-sql-runner-west-us");
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName } from "../utils/shared";
|
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
||||||
import { waitForExplorer } from "../utils/waitForExplorer";
|
import { waitForExplorer } from "../utils/waitForExplorer";
|
||||||
|
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
test("Tables CRUD", async () => {
|
test("Tables CRUD", async () => {
|
||||||
const tableId = generateUniqueName("table");
|
const tableId = generateUniqueName("table");
|
||||||
|
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
||||||
|
const token = await getAzureCLICredentialsToken();
|
||||||
page.setDefaultTimeout(50000);
|
page.setDefaultTimeout(50000);
|
||||||
|
|
||||||
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
|
await page.goto(`https://localhost:1234/testExplorer.html?accountName=portal-tables-runner&token=${token}`);
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await page.waitForSelector('text="Querying databases"', { state: "detached" });
|
await page.waitForSelector('text="Querying databases"', { state: "detached" });
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { ClientSecretCredential } from "@azure/identity";
|
|
||||||
import "../../less/hostedexplorer.less";
|
import "../../less/hostedexplorer.less";
|
||||||
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
import { DataExplorerInputsFrame } from "../../src/Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../src/UserContext";
|
import { updateUserContext } from "../../src/UserContext";
|
||||||
@@ -11,29 +10,13 @@ const urlSearchParams = new URLSearchParams(window.location.search);
|
|||||||
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
|
const accountName = urlSearchParams.get("accountName") || "portal-sql-runner-west-us";
|
||||||
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
const selfServeType = urlSearchParams.get("selfServeType") || "example";
|
||||||
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
const iframeSrc = urlSearchParams.get("iframeSrc") || "explorer.html?platform=Portal&disablePortalInitCache";
|
||||||
|
const token = urlSearchParams.get("token");
|
||||||
if (!process.env.AZURE_CLIENT_SECRET) {
|
|
||||||
throw new Error(
|
|
||||||
"process.env.AZURE_CLIENT_SECRET was not set! Set it in your .env file and restart webpack dev server",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Azure SDK clients accept the credential as a parameter
|
|
||||||
const credentials = new ClientSecretCredential(
|
|
||||||
process.env.AZURE_TENANT_ID,
|
|
||||||
process.env.AZURE_CLIENT_ID,
|
|
||||||
process.env.AZURE_CLIENT_SECRET,
|
|
||||||
{
|
|
||||||
authorityHost: "https://localhost:1234",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Resource Group:", resourceGroup);
|
console.log("Resource Group:", resourceGroup);
|
||||||
console.log("Subcription: ", subscriptionId);
|
console.log("Subcription: ", subscriptionId);
|
||||||
console.log("Account Name: ", accountName);
|
console.log("Account Name: ", accountName);
|
||||||
|
|
||||||
const initTestExplorer = async (): Promise<void> => {
|
const initTestExplorer = async (): Promise<void> => {
|
||||||
const { token } = await credentials.getToken("https://management.azure.com//.default");
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
authorizationToken: `bearer ${token}`,
|
authorizationToken: `bearer ${token}`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
|
||||||
export function generateUniqueName(baseName = "", length = 4): string {
|
export function generateUniqueName(baseName = "", length = 4): string {
|
||||||
@@ -7,3 +8,13 @@ export function generateUniqueName(baseName = "", length = 4): string {
|
|||||||
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
|
export function generateDatabaseNameWithTimestamp(baseName = "db", length = 1): string {
|
||||||
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
return `${baseName}${crypto.randomBytes(length).toString("hex")}-${Date.now()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAzureCLICredentials(): Promise<AzureCliCredentials> {
|
||||||
|
return await AzureCliCredentials.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAzureCLICredentialsToken(): Promise<string> {
|
||||||
|
const credentials = await getAzureCLICredentials();
|
||||||
|
const token = (await credentials.getToken()).accessToken;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ const msRestNodeAuth = require("@azure/ms-rest-nodeauth");
|
|||||||
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
|
const { CosmosDBManagementClient } = require("@azure/arm-cosmosdb");
|
||||||
const ms = require("ms");
|
const ms = require("ms");
|
||||||
|
|
||||||
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_ID"];
|
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
|
||||||
const secret = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET"];
|
|
||||||
const tenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
|
|
||||||
const subscriptionId = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
|
|
||||||
const resourceGroupName = "runners";
|
const resourceGroupName = "runners";
|
||||||
|
|
||||||
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
|
const thirtyMinutesAgo = new Date(Date.now() - 1000 * 60 * 30).getTime();
|
||||||
@@ -19,7 +16,7 @@ function friendlyTime(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
const credentials = await msRestNodeAuth.AzureCliCredentials.create();
|
||||||
const client = new CosmosDBManagementClient(credentials, subscriptionId);
|
const client = new CosmosDBManagementClient(credentials, subscriptionId);
|
||||||
const accounts = await client.databaseAccounts.list(resourceGroupName);
|
const accounts = await client.databaseAccounts.list(resourceGroupName);
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
@@ -38,7 +35,7 @@ async function main() {
|
|||||||
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
|
} else if (account.capabilities.find((c) => c.name === "EnableCassandra")) {
|
||||||
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
|
const cassandraDatabases = await client.cassandraResources.listCassandraKeyspaces(
|
||||||
resourceGroupName,
|
resourceGroupName,
|
||||||
account.name
|
account.name,
|
||||||
);
|
);
|
||||||
for (const database of cassandraDatabases) {
|
for (const database of cassandraDatabases) {
|
||||||
const timestamp = Number(database.resource._ts) * 1000;
|
const timestamp = Number(database.resource._ts) * 1000;
|
||||||
|
|||||||
Reference in New Issue
Block a user