Compare commits

...

15 Commits

Author SHA1 Message Date
sunghyunkang1111
e7680c6c9e test commit 2024-03-14 15:36:08 -05:00
JustinKol
f881f7fd2f Enabled the ability to close the home tab (#1765) 2024-03-13 16:32:59 -04:00
vchske
4c74525b5d Adding computed properties to Settings tab for containers (#1763)
* Adding computed properties to Settings tab for containers

* Fixing files for prettier and a test snapshot
2024-03-12 14:55:14 -07:00
sindhuba
1a6d8d5357 Add CassandraProxy support in DE (#1764) 2024-03-11 15:17:01 -07:00
sunghyunkang1111
56c0049e9a add feedback policies integration with copilot (#1745)
* add feedback policies integration with copilot

* remove teaching bubble and welcome modal

* force prod phoenix endpoint in MPAC

* force prod phoenix endpoint in MPAC
2024-03-06 10:33:21 -06:00
MokireddySampath
b3837a089d color of the link has been changed to get the approved color contrast ratio of 4.5:1 (#1710) 2024-03-06 12:57:05 +05:30
MokireddySampath
e68aaebca6 Asterisk has beenadded beside the heading of input to show that it is a mandatory input (#1749) 2024-03-06 12:56:34 +05:30
MokireddySampath
6e1c4fd037 Bug 1242529: [Usable - Azure Cosmos DB - Input Parameters]: Aria-label is not descriptive enough for the 'Close(x)' button of 'Input Parameters' blade. (#1760)
* screen reader name for the button has been changed to read out the name of the dialog box

* tests have been updated
2024-03-06 12:56:07 +05:30
MokireddySampath
0039adf1c2 Border has been added to distinguish clear notifications from text (#1750) 2024-03-06 12:54:44 +05:30
MokireddySampath
5d4e9d82bb Bug 1240907: Aria-label is not descriptive enough for 'More(...)' button present under 'SQL API' section. (#1748)
* screen reader name for the more button has been modified as suggested

* e2e test have been updated

* e2e tests updated
2024-03-06 12:48:46 +05:30
MokireddySampath
47bdc9c426 styling changes have been made o remove the overlaping of focus outlines (#1721) 2024-03-06 12:47:57 +05:30
MokireddySampath
b8457e3bf9 defect2278780 (#1472)
* arialabel has been added to close button of invitational youtube video

* heading role has been addedd and tag has been changed to h1

* outline has been restored to choose columns link in entities page

* Update QuickstartCarousel.tsx

* Update SplashScreen.tsx

* Update TableEntity.tsx

* outline for edit entity has been added on focus

* keyboard accessibility added to rows in table entities

* Update queryBuilder.less

* Update TableEntity.tsx

* Update PanelComponent.less

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts
2024-03-06 12:43:44 +05:30
Vsevolod Kukol
533e9c887c Small fixes for Fabric PuPr (#1761)
* Hide the RU Threshold Message in Fabric

Fabric is RO and the Settings button is hidden, hence the message doesn't make sense. If customers hit the limits they can go to Portal and change the settings there.

* Change the toolbar font size and icon color in Fabric
2024-03-06 01:41:50 +01:00
jawelton74
76ad930930 Improve error handling when acquiring aad tokens (#1746)
* Mostly working - some cosmetic changes remaining.

* Cosmetic changes and other tidy ups.

* More clean up.

* Move msal back to dependencies. Fix typo.

* msal should be prod dependency

* Revert msal package update as it is causing issues with unit test
execution.

* Add tracing for unhandled exceptions when acquiring tokens.
2024-03-04 16:08:13 -08:00
JustinKol
932f211038 Revert "Revert "Adding CESCVA feedback button (#1736)" (#1753)" (#1759)
This reverts commit 5a64fc2582.
2024-03-04 17:19:12 -05:00
75 changed files with 1138 additions and 280 deletions

View File

@@ -20,8 +20,8 @@
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.organizeImports": true "source.organizeImports": "explicit"
}, },
"typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"

View File

@@ -1,5 +1,5 @@
{ {
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", "JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
"isTerminalEnabled" : true, "isTerminalEnabled": true,
"isPhoenixEnabled" : true "isPhoenixEnabled": true
} }

3
images/Home_16.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.31299 1.26164C7.69849 0.897163 8.30151 0.897163 8.68701 1.26164L13.5305 5.84098C13.8302 6.12431 14 6.51853 14 6.93094V12.5002C14 13.3286 13.3284 14.0002 12.5 14.0002H10.5C9.67157 14.0002 9 13.3286 9 12.5002V10.0002C9 9.72407 8.77614 9.50021 8.5 9.50021H7.5C7.22386 9.50021 7 9.72407 7 10.0002V12.5002C7 13.3286 6.32843 14.0002 5.5 14.0002H3.5C2.67157 14.0002 2 13.3286 2 12.5002V6.93094C2 6.51853 2.1698 6.12431 2.46948 5.84098L7.31299 1.26164ZM8 1.98828L3.15649 6.56762C3.0566 6.66207 3 6.79347 3 6.93094V12.5002C3 12.7763 3.22386 13.0002 3.5 13.0002H5.5C5.77614 13.0002 6 12.7763 6 12.5002V10.0002C6 9.17179 6.67157 8.50022 7.5 8.50022H8.5C9.32843 8.50022 10 9.17179 10 10.0002V12.5002C10 12.7763 10.2239 13.0002 10.5 13.0002H12.5C12.7761 13.0002 13 12.7763 13 12.5002V6.93094C13 6.79347 12.9434 6.66207 12.8435 6.56762L8 1.98828Z" fill="#0078D4" />
</svg>

After

Width:  |  Height:  |  Size: 968 B

View File

@@ -163,6 +163,7 @@
/**********************************************************************************/ /**********************************************************************************/
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; @FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
@FabricToolbarIconColor: "brightness(0) saturate(100%) invert(50%) sepia(17%) saturate(1459%) hue-rotate(81deg) brightness(99%) contrast(94%)";
@FabricBoxBorderRadius: 8px; @FabricBoxBorderRadius: 8px;
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px; @FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;

View File

@@ -78,8 +78,8 @@
"mkdirp": "1.0.4", "mkdirp": "1.0.4",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
"ms": "2.1.3", "ms": "2.1.3",
"patch-package": "8.0.0",
"p-retry": "4.6.2", "p-retry": "4.6.2",
"patch-package": "8.0.0",
"plotly.js-cartesian-dist-min": "1.52.3", "plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42", "post-robot": "10.0.42",
"q": "1.5.1", "q": "1.5.1",
@@ -238,4 +238,4 @@
"printWidth": 120, "printWidth": 120,
"endOfLine": "auto" "endOfLine": "auto"
} }
} }

View File

@@ -124,7 +124,7 @@ export enum MongoBackendEndpointType {
remote, remote,
} }
// TODO: 435619 Add default endpoints per cloud and use regional only when available //TODO: Remove this when new backend is migrated over
export class CassandraBackend { export class CassandraBackend {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete"; public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
@@ -136,6 +136,17 @@ export class CassandraBackend {
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
} }
export class CassandraProxyAPIs {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra/postquery";
public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra";
public static readonly keysApi: string = "api/cassandra/keys";
public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys";
public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
}
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";

View File

@@ -44,6 +44,8 @@ export interface ConfigContext {
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean; MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[]; NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT?: string; CASSANDRA_PROXY_ENDPOINT?: string;
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string; PROXY_PATH?: string;
JUNO_ENDPOINT: string; JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string; GITHUB_CLIENT_ID: string;
@@ -99,6 +101,13 @@ let configContext: Readonly<ConfigContext> = {
], ],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: [
// "postQuery",
// "createOrDelete",
// "getKeys",
// "getSchema",
],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false, isTerminalEnabled: false,
isPhoenixEnabled: false, isPhoenixEnabled: false,
}; };

View File

@@ -159,6 +159,7 @@ export interface Collection extends Resource {
geospatialConfig?: GeospatialConfig; geospatialConfig?: GeospatialConfig;
schema?: ISchema; schema?: ISchema;
requestSchema?: () => void; requestSchema?: () => void;
computedProperties?: ComputedProperties;
} }
export interface CollectionsWithPagination { export interface CollectionsWithPagination {
@@ -197,6 +198,13 @@ export interface IndexingPolicy {
spatialIndexes?: any; spatialIndexes?: any;
} }
export interface ComputedProperty {
name: string;
query: string;
}
export type ComputedProperties = ComputedProperty[];
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];
kind: "Hash" | "Range" | "MultiHash"; kind: "Hash" | "Range" | "MultiHash";

View File

@@ -135,6 +135,7 @@ export interface Collection extends CollectionBase {
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>; changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
geospatialConfig: ko.Observable<DataModels.GeospatialConfig>; geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
documentIds: ko.ObservableArray<DocumentId>; documentIds: ko.ObservableArray<DocumentId>;
computedProperties: ko.Observable<DataModels.ComputedProperties>;
cassandraKeys: CassandraTableKeys; cassandraKeys: CassandraTableKeys;
cassandraSchema: CassandraTableKey[]; cassandraSchema: CassandraTableKey[];
@@ -407,6 +408,7 @@ export interface DataExplorerInputsFrame {
features?: { features?: {
[key: string]: string; [key: string]: string;
}; };
feedbackPolicies?: any;
} }
export interface SelfServeFrameInputs { export interface SelfServeFrameInputs {

View File

@@ -1,4 +1,8 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react"; import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
import {
ComputedPropertiesComponent,
ComputedPropertiesComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react"; import * as React from "react";
@@ -108,6 +112,11 @@ export interface SettingsComponentState {
indexesToAdd: AddMongoIndexProps[]; indexesToAdd: AddMongoIndexProps[];
indexTransformationProgress: number; indexTransformationProgress: number;
computedPropertiesContent: DataModels.ComputedProperties;
computedPropertiesContentBaseline: DataModels.ComputedProperties;
shouldDiscardComputedProperties: boolean;
isComputedPropertiesDirty: boolean;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
conflictResolutionPolicyPath: string; conflictResolutionPolicyPath: string;
@@ -132,6 +141,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private offer: DataModels.Offer; private offer: DataModels.Offer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: boolean;
private shouldShowComputedPropertiesEditor: boolean;
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
private shouldShowPartitionKeyEditor: boolean; private shouldShowPartitionKeyEditor: boolean;
private totalThroughputUsed: number; private totalThroughputUsed: number;
@@ -145,6 +155,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.collection = this.props.settingsTab.collection as ViewModels.Collection; this.collection = this.props.settingsTab.collection as ViewModels.Collection;
this.offer = this.collection?.offer(); this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl(); this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL";
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo"; this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud(); this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
@@ -198,6 +209,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isMongoIndexingPolicyDiscardable: false, isMongoIndexingPolicyDiscardable: false,
indexTransformationProgress: undefined, indexTransformationProgress: undefined,
computedPropertiesContent: undefined,
computedPropertiesContentBaseline: undefined,
shouldDiscardComputedProperties: false,
isComputedPropertiesDirty: false,
conflictResolutionPolicyMode: undefined, conflictResolutionPolicyMode: undefined,
conflictResolutionPolicyModeBaseline: undefined, conflictResolutionPolicyModeBaseline: undefined,
conflictResolutionPolicyPath: undefined, conflictResolutionPolicyPath: undefined,
@@ -288,6 +304,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isSubSettingsSaveable || this.state.isSubSettingsSaveable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
); );
}; };
@@ -298,6 +315,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isSubSettingsDiscardable || this.state.isSubSettingsDiscardable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
); );
}; };
@@ -402,6 +420,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isMongoIndexingPolicySaveable: false, isMongoIndexingPolicySaveable: false,
isMongoIndexingPolicyDiscardable: false, isMongoIndexingPolicyDiscardable: false,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
computedPropertiesContent: this.state.computedPropertiesContentBaseline,
shouldDiscardComputedProperties: true,
isComputedPropertiesDirty: false,
}); });
}; };
@@ -521,6 +542,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void => private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
this.setState({ isMongoIndexingPolicyDiscardable }); this.setState({ isMongoIndexingPolicyDiscardable });
private onComputedPropertiesContentChange = (newComputedProperties: DataModels.ComputedProperties): void =>
this.setState({ computedPropertiesContent: newComputedProperties });
private resetShouldDiscardComputedProperties = (): void => this.setState({ shouldDiscardComputedProperties: false });
private logComputedPropertiesSuccessMessage = (): void => {
if (this.props.settingsTab.onLoadStartKey) {
traceSuccess(
Action.Tab,
{
databaseName: this.collection.databaseId,
collectionName: this.collection.id(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
},
this.props.settingsTab.onLoadStartKey,
);
this.props.settingsTab.onLoadStartKey = undefined;
}
};
private onComputedPropertiesDirtyChange = (isComputedPropertiesDirty: boolean): void =>
this.setState({ isComputedPropertiesDirty: isComputedPropertiesDirty });
private calculateTotalThroughputUsed = (): void => { private calculateTotalThroughputUsed = (): void => {
this.totalThroughputUsed = 0; this.totalThroughputUsed = 0;
(useDatabases.getState().databases || []).forEach(async (database) => { (useDatabases.getState().databases || []).forEach(async (database) => {
@@ -643,7 +689,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const indexingPolicyContent = this.collection.indexingPolicy(); const indexingPolicyContent = this.collection.indexingPolicy();
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy = const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy(); this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode); const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode);
const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath; const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath;
const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure( const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure(
@@ -652,6 +697,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const geospatialConfigTypeString: string = const geospatialConfigTypeString: string =
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry; (this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType]; const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
let computedPropertiesContent = this.collection.computedProperties();
if (!computedPropertiesContent || computedPropertiesContent.length === 0) {
computedPropertiesContent = [
{ name: "name_of_property", query: "query_to_compute_property" },
] as DataModels.ComputedProperties;
}
return { return {
throughput: offerThroughput, throughput: offerThroughput,
@@ -678,6 +729,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure, conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
geospatialConfigType: geoSpatialConfigType, geospatialConfigType: geoSpatialConfigType,
geospatialConfigTypeBaseline: geoSpatialConfigType, geospatialConfigTypeBaseline: geoSpatialConfigType,
computedPropertiesContent: computedPropertiesContent,
computedPropertiesContentBaseline: computedPropertiesContent,
}; };
}; };
@@ -794,7 +847,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private saveCollectionSettings = async (startKey: number): Promise<void> => { private saveCollectionSettings = async (startKey: number): Promise<void> => {
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel }; const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) { if (
this.state.isSubSettingsSaveable ||
this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty
) {
let defaultTtl: number; let defaultTtl: number;
switch (this.state.timeToLive) { switch (this.state.timeToLive) {
case TtlType.On: case TtlType.On:
@@ -832,6 +890,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newCollection.conflictResolutionPolicy = conflictResolutionChanges; newCollection.conflictResolutionPolicy = conflictResolutionChanges;
} }
if (this.state.isComputedPropertiesDirty) {
newCollection.computedProperties = this.state.computedPropertiesContent;
}
const updatedCollection: DataModels.Collection = await updateCollection( const updatedCollection: DataModels.Collection = await updateCollection(
this.collection.databaseId, this.collection.databaseId,
this.collection.id(), this.collection.id(),
@@ -845,6 +907,7 @@ 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);
this.collection.computedProperties(updatedCollection.computedProperties);
if (wasIndexingPolicyModified) { if (wasIndexingPolicyModified) {
await this.refreshIndexTransformationProgress(); await this.refreshIndexTransformationProgress();
@@ -855,6 +918,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isSubSettingsDiscardable: false, isSubSettingsDiscardable: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
isComputedPropertiesDirty: false,
}); });
} }
@@ -1049,6 +1113,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange, onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange,
}; };
const computedPropertiesComponentProps: ComputedPropertiesComponentProps = {
computedPropertiesContent: this.state.computedPropertiesContent,
computedPropertiesContentBaseline: this.state.computedPropertiesContentBaseline,
logComputedPropertiesSuccessMessage: this.logComputedPropertiesSuccessMessage,
onComputedPropertiesContentChange: this.onComputedPropertiesContentChange,
onComputedPropertiesDirtyChange: this.onComputedPropertiesDirtyChange,
resetShouldDiscardComputedProperties: this.resetShouldDiscardComputedProperties,
shouldDiscardComputedProperties: this.state.shouldDiscardComputedProperties,
};
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = { const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
collection: this.collection, collection: this.collection,
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode, conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
@@ -1111,6 +1185,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}); });
} }
if (this.shouldShowComputedPropertiesEditor) {
tabs.push({
tab: SettingsV2TabTypes.ComputedPropertiesTab,
content: <ComputedPropertiesComponent {...computedPropertiesComponentProps} />,
});
}
const pivotProps: IPivotProps = { const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange, onLinkClick: this.onPivotChange,
selectedKey: SettingsV2TabTypes[this.state.selectedTab], selectedKey: SettingsV2TabTypes[this.state.selectedTab],

View File

@@ -11,7 +11,6 @@ import {
getThroughputApplyLongDelayMessage, getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage, getThroughputApplyShortDelayMessage,
getToolTipContainer, getToolTipContainer,
indexingPolicynUnsavedWarningMessage,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement,
mongoIndexTransformationRefreshingMessage, mongoIndexTransformationRefreshingMessage,
mongoIndexingPolicyAADError, mongoIndexingPolicyAADError,
@@ -39,7 +38,6 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{manualToAutoscaleDisclaimerElement} {manualToAutoscaleDisclaimerElement}
{ttlWarning} {ttlWarning}
{indexingPolicynUnsavedWarningMessage}
{updateThroughputDelayedApplyWarningMessage} {updateThroughputDelayedApplyWarningMessage}
{getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)} {getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)}

View File

@@ -61,6 +61,8 @@ export interface PriceBreakdown {
currencySign: string; currencySign: string;
} }
export type editorType = "indexPolicy" | "computedProperties";
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
@@ -254,9 +256,10 @@ export const ttlWarning: JSX.Element = (
</Text> </Text>
); );
export const indexingPolicynUnsavedWarningMessage: JSX.Element = ( export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. You have not saved the latest changes made to your{" "}
{editor === "indexPolicy" ? "indexing policy" : "computed properties"}. Please click save to confirm the changes.
</Text> </Text>
); );

View File

@@ -0,0 +1,56 @@
import * as DataModels from "Contracts/DataModels";
import { shallow } from "enzyme";
import React from "react";
import { ComputedPropertiesComponent, ComputedPropertiesComponentProps } from "./ComputedPropertiesComponent";
describe("ComputedPropertiesComponent", () => {
const initialComputedPropertiesContent: DataModels.ComputedProperties = [
{
name: "prop1",
query: "query1",
},
];
const baseProps: ComputedPropertiesComponentProps = {
computedPropertiesContent: initialComputedPropertiesContent,
computedPropertiesContentBaseline: initialComputedPropertiesContent,
logComputedPropertiesSuccessMessage: () => {
return;
},
onComputedPropertiesContentChange: () => {
return;
},
onComputedPropertiesDirtyChange: () => {
return;
},
resetShouldDiscardComputedProperties: () => {
return;
},
shouldDiscardComputedProperties: false,
};
it("renders", () => {
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
expect(wrapper).toMatchSnapshot();
});
it("computed properties are reset", () => {
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
const computedPropertiesComponentInstance = wrapper.instance() as ComputedPropertiesComponent;
const resetComputedPropertiesEditorMockFn = jest.fn();
computedPropertiesComponentInstance.resetComputedPropertiesEditor = resetComputedPropertiesEditorMockFn;
wrapper.setProps({ shouldDiscardComputedProperties: true });
wrapper.update();
expect(resetComputedPropertiesEditorMockFn.mock.calls.length).toEqual(1);
});
it("dirty is set", () => {
let computedPropertiesComponent = new ComputedPropertiesComponent(baseProps);
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(false);
const newProps = { ...baseProps, computedPropertiesContent: undefined as DataModels.ComputedProperties };
computedPropertiesComponent = new ComputedPropertiesComponent(newProps);
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(true);
});
});

View File

@@ -0,0 +1,128 @@
import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react";
import * as DataModels from "Contracts/DataModels";
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils";
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
import { loadMonaco } from "Explorer/LazyMonaco";
import * as monaco from "monaco-editor";
import * as React from "react";
export interface ComputedPropertiesComponentProps {
computedPropertiesContent: DataModels.ComputedProperties;
computedPropertiesContentBaseline: DataModels.ComputedProperties;
logComputedPropertiesSuccessMessage: () => void;
onComputedPropertiesContentChange: (newComputedProperties: DataModels.ComputedProperties) => void;
onComputedPropertiesDirtyChange: (isComputedPropertiesDirty: boolean) => void;
resetShouldDiscardComputedProperties: () => void;
shouldDiscardComputedProperties: boolean;
}
interface ComputedPropertiesComponentState {
computedPropertiesContentIsValid: boolean;
}
export class ComputedPropertiesComponent extends React.Component<
ComputedPropertiesComponentProps,
ComputedPropertiesComponentState
> {
private shouldCheckComponentIsDirty = true;
private computedPropertiesDiv = React.createRef<HTMLDivElement>();
private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor;
constructor(props: ComputedPropertiesComponentProps) {
super(props);
this.state = {
computedPropertiesContentIsValid: true,
};
}
componentDidUpdate(): void {
if (this.props.shouldDiscardComputedProperties) {
this.resetComputedPropertiesEditor();
this.props.resetShouldDiscardComputedProperties();
}
this.onComponentUpdate();
}
componentDidMount(): void {
this.resetComputedPropertiesEditor();
this.onComponentUpdate();
}
public resetComputedPropertiesEditor = (): void => {
if (!this.computedPropertiesEditor) {
this.createComputedPropertiesEditor();
} else {
const indexingPolicyEditorModel = this.computedPropertiesEditor.getModel();
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
indexingPolicyEditorModel.setValue(value);
}
this.onComponentUpdate();
};
private onComponentUpdate = (): void => {
if (!this.shouldCheckComponentIsDirty) {
this.shouldCheckComponentIsDirty = true;
return;
}
this.props.onComputedPropertiesDirtyChange(this.IsComponentDirty());
this.shouldCheckComponentIsDirty = false;
};
public IsComponentDirty = (): boolean => {
if (
isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) &&
this.state.computedPropertiesContentIsValid
) {
return true;
}
return false;
};
private async createComputedPropertiesEditor(): Promise<void> {
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
const monaco = await loadMonaco();
this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, {
value: value,
language: "json",
ariaLabel: "Computed properties",
});
if (this.computedPropertiesEditor) {
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
this.props.logComputedPropertiesSuccessMessage();
}
}
private onEditorContentChange = (): void => {
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
try {
const newComputedPropertiesContent = JSON.parse(
computedPropertiesEditorModel.getValue(),
) as DataModels.ComputedProperties;
this.props.onComputedPropertiesContentChange(newComputedPropertiesContent);
this.setState({ computedPropertiesContentIsValid: true });
} catch (e) {
this.setState({ computedPropertiesContentIsValid: false });
}
};
public render(): JSX.Element {
return (
<Stack {...titleAndInputStackProps}>
{isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>
{unsavedEditorWarningMessage("computedProperties")}
</MessageBar>
)}
<Text style={{ marginLeft: "30px", marginBottom: "10px" }}>
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
</Link>
&#160; about how to define computed properties and how to use them.
</Text>
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
</Stack>
);
}
}

View File

@@ -3,7 +3,7 @@ import * as monaco from "monaco-editor";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { loadMonaco } from "../../../LazyMonaco"; import { loadMonaco } from "../../../LazyMonaco";
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
@@ -120,7 +120,7 @@ export class IndexingPolicyComponent extends React.Component<
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress} refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
/> />
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
)} )}
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div> <div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
</Stack> </Stack>

View File

@@ -19,7 +19,6 @@ import {
addMongoIndexStackProps, addMongoIndexStackProps,
createAndAddMongoIndexStackProps, createAndAddMongoIndexStackProps,
customDetailsListStyles, customDetailsListStyles,
indexingPolicynUnsavedWarningMessage,
infoAndToolTipTextStyle, infoAndToolTipTextStyle,
mediumWidthStackStyles, mediumWidthStackStyles,
mongoCompoundIndexNotSupportedMessage, mongoCompoundIndexNotSupportedMessage,
@@ -27,15 +26,16 @@ import {
onRenderRow, onRenderRow,
separatorStyles, separatorStyles,
subComponentStackProps, subComponentStackProps,
unsavedEditorWarningMessage,
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { import {
AddMongoIndexProps, AddMongoIndexProps,
getMongoIndexType,
getMongoIndexTypeText,
isIndexTransforming,
MongoIndexIdField, MongoIndexIdField,
MongoIndexTypes, MongoIndexTypes,
MongoNotificationType, MongoNotificationType,
getMongoIndexType,
getMongoIndexTypeText,
isIndexTransforming,
} from "../../SettingsUtils"; } from "../../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
@@ -297,7 +297,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
if (this.getMongoWarningNotificationMessage()) { if (this.getMongoWarningNotificationMessage()) {
warningMessage = this.getMongoWarningNotificationMessage(); warningMessage = this.getMongoWarningNotificationMessage();
} else if (this.isMongoIndexingPolicySaveable()) { } else if (this.isMongoIndexingPolicySaveable()) {
warningMessage = indexingPolicynUnsavedWarningMessage; warningMessage = unsavedEditorWarningMessage("indexPolicy");
} }
return ( return (

View File

@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ComputedPropertiesComponent renders 1`] = `
<Stack
tokens={
Object {
"childrenGap": 5,
}
}
>
<Text
style={
Object {
"marginBottom": "10px",
"marginLeft": "30px",
}
}
>
<StyledLinkBase
href="https://aka.ms/computed-properties-preview/"
target="_blank"
>
Learn more
<FontIcon
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
  about how to define computed properties and how to use them.
</Text>
<div
className="settingsV2IndexingPolicyEditor"
tabIndex={0}
/>
</Stack>
`;

View File

@@ -4,7 +4,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
const zeroValue = 0; const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
export const TtlOnNoDefault = "on-nodefault"; export const TtlOnNoDefault = "on-nodefault";
@@ -46,6 +46,7 @@ export enum SettingsV2TabTypes {
SubSettingsTab, SubSettingsTab,
IndexingPolicyTab, IndexingPolicyTab,
PartitionKeyTab, PartitionKeyTab,
ComputedPropertiesTab,
} }
export interface IsComponentDirtyResult { export interface IsComponentDirtyResult {
@@ -149,6 +150,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
return "Indexing Policy"; return "Indexing Policy";
case SettingsV2TabTypes.PartitionKeyTab: case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys"; return "Partition Keys";
case SettingsV2TabTypes.ComputedPropertiesTab:
return "Computed Properties (preview)";
default: default:
throw new Error(`Unknown tab ${tab}`); throw new Error(`Unknown tab ${tab}`);
} }

View File

@@ -40,6 +40,12 @@ export const collection = {
version: 2, version: 2,
}, },
partitionKeyProperties: ["partitionKey"], partitionKeyProperties: ["partitionKey"],
computedProperties: ko.observable<DataModels.ComputedProperties>([
{
name: "queryName",
query: "query",
},
]),
readSettings: () => { readSettings: () => {
return; return;
}, },

View File

@@ -26,6 +26,7 @@ exports[`SettingsComponent renders 1`] = `
Object { Object {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
@@ -103,6 +104,7 @@ exports[`SettingsComponent renders 1`] = `
Object { Object {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
@@ -219,6 +221,7 @@ exports[`SettingsComponent renders 1`] = `
Object { Object {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
@@ -296,6 +299,40 @@ exports[`SettingsComponent renders 1`] = `
} }
/> />
</PivotItem> </PivotItem>
<PivotItem
headerText="Computed Properties (preview)"
itemKey="ComputedPropertiesTab"
key="ComputedPropertiesTab"
style={
Object {
"marginTop": 20,
}
}
>
<ComputedPropertiesComponent
computedPropertiesContent={
Array [
Object {
"name": "queryName",
"query": "query",
},
]
}
computedPropertiesContentBaseline={
Array [
Object {
"name": "queryName",
"query": "query",
},
]
}
logComputedPropertiesSuccessMessage={[Function]}
onComputedPropertiesContentChange={[Function]}
onComputedPropertiesDirtyChange={[Function]}
resetShouldDiscardComputedProperties={[Function]}
shouldDiscardComputedProperties={false}
/>
</PivotItem>
</StyledPivot> </StyledPivot>
</div> </div>
</div> </div>

View File

@@ -99,18 +99,6 @@ exports[`SettingsUtils functions render 1`] = `
</StyledLinkBase> </StyledLinkBase>
. .
</Text> </Text>
<Text
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
}
>
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
</Text>
<Text <Text
id="updateThroughputDelayedApplyWarningMessage" id="updateThroughputDelayedApplyWarningMessage"
styles={ styles={

View File

@@ -14,7 +14,13 @@
.throughputInputSpacing > :not(:last-child) { .throughputInputSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.capacitycalculator-link:focus{
.capacitycalculator-link:focus {
text-decoration: underline; text-decoration: underline;
outline-offset: 2px; outline-offset: 2px;
} }
.copyQuery:focus::after,
.deleteQuery:focus::after {
outline: none !important;
}

View File

@@ -247,7 +247,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
name="More" name="More"
title="More" title="More"
className="treeMenuEllipsis" className="treeMenuEllipsis"
ariaLabel={menuItemLabel} ariaLabel={`${menuItemLabel} options`}
menuIconProps={{ menuIconProps={{
iconName: menuItemLabel, iconName: menuItemLabel,
styles: { root: { fontSize: "18px", fontWeight: "bold" } }, styles: { root: { fontSize: "18px", fontWeight: "bold" } },

View File

@@ -172,7 +172,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
onKeyPress={[Function]} onKeyPress={[Function]}
> >
<CustomizedIconButton <CustomizedIconButton
ariaLabel="More" ariaLabel="More options"
className="treeMenuEllipsis" className="treeMenuEllipsis"
menuIconProps={ menuIconProps={
Object { Object {
@@ -397,7 +397,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
onKeyPress={[Function]} onKeyPress={[Function]}
> >
<CustomizedIconButton <CustomizedIconButton
ariaLabel="More" ariaLabel="More options"
className="treeMenuEllipsis" className="treeMenuEllipsis"
menuIconProps={ menuIconProps={
Object { Object {

View File

@@ -296,6 +296,14 @@ export default class Explorer {
} }
} }
public async openCESCVAFeedbackBlade(): Promise<void> {
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
Logger.logInfo(
`CES CVA Feedback logging current date when survey is shown ${Date.now().toString()}`,
"Explorer/openCESCVAFeedbackBlade",
);
}
public async refreshDatabaseForResourceToken(): Promise<void> { public async refreshDatabaseForResourceToken(): Promise<void> {
const databaseId = userContext.parsedResourceToken?.databaseId; const databaseId = userContext.parsedResourceToken?.databaseId;
const collectionId = userContext.parsedResourceToken?.collectionId; const collectionId = userContext.parsedResourceToken?.collectionId;

View File

@@ -1,3 +1,4 @@
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react"; import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg"; import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
@@ -8,6 +9,7 @@ import AddUdfIcon from "../../../../images/AddUdf.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import HomeIcon from "../../../../images/Home_16.svg";
import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import GitHubIcon from "../../../../images/github.svg"; import GitHubIcon from "../../../../images/github.svg";
@@ -56,6 +58,9 @@ export function createStaticCommandBarButtons(
} }
}; };
const homeBtn = createHomeButton();
buttons.push(homeBtn);
if (configContext.platform !== Platform.Fabric) { if (configContext.platform !== Platform.Fabric) {
const newCollectionBtn = createNewCollectionGroup(container); const newCollectionBtn = createNewCollectionGroup(container);
buttons.push(newCollectionBtn); buttons.push(newCollectionBtn);
@@ -240,7 +245,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt
const feedbackButtonOptions: CommandButtonComponentProps = { const feedbackButtonOptions: CommandButtonComponentProps = {
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: label, iconAlt: label,
onCommandClick: () => container.provideFeedbackEmail(), onCommandClick: () => container.openCESCVAFeedbackBlade(),
commandButtonLabel: undefined, commandButtonLabel: undefined,
ariaLabel: label, ariaLabel: label,
tooltipText: label, tooltipText: label,
@@ -285,6 +290,18 @@ function createNewCollectionGroup(container: Explorer): CommandButtonComponentPr
}; };
} }
function createHomeButton(): CommandButtonComponentProps {
const label = "Home";
return {
iconSrc: HomeIcon,
iconAlt: label,
onCommandClick: () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Home),
commandButtonLabel: label,
hasPopup: false,
ariaLabel: label,
};
}
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
return undefined; return undefined;

View File

@@ -37,7 +37,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
if (isDisabled) { if (isDisabled) {
return StyleConstants.GrayScale; return StyleConstants.GrayScale;
} }
return configContext.platform == Platform.Fabric ? StyleConstants.NoColor : undefined; return configContext.platform == Platform.Fabric ? StyleConstants.FabricToolbarIconColor : undefined;
}; };
return btns return btns
@@ -96,7 +96,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
}, },
width: 16, width: 16,
}, },
label: { fontSize: StyleConstants.mediumFontSize }, label: {
fontSize:
configContext.platform == Platform.Fabric
? StyleConstants.DefaultFontSize
: StyleConstants.mediumFontSize,
},
rootHovered: { backgroundColor: hoverColor }, rootHovered: { backgroundColor: hoverColor },
rootPressed: { backgroundColor: hoverColor }, rootPressed: { backgroundColor: hoverColor },
splitButtonMenuButtonExpanded: { splitButtonMenuButtonExpanded: {
@@ -133,7 +138,12 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
// TODO Figure out how to do it the proper way with subComponentStyles. // TODO Figure out how to do it the proper way with subComponentStyles.
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes // TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
selectors: { selectors: {
".ms-ContextualMenu-itemText": { fontSize: StyleConstants.mediumFontSize }, ".ms-ContextualMenu-itemText": {
fontSize:
configContext.platform == Platform.Fabric
? StyleConstants.DefaultFontSize
: StyleConstants.mediumFontSize,
},
".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor }, ".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor },
".ms-ContextualMenu-icon": { width: 16, height: 16 }, ".ms-ContextualMenu-icon": { width: 16, height: 16 },
}, },

View File

@@ -162,6 +162,7 @@ export class NotificationConsoleComponent extends React.Component<
role="button" role="button"
onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)} onKeyDown={(event: React.KeyboardEvent<HTMLSpanElement>) => this.onClearNotificationsKeyPress(event)}
tabIndex={0} tabIndex={0}
style={{ border: "1px solid black", borderRadius: "2px" }}
> >
<img src={ClearIcon} alt="clear notifications image" /> <img src={ClearIcon} alt="clear notifications image" />
Clear Notifications Clear Notifications

View File

@@ -146,6 +146,12 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button" role="button"
style={
Object {
"border": "1px solid black",
"borderRadius": "2px",
}
}
tabIndex={0} tabIndex={0}
> >
<img <img
@@ -311,6 +317,12 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
onClick={[Function]} onClick={[Function]}
onKeyDown={[Function]} onKeyDown={[Function]}
role="button" role="button"
style={
Object {
"border": "1px solid black",
"borderRadius": "2px",
}
}
tabIndex={0} tabIndex={0}
> >
<img <img

View File

@@ -58,7 +58,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
onDismiss={this.onDissmiss} onDismiss={this.onDissmiss}
isLightDismiss isLightDismiss
type={PanelType.custom} type={PanelType.custom}
closeButtonAriaLabel="Close" closeButtonAriaLabel={`Close ${this.props.headerText}`}
customWidth={this.props.panelWidth ? this.props.panelWidth : "440px"} customWidth={this.props.panelWidth ? this.props.panelWidth : "440px"}
headerClassName="panelHeader" headerClassName="panelHeader"
onRenderNavigationContent={this.props.onRenderNavigationContent} onRenderNavigationContent={this.props.onRenderNavigationContent}

View File

@@ -486,4 +486,4 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
isButtonDisabled={false} isButtonDisabled={false}
/> />
</form> </form>
`; `;

View File

@@ -2,7 +2,7 @@
exports[`PaneContainerComponent test should be resize if notification console is expanded 1`] = ` exports[`PaneContainerComponent test should be resize if notification console is expanded 1`] = `
<StyledPanelBase <StyledPanelBase
closeButtonAriaLabel="Close" closeButtonAriaLabel="Close test"
customWidth="440px" customWidth="440px"
headerClassName="panelHeader" headerClassName="panelHeader"
headerText="test" headerText="test"
@@ -42,7 +42,7 @@ exports[`PaneContainerComponent test should render nothing if content is undefin
exports[`PaneContainerComponent test should render with panel content and header 1`] = ` exports[`PaneContainerComponent test should render with panel content and header 1`] = `
<StyledPanelBase <StyledPanelBase
closeButtonAriaLabel="Close" closeButtonAriaLabel="Close test"
customWidth="440px" customWidth="440px"
headerClassName="panelHeader" headerClassName="panelHeader"
headerText="test" headerText="test"

View File

@@ -21,7 +21,6 @@ import {
import { HttpStatusCodes } from "Common/Constants"; import { HttpStatusCodes } from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils"; import { handleError } from "Common/ErrorHandlingUtils";
import { createUri } from "Common/UrlUtility"; import { createUri } from "Common/UrlUtility";
import { WelcomeModal } from "Explorer/QueryCopilot/Modal/WelcomeModal";
import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup"; import { CopyPopup } from "Explorer/QueryCopilot/Popup/CopyPopup";
import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup"; import { DeletePopup } from "Explorer/QueryCopilot/Popup/DeletePopup";
import { import {
@@ -272,28 +271,11 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
} }
}; };
const showTeachingBubble = (): void => {
if (showPromptTeachingBubble && !inputEdited.current) {
setTimeout(() => {
if (!inputEdited.current && !isWelcomModalVisible()) {
setCopilotTeachingBubbleVisible(true);
inputEdited.current = true;
}
}, 30000);
} else {
toggleCopilotTeachingBubbleVisible(false);
}
};
const toggleCopilotTeachingBubbleVisible = (visible: boolean): void => { const toggleCopilotTeachingBubbleVisible = (visible: boolean): void => {
setCopilotTeachingBubbleVisible(visible); setCopilotTeachingBubbleVisible(visible);
setShowPromptTeachingBubble(visible); setShowPromptTeachingBubble(visible);
}; };
const isWelcomModalVisible = (): boolean => {
return localStorage.getItem("hideWelcomeModal") !== "true";
};
const clearFeedback = () => { const clearFeedback = () => {
resetButtonState(); resetButtonState();
resetQueryResults(); resetQueryResults();
@@ -315,14 +297,13 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
if (isGeneratingQuery === null) { if (isGeneratingQuery === null) {
return " "; return " ";
} else if (isGeneratingQuery) { } else if (isGeneratingQuery) {
return "Content is loading"; return "Content is loading!";
} else { } else {
return "Content is updated"; return "Content is updated!";
} }
}; };
React.useEffect(() => { React.useEffect(() => {
showTeachingBubble();
useTabs.getState().setIsQueryErrorThrown(false); useTabs.getState().setIsQueryErrorThrown(false);
}, []); }, []);
@@ -511,7 +492,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
<Stack style={{ margin: "8px 0" }}> <Stack style={{ margin: "8px 0" }}>
<Text style={{ fontSize: 12 }}> <Text style={{ fontSize: 12 }}>
AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "} AI-generated content can have mistakes. Make sure it&apos;s accurate and appropriate before using it.{" "}
<Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072c9" }}> <Link href="https://aka.ms/cdb-copilot-preview-terms" target="_blank" style={{ color: "#0072D4" }}>
Read preview terms Read preview terms
</Link> </Link>
{showErrorMessageBar && ( {showErrorMessageBar && (
@@ -539,85 +520,92 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
</Stack> </Stack>
{showFeedbackBar && ( {showFeedbackBar && (
<Stack style={{ backgroundColor: "#FFF8F0", padding: "2px 8px" }} horizontal verticalAlign="center"> <Stack
<Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text> style={{ backgroundColor: "#FFF8F0", padding: "2px 8px", minHeight: 32 }}
{showCallout && !hideFeedbackModalForLikedQueries && ( horizontal
<Callout verticalAlign="center"
role="status" >
style={{ padding: 8 }} {userContext.feedbackPolicies?.policyAllowFeedback && (
target="#likeBtn" <Stack horizontal verticalAlign="center">
onDismiss={() => { <Text style={{ fontWeight: 600, fontSize: 12 }}>Provide feedback on the query generated</Text>
setShowCallout(false); {showCallout && !hideFeedbackModalForLikedQueries && (
SubmitFeedback({ <Callout
params: { role="status"
generatedQuery: generatedQuery, style={{ padding: 8 }}
likeQuery: likeQuery, target="#likeBtn"
description: "", onDismiss={() => {
userPrompt: userPrompt,
},
explorer,
databaseId,
containerId,
mode: isSampleCopilotActive ? "Sample" : "User",
});
}}
directionalHint={DirectionalHint.topCenter}
>
<Text>
Thank you. Need to give{" "}
<Link
onClick={() => {
setShowCallout(false); setShowCallout(false);
openFeedbackModal(generatedQuery, true, userPrompt); SubmitFeedback({
params: {
generatedQuery: generatedQuery,
likeQuery: likeQuery,
description: "",
userPrompt: userPrompt,
},
explorer,
databaseId,
containerId,
mode: isSampleCopilotActive ? "Sample" : "User",
});
}} }}
directionalHint={DirectionalHint.topCenter}
> >
more feedback? <Text>
</Link> Thank you. Need to give{" "}
</Text> <Link
</Callout> onClick={() => {
setShowCallout(false);
openFeedbackModal(generatedQuery, true, userPrompt);
}}
>
more feedback?
</Link>
</Text>
</Callout>
)}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
aria-label="Like"
role="toggle"
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (likeQuery === true) {
document.getElementById("likeStatus").innerHTML = "Unpressed";
}
if (likeQuery === false) {
document.getElementById("likeStatus").innerHTML = "Liked";
}
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 10px" }}
role="toggle"
aria-label="Dislike"
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
let toggleStatusValue = "Unpressed";
if (!dislikeQuery) {
openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
toggleStatusValue = "Disliked";
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
}}
/>
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
<Separator vertical style={{ color: "#EDEBE9" }} />
</Stack>
)} )}
<IconButton
id="likeBtn"
style={{ marginLeft: 20 }}
aria-label="Like"
role="toggle"
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
onClick={() => {
setShowCallout(!likeQuery);
setLikeQuery(!likeQuery);
if (likeQuery === true) {
document.getElementById("likeStatus").innerHTML = "Unpressed";
}
if (likeQuery === false) {
document.getElementById("likeStatus").innerHTML = "Liked";
}
if (dislikeQuery) {
setDislikeQuery(!dislikeQuery);
}
}}
/>
<IconButton
style={{ margin: "0 10px" }}
role="toggle"
aria-label="Dislike"
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
onClick={() => {
let toggleStatusValue = "Unpressed";
if (!dislikeQuery) {
openFeedbackModal(generatedQuery, false, userPrompt);
setLikeQuery(false);
toggleStatusValue = "Disliked";
}
setDislikeQuery(!dislikeQuery);
setShowCallout(false);
document.getElementById("likeStatus").innerHTML = toggleStatusValue;
}}
/>
<span role="status" style={{ position: "absolute", left: "-9999px" }} id="likeStatus"></span>
<Separator vertical style={{ color: "#EDEBE9" }} />
<CommandBarButton <CommandBarButton
className="copyQuery"
onClick={copyGeneratedCode} onClick={copyGeneratedCode}
iconProps={{ iconName: "Copy" }} iconProps={{ iconName: "Copy" }}
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }} style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
@@ -625,6 +613,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
Copy query Copy query
</CommandBarButton> </CommandBarButton>
<CommandBarButton <CommandBarButton
className="deleteQuery"
onClick={() => { onClick={() => {
setShowDeletePopup(true); setShowDeletePopup(true);
}} }}
@@ -635,7 +624,6 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
</CommandBarButton> </CommandBarButton>
</Stack> </Stack>
)} )}
<WelcomeModal visible={isWelcomModalVisible()} />
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />} {isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
{query !== "" && query.trim().length !== 0 && ( {query !== "" && query.trim().length !== 0 && (
<DeletePopup <DeletePopup

View File

@@ -42,6 +42,11 @@ function bindDataTable(element: any, valueAccessor: any, allBindings: any, viewM
createDataTable(0, tableEntityListViewModel, queryTablesTab); // Fake a DataTable to start. createDataTable(0, tableEntityListViewModel, queryTablesTab); // Fake a DataTable to start.
$(window).resize(updateTableScrollableRegionMetrics); $(window).resize(updateTableScrollableRegionMetrics);
operationManager.focusTable(); // Also selects the first row if needed.
// Attach the arrow key event handler to the table element
$dataTable.on("keydown", (event: JQueryEventObject) => {
handleArrowKey(element, valueAccessor, allBindings, viewModel, bindingContext, event);
});
} }
function onTableColumnChange(enablePrompt: boolean = true, queryTablesTab: QueryTablesTab) { function onTableColumnChange(enablePrompt: boolean = true, queryTablesTab: QueryTablesTab) {
@@ -210,6 +215,39 @@ function selectionChanged(element: any, valueAccessor: any, allBindings: any, vi
}); });
//selected = bindingContext.$data.selected(); //selected = bindingContext.$data.selected();
} }
function handleArrowKey(
element: any,
valueAccessor: any,
allBindings: any,
viewModel: any,
bindingContext: any,
event: JQueryEventObject,
) {
const isUpArrowKey: boolean = event.keyCode === Constants.keyCodes.UpArrow;
const isDownArrowKey: boolean = event.keyCode === Constants.keyCodes.DownArrow;
if (isUpArrowKey || isDownArrowKey) {
const $dataTable = $(element);
let $selectedRow = $dataTable.find("tr.selected");
if ($selectedRow.length === 0) {
// No row is currently selected, select the first row
$selectedRow = $dataTable.find("tr:first");
$selectedRow.addClass("selected");
} else {
const $targetRow = isUpArrowKey ? $selectedRow.prev("tr") : $selectedRow.next("tr");
if ($targetRow.length > 0) {
// Remove the selected class from the current row and add it to the target row
$selectedRow.removeClass("selected").attr("tabindex", "-1");
$targetRow.addClass("selected").attr("tabindex", "0");
$targetRow.focus();
}
}
event.preventDefault();
}
}
function dataChanged(element: any, valueAccessor: any, allBindings: any, viewModel: any, bindingContext: any) { function dataChanged(element: any, valueAccessor: any, allBindings: any, viewModel: any, bindingContext: any) {
// do nothing for now // do nothing for now

View File

@@ -19,6 +19,7 @@ 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 } from "../../Common/Constants";
export interface CassandraTableKeys { export interface CassandraTableKeys {
partitionKeys: CassandraTableKey[]; partitionKeys: CassandraTableKey[];
@@ -261,6 +262,57 @@ export class CassandraAPIDataClient extends TableDataClient {
query: string, query: string,
shouldNotify?: boolean, shouldNotify?: boolean,
paginationToken?: string, paginationToken?: string,
): Promise<Entities.IListTableEntitiesResult> {
if (!this.useCassandraProxyEndpoint("postQuery")) {
return this.queryDocuments_ToBeDeprecated(collection, query, shouldNotify, paginationToken);
}
const clearMessage =
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
try {
const { authType, databaseAccount } = userContext;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? CassandraProxyAPIs.connectionStringQueryApi
: CassandraProxyAPIs.queryApi;
const data: any = await $.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, {
type: "POST",
contentType: Constants.ContentType.applicationJson,
data: JSON.stringify({
accountName: databaseAccount?.name,
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
resourceId: databaseAccount?.id,
keyspaceId: collection.databaseId,
tableId: collection.id(),
query,
paginationToken,
}),
beforeSend: this.setAuthorizationHeader as any,
cache: false,
});
shouldNotify &&
NotificationConsoleUtils.logConsoleInfo(
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`,
);
return {
Results: data.result,
ContinuationToken: data.paginationToken,
};
} catch (error) {
shouldNotify &&
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
throw error;
} finally {
clearMessage?.();
}
}
public async queryDocuments_ToBeDeprecated(
collection: ViewModels.Collection,
query: string,
shouldNotify?: boolean,
paginationToken?: string,
): Promise<Entities.IListTableEntitiesResult> { ): Promise<Entities.IListTableEntitiesResult> {
const clearMessage = const clearMessage =
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`); shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
@@ -294,7 +346,11 @@ export class CassandraAPIDataClient extends TableDataClient {
}; };
} catch (error) { } catch (error) {
shouldNotify && shouldNotify &&
handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`); handleError(
error,
"QueryDocuments_ToBeDeprecated_Cassandra",
`Failed to query rows for table ${collection.id()}`,
);
throw error; throw error;
} finally { } finally {
clearMessage?.(); clearMessage?.();
@@ -402,6 +458,50 @@ export class CassandraAPIDataClient extends TableDataClient {
} }
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> { public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!this.useCassandraProxyEndpoint("getTableKeys")) {
return this.getTableKeys_ToBeDeprecated(collection);
}
if (!!collection.cassandraKeys) {
return Q.resolve(collection.cassandraKeys);
}
const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`);
const { authType, databaseAccount } = userContext;
const apiEndpoint: string =
authType === AuthType.EncryptedToken ? CassandraProxyAPIs.connectionStringKeysApi : CassandraProxyAPIs.keysApi;
let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKeys>();
$.ajax(endpoint, {
type: "POST",
contentType: Constants.ContentType.applicationJson,
data: JSON.stringify({
accountName: databaseAccount?.name,
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
resourceId: databaseAccount?.id,
keyspaceId: collection.databaseId,
tableId: collection.id(),
}),
beforeSend: this.setAuthorizationHeader as any,
cache: false,
})
.then(
(data: CassandraTableKeys) => {
collection.cassandraKeys = data;
logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`);
deferred.resolve(data);
},
(error: any) => {
handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
deferred.reject(error);
},
)
.done(clearInProgressMessage);
return deferred.promise;
}
public getTableKeys_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
if (!!collection.cassandraKeys) { if (!!collection.cassandraKeys) {
return Q.resolve(collection.cassandraKeys); return Q.resolve(collection.cassandraKeys);
} }
@@ -442,6 +542,51 @@ export class CassandraAPIDataClient extends TableDataClient {
} }
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> { public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
if (!this.useCassandraProxyEndpoint("getSchema")) {
return this.getTableSchema_ToBeDeprecated(collection);
}
if (!!collection.cassandraSchema) {
return Q.resolve(collection.cassandraSchema);
}
const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`);
const { databaseAccount, authType } = userContext;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? CassandraProxyAPIs.connectionStringSchemaApi
: CassandraProxyAPIs.schemaApi;
let endpoint = `${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`;
const deferred = Q.defer<CassandraTableKey[]>();
$.ajax(endpoint, {
type: "POST",
contentType: Constants.ContentType.applicationJson,
data: JSON.stringify({
accountName: databaseAccount?.name,
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
resourceId: databaseAccount?.id,
keyspaceId: collection.databaseId,
tableId: collection.id(),
}),
beforeSend: this.setAuthorizationHeader as any,
cache: false,
})
.then(
(data: any) => {
collection.cassandraSchema = data.columns;
logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`);
deferred.resolve(data.columns);
},
(error: any) => {
handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
deferred.reject(error);
},
)
.done(clearInProgressMessage);
return deferred.promise;
}
public getTableSchema_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
if (!!collection.cassandraSchema) { if (!!collection.cassandraSchema) {
return Q.resolve(collection.cassandraSchema); return Q.resolve(collection.cassandraSchema);
} }
@@ -482,6 +627,44 @@ export class CassandraAPIDataClient extends TableDataClient {
} }
private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> { private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> {
if (!this.useCassandraProxyEndpoint("createOrDelete")) {
return this.createOrDeleteQuery_ToBeDeprecated(cassandraEndpoint, resourceId, query);
}
const deferred = Q.defer();
const { authType, databaseAccount } = userContext;
const apiEndpoint: string =
authType === AuthType.EncryptedToken
? CassandraProxyAPIs.connectionStringCreateOrDeleteApi
: CassandraProxyAPIs.createOrDeleteApi;
$.ajax(`${configContext.CASSANDRA_PROXY_ENDPOINT}/${apiEndpoint}`, {
type: "POST",
contentType: Constants.ContentType.applicationJson,
data: JSON.stringify({
accountName: databaseAccount?.name,
cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint),
resourceId: resourceId,
query: query,
}),
beforeSend: this.setAuthorizationHeader as any,
cache: false,
}).then(
(data: any) => {
deferred.resolve();
},
(reason) => {
deferred.reject(reason);
},
);
return deferred.promise;
}
private createOrDeleteQuery_ToBeDeprecated(
cassandraEndpoint: string,
resourceId: string,
query: string,
): Q.Promise<any> {
const deferred = Q.defer(); const deferred = Q.defer();
const { authType, databaseAccount } = userContext; const { authType, databaseAccount } = userContext;
const apiEndpoint: string = const apiEndpoint: string =
@@ -547,4 +730,19 @@ export class CassandraAPIDataClient extends TableDataClient {
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string { private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
return collection.cassandraKeys.partitionKeys[0].property; return collection.cassandraKeys.partitionKeys[0].property;
} }
private useCassandraProxyEndpoint(api: string): boolean {
let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessCassandraProxy &&
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
[Constants.CassandraProxyEndpoints.Development, Constants.CassandraProxyEndpoints.Mpac].includes(
configContext.CASSANDRA_PROXY_ENDPOINT,
)
);
}
} }

View File

@@ -1,8 +1,8 @@
import { Resource, StoredProcedureDefinition } from "@azure/cosmos"; import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { Pivot, PivotItem } from "@fluentui/react"; import { Pivot, PivotItem } from "@fluentui/react";
import React from "react"; import React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg"; import SaveIcon from "../../../../images/save-cosmos.svg";
import { NormalizedEventKey } from "../../../Common/Constants"; import { NormalizedEventKey } from "../../../Common/Constants";
import { createStoredProcedure } from "../../../Common/dataAccess/createStoredProcedure"; import { createStoredProcedure } from "../../../Common/dataAccess/createStoredProcedure";
@@ -512,7 +512,12 @@ export default class StoredProcedureTabComponent extends React.Component<
return ( return (
<div className="tab-pane flexContainer stored-procedure-tab" role="tabpanel"> <div className="tab-pane flexContainer stored-procedure-tab" role="tabpanel">
<div className="storedTabForm flexContainer"> <div className="storedTabForm flexContainer">
<div className="formTitleFirst">Stored Procedure Id</div> <div className="formTitleFirst">
Stored Procedure Id
<span className="mandatoryStar" style={{ color: "#ff0707", fontSize: "14px", fontWeight: "bold" }}>
*&nbsp;
</span>
</div>
<span className="formTitleTextbox"> <span className="formTitleTextbox">
<input <input
className="formTree" className="formTree"

View File

@@ -1,7 +1,7 @@
import { Link, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react"; import { Link, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants"; import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { configContext, updateConfigContext } from "ConfigContext"; import { Platform, configContext, updateConfigContext } from "ConfigContext";
import { IpRule } from "Contracts/DataModels"; import { IpRule } from "Contracts/DataModels";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { CollectionTabKind } from "Contracts/ViewModels"; import { CollectionTabKind } from "Contracts/ViewModels";
@@ -35,7 +35,7 @@ interface TabsProps {
export const Tabs = ({ explorer }: TabsProps): JSX.Element => { export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs(); const { openedTabs, openedReactTabs, activeTab, activeReactTab, networkSettingsWarning } = useTabs();
const [showRUThresholdMessageBar, setShowRUThresholdMessageBar] = useState<boolean>( const [showRUThresholdMessageBar, setShowRUThresholdMessageBar] = useState<boolean>(
userContext.apiType === "SQL" && !hasRUThresholdBeenConfigured(), userContext.apiType === "SQL" && configContext.platform !== Platform.Fabric && !hasRUThresholdBeenConfigured(),
); );
const [ const [
showMongoAndCassandraProxiesNetworkSettingsWarningState, showMongoAndCassandraProxiesNetworkSettingsWarningState,
@@ -182,11 +182,9 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
)} )}
</span> </span>
<span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span> <span className="tabNavText">{useObservable(tab?.tabTitle || getReactTabTitle())}</span>
{tabKind !== ReactTabKind.Home && ( <span className="tabIconSection">
<span className="tabIconSection"> <CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} />
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} /> </span>
</span>
)}
</div> </div>
</span> </span>
</li> </li>

View File

@@ -58,6 +58,7 @@ export default class Collection implements ViewModels.Collection {
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>; public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy; public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
public usageSizeInKB: ko.Observable<number>; public usageSizeInKB: ko.Observable<number>;
public computedProperties: ko.Observable<DataModels.ComputedProperties>;
public offer: ko.Observable<DataModels.Offer>; public offer: ko.Observable<DataModels.Offer>;
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>; public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
@@ -121,6 +122,7 @@ export default class Collection implements ViewModels.Collection {
this.schema = data.schema; this.schema = data.schema;
this.requestSchema = data.requestSchema; this.requestSchema = data.requestSchema;
this.geospatialConfig = ko.observable(data.geospatialConfig); this.geospatialConfig = ko.observable(data.geospatialConfig);
this.computedProperties = ko.observable(data.computedProperties);
this.partitionKeyPropertyHeaders = this.partitionKey?.paths; this.partitionKeyPropertyHeaders = this.partitionKey?.paths;
this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => { this.partitionKeyProperties = this.partitionKeyPropertyHeaders?.map((partitionKeyPropertyHeader, i) => {

View File

@@ -1,5 +1,6 @@
import { initializeIcons } from "@fluentui/react"; import { initializeIcons } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure";
import * as React from "react"; import * as React from "react";
import { render } from "react-dom"; import { render } from "react-dom";
import ChevronRight from "../images/chevron-right.svg"; import ChevronRight from "../images/chevron-right.svg";
@@ -32,7 +33,8 @@ const App: React.FunctionComponent = () => {
// For showing/hiding panel // For showing/hiding panel
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false); const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
const config = useConfig(); const config = useConfig();
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant } = useAADAuth(); const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } =
useAADAuth();
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>(); const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined); const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
const [connectionString, setConnectionString] = React.useState<string>(); const [connectionString, setConnectionString] = React.useState<string>();
@@ -136,7 +138,10 @@ const App: React.FunctionComponent = () => {
{!isLoggedIn && !encryptedTokenMetadata && ( {!isLoggedIn && !encryptedTokenMetadata && (
<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} /> <ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />
)} )}
{isLoggedIn && <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />} {isLoggedIn && authFailure && <AadAuthorizationFailure {...{ authFailure }} />}
{isLoggedIn && !authFailure && (
<DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />
)}
</> </>
); );
}; };

View File

@@ -0,0 +1,52 @@
.aadAuthFailureContainer {
height: 100%;
width: 100%;
}
.aadAuthFailureContainer .aadAuthFailureFormContainer {
display: -webkit-flex;
display: -ms-flexbox;
display: -ms-flex;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
height: 100%;
width: 100%;
}
.aadAuthFailureContainer .aadAuthFailure {
text-align: center;
display: -webkit-flex;
display: -ms-flexbox;
display: -ms-flex;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
justify-content: center;
height: 100%;
margin-bottom: 60px;
}
.aadAuthFailureContainer .aadAuthFailure .authFailureTitle {
font-size: 16px;
font-weight: 500;
color: #d12d2d;
margin: 16px 8px 8px 8px;
}
.aadAuthFailureContainer .aadAuthFailure .authFailureMessage {
font-size: 14px;
color: #393939;
margin: 16px 16px 16px 16px;
word-wrap: break-word;
white-space: pre-wrap;
}
.aadAuthFailureContainer .aadAuthFailure .authFailureLink {
margin: 8px;
font-size: 14px;
color: #0058ad;
cursor: pointer;
}
.aadAuthFailureContainer .aadAuthFailure .aadAuthFailureContent {
margin: 8px;
color: #393939;
}

View File

@@ -0,0 +1,29 @@
import { AadAuthFailure } from "hooks/useAADAuth";
import * as React from "react";
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
import "../AadAuthorizationFailure.less";
interface Props {
authFailure: AadAuthFailure;
}
export const AadAuthorizationFailure: React.FunctionComponent<Props> = ({ authFailure }: Props) => {
return (
<div id="aadAuthFailure" className="aadAuthFailureContainer" style={{ display: "flex" }}>
<div className="aadAuthFailureFormContainer">
<div className="aadAuthFailure">
<p className="aadAuthFailureContent">
<img src={ConnectImage} alt="Azure Cosmos DB" />
</p>
<p className="authFailureTitle">Authorization Failure</p>
<p className="authFailureMessage">{authFailure.failureMessage}</p>
{authFailure.failureLinkTitle && (
<p className="authFailureLink" onClick={authFailure.failureLinkAction}>
{authFailure.failureLinkTitle}
</p>
)}
</div>
</div>
</div>
);
};

View File

@@ -53,6 +53,21 @@ interface FabricContext {
isReadOnly: boolean; isReadOnly: boolean;
} }
export type AdminFeedbackControlPolicy =
| "connectedExperiences"
| "policyAllowFeedback"
| "policyAllowSurvey"
| "policyAllowScreenshot"
| "policyAllowContact"
| "policyAllowContent"
| "policyEmailCollectionDefault"
| "policyScreenshotDefault"
| "policyContentSamplesDefault";
export type AdminFeedbackPolicySettings = {
[key in AdminFeedbackControlPolicy]: boolean;
};
interface UserContext { interface UserContext {
readonly fabricContext?: FabricContext; readonly fabricContext?: FabricContext;
readonly authType?: AuthType; readonly authType?: AuthType;
@@ -84,6 +99,7 @@ interface UserContext {
collectionCreationDefaults: CollectionCreationDefaults; collectionCreationDefaults: CollectionCreationDefaults;
sampleDataConnectionInfo?: ParsedResourceTokenConnectionString; sampleDataConnectionInfo?: ParsedResourceTokenConnectionString;
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams; readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
readonly feedbackPolicies?: AdminFeedbackPolicySettings;
} }
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";

View File

@@ -1,9 +1,11 @@
import * as msal from "@azure/msal-browser"; import * as msal from "@azure/msal-browser";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata { export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
@@ -43,8 +45,8 @@ export function decryptJWTToken(token: string) {
return JSON.parse(tokenPayload); return JSON.parse(tokenPayload);
} }
export function getMsalInstance() { export async function getMsalInstance() {
const config: msal.Configuration = { const msalConfig: msal.Configuration = {
cache: { cache: {
cacheLocation: "localStorage", cacheLocation: "localStorage",
}, },
@@ -55,8 +57,46 @@ export function getMsalInstance() {
}; };
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
config.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net"; msalConfig.auth.redirectUri = "https://dataexplorer-dev.azurewebsites.net";
} }
const msalInstance = new msal.PublicClientApplication(config);
const msalInstance = new msal.PublicClientApplication(msalConfig);
return msalInstance; return msalInstance;
} }
export async function acquireTokenWithMsal(msalInstance: msal.IPublicClientApplication, request: msal.SilentRequest) {
const tokenRequest = {
account: msalInstance.getActiveAccount() || null,
...request,
};
try {
// attempt silent acquisition first
return (await msalInstance.acquireTokenSilent(tokenRequest)).accessToken;
} catch (silentError) {
if (silentError instanceof msal.InteractionRequiredAuthError) {
try {
// The error indicates that we need to acquire the token interactively.
// This will display a pop-up to re-establish authorization. If user does not
// have pop-ups enabled in their browser, this will fail.
return (await msalInstance.acquireTokenPopup(tokenRequest)).accessToken;
} catch (interactiveError) {
traceFailure(Action.SignInAad, {
request: JSON.stringify(tokenRequest),
acquireTokenType: "interactive",
errorMessage: JSON.stringify(interactiveError),
});
throw interactiveError;
}
} else {
traceFailure(Action.SignInAad, {
request: JSON.stringify(tokenRequest),
acquireTokenType: "silent",
errorMessage: JSON.stringify(silentError),
});
throw silentError;
}
}
}

View File

@@ -98,6 +98,14 @@ export const allowedCassandraProxyEndpoints: ReadonlyArray<string> = [
CassandraProxyEndpoints.Mooncake, CassandraProxyEndpoints.Mooncake,
]; ];
export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
"https://main.cosmos.ext.azure",
"https://localhost:12901",
];
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = { export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"], [CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"], [CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */ /* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
export async function listCassandraKeyspaces( export async function listCassandraKeyspaces(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and collection. */ /* Retrieves the metrics determined by the given filter for the given database account and collection. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */ /* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */ /* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */ /* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and database. */ /* Retrieves the metrics determined by the given filter for the given database account and database. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account and region. */ /* Retrieves the metrics determined by the given filter for the given database account and region. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the properties of an existing Azure Cosmos DB database account. */ /* Retrieves the properties of an existing Azure Cosmos DB database account. */
export async function get( export async function get(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the graphs under an existing Azure Cosmos DB database account. */ /* Lists the graphs under an existing Azure Cosmos DB database account. */
export async function listGraphs( export async function listGraphs(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */ /* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */
export async function listGremlinDatabases( export async function listGremlinDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* List Cosmos DB locations and their properties */ /* List Cosmos DB locations and their properties */
export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> { export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> {

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */ /* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */
export async function listMongoDBDatabases( export async function listMongoDBDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists all of the available Cosmos DB Resource Provider operations. */ /* Lists all of the available Cosmos DB Resource Provider operations. */
export async function list(): Promise<Types.OperationListResult> { export async function list(): Promise<Types.OperationListResult> {

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id. */ /* Retrieves the metrics determined by the given filter for the given partition key range id. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id and region. */ /* Retrieves the metrics determined by the given filter for the given partition key range id and region. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */ /* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */ /* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */ /* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the SQL databases under an existing Azure Cosmos DB database account. */ /* Lists the SQL databases under an existing Azure Cosmos DB database account. */
export async function listSqlDatabases( export async function listSqlDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-02-15-preview";
const apiVersion = "2023-09-15-preview";
/* Lists the Tables under an existing Azure Cosmos DB database account. */ /* Lists the Tables under an existing Azure Cosmos DB database account. */
export async function listTables( export async function listTables(

View File

@@ -3,7 +3,7 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2023-09-15-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
*/ */
/* The List operation response, that contains the client encryption keys and their properties. */ /* The List operation response, that contains the client encryption keys and their properties. */
@@ -566,12 +566,14 @@ export interface DatabaseAccountGetProperties {
minimalTlsVersion?: MinimalTlsVersion; minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
customerManagedKeyStatus?: CustomerManagedKeyStatus; customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean; enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */ /* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel; defaultPriorityLevel?: DefaultPriorityLevel;
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
} }
/* Properties to create and update Azure Cosmos DB database accounts. */ /* Properties to create and update Azure Cosmos DB database accounts. */
@@ -663,12 +665,14 @@ export interface DatabaseAccountCreateUpdateProperties {
minimalTlsVersion?: MinimalTlsVersion; minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
customerManagedKeyStatus?: CustomerManagedKeyStatus; customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean; enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */ /* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel; defaultPriorityLevel?: DefaultPriorityLevel;
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
} }
/* Parameters to create and update Cosmos DB database accounts. */ /* Parameters to create and update Cosmos DB database accounts. */
@@ -763,12 +767,14 @@ export interface DatabaseAccountUpdateProperties {
minimalTlsVersion?: MinimalTlsVersion; minimalTlsVersion?: MinimalTlsVersion;
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */ /* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
customerManagedKeyStatus?: CustomerManagedKeyStatus; customerManagedKeyStatus?: string;
/* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */ /* Flag to indicate enabling/disabling of Priority Based Execution Preview feature on the account */
enablePriorityBasedExecution?: boolean; enablePriorityBasedExecution?: boolean;
/* Enum to indicate default Priority Level of request for Priority Based Execution. */ /* Enum to indicate default Priority Level of request for Priority Based Execution. */
defaultPriorityLevel?: DefaultPriorityLevel; defaultPriorityLevel?: DefaultPriorityLevel;
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
} }
/* Parameters for patching Azure Cosmos DB database account properties. */ /* Parameters for patching Azure Cosmos DB database account properties. */
@@ -1256,6 +1262,9 @@ export interface SqlContainerResource {
/* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */ /* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */
materializedViewDefinition?: MaterializedViewDefinition; materializedViewDefinition?: MaterializedViewDefinition;
/* List of computed properties */
computedProperties?: ComputedProperty[];
} }
/* Cosmos DB indexing policy */ /* Cosmos DB indexing policy */
@@ -1325,6 +1334,14 @@ export interface SpatialSpec {
/* Indicates the spatial type of index. */ /* Indicates the spatial type of index. */
export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon"; export type SpatialType = "Point" | "LineString" | "Polygon" | "MultiPolygon";
/* The definition of a computed property */
export interface ComputedProperty {
/* The name of a computed property, for example - "cp_lowerName" */
name?: string;
/* The query that evaluates the value for computed property, for example - "SELECT VALUE LOWER(c.name) FROM c" */
query?: string;
}
/* The configuration of the partition key to be used for partitioning data into multiple partitions */ /* The configuration of the partition key to be used for partitioning data into multiple partitions */
export interface ContainerPartitionKey { export interface ContainerPartitionKey {
/* List of paths using which data within the container can be partitioned */ /* List of paths using which data within the container can be partitioned */
@@ -1929,6 +1946,8 @@ export interface RestoreParametersBase {
restoreSource?: string; restoreSource?: string;
/* Time to which the account has to be restored (ISO-8601 format). */ /* Time to which the account has to be restored (ISO-8601 format). */
restoreTimestampInUtc?: string; restoreTimestampInUtc?: string;
/* Specifies whether the restored account will have Time-To-Live disabled upon the successful restore. */
restoreWithTtlDisabled?: boolean;
} }
/* Parameters to indicate the information about the restore. */ /* Parameters to indicate the information about the restore. */
@@ -2072,19 +2091,5 @@ export type ContinuousTier = "Continuous7Days" | "Continuous30Days";
/* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */ /* Indicates the minimum allowed Tls version. The default is Tls 1.0, except for Cassandra and Mongo API's, which only work with Tls 1.2. */
export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12"; export type MinimalTlsVersion = "Tls" | "Tls11" | "Tls12";
/* Indicates the status of the Customer Managed Key feature on the account. In case there are errors, the property provides troubleshooting guidance. */
export type CustomerManagedKeyStatus =
| "Access to your account is currently revoked because the Azure Cosmos DB service is unable to obtain the AAD authentication token for the account's default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-active-directory-token-acquisition-error (4000)."
| "Access to your account is currently revoked because the Azure Cosmos DB account's key vault key URI does not follow the expected format; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#improper-syntax-detected-on-the-key-vault-uri-property (4006)."
| "Access to your account is currently revoked because the current default identity no longer has permission to the associated Key Vault key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#default-identity-is-unauthorized-to-access-the-azure-key-vault-key (4002)."
| "Access to your account is currently revoked because the Azure Key Vault DNS name specified by the account's keyvaultkeyuri property could not be resolved; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#unable-to-resolve-the-key-vaults-dns (4009)."
| "Access to your account is currently revoked because the correspondent key is not found on the specified Key Vault; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4003)."
| "Access to your account is currently revoked because the Azure Cosmos DB service is unable to wrap or unwrap the key; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#internal-unwrapping-procedure-error (4005)."
| "Access to your account is currently revoked because the Azure Cosmos DB account has an undefined default identity; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#invalid-azure-cosmos-db-default-identity (4015)."
| "Access to your account is currently revoked because the access rules are blocking outbound requests to the Azure Key Vault service; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide (4016)."
| "Access to your account is currently revoked because the correspondent Azure Key Vault was not found; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide#azure-key-vault-resource-not-found (4017)."
| "Access to your account is currently revoked; for more details about this error and how to restore access to your account please visit https://learn.microsoft.com/en-us/azure/cosmos-db/cmk-troubleshooting-guide"
| "Access to the configured customer managed key confirmed.";
/* Enum to indicate default priorityLevel of requests */ /* Enum to indicate default priorityLevel of requests */
export type DefaultPriorityLevel = "High" | "Low"; export type DefaultPriorityLevel = "High" | "Low";

View File

@@ -2,9 +2,9 @@ import * as msal from "@azure/msal-browser";
import { useBoolean } from "@fluentui/react-hooks"; import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react"; import * as React from "react";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { getMsalInstance } from "../Utils/AuthorizationUtils"; import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
const msalInstance = getMsalInstance(); const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0]; const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId"); const cachedTenantId = localStorage.getItem("cachedTenantId");
@@ -18,6 +18,13 @@ interface ReturnType {
tenantId: string; tenantId: string;
account: msal.AccountInfo; account: msal.AccountInfo;
switchTenant: (tenantId: string) => void; switchTenant: (tenantId: string) => void;
authFailure: AadAuthFailure;
}
export interface AadAuthFailure {
failureMessage: string;
failureLinkTitle?: string;
failureLinkAction?: () => void;
} }
export function useAADAuth(): ReturnType { export function useAADAuth(): ReturnType {
@@ -28,6 +35,7 @@ export function useAADAuth(): ReturnType {
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId); const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
const [graphToken, setGraphToken] = React.useState<string>(); const [graphToken, setGraphToken] = React.useState<string>();
const [armToken, setArmToken] = React.useState<string>(); const [armToken, setArmToken] = React.useState<string>();
const [authFailure, setAuthFailure] = React.useState<AadAuthFailure>(undefined);
msalInstance.setActiveAccount(account); msalInstance.setActiveAccount(account);
const login = React.useCallback(async () => { const login = React.useCallback(async () => {
@@ -61,24 +69,60 @@ export function useAADAuth(): ReturnType {
[account, tenantId], [account, tenantId],
); );
React.useEffect(() => { const acquireTokens = React.useCallback(async () => {
if (account && tenantId) { if (!(account && tenantId)) {
Promise.all([ return;
msalInstance.acquireTokenSilent({ }
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.GRAPH_ENDPOINT}/.default`], try {
}), const armToken = await acquireTokenWithMsal(msalInstance, {
msalInstance.acquireTokenSilent({ authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
authority: `${configContext.AAD_ENDPOINT}${tenantId}`, scopes: [`${configContext.ARM_ENDPOINT}/.default`],
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
}),
]).then(([graphTokenResponse, armTokenResponse]) => {
setGraphToken(graphTokenResponse.accessToken);
setArmToken(armTokenResponse.accessToken);
}); });
setArmToken(armToken);
setAuthFailure(null);
} catch (error) {
if (error instanceof msal.AuthError && error.errorCode === msal.BrowserAuthErrorMessage.popUpWindowError.code) {
// This error can occur when acquireTokenWithMsal() has attempted to acquire token interactively
// and user has popups disabled in browser. This fails as the popup is not the result of a explicit user
// action. In this case, we display the failure and a link to repeat the operation. Clicking on the
// link is a user action so it will work even if popups have been disabled.
// See: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/76#issuecomment-324787539
setAuthFailure({
failureMessage:
"We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease click below to retry authorization without requiring popups being enabled.",
failureLinkTitle: "Retry Authorization",
failureLinkAction: acquireTokens,
});
} else {
const errorJson = JSON.stringify(error);
setAuthFailure({
failureMessage: `We were unable to establish authorization for this account, due to the following error: \n${errorJson}`,
});
}
}
try {
const graphToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
});
setGraphToken(graphToken);
} catch (error) {
// Graph token is used only for retrieving user photo at the moment, so
// it's not critical if this fails.
console.warn("Error acquiring graph token: " + error);
} }
}, [account, tenantId]); }, [account, tenantId]);
React.useEffect(() => {
if (account && tenantId && !authFailure) {
acquireTokens();
}
}, [account, tenantId, acquireTokens, authFailure]);
return { return {
account, account,
tenantId, tenantId,
@@ -88,5 +132,6 @@ export function useAADAuth(): ReturnType {
login, login,
logout, logout,
switchTenant, switchTenant,
authFailure,
}; };
} }

View File

@@ -6,6 +6,7 @@ import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdap
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -35,7 +36,7 @@ import {
import { extractFeatures } from "../Platform/Hosted/extractFeatures"; import { extractFeatures } from "../Platform/Hosted/extractFeatures";
import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility"; import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext"; import { Node, PortalEnv, updateUserContext, userContext } from "../UserContext";
import { getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils"; import { acquireTokenWithMsal, getAuthorizationHeader, getMsalInstance } from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation"; import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; import { listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types"; import { DatabaseAccountListKeysResult } from "../Utils/arm/generatedClients/cosmos/types";
@@ -243,16 +244,19 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
let keys: DatabaseAccountListKeysResult = {}; let keys: DatabaseAccountListKeysResult = {};
if (account.properties?.documentEndpoint) { if (account.properties?.documentEndpoint) {
const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default"); const hrefEndpoint = new URL(account.properties.documentEndpoint).href.replace(/\/$/, "/.default");
const msalInstance = getMsalInstance(); const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0]; const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount); msalInstance.setActiveAccount(cachedAccount);
const cachedTenantId = localStorage.getItem("cachedTenantId"); const cachedTenantId = localStorage.getItem("cachedTenantId");
const aadTokenResponse = await msalInstance.acquireTokenSilent({ try {
forceRefresh: true, aadToken = await acquireTokenWithMsal(msalInstance, {
scopes: [hrefEndpoint], forceRefresh: true,
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`, scopes: [hrefEndpoint],
}); authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
aadToken = aadTokenResponse.accessToken; });
} catch (authError) {
logConsoleError("Failed to acquire authorization token: " + authError);
}
} }
try { try {
if (!account.properties.disableLocalAuth) { if (!account.properties.disableLocalAuth) {
@@ -495,6 +499,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
hasWriteAccess: inputs.hasWriteAccess ?? true, hasWriteAccess: inputs.hasWriteAccess ?? true,
collectionCreationDefaults: inputs.defaultCollectionThroughput, collectionCreationDefaults: inputs.defaultCollectionThroughput,
isTryCosmosDBSubscription: inputs.isTryCosmosDBSubscription, isTryCosmosDBSubscription: inputs.isTryCosmosDBSubscription,
feedbackPolicies: inputs.feedbackPolicies,
}); });
if (inputs.isPostgresAccount) { if (inputs.isPostgresAccount) {

View File

@@ -105,7 +105,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
return true; return true;
}); });
if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) { if (updatedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home }); set({ activeTab: undefined, activeReactTab: undefined });
} }
if (tab.tabId === activeTab.tabId && tabIndex !== -1) { if (tab.tabId === activeTab.tabId && tabIndex !== -1) {
@@ -143,7 +143,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
}); });
if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) { if (get().openedTabs.length === 0 && configContext.platform !== Platform.Fabric) {
set({ activeTab: undefined, activeReactTab: ReactTabKind.Home }); set({ activeTab: undefined, activeReactTab: undefined });
} }
} }
}, },

View File

@@ -20,11 +20,11 @@ test("Cassandra keyspace and table CRUD", async () => {
await explorer.fill('[aria-label="addCollection-table Id Create table"]', tableId); await explorer.fill('[aria-label="addCollection-table Id Create table"]', tableId);
await explorer.click("#sidePanelOkButton"); await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${keyspaceId}`); await explorer.click(`.nodeItem >> text=${keyspaceId}`);
await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`); await explorer.click(`[data-test="${tableId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Table")'); await explorer.click('button[role="menuitem"]:has-text("Delete Table")');
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId); await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');
await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More"]`); await explorer.click(`[data-test="${keyspaceId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")'); await explorer.click('button[role="menuitem"]:has-text("Delete Keyspace")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', keyspaceId); await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', keyspaceId);

View File

@@ -21,11 +21,11 @@ test("Graph CRUD", async () => {
await explorer.click(`.nodeItem >> text=${databaseId}`); await explorer.click(`.nodeItem >> text=${databaseId}`);
await explorer.click(`.nodeItem >> text=${containerId}`); await explorer.click(`.nodeItem >> text=${containerId}`);
// Delete database and graph // Delete database and graph
await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`); await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Graph")'); await explorer.click('button[role="menuitem"]:has-text("Delete Graph")');
await explorer.fill('text=* Confirm by typing the graph id >> input[type="text"]', containerId); await explorer.fill('text=* Confirm by typing the graph id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);

View File

@@ -32,11 +32,11 @@ test("Mongo CRUD", async () => {
await explorer.click('[aria-label="Delete index Button"]'); await explorer.click('[aria-label="Delete index Button"]');
await explorer.click('[data-test="Save"]'); await explorer.click('[data-test="Save"]');
// Delete database and collection // Delete database and collection
await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`); await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Collection")'); await explorer.click('button[role="menuitem"]:has-text("Delete Collection")');
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId); await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);

View File

@@ -21,11 +21,11 @@ test("Mongo CRUD", async () => {
explorer.click(`.nodeItem >> text=${databaseId}`); explorer.click(`.nodeItem >> text=${databaseId}`);
explorer.click(`.nodeItem >> text=${containerId}`); explorer.click(`.nodeItem >> text=${containerId}`);
// Delete database and collection // Delete database and collection
explorer.click(`[data-test="${containerId}"] [aria-label="More"]`); explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
explorer.click('button[role="menuitem"]:has-text("Delete Collection")'); explorer.click('button[role="menuitem"]:has-text("Delete Collection")');
await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId); await explorer.fill('text=* Confirm by typing the collection id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);

View File

@@ -18,11 +18,11 @@ test("SQL CRUD", async () => {
await explorer.fill('[aria-label="Partition key"]', "/pk"); await explorer.fill('[aria-label="Partition key"]', "/pk");
await explorer.click("#sidePanelOkButton"); await explorer.click("#sidePanelOkButton");
await explorer.click(`.nodeItem >> text=${databaseId}`); await explorer.click(`.nodeItem >> text=${databaseId}`);
await explorer.click(`[data-test="${containerId}"] [aria-label="More"]`); await explorer.click(`[data-test="${containerId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Container")'); await explorer.click('button[role="menuitem"]:has-text("Delete Container")');
await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId); await explorer.fill('text=* Confirm by typing the container id >> input[type="text"]', containerId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');
await explorer.click(`[data-test="${databaseId}"] [aria-label="More"]`); await explorer.click(`[data-test="${databaseId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Database")'); await explorer.click('button[role="menuitem"]:has-text("Delete Database")');
await explorer.click('text=* Confirm by typing the database id >> input[type="text"]'); await explorer.click('text=* Confirm by typing the database id >> input[type="text"]');
await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId); await explorer.fill('text=* Confirm by typing the database id >> input[type="text"]', databaseId);

View File

@@ -17,7 +17,7 @@ test("Tables CRUD", async () => {
await explorer.fill('[aria-label="Table id, Example Table1"]', tableId); await explorer.fill('[aria-label="Table id, Example Table1"]', tableId);
await explorer.click("#sidePanelOkButton"); await explorer.click("#sidePanelOkButton");
await explorer.click(`[data-test="TablesDB"]`); await explorer.click(`[data-test="TablesDB"]`);
await explorer.click(`[data-test="${tableId}"] [aria-label="More"]`); await explorer.click(`[data-test="${tableId}"] [aria-label="More options"]`);
await explorer.click('button[role="menuitem"]:has-text("Delete Table")'); await explorer.click('button[role="menuitem"]:has-text("Delete Table")');
await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId); await explorer.fill('text=* Confirm by typing the table id >> input[type="text"]', tableId);
await explorer.click('[aria-label="OK"]'); await explorer.click('[aria-label="OK"]');

View File

@@ -16,7 +16,7 @@ Results of this file should be checked into the repo.
*/ */
// CHANGE THESE VALUES TO GENERATE NEW CLIENTS // CHANGE THESE VALUES TO GENERATE NEW CLIENTS
const version = "2023-11-15-preview"; const version = "2024-02-15-preview";
/* The following are legal options for resourceName but you generally will only use cosmos-db: /* The following are legal options for resourceName but you generally will only use cosmos-db:
"cosmos-db" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" | "cosmos-db" | "managedCassandra" | "mongorbac" | "notebook" | "privateEndpointConnection" | "privateLinkResources" |
"rbac" | "restorable" | "services" | "dataTransferService" "rbac" | "restorable" | "services" | "dataTransferService"