Add new Portal Backend Sample Data API and remove Notifications API references (#1965)
* Fixed Sample Data logic and remove notifications references * fixed undefined * fixed unit tests * fixed format test * cleanup --------- Co-authored-by: Asier Isayas <aisayas@microsoft.com>
This commit is contained in:
parent
825a5d5257
commit
82bdeff158
|
@ -136,6 +136,7 @@ export class BackendApi {
|
||||||
public static readonly AccountRestrictions: string = "AccountRestrictions";
|
public static readonly AccountRestrictions: string = "AccountRestrictions";
|
||||||
public static readonly RuntimeProxy: string = "RuntimeProxy";
|
public static readonly RuntimeProxy: string = "RuntimeProxy";
|
||||||
public static readonly DisallowedLocations: string = "DisallowedLocations";
|
public static readonly DisallowedLocations: string = "DisallowedLocations";
|
||||||
|
public static readonly SampleData: string = "SampleData";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PortalBackendEndpoints {
|
export class PortalBackendEndpoints {
|
||||||
|
@ -503,7 +504,7 @@ export class PriorityLevel {
|
||||||
public static readonly Default = "low";
|
public static readonly Default = "low";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDb";
|
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
||||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||||
|
|
||||||
export const QueryCopilotSampleContainerSchema = {
|
export const QueryCopilotSampleContainerSchema = {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { PortalBackendEndpoints } from "Common/Constants";
|
||||||
|
import { updateConfigContext } from "ConfigContext";
|
||||||
import * as EnvironmentUtility from "./EnvironmentUtility";
|
import * as EnvironmentUtility from "./EnvironmentUtility";
|
||||||
|
|
||||||
describe("Environment Utility Test", () => {
|
describe("Environment Utility Test", () => {
|
||||||
|
@ -11,4 +13,18 @@ describe("Environment Utility Test", () => {
|
||||||
const expectedResult = "test/";
|
const expectedResult = "test/";
|
||||||
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
|
expect(EnvironmentUtility.normalizeArmEndpoint(uri)).toEqual(expectedResult);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Detect environment is Mpac", () => {
|
||||||
|
updateConfigContext({
|
||||||
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
||||||
|
});
|
||||||
|
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Mpac);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Detect environment is Development", () => {
|
||||||
|
updateConfigContext({
|
||||||
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Development,
|
||||||
|
});
|
||||||
|
expect(EnvironmentUtility.getEnvironment()).toBe(EnvironmentUtility.Environment.Development);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,29 @@
|
||||||
|
import { PortalBackendEndpoints } from "Common/Constants";
|
||||||
|
import { configContext } from "ConfigContext";
|
||||||
|
|
||||||
export function normalizeArmEndpoint(uri: string): string {
|
export function normalizeArmEndpoint(uri: string): string {
|
||||||
if (uri && uri.slice(-1) !== "/") {
|
if (uri && uri.slice(-1) !== "/") {
|
||||||
return `${uri}/`;
|
return `${uri}/`;
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Environment {
|
||||||
|
Development = "Development",
|
||||||
|
Mpac = "MPAC",
|
||||||
|
Prod = "Prod",
|
||||||
|
Fairfax = "Fairfax",
|
||||||
|
Mooncake = "Mooncake",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEnvironment = (): Environment => {
|
||||||
|
const environmentMap: { [key: string]: Environment } = {
|
||||||
|
[PortalBackendEndpoints.Development]: Environment.Development,
|
||||||
|
[PortalBackendEndpoints.Mpac]: Environment.Mpac,
|
||||||
|
[PortalBackendEndpoints.Prod]: Environment.Prod,
|
||||||
|
[PortalBackendEndpoints.Fairfax]: Environment.Fairfax,
|
||||||
|
[PortalBackendEndpoints.Mooncake]: Environment.Mooncake,
|
||||||
|
};
|
||||||
|
|
||||||
|
return environmentMap[configContext.PORTAL_BACKEND_ENDPOINT];
|
||||||
|
};
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { userContext } from "../UserContext";
|
|
||||||
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|
||||||
|
|
||||||
const notificationsPath = () => {
|
|
||||||
switch (configContext.platform) {
|
|
||||||
case Platform.Hosted:
|
|
||||||
return "/api/guest/notifications";
|
|
||||||
case Platform.Portal:
|
|
||||||
return "/api/notifications";
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown platform: ${configContext.platform}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
|
|
||||||
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
|
|
||||||
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
|
|
||||||
databaseAccount.name
|
|
||||||
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
|
|
||||||
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
|
||||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
|
||||||
|
|
||||||
const response = await window.fetch(url, {
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(await response.text());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await response.json()) as DataModels.Notification[];
|
|
||||||
};
|
|
|
@ -49,13 +49,13 @@ export interface ConfigContext {
|
||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
BACKEND_ENDPOINT?: string;
|
||||||
PORTAL_BACKEND_ENDPOINT?: string;
|
PORTAL_BACKEND_ENDPOINT: string;
|
||||||
NEW_BACKEND_APIS?: BackendApi[];
|
NEW_BACKEND_APIS?: BackendApi[];
|
||||||
MONGO_BACKEND_ENDPOINT?: string;
|
MONGO_BACKEND_ENDPOINT?: string;
|
||||||
MONGO_PROXY_ENDPOINT?: string;
|
MONGO_PROXY_ENDPOINT: string;
|
||||||
NEW_MONGO_APIS?: string[];
|
NEW_MONGO_APIS?: string[];
|
||||||
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
|
||||||
CASSANDRA_PROXY_ENDPOINT?: string;
|
CASSANDRA_PROXY_ENDPOINT: string;
|
||||||
NEW_CASSANDRA_APIS?: string[];
|
NEW_CASSANDRA_APIS?: string[];
|
||||||
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
|
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
|
||||||
PROXY_PATH?: string;
|
PROXY_PATH?: string;
|
||||||
|
|
|
@ -98,7 +98,6 @@ export interface Database extends TreeNode {
|
||||||
openAddCollection(database: Database, event: MouseEvent): void;
|
openAddCollection(database: Database, event: MouseEvent): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
loadOffer(): Promise<void>;
|
loadOffer(): Promise<void>;
|
||||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionBase extends TreeNode {
|
export interface CollectionBase extends TreeNode {
|
||||||
|
@ -191,8 +190,6 @@ export interface Collection extends CollectionBase {
|
||||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||||
|
|
||||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -134,7 +134,6 @@ describe("SettingsComponent", () => {
|
||||||
readSettings: undefined,
|
readSettings: undefined,
|
||||||
onSettingsClick: undefined,
|
onSettingsClick: undefined,
|
||||||
loadOffer: undefined,
|
loadOffer: undefined,
|
||||||
getPendingThroughputSplitNotification: undefined,
|
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
newCollection.getDatabase = () => newDatabase;
|
newCollection.getDatabase = () => newDatabase;
|
||||||
newCollection.offer = ko.observable(undefined);
|
newCollection.offer = ko.observable(undefined);
|
||||||
|
|
|
@ -130,7 +130,6 @@ export interface SettingsComponentState {
|
||||||
conflictResolutionPolicyProcedureBaseline: string;
|
conflictResolutionPolicyProcedureBaseline: string;
|
||||||
isConflictResolutionDirty: boolean;
|
isConflictResolutionDirty: boolean;
|
||||||
|
|
||||||
initialNotification: DataModels.Notification;
|
|
||||||
selectedTab: SettingsV2TabTypes;
|
selectedTab: SettingsV2TabTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +228,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||||
conflictResolutionPolicyProcedureBaseline: undefined,
|
conflictResolutionPolicyProcedureBaseline: undefined,
|
||||||
isConflictResolutionDirty: false,
|
isConflictResolutionDirty: false,
|
||||||
|
|
||||||
initialNotification: undefined,
|
|
||||||
selectedTab: SettingsV2TabTypes.ScaleTab,
|
selectedTab: SettingsV2TabTypes.ScaleTab,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1052,7 +1050,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||||
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
|
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
|
||||||
onScaleSaveableChange: this.onScaleSaveableChange,
|
onScaleSaveableChange: this.onScaleSaveableChange,
|
||||||
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
onScaleDiscardableChange: this.onScaleDiscardableChange,
|
||||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
|
||||||
throughputError: this.state.throughputError,
|
throughputError: this.state.throughputError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
import { shallow } from "enzyme";
|
|
||||||
import ko from "knockout";
|
|
||||||
import React from "react";
|
|
||||||
import * as Constants from "../../../../Common/Constants";
|
import * as Constants from "../../../../Common/Constants";
|
||||||
import * as DataModels from "../../../../Contracts/DataModels";
|
|
||||||
import { updateUserContext } from "../../../../UserContext";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { throughputUnit } from "../SettingsRenderUtils";
|
|
||||||
import { collection } from "../TestUtils";
|
import { collection } from "../TestUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
|
||||||
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
|
|
||||||
|
|
||||||
describe("ScaleComponent", () => {
|
describe("ScaleComponent", () => {
|
||||||
const targetThroughput = 6000;
|
|
||||||
|
|
||||||
const baseProps: ScaleComponentProps = {
|
const baseProps: ScaleComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
database: undefined,
|
database: undefined,
|
||||||
|
@ -36,39 +28,8 @@ describe("ScaleComponent", () => {
|
||||||
onScaleDiscardableChange: () => {
|
onScaleDiscardableChange: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
initialNotification: {
|
|
||||||
description: `Throughput update for ${targetThroughput} ${throughputUnit}`,
|
|
||||||
} as DataModels.Notification,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders with correct initial notification", () => {
|
|
||||||
let wrapper = shallow(<ScaleComponent {...baseProps} />);
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
|
|
||||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
|
|
||||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
|
|
||||||
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(`${targetThroughput}`);
|
|
||||||
|
|
||||||
const newCollection = { ...collection };
|
|
||||||
const maxThroughput = 5000;
|
|
||||||
newCollection.offer = ko.observable({
|
|
||||||
manualThroughput: undefined,
|
|
||||||
autoscaleMaxThroughput: maxThroughput,
|
|
||||||
minimumThroughput: 400,
|
|
||||||
id: "offer",
|
|
||||||
offerReplacePending: true,
|
|
||||||
});
|
|
||||||
const newProps = {
|
|
||||||
...baseProps,
|
|
||||||
initialNotification: undefined as DataModels.Notification,
|
|
||||||
collection: newCollection,
|
|
||||||
};
|
|
||||||
wrapper = shallow(<ScaleComponent {...newProps} />);
|
|
||||||
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
|
|
||||||
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
|
|
||||||
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(`${maxThroughput}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("autoScale disabled", () => {
|
it("autoScale disabled", () => {
|
||||||
const scaleComponent = new ScaleComponent(baseProps);
|
const scaleComponent = new ScaleComponent(baseProps);
|
||||||
expect(scaleComponent.isAutoScaleEnabled()).toEqual(false);
|
expect(scaleComponent.isAutoScaleEnabled()).toEqual(false);
|
||||||
|
|
|
@ -10,7 +10,6 @@ import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
|
||||||
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
getTextFieldStyles,
|
||||||
getThroughputApplyLongDelayMessage,
|
|
||||||
getThroughputApplyShortDelayMessage,
|
getThroughputApplyShortDelayMessage,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
throughputUnit,
|
throughputUnit,
|
||||||
|
@ -34,7 +33,6 @@ export interface ScaleComponentProps {
|
||||||
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
|
||||||
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
onScaleSaveableChange: (isScaleSaveable: boolean) => void;
|
||||||
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
|
||||||
initialNotification: DataModels.Notification;
|
|
||||||
throughputError?: string;
|
throughputError?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,10 +100,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public getInitialNotificationElement = (): JSX.Element => {
|
public getInitialNotificationElement = (): JSX.Element => {
|
||||||
if (this.props.initialNotification) {
|
|
||||||
return this.getLongDelayMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.offer?.offerReplacePending) {
|
if (this.offer?.offerReplacePending) {
|
||||||
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
|
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
|
||||||
return getThroughputApplyShortDelayMessage(
|
return getThroughputApplyShortDelayMessage(
|
||||||
|
@ -120,26 +114,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
public getLongDelayMessage = (): JSX.Element => {
|
|
||||||
const matches: string[] = this.props.initialNotification?.description.match(
|
|
||||||
`Throughput update for (.*) ${throughputUnit}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const throughput = this.props.throughputBaseline;
|
|
||||||
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
|
|
||||||
if (targetThroughput) {
|
|
||||||
return getThroughputApplyLongDelayMessage(
|
|
||||||
this.props.wasAutopilotOriginallySet,
|
|
||||||
throughput,
|
|
||||||
throughputUnit,
|
|
||||||
this.databaseId,
|
|
||||||
this.collectionId,
|
|
||||||
targetThroughput,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
private getThroughputInputComponent = (): JSX.Element => (
|
private getThroughputInputComponent = (): JSX.Element => (
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
databaseAccount={userContext?.databaseAccount}
|
databaseAccount={userContext?.databaseAccount}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<StyledMessageBar
|
|
||||||
messageBarType={5}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
id="throughputApplyLongDelayMessage"
|
|
||||||
styles={
|
|
||||||
{
|
|
||||||
"root": {
|
|
||||||
"color": "windowtext",
|
|
||||||
"fontSize": 14,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
|
|
||||||
<br />
|
|
||||||
Database: test, Container: test
|
|
||||||
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
|
||||||
</Text>
|
|
||||||
</StyledMessageBar>
|
|
||||||
<Stack
|
|
||||||
tokens={
|
|
||||||
{
|
|
||||||
"childrenGap": 20,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<ThroughputInputAutoPilotV3Component
|
|
||||||
canExceedMaximumValue={true}
|
|
||||||
collectionName="test"
|
|
||||||
databaseName="test"
|
|
||||||
isAutoPilotSelected={false}
|
|
||||||
isEmulator={false}
|
|
||||||
isEnabled={true}
|
|
||||||
isFixed={false}
|
|
||||||
label="Throughput (6,000 - unlimited RU/s)"
|
|
||||||
maxAutoPilotThroughput={4000}
|
|
||||||
maxAutoPilotThroughputBaseline={4000}
|
|
||||||
maximum={1000000}
|
|
||||||
minimum={6000}
|
|
||||||
onAutoPilotSelected={[Function]}
|
|
||||||
onMaxAutoPilotThroughputChange={[Function]}
|
|
||||||
onScaleDiscardableChange={[Function]}
|
|
||||||
onScaleSaveableChange={[Function]}
|
|
||||||
onThroughputChange={[Function]}
|
|
||||||
spendAckChecked={false}
|
|
||||||
throughput={1000}
|
|
||||||
throughputBaseline={1000}
|
|
||||||
usageSizeInKB={100}
|
|
||||||
wasAutopilotOriginallySet={true}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
`;
|
|
|
@ -44,7 +44,6 @@ describe("SettingsUtils", () => {
|
||||||
readSettings: undefined,
|
readSettings: undefined,
|
||||||
onSettingsClick: undefined,
|
onSettingsClick: undefined,
|
||||||
loadOffer: undefined,
|
loadOffer: undefined,
|
||||||
getPendingThroughputSplitNotification: undefined,
|
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
};
|
};
|
||||||
newCollection.offer(undefined);
|
newCollection.offer(undefined);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as msal from "@azure/msal-browser";
|
import * as msal from "@azure/msal-browser";
|
||||||
import { Link } from "@fluentui/react/lib/Link";
|
import { Link } from "@fluentui/react/lib/Link";
|
||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||||
|
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
|
||||||
import { sendMessage } from "Common/MessageHandler";
|
import { sendMessage } from "Common/MessageHandler";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import { MessageTypes } from "Contracts/ExplorerContracts";
|
import { MessageTypes } from "Contracts/ExplorerContracts";
|
||||||
|
@ -1178,7 +1179,11 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async configureCopilot(): Promise<void> {
|
public async configureCopilot(): Promise<void> {
|
||||||
if (userContext.apiType !== "SQL" || !userContext.subscriptionId) {
|
if (
|
||||||
|
userContext.apiType !== "SQL" ||
|
||||||
|
!userContext.subscriptionId ||
|
||||||
|
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const copilotEnabledPromise = getCopilotEnabled();
|
const copilotEnabledPromise = getCopilotEnabled();
|
||||||
|
|
|
@ -171,7 +171,7 @@ export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||||
the query builder.
|
the query builder.
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 24 }}>Database Id</Text>
|
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 24 }}>Database Id</Text>
|
||||||
<Text style={{ fontSize: 13 }}>CopilotSampleDb</Text>
|
<Text style={{ fontSize: 13 }}>CopilotSampleDB</Text>
|
||||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database throughput (autoscale)</Text>
|
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database throughput (autoscale)</Text>
|
||||||
<Text style={{ fontSize: 13 }}>Autoscale</Text>
|
<Text style={{ fontSize: 13 }}>Autoscale</Text>
|
||||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database Max RU/s</Text>
|
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database Max RU/s</Text>
|
||||||
|
|
|
@ -90,7 +90,7 @@ describe("QueryCopilotUtilities", () => {
|
||||||
|
|
||||||
// Mock the items.query method to return the mockResult
|
// Mock the items.query method to return the mockResult
|
||||||
(
|
(
|
||||||
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").items.query as jest.Mock
|
sampleDataClient().database("CopilotSampleDB").container("SampleContainer").items.query as jest.Mock
|
||||||
).mockReturnValue(mockResult);
|
).mockReturnValue(mockResult);
|
||||||
|
|
||||||
const result = querySampleDocuments(query, options);
|
const result = querySampleDocuments(query, options);
|
||||||
|
@ -119,10 +119,10 @@ describe("QueryCopilotUtilities", () => {
|
||||||
const result = await readSampleDocument(documentId);
|
const result = await readSampleDocument(documentId);
|
||||||
|
|
||||||
expect(sampleDataClient).toHaveBeenCalled();
|
expect(sampleDataClient).toHaveBeenCalled();
|
||||||
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
|
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDB");
|
||||||
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
|
expect(sampleDataClient().database("CopilotSampleDB").container).toHaveBeenCalledWith("SampleContainer");
|
||||||
expect(
|
expect(
|
||||||
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read,
|
sampleDataClient().database("CopilotSampleDB").container("SampleContainer").item("DocumentId", undefined).read,
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
expect(result).toEqual(expectedResponse);
|
expect(result).toEqual(expectedResponse);
|
||||||
});
|
});
|
||||||
|
@ -144,10 +144,10 @@ describe("QueryCopilotUtilities", () => {
|
||||||
await expect(readSampleDocument(documentId)).rejects.toStrictEqual(errorMock);
|
await expect(readSampleDocument(documentId)).rejects.toStrictEqual(errorMock);
|
||||||
|
|
||||||
expect(sampleDataClient).toHaveBeenCalled();
|
expect(sampleDataClient).toHaveBeenCalled();
|
||||||
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDb");
|
expect(sampleDataClient().database).toHaveBeenCalledWith("CopilotSampleDB");
|
||||||
expect(sampleDataClient().database("CopilotSampleDb").container).toHaveBeenCalledWith("SampleContainer");
|
expect(sampleDataClient().database("CopilotSampleDB").container).toHaveBeenCalledWith("SampleContainer");
|
||||||
expect(
|
expect(
|
||||||
sampleDataClient().database("CopilotSampleDb").container("SampleContainer").item("DocumentId", undefined).read,
|
sampleDataClient().database("CopilotSampleDB").container("SampleContainer").item("DocumentId", undefined).read,
|
||||||
).toHaveBeenCalled();
|
).toHaveBeenCalled();
|
||||||
expect(handleError).toHaveBeenCalledWith(errorMock, "ReadDocument", expect.any(String));
|
expect(handleError).toHaveBeenCalledWith(errorMock, "ReadDocument", expect.any(String));
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
||||||
>
|
>
|
||||||
<QueryCopilotPromptbar
|
<QueryCopilotPromptbar
|
||||||
containerId="SampleContainer"
|
containerId="SampleContainer"
|
||||||
databaseId="CopilotSampleDb"
|
databaseId="CopilotSampleDB"
|
||||||
explorer={
|
explorer={
|
||||||
Explorer {
|
Explorer {
|
||||||
"_isInitializingNotebooks": false,
|
"_isInitializingNotebooks": false,
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe("QueryTabComponent", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const propsMock: Readonly<IQueryTabComponentProps> = {
|
const propsMock: Readonly<IQueryTabComponentProps> = {
|
||||||
collection: { databaseId: "CopilotSampleDb" },
|
collection: { databaseId: "CopilotSampleDB" },
|
||||||
onTabAccessor: () => jest.fn(),
|
onTabAccessor: () => jest.fn(),
|
||||||
isExecutionError: false,
|
isExecutionError: false,
|
||||||
tabId: "mockTabId",
|
tabId: "mockTabId",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as ThemeUtility from "../../Common/ThemeUtility";
|
import * as ThemeUtility from "../../Common/ThemeUtility";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
@ -28,7 +27,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||||
public tabPath: ko.Observable<string>;
|
public tabPath: ko.Observable<string>;
|
||||||
public isExecutionError = ko.observable(false);
|
public isExecutionError = ko.observable(false);
|
||||||
public isExecuting = ko.observable(false);
|
public isExecuting = ko.observable(false);
|
||||||
public pendingNotification?: ko.Observable<DataModels.Notification>;
|
|
||||||
protected _theme: string;
|
protected _theme: string;
|
||||||
public onLoadStartKey: number;
|
public onLoadStartKey: number;
|
||||||
|
|
||||||
|
@ -45,7 +43,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||||
this.tabPath =
|
this.tabPath =
|
||||||
this.collection &&
|
this.collection &&
|
||||||
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`);
|
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`);
|
||||||
this.pendingNotification = ko.observable<DataModels.Notification>(undefined);
|
|
||||||
this.onLoadStartKey = options.onLoadStartKey;
|
this.onLoadStartKey = options.onLoadStartKey;
|
||||||
this.closeTabButton = {
|
this.closeTabButton = {
|
||||||
enabled: ko.computed<boolean>(() => {
|
enabled: ko.computed<boolean>(() => {
|
||||||
|
|
|
@ -5,8 +5,6 @@ import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
|
||||||
import { bulkCreateDocument } from "../../Common/dataAccess/bulkCreateDocument";
|
import { bulkCreateDocument } from "../../Common/dataAccess/bulkCreateDocument";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
import { getCollectionUsageSizeInKB } from "../../Common/dataAccess/getCollectionDataUsageSize";
|
||||||
|
@ -1020,41 +1018,6 @@ export default class Collection implements ViewModels.Collection {
|
||||||
this.uploadFiles(event.originalEvent.dataTransfer.files);
|
this.uploadFiles(event.originalEvent.dataTransfer.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
|
||||||
if (!this.container) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const notifications: DataModels.Notification[] = await fetchPortalNotifications();
|
|
||||||
if (!notifications || notifications.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.find(notifications, (notification: DataModels.Notification) => {
|
|
||||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
|
||||||
return (
|
|
||||||
notification.kind === "message" &&
|
|
||||||
notification.collectionName === this.id() &&
|
|
||||||
notification.description &&
|
|
||||||
throughputUpdateRegExp.test(notification.description)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
accountName: userContext?.databaseAccount,
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id(),
|
|
||||||
}),
|
|
||||||
"Settings tree node",
|
|
||||||
);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async uploadFiles(files: FileList): Promise<{ data: UploadDetailsRecord[] }> {
|
public async uploadFiles(files: FileList): Promise<{ data: UploadDetailsRecord[] }> {
|
||||||
const data = await Promise.all(Array.from(files).map((file) => this.uploadFile(file)));
|
const data = await Promise.all(Array.from(files).map((file) => this.uploadFile(file)));
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ import * as _ from "underscore";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as Logger from "../../Common/Logger";
|
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
|
||||||
import { readCollections, readCollectionsWithPagination } from "../../Common/dataAccess/readCollections";
|
import { readCollections, readCollectionsWithPagination } from "../../Common/dataAccess/readCollections";
|
||||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
@ -76,7 +74,6 @@ export default class Database implements ViewModels.Database {
|
||||||
await useDatabases.getState().loadAllOffers();
|
await useDatabases.getState().loadAllOffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
|
||||||
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
const tabKind = ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||||
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
const matchingTabs = useTabs.getState().getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||||
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
|
let settingsTab = matchingTabs?.[0] as DatabaseSettingsTabV2;
|
||||||
|
@ -87,10 +84,8 @@ export default class Database implements ViewModels.Database {
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: "Scale",
|
tabTitle: "Scale",
|
||||||
});
|
});
|
||||||
pendingNotificationsPromise.then(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
try {
|
||||||
(data: any) => {
|
|
||||||
const pendingNotification: DataModels.Notification = data?.[0];
|
|
||||||
const tabOptions: ViewModels.TabOptions = {
|
const tabOptions: ViewModels.TabOptions = {
|
||||||
tabKind,
|
tabKind,
|
||||||
title: "Scale",
|
title: "Scale",
|
||||||
|
@ -101,10 +96,8 @@ export default class Database implements ViewModels.Database {
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
};
|
};
|
||||||
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
settingsTab = new DatabaseSettingsTabV2(tabOptions);
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
useTabs.getState().activateNewTab(settingsTab);
|
useTabs.getState().activateNewTab(settingsTab);
|
||||||
},
|
} catch (error) {
|
||||||
(error) => {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.Tab,
|
Action.Tab,
|
||||||
|
@ -121,19 +114,9 @@ export default class Database implements ViewModels.Database {
|
||||||
);
|
);
|
||||||
logConsoleError(`Error while fetching database settings for database ${this.id()}: ${errorMessage}`);
|
logConsoleError(`Error while fetching database settings for database ${this.id()}: ${errorMessage}`);
|
||||||
throw error;
|
throw error;
|
||||||
},
|
}
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
pendingNotificationsPromise.then(
|
|
||||||
(pendingNotification: DataModels.Notification) => {
|
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
|
||||||
useTabs.getState().activateTab(settingsTab);
|
useTabs.getState().activateTab(settingsTab);
|
||||||
},
|
|
||||||
() => {
|
|
||||||
settingsTab.pendingNotification(undefined);
|
|
||||||
useTabs.getState().activateTab(settingsTab);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -260,42 +243,6 @@ export default class Database implements ViewModels.Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
|
||||||
if (!this.container) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const notifications: DataModels.Notification[] = await fetchPortalNotifications();
|
|
||||||
if (!notifications || notifications.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.find(notifications, (notification: DataModels.Notification) => {
|
|
||||||
const throughputUpdateRegExp = new RegExp("Throughput update (.*) in progress");
|
|
||||||
return (
|
|
||||||
notification.kind === "message" &&
|
|
||||||
!notification.collectionName &&
|
|
||||||
notification.databaseName === this.id() &&
|
|
||||||
notification.description &&
|
|
||||||
throughputUpdateRegExp.test(notification.description)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
accountName: userContext?.databaseAccount,
|
|
||||||
databaseName: this.id(),
|
|
||||||
collectionName: this.id(),
|
|
||||||
}),
|
|
||||||
"Settings tree node",
|
|
||||||
);
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDeltaCollections(updatedCollectionsList: DataModels.Collection[]): {
|
private getDeltaCollections(updatedCollectionsList: DataModels.Collection[]): {
|
||||||
toAdd: DataModels.Collection[];
|
toAdd: DataModels.Collection[];
|
||||||
toDelete: Collection[];
|
toDelete: Collection[];
|
||||||
|
|
|
@ -192,6 +192,11 @@ export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
||||||
PortalBackendEndpoints.Fairfax,
|
PortalBackendEndpoints.Fairfax,
|
||||||
PortalBackendEndpoints.Mooncake,
|
PortalBackendEndpoints.Mooncake,
|
||||||
],
|
],
|
||||||
|
[BackendApi.SampleData]: [
|
||||||
|
PortalBackendEndpoints.Development,
|
||||||
|
PortalBackendEndpoints.Mpac,
|
||||||
|
PortalBackendEndpoints.Prod,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
|
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
|
@ -760,11 +761,17 @@ async function updateContextForSampleData(explorer: Explorer): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
if (useNewPortalBackendEndpoint(Constants.BackendApi.SampleData)) {
|
||||||
|
url = createUri(configContext.PORTAL_BACKEND_ENDPOINT, "/api/sampledata");
|
||||||
|
} else {
|
||||||
const sampleDatabaseEndpoint = useQueryCopilot.getState().copilotUserDBEnabled
|
const sampleDatabaseEndpoint = useQueryCopilot.getState().copilotUserDBEnabled
|
||||||
? `/api/tokens/sampledataconnection/v2`
|
? `/api/tokens/sampledataconnection/v2`
|
||||||
: `/api/tokens/sampledataconnection`;
|
: `/api/tokens/sampledataconnection`;
|
||||||
|
|
||||||
const url = createUri(`${configContext.BACKEND_ENDPOINT}`, sampleDatabaseEndpoint);
|
url = createUri(`${configContext.BACKEND_ENDPOINT}`, sampleDatabaseEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
const headers = { [authorizationHeader.header]: authorizationHeader.token };
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue