mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-31 06:41:35 +00:00
Compare commits
15 Commits
ashleyst/c
...
add-dp-rba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a8505b9a | ||
|
|
ef7c2fe2f7 | ||
|
|
4c7aca95e1 | ||
|
|
2243ad895a | ||
|
|
b2d5f91fe1 | ||
|
|
a712193477 | ||
|
|
5ee411693c | ||
|
|
16c7b2567b | ||
|
|
78d9a0cd8d | ||
|
|
c6ad538559 | ||
|
|
2bc09a6efe | ||
|
|
d3a3033b25 | ||
|
|
6bdc714e11 | ||
|
|
5042f28229 | ||
|
|
e1430fd06f |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -8,9 +8,6 @@ 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
|
||||||
@@ -137,7 +134,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -148,18 +145,11 @@ 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,10 +9,6 @@ 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"
|
||||||
@@ -20,17 +16,10 @@ jobs:
|
|||||||
name: "Cleanup Test Database Accounts"
|
name: "Cleanup Test Database Accounts"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_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:
|
||||||
|
|||||||
@@ -179,6 +179,9 @@ export class CassandraProxyAPIs {
|
|||||||
export class Queries {
|
export class Queries {
|
||||||
public static CustomPageOption: string = "custom";
|
public static CustomPageOption: string = "custom";
|
||||||
public static UnlimitedPageOption: string = "unlimited";
|
public static UnlimitedPageOption: string = "unlimited";
|
||||||
|
public static setAutomaticRBACOption: string = "Automatic";
|
||||||
|
public static setTrueRBACOption: string = "True";
|
||||||
|
public static setFalseRBACOption: string = "False";
|
||||||
public static itemsPerPage: number = 100;
|
public static itemsPerPage: number = 100;
|
||||||
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions
|
||||||
public static containersPerPage: number = 50;
|
public static containersPerPage: number = 50;
|
||||||
|
|||||||
@@ -54,17 +54,13 @@ 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) {
|
||||||
if (this.props.isReadOnly) {
|
this.editor.pushUndoStop();
|
||||||
this.editor.setValue(this.props.content);
|
this.editor.executeEdits("", [
|
||||||
} else {
|
{
|
||||||
this.editor.pushUndoStop();
|
range: this.editor.getModel().getFullModelRange(),
|
||||||
this.editor.executeEdits("", [
|
text: this.props.content,
|
||||||
{
|
},
|
||||||
range: this.editor.getModel().getFullModelRange(),
|
]);
|
||||||
text: this.props.content,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,8 +202,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
required={true}
|
required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
styles={getTextFieldStyles()}
|
styles={getTextFieldStyles()}
|
||||||
pattern="[^/?#\\-]*[^/?#- \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?' '-'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
placeholder="Type a new keyspace id"
|
placeholder="Type a new keyspace id"
|
||||||
size={40}
|
size={40}
|
||||||
value={newKeyspaceId}
|
value={newKeyspaceId}
|
||||||
@@ -292,8 +292,8 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
required={true}
|
required={true}
|
||||||
ariaLabel="addCollection-table Id Create table"
|
ariaLabel="addCollection-table Id Create table"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
pattern="[^/?#\\-]*[^/?#- \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?' '-'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
placeholder="Enter table Id"
|
placeholder="Enter table Id"
|
||||||
size={20}
|
size={20}
|
||||||
value={tableId}
|
value={tableId}
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
? Constants.Queries.UnlimitedPageOption
|
? Constants.Queries.UnlimitedPageOption
|
||||||
: Constants.Queries.CustomPageOption,
|
: Constants.Queries.CustomPageOption,
|
||||||
);
|
);
|
||||||
|
const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState<string>(
|
||||||
|
LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setAutomaticRBACOption
|
||||||
|
? Constants.Queries.setAutomaticRBACOption
|
||||||
|
: LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setTrueRBACOption
|
||||||
|
? Constants.Queries.setTrueRBACOption
|
||||||
|
: Constants.Queries.setFalseRBACOption
|
||||||
|
);
|
||||||
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
const [ruThresholdEnabled, setRUThresholdEnabled] = useState<boolean>(isRUThresholdEnabled());
|
||||||
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
const [ruThreshold, setRUThreshold] = useState<number>(getRUThreshold());
|
||||||
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState<boolean>(
|
||||||
@@ -110,7 +117,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
StorageKey.ActualItemPerPage,
|
StorageKey.ActualItemPerPage,
|
||||||
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
|
||||||
|
|
||||||
|
LocalStorageUtility.setEntryString(
|
||||||
|
StorageKey.DataPlaneRbacEnabled,
|
||||||
|
enableDataPlaneRBACOption
|
||||||
|
);
|
||||||
|
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled);
|
||||||
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled);
|
||||||
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts);
|
||||||
@@ -197,6 +211,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
{ key: Constants.PriorityLevel.High, text: "High" },
|
{ key: Constants.PriorityLevel.High, text: "High" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [
|
||||||
|
{ key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" },
|
||||||
|
{ key: Constants.Queries.setTrueRBACOption, text: "True" },
|
||||||
|
{ key: Constants.Queries.setFalseRBACOption, text: "False"}
|
||||||
|
];
|
||||||
|
|
||||||
const handleOnPriorityLevelOptionChange = (
|
const handleOnPriorityLevelOptionChange = (
|
||||||
ev: React.FormEvent<HTMLInputElement>,
|
ev: React.FormEvent<HTMLInputElement>,
|
||||||
option: IChoiceGroupOption,
|
option: IChoiceGroupOption,
|
||||||
@@ -208,6 +228,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
setPageOption(option.key);
|
setPageOption(option.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnDataPlaneRBACOptionChange = (ev: React.FormEvent<HTMLInputElement>, option: IChoiceGroupOption): void => {
|
||||||
|
setEnableDataPlaneRBACOption(option.key);
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
const handleOnRUThresholdToggleChange = (ev: React.MouseEvent<HTMLElement>, checked?: boolean): void => {
|
||||||
setRUThresholdEnabled(checked);
|
setRUThresholdEnabled(checked);
|
||||||
};
|
};
|
||||||
@@ -361,6 +385,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{(
|
||||||
|
<div className="settingsSection">
|
||||||
|
<div className="settingsSectionPart">
|
||||||
|
<fieldset>
|
||||||
|
<legend id="enableDataPlaneRBACOptions" className="settingsSectionLabel legendLabel">
|
||||||
|
Enable DataPlane RBAC
|
||||||
|
</legend>
|
||||||
|
<InfoTooltip>
|
||||||
|
Choose Automatic to enable DataPlane RBAC automatically. True/False to voluntarily enable/disable DataPlane RBAC
|
||||||
|
</InfoTooltip>
|
||||||
|
<ChoiceGroup
|
||||||
|
ariaLabelledBy="enableDataPlaneRBACOptions"
|
||||||
|
selectedKey={enableDataPlaneRBACOption}
|
||||||
|
options={dataPlaneRBACOptionsList}
|
||||||
|
styles={choiceButtonStyles}
|
||||||
|
onChange={handleOnDataPlaneRBACOptionChange}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{userContext.apiType === "SQL" && (
|
{userContext.apiType === "SQL" && (
|
||||||
<>
|
<>
|
||||||
<div className="settingsSection">
|
<div className="settingsSection">
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
|
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||||
try {
|
try {
|
||||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
|
||||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
tableEntityListViewModel.redrawTableThrottled();
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A"
|
generateSQLQueryResponse.explanation ? generateSQLQueryResponse.explanation : "N/A"
|
||||||
}\r\n`;
|
}\r\n`;
|
||||||
const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql;
|
const currentGeneratedQuery = queryExplanation + generateSQLQueryResponse.sql;
|
||||||
const lastQuery = query ? `${query}\r\n` : "";
|
const lastQuery = generatedQuery && query ? `${query}\r\n` : "";
|
||||||
setQuery(`${lastQuery}${currentGeneratedQuery}`);
|
setQuery(`${lastQuery}${currentGeneratedQuery}`);
|
||||||
setGeneratedQuery(generateSQLQueryResponse.sql);
|
setGeneratedQuery(generateSQLQueryResponse.sql);
|
||||||
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
setGeneratedQueryComments(generateSQLQueryResponse.explanation);
|
||||||
|
|||||||
@@ -172,9 +172,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(entity);
|
deferred.resolve(entity);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
||||||
handleError(errorText, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -407,13 +406,12 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
|
||||||
handleError(
|
handleError(
|
||||||
errorText,
|
error,
|
||||||
"CreateKeyspaceCassandra",
|
"CreateKeyspaceCassandra",
|
||||||
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
`Error while creating a keyspace with query ${createKeyspaceQuery}`,
|
||||||
);
|
);
|
||||||
deferred.reject(errorText);
|
deferred.reject(error);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -446,13 +444,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
|
||||||
handleError(
|
deferred.reject(error);
|
||||||
errorText,
|
|
||||||
"CreateTableCassandra",
|
|
||||||
`Error while creating a table with query ${createTableQuery}`,
|
|
||||||
);
|
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.finally(clearInProgressMessage);
|
.finally(clearInProgressMessage);
|
||||||
@@ -500,9 +493,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -541,9 +533,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -587,9 +578,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data.columns);
|
deferred.resolve(data.columns);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -628,9 +618,8 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
deferred.resolve(data.columns);
|
deferred.resolve(data.columns);
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
||||||
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
deferred.reject(error);
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.done(clearInProgressMessage);
|
.done(clearInProgressMessage);
|
||||||
@@ -744,7 +733,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
|
|
||||||
private useCassandraProxyEndpoint(api: string): boolean {
|
private useCassandraProxyEndpoint(api: string): boolean {
|
||||||
const activeCassandraProxyEndpoints: string[] = [
|
const activeCassandraProxyEndpoints: string[] = [
|
||||||
CassandraProxyEndpoints.Development,
|
|
||||||
CassandraProxyEndpoints.Mpac,
|
CassandraProxyEndpoints.Mpac,
|
||||||
CassandraProxyEndpoints.Prod,
|
CassandraProxyEndpoints.Prod,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export type Features = {
|
|||||||
readonly enableTtl: boolean;
|
readonly enableTtl: boolean;
|
||||||
readonly executeSproc: boolean;
|
readonly executeSproc: boolean;
|
||||||
readonly enableAadDataPlane: boolean;
|
readonly enableAadDataPlane: boolean;
|
||||||
|
readonly enableDataPlaneRbac: boolean;
|
||||||
readonly enableResourceGraph: boolean;
|
readonly enableResourceGraph: boolean;
|
||||||
readonly enableKoResourceTree: boolean;
|
readonly enableKoResourceTree: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
@@ -74,6 +75,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
canExceedMaximumValue: "true" === get("canexceedmaximumvalue"),
|
||||||
cosmosdb: "true" === get("cosmosdb"),
|
cosmosdb: "true" === get("cosmosdb"),
|
||||||
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
enableAadDataPlane: "true" === get("enableaaddataplane"),
|
||||||
|
enableDataPlaneRbac: "true" === get("enabledataplanerbac"),
|
||||||
enableResourceGraph: "true" === get("enableresourcegraph"),
|
enableResourceGraph: "true" === get("enableresourcegraph"),
|
||||||
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"),
|
||||||
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"),
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import * as StringUtility from "./StringUtility";
|
|||||||
export { LocalStorageUtility, SessionStorageUtility };
|
export { LocalStorageUtility, SessionStorageUtility };
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
ActualItemPerPage,
|
ActualItemPerPage,
|
||||||
|
DataPlaneRbacEnabled,
|
||||||
|
DataPlaneRbacDisabled,
|
||||||
|
isDataPlaneRbacAutomatic,
|
||||||
RUThresholdEnabled,
|
RUThresholdEnabled,
|
||||||
RUThreshold,
|
RUThreshold,
|
||||||
QueryTimeoutEnabled,
|
QueryTimeoutEnabled,
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ interface UserContext {
|
|||||||
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
|
||||||
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
|
||||||
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
|
||||||
|
readonly dataPlaneRbacEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesCon
|
|||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
@@ -270,8 +271,30 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!account.properties.disableLocalAuth) {
|
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
|
if (isDataPlaneRbacSetting == "Automatic")
|
||||||
|
{
|
||||||
|
if (!account.properties.disableLocalAuth) {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isDataPlaneRbacSetting == "True") {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (userContext.features.enableAadDataPlane) {
|
if (userContext.features.enableAadDataPlane) {
|
||||||
@@ -393,8 +416,9 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
updateUserContext({
|
updateUserContext({
|
||||||
authType: AuthType.AAD,
|
authType: AuthType.AAD,
|
||||||
});
|
});
|
||||||
|
|
||||||
let explorer: Explorer;
|
let explorer: Explorer;
|
||||||
return new Promise((resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
// In development mode, try to load the iframe message from session storage.
|
// In development mode, try to load the iframe message from session storage.
|
||||||
// This allows webpack hot reload to function properly in the portal
|
// This allows webpack hot reload to function properly in the portal
|
||||||
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
|
||||||
@@ -407,6 +431,7 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
console.dir(message);
|
console.dir(message);
|
||||||
updateContextsFromPortalMessage(message);
|
updateContextsFromPortalMessage(message);
|
||||||
explorer = new Explorer();
|
explorer = new Explorer();
|
||||||
|
|
||||||
// In development mode, save the iframe message from the portal in session storage.
|
// In development mode, save the iframe message from the portal in session storage.
|
||||||
// This allows webpack hot reload to funciton properly
|
// This allows webpack hot reload to funciton properly
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
@@ -419,7 +444,7 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
// In the Portal, configuration of Explorer happens via iframe message
|
// In the Portal, configuration of Explorer happens via iframe message
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"message",
|
"message",
|
||||||
(event) => {
|
async (event) => {
|
||||||
if (isInvalidParentFrameOrigin(event)) {
|
if (isInvalidParentFrameOrigin(event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -449,6 +474,37 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
setTimeout(() => explorer.openNPSSurveyDialog(), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dbAccount = userContext.databaseAccount;
|
||||||
|
let keys: DatabaseAccountListKeysResult = {};
|
||||||
|
const account = userContext.databaseAccount;
|
||||||
|
const subscriptionId = userContext.subscriptionId;
|
||||||
|
const resourceGroup = userContext.resourceGroup;
|
||||||
|
if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) {
|
||||||
|
var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled);
|
||||||
|
if (isDataPlaneRbacSetting == "Automatic")
|
||||||
|
{
|
||||||
|
if (!account.properties.disableLocalAuth) {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(isDataPlaneRbacSetting == "True") {
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
keys = await listKeys(subscriptionId, resourceGroup, account.name);
|
||||||
|
updateUserContext({
|
||||||
|
dataPlaneRbacEnabled: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (openAction) {
|
if (openAction) {
|
||||||
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
handleOpenAction(openAction, useDatabases.getState().databases, explorer);
|
||||||
}
|
}
|
||||||
@@ -471,7 +527,9 @@ async function configurePortal(): Promise<Explorer> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
sendReadyMessage();
|
sendReadyMessage();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-cassandra-runner");
|
||||||
await page.waitForSelector("iframe");
|
await page.waitForSelector("iframe");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-gremlin-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and graph
|
// Create new database and graph
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateDatabaseNameWithTimestamp, generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateDatabaseNameWithTimestamp, generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-mongo32-runner");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
// Create new database and collection
|
// Create new database and collection
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { getAzureCLICredentialsToken } from "../utils/shared";
|
|
||||||
|
|
||||||
test("Self Serve", async () => {
|
test("Self Serve", async () => {
|
||||||
// We can't retrieve AZ CLI credentials from the browser so we get them here.
|
await page.goto("https://localhost:1234/testExplorer.html?iframeSrc=selfServe.html");
|
||||||
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,18 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-sql-runner-west-us");
|
||||||
const explorer = await waitForExplorer();
|
const explorer = await waitForExplorer();
|
||||||
|
|
||||||
await explorer.click('[data-test="New Container"]');
|
await explorer.click('[data-test="New Container"]');
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
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, getAzureCLICredentials } from "../utils/shared";
|
import { generateUniqueName } from "../utils/shared";
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] ?? "";
|
const clientId = "fd8753b0-0707-4e32-84e9-2532af865fb4";
|
||||||
|
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 getAzureCLICredentials();
|
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||||
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,17 +1,15 @@
|
|||||||
import { jest } from "@jest/globals";
|
import { jest } from "@jest/globals";
|
||||||
import "expect-playwright";
|
import "expect-playwright";
|
||||||
import { generateUniqueName, getAzureCLICredentialsToken } from "../utils/shared";
|
import { generateUniqueName } 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&token=${token}`);
|
await page.goto("https://localhost:1234/testExplorer.html?accountName=portal-tables-runner");
|
||||||
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,4 +1,5 @@
|
|||||||
/* 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";
|
||||||
@@ -10,13 +11,29 @@ 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}`,
|
||||||
});
|
});
|
||||||
@@ -35,9 +52,6 @@ const initTestExplorer = async (): Promise<void> => {
|
|||||||
dnsSuffix: "documents.azure.com",
|
dnsSuffix: "documents.azure.com",
|
||||||
serverId: "prod1",
|
serverId: "prod1",
|
||||||
extensionEndpoint: "/proxy",
|
extensionEndpoint: "/proxy",
|
||||||
portalBackendEndpoint: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
|
|
||||||
mongoProxyEndpoint: "https://cdb-ms-mpac-mp.cosmos.azure.com",
|
|
||||||
cassandraProxyEndpoint: "https://cdb-ms-mpac-cp.cosmos.azure.com",
|
|
||||||
subscriptionType: 3,
|
subscriptionType: 3,
|
||||||
quotaId: "Internal_2014-09-01",
|
quotaId: "Internal_2014-09-01",
|
||||||
isTryCosmosDBSubscription: false,
|
isTryCosmosDBSubscription: false,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
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 {
|
||||||
@@ -8,13 +7,3 @@ 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,7 +2,10 @@ 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 subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"];
|
const clientId = process.env["NOTEBOOKS_TEST_RUNNER_CLIENT_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();
|
||||||
@@ -16,7 +19,7 @@ function friendlyTime(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const credentials = await msRestNodeAuth.AzureCliCredentials.create();
|
const credentials = await msRestNodeAuth.loginWithServicePrincipalSecret(clientId, secret, tenantId);
|
||||||
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) {
|
||||||
@@ -35,7 +38,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