Compare commits

..

3 Commits

Author SHA1 Message Date
Predrag Klepic
863fdb4f7e Merge branch 'master' into users/v-prklepic/V2deleteHistoryButton 2023-09-19 10:18:34 +02:00
Predrag Klepic
c9bd0b6c7f updated snapshot 2023-09-06 11:19:16 +02:00
Predrag Klepic
8ce7aaa728 Delete chat history button 2023-09-06 11:03:04 +02:00
24 changed files with 253 additions and 462 deletions

View File

@@ -23,6 +23,5 @@
"source.fixAll.eslint": true,
"source.organizeImports": true
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode"
"typescript.preferences.importModuleSpecifier": "non-relative"
}

View File

@@ -199,7 +199,6 @@
"pack:fast": "webpack --mode development --progress",
"copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest",
"test:debug": "jest --runInBand",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",

View File

@@ -68,7 +68,7 @@ let configContext: Readonly<ConfigContext> = {
ARM_AUTH_AREA: "https://management.azure.com/",
ARM_ENDPOINT: "https://management.azure.com/",
ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.microsoft.com",
GRAPH_ENDPOINT: "https://graph.windows.net",
GRAPH_API_VERSION: "1.6",
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",

View File

@@ -32,20 +32,6 @@ const testCassandraAccount: DataModels.DatabaseAccount = {
},
};
const testPostgresAccount: DataModels.DatabaseAccount = {
...testAccount,
properties: {
postgresqlEndpoint: "https://testPostgresEndpoint.azure.com/",
},
};
const testVCoreMongoAccount: DataModels.DatabaseAccount = {
...testAccount,
properties: {
vcoreMongoEndpoint: "https://testVCoreMongoEndpoint.azure.com/",
},
};
const testNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com",
@@ -64,18 +50,6 @@ const testCassandraNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInf
forwardingId: "Id",
};
const testPostgresNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/postgresql",
forwardingId: "Id",
};
const testVCoreMongoNotebookServerInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: "authToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongovcore",
forwardingId: "Id",
};
describe("NotebookTerminalComponent", () => {
it("renders terminal", () => {
const props: NotebookTerminalComponentProps = {
@@ -120,27 +94,4 @@ describe("NotebookTerminalComponent", () => {
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders Postgres shell", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testPostgresAccount,
notebookServerInfo: testPostgresNotebookServerInfo,
tabId: undefined,
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
it("renders vCore Mongo shell", () => {
const props: NotebookTerminalComponentProps = {
databaseAccount: testVCoreMongoAccount,
notebookServerInfo: testVCoreMongoNotebookServerInfo,
tabId: undefined,
username: "username",
};
const wrapper = shallow(<NotebookTerminalComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,17 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NotebookTerminalComponent renders Postgres shell 1`] = `
<div
className="notebookTerminalContainer"
>
<iframe
onLoad={[Function]}
src="terminal.html"
title="Terminal to Notebook Server"
/>
</div>
`;
exports[`NotebookTerminalComponent renders cassandra shell 1`] = `
<div
className="notebookTerminalContainer"
@@ -59,15 +47,3 @@ exports[`NotebookTerminalComponent renders terminal 1`] = `
/>
</div>
`;
exports[`NotebookTerminalComponent renders vCore Mongo shell 1`] = `
<div
className="notebookTerminalContainer"
>
<iframe
onLoad={[Function]}
src="terminal.html"
title="Terminal to Notebook Server"
/>
</div>
`;

View File

@@ -343,31 +343,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
});
});
describe("Open Postgres and vCore Mongo buttons", () => {
const openPostgresShellButtonLabel = "Open PSQL shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vcore) shell";
beforeAll(() => {
mockExplorer = {} as Explorer;
});
it("creates Postgres shell button", () => {
const buttons = CommandBarComponentButtonFactory.createPostgreButtons(mockExplorer);
const openPostgresShellButton = buttons.find(
(button) => button.commandButtonLabel === openPostgresShellButtonLabel
);
expect(openPostgresShellButton).toBeDefined();
});
it("creates vCore Mongo shell button", () => {
const buttons = CommandBarComponentButtonFactory.createVCoreMongoButtons(mockExplorer);
const openVCoreMongoShellButton = buttons.find(
(button) => button.commandButtonLabel === openVCoreMongoShellButtonLabel
);
expect(openVCoreMongoShellButton).toBeDefined();
});
});
describe("GitHub buttons", () => {
const connectToGitHubBtnLabel = "Connect to GitHub";
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";

View File

@@ -1,15 +1,8 @@
import {
DetailsList,
DetailsListLayoutMode,
DirectionalHint,
IColumn,
SelectionMode,
TooltipHost,
} from "@fluentui/react";
import { DetailsList, DetailsListLayoutMode, IColumn, SelectionMode } from "@fluentui/react";
import { Upload } from "Common/Upload/Upload";
import { UploadDetailsRecord } from "Contracts/ViewModels";
import { logConsoleError } from "Utils/NotificationConsoleUtils";
import React, { ChangeEvent, FunctionComponent, useState } from "react";
import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { getErrorMessage } from "../../Tables/Utilities";
import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
@@ -81,21 +74,12 @@ export const UploadItemsPane: FunctionComponent = () => {
];
const _renderItemColumn = (item: UploadDetailsRecord, index: number, column: IColumn) => {
let fieldContent: string;
const tooltipId = `tooltip-${index}-${column.key}`;
switch (column.key) {
case "status":
fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
break;
return `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
default:
fieldContent = item.fileName;
return item.fileName;
}
return (
<TooltipHost content={fieldContent} id={tooltipId} directionalHint={DirectionalHint.rightCenter}>
{fieldContent}
</TooltipHost>
);
};
return (

View File

@@ -60,7 +60,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text>
Ask Copilot to generate a query by describing the query in your words.
<br />
<Link href="https://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
@@ -78,7 +78,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
<Text>
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<Link href="https://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
<Link href="http://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
</Text>
</Stack.Item>
<Stack.Item align="center" className="text">
@@ -97,7 +97,7 @@ export const WelcomeModal = ({ visible }: { visible: boolean }): JSX.Element =>
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you
at no cost.
<br />
<Link href="https://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
</Text>
</Stack.Item>
</Stack>

View File

@@ -102,7 +102,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
Ask Copilot to generate a query by describing the query in your words.
<br />
<StyledLinkBase
href="https://aka.ms/cdb-copilot-learn-more"
href="http://aka.ms/cdb-copilot-learn-more"
>
Learn more
</StyledLinkBase>
@@ -138,7 +138,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<StyledLinkBase
href="https://aka.ms/cdb-copilot-preview-terms"
href="http://aka.ms/cdb-copilot-preview-terms"
>
Read preview terms
</StyledLinkBase>
@@ -174,7 +174,7 @@ exports[`Query Copilot Welcome Modal snapshot test should render when isOpen is
While in Private Preview, Query Copilot is setup to work on sample database we have configured for you at no cost.
<br />
<StyledLinkBase
href="https://aka.ms/cdb-copilot-learn-more"
href="http://aka.ms/cdb-copilot-learn-more"
>
Learn more
</StyledLinkBase>

View File

@@ -434,7 +434,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
}}
>
Learn about{" "}
<Link target="_blank" href="https://aka.ms/cdb-copilot-writing">
<Link target="_blank" href="http://aka.ms/cdb-copilot-writing">
writing effective prompts
</Link>
</Text>
@@ -448,7 +448,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
<Stack style={{ marginTop: 8, marginBottom: 24 }}>
<Text style={{ fontSize: 12 }}>
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">
<Link href="http://aka.ms/cdb-copilot-preview-terms" target="_blank">
Read preview terms
</Link>
{showErrorMessageBar && (

View File

@@ -1,39 +1,81 @@
import { IconButton, Image, Stack, Text } from "@fluentui/react";
import { DefaultButton, IconButton, Image, Modal, PrimaryButton, Stack, Text } from "@fluentui/react";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import React from "react";
import CopilotIcon from "../../../../../images/CopilotSidebarLogo.svg";
export const Header: React.FC = (): JSX.Element => {
const { setShowCopilotSidebar } = useQueryCopilot();
const { setShowCopilotSidebar, chatMessages, setChatMessages, setShowExplanationBubble } = useQueryCopilot();
const [showDeleteHistoryModal, setShowDeleteHistoryModal] = React.useState(false);
const getDeleteHistoryModal = () => {
return (
<Modal isOpen={showDeleteHistoryModal} styles={{ main: { minHeight: "122px", minWidth: "480px" } }}>
<Stack style={{ padding: "16px 24px", height: "auto" }}>
<Text style={{ height: 24, fontSize: "18px" }}>
<b>Delete chat history?</b>
</Text>
<Text style={{ marginTop: 10, marginBottom: 20 }}>
This action will clear all chat history. Are you sure you want to continue?
</Text>
<Stack horizontal tokens={{ childrenGap: 10 }} horizontalAlign="start">
<PrimaryButton
style={{ padding: "0px 20px", height: 24 }}
onClick={() => {
setChatMessages([]);
setShowExplanationBubble(false);
setShowDeleteHistoryModal(false);
}}
>
Continue
</PrimaryButton>
<DefaultButton style={{ padding: "0px 20px", height: 24 }} onClick={() => setShowDeleteHistoryModal(false)}>
Close
</DefaultButton>
</Stack>
</Stack>
</Modal>
);
};
return (
<Stack
style={{ margin: "15px 0px 0px 0px", padding: "5px", display: "flex", justifyContent: "space-between" }}
horizontal
verticalAlign="center"
>
<Stack horizontal verticalAlign="center">
<Image src={CopilotIcon} />
<Text style={{ marginLeft: "5px", fontWeight: "bold" }}>Copilot</Text>
<Text
style={{
background: "#f0f0f0",
fontSize: "10px",
padding: "2px 4px",
marginLeft: "5px",
borderRadius: "8px",
}}
>
Preview
</Text>
<>
<Stack
style={{ margin: "15px 0px 0px 0px", padding: "5px", display: "flex", justifyContent: "space-between" }}
horizontal
verticalAlign="center"
>
<Stack horizontal verticalAlign="center">
<Image src={CopilotIcon} />
<Text style={{ marginLeft: "5px", fontWeight: "bold" }}>Copilot</Text>
<Text
style={{
background: "#f0f0f0",
fontSize: "10px",
padding: "2px 4px",
marginLeft: "5px",
borderRadius: "8px",
}}
>
Preview
</Text>
</Stack>
<IconButton
onClick={() => setShowDeleteHistoryModal(true)}
iconProps={{ iconName: "History" }}
title="Delete history"
ariaLabel="Delete history"
style={{ color: "#424242", verticalAlign: "middle" }}
disabled={chatMessages.length === 0}
/>
<IconButton
onClick={() => setShowCopilotSidebar(false)}
iconProps={{ iconName: "Cancel" }}
title="Exit"
ariaLabel="Exit"
style={{ color: "#424242", verticalAlign: "middle" }}
/>
</Stack>
<IconButton
onClick={() => setShowCopilotSidebar(false)}
iconProps={{ iconName: "Cancel" }}
title="Exit"
ariaLabel="Exit"
style={{ color: "#424242", verticalAlign: "middle" }}
/>
</Stack>
{getDeleteHistoryModal()}
</>
);
};

View File

@@ -1,64 +1,158 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header snapshot test should close on button click 1`] = `
<Stack
horizontal={true}
style={
Object {
"display": "flex",
"justifyContent": "space-between",
"margin": "15px 0px 0px 0px",
"padding": "5px",
}
}
verticalAlign="center"
>
<Fragment>
<Stack
horizontal={true}
verticalAlign="center"
>
<Image
src={Object {}}
/>
<Text
style={
Object {
"fontWeight": "bold",
"marginLeft": "5px",
}
}
>
Copilot
</Text>
<Text
style={
Object {
"background": "#f0f0f0",
"borderRadius": "8px",
"fontSize": "10px",
"marginLeft": "5px",
"padding": "2px 4px",
}
}
>
Preview
</Text>
</Stack>
<CustomizedIconButton
ariaLabel="Exit"
iconProps={
Object {
"iconName": "Cancel",
}
}
onClick={[Function]}
style={
Object {
"color": "#424242",
"verticalAlign": "middle",
"display": "flex",
"justifyContent": "space-between",
"margin": "15px 0px 0px 0px",
"padding": "5px",
}
}
title="Exit"
/>
</Stack>
verticalAlign="center"
>
<Stack
horizontal={true}
verticalAlign="center"
>
<Image
src={Object {}}
/>
<Text
style={
Object {
"fontWeight": "bold",
"marginLeft": "5px",
}
}
>
Copilot
</Text>
<Text
style={
Object {
"background": "#f0f0f0",
"borderRadius": "8px",
"fontSize": "10px",
"marginLeft": "5px",
"padding": "2px 4px",
}
}
>
Preview
</Text>
</Stack>
<CustomizedIconButton
ariaLabel="Delete history"
disabled={true}
iconProps={
Object {
"iconName": "History",
}
}
onClick={[Function]}
style={
Object {
"color": "#424242",
"verticalAlign": "middle",
}
}
title="Delete history"
/>
<CustomizedIconButton
ariaLabel="Exit"
iconProps={
Object {
"iconName": "Cancel",
}
}
onClick={[Function]}
style={
Object {
"color": "#424242",
"verticalAlign": "middle",
}
}
title="Exit"
/>
</Stack>
<Modal
isOpen={true}
styles={
Object {
"main": Object {
"minHeight": "122px",
"minWidth": "480px",
},
}
}
>
<Stack
style={
Object {
"height": "auto",
"padding": "16px 24px",
}
}
>
<Text
style={
Object {
"fontSize": "18px",
"height": 24,
}
}
>
<b>
Delete chat history?
</b>
</Text>
<Text
style={
Object {
"marginBottom": 20,
"marginTop": 10,
}
}
>
This action will clear all chat history. Are you sure you want to continue?
</Text>
<Stack
horizontal={true}
horizontalAlign="start"
tokens={
Object {
"childrenGap": 10,
}
}
>
<CustomizedPrimaryButton
onClick={[Function]}
style={
Object {
"height": 24,
"padding": "0px 20px",
}
}
>
Continue
</CustomizedPrimaryButton>
<CustomizedDefaultButton
onClick={[Function]}
style={
Object {
"height": 24,
"padding": "0px 20px",
}
}
>
Close
</CustomizedDefaultButton>
</Stack>
</Stack>
</Modal>
</Fragment>
`;

View File

@@ -65,7 +65,7 @@ export const WelcomeSidebarModal: React.FC = (): JSX.Element => {
<Text>
Ask Copilot to generate a query by describing the query in your words.
<br />
<Link href="https://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
</Text>
</Stack.Item>
</Stack>
@@ -87,7 +87,7 @@ export const WelcomeSidebarModal: React.FC = (): JSX.Element => {
<Text>
AI-generated content can have mistakes. Make sure its accurate and appropriate before using it.
<br />
<Link href="https://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
<Link href="http://aka.ms/cdb-copilot-preview-terms">Read preview terms</Link>
</Text>
</Stack.Item>
</Stack>
@@ -109,7 +109,7 @@ export const WelcomeSidebarModal: React.FC = (): JSX.Element => {
<Text>
Copilot is setup on a sample database we have configured for you at no cost
<br />
<Link href="https://aka.ms/cdb-copilot-learn-more">Learn more</Link>
<Link href="http://aka.ms/cdb-copilot-learn-more">Learn more</Link>
</Text>
</Stack.Item>
</Stack>

View File

@@ -102,7 +102,7 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
<StyledLinkBase
href="https://aka.ms/cdb-copilot-preview-terms"
href="http://aka.ms/cdb-copilot-preview-terms"
target="_blank"
>
Read preview terms

View File

@@ -4,6 +4,7 @@ import { traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import { useCarousel } from "hooks/useCarousel";
import React, { useState } from "react";
import Youtube from "react-youtube";
import Image1 from "../../../images/CarouselImage1.svg";
import Image2 from "../../../images/CarouselImage2.svg";
@@ -78,12 +79,7 @@ const getHeaderText = (page: number): string => {
const getContent = (page: number): JSX.Element => {
switch (page) {
case 1:
return (
<video controls width="640" height="360" controlsList="nofullscreen nodownload ">
<source src="src/Explorer/Quickstart/Videos/Cosmos-db-turorial.mp4" type="video/mp4"></source>
Your browser does not support the video tag.
</video>
);
return <Youtube videoId="Jvgh64rvdXU" onPlay={() => traceSuccess(Action.PlayCarouselVideo)} />;
case 2:
return <Image style={{ width: 640 }} src={Image1} />;
case 3:

View File

@@ -1,91 +0,0 @@
import { DatabaseAccount, FirewallRule } from "Contracts/DataModels";
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { updateUserContext } from "UserContext";
import { mockFunction } from "Utils/JestUtils";
import { armRequest } from "Utils/arm/request";
import React from "react";
jest.mock("Utils/arm/request");
const armRequestMock = mockFunction(armRequest);
describe("CheckFirewallRule tests", () => {
const apiVersion = "2023-03-15-preview";
const rulePredicate = (rule: FirewallRule) =>
rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255";
let isAllPublicIPAddressEnabled: boolean;
const setIsAllPublicIPAddressEnabled = jest.fn((value) => (isAllPublicIPAddressEnabled = value));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const useStateMock: any = (initState: any) => [initState, setIsAllPublicIPAddressEnabled];
jest.spyOn(React, "useState").mockImplementation(useStateMock);
beforeAll(() => {
updateUserContext({
databaseAccount: {
id: "testResourceId",
} as DatabaseAccount,
});
});
it("returns 'all public IP addresses' is enabled for account with the proper firewall rule", async () => {
armRequestMock.mockResolvedValueOnce({
value: [
{
id: "resourceId",
name: "AllowAll",
type: "Microsoft.DocumentDB/mongoClusters/firewallRules",
properties: {
provisioningState: "Succeeded",
startIpAddress: "0.0.0.0",
endIpAddress: "255.255.255.255",
},
},
],
});
await checkFirewallRules(apiVersion, rulePredicate, setIsAllPublicIPAddressEnabled);
expect(isAllPublicIPAddressEnabled).toBe(true);
});
it("returns 'all public IP addresses' is NOT enabled for account without the proper firewall rule", async () => {
armRequestMock.mockResolvedValueOnce([
{
id: "resourceId",
name: "AllowAll",
type: "Microsoft.DocumentDB/mongoClusters/firewallRules",
properties: {
provisioningState: "Succeeded",
startIpAddress: "10.10.10.10",
endIpAddress: "10.10.10.10",
},
},
]);
await checkFirewallRules(apiVersion, rulePredicate, setIsAllPublicIPAddressEnabled);
expect(isAllPublicIPAddressEnabled).toBe(false);
});
it("sets message for account without the proper firewall rule", async () => {
armRequestMock.mockResolvedValueOnce([
{
id: "resourceId",
name: "AllowAll",
type: "Microsoft.DocumentDB/mongoClusters/firewallRules",
properties: {
provisioningState: "Succeeded",
startIpAddress: "0.0.0.0",
endIpAddress: "255.255.255.255",
},
},
]);
const warningMessage = "This is a warning message";
let warningMessageResult: string;
const warningMessageFunc = (msg: string) => (warningMessageResult = msg);
await checkFirewallRules(apiVersion, rulePredicate, undefined, warningMessageFunc, warningMessage);
expect(warningMessageResult).toEqual(warningMessage);
});
});

View File

@@ -55,17 +55,6 @@ export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
"https://localhost:1234",
];
export const PortalBackendIPs: { [key: string]: string[] } = {
"https://main.documentdb.ext.azure.com": ["104.42.195.92", "40.76.54.131"],
// DE doesn't talk to prod2 (main2) but it might be added
//"https://main2.documentdb.ext.azure.com": ["104.42.196.69"],
"https://main.documentdb.ext.azure.cn": ["139.217.8.252"],
"https://main.documentdb.ext.azure.us": ["52.244.48.71"],
// Add ussec and usnat when endpoint address is known:
//ussec: ["29.26.26.67", "29.26.26.66"],
//usnat: ["7.28.202.68"],
};
export const allowedMongoProxyEndpoints: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
@@ -78,7 +67,7 @@ export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localho
export const allowedMongoBackendEndpoints: ReadonlyArray<string> = ["https://localhost:1234"];
export const allowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.microsoft.com"];
export const allowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.windows.net"];
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];

View File

@@ -1,4 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mockFunction<T extends (...args: any[]) => any>(fn: T): jest.MockedFunction<T> {
return fn as jest.MockedFunction<T>;
}

View File

@@ -1,106 +0,0 @@
import { resetConfigContext, updateConfigContext } from "ConfigContext";
import { DatabaseAccount, IpRule } from "Contracts/DataModels";
import { updateUserContext } from "UserContext";
import { PortalBackendIPs } from "Utils/EndpointValidation";
import { getNetworkSettingsWarningMessage } from "./NetworkUtility";
describe("NetworkUtility tests", () => {
describe("getNetworkSettingsWarningMessage", () => {
const publicAccessMessagePart = "Please enable public access to proceed";
const accessMessagePart = "Please allow access from Azure Portal to proceed";
// validEnpoints are a subset of those from Utils/EndpointValidation/PortalBackendIPs
const validEndpoints = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
];
let warningMessageResult: string;
const warningMessageFunc = (msg: string) => (warningMessageResult = msg);
beforeEach(() => {
warningMessageResult = undefined;
});
afterEach(() => {
resetConfigContext();
});
it("should return no message when publicNetworkAccess is enabled", async () => {
updateUserContext({
databaseAccount: {
properties: {
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
await getNetworkSettingsWarningMessage(warningMessageFunc);
expect(warningMessageResult).toBeUndefined();
});
it("should return publicAccessMessage when publicNetworkAccess is disabled", async () => {
updateUserContext({
databaseAccount: {
properties: {
publicNetworkAccess: "Disabled",
},
} as DatabaseAccount,
});
await getNetworkSettingsWarningMessage(warningMessageFunc);
expect(warningMessageResult).toContain(publicAccessMessagePart);
});
it(`should return no message when the appropriate ip rules are added to mongo/cassandra account per endpoint`, () => {
validEndpoints.forEach(async (endpoint) => {
updateUserContext({
databaseAccount: {
kind: "MongoDB",
properties: {
ipRules: PortalBackendIPs[endpoint].map((ip: string) => ({ ipAddressOrRange: ip } as IpRule)),
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
updateConfigContext({
BACKEND_ENDPOINT: endpoint,
});
let asyncWarningMessageResult: string;
const asyncWarningMessageFunc = (msg: string) => (asyncWarningMessageResult = msg);
await getNetworkSettingsWarningMessage(asyncWarningMessageFunc);
expect(asyncWarningMessageResult).toBeUndefined();
});
});
it("should return accessMessage when incorrent ip rule is added to mongo/cassandra account per endpoint", () => {
validEndpoints.forEach(async (endpoint) => {
updateUserContext({
databaseAccount: {
kind: "MongoDB",
properties: {
ipRules: [{ ipAddressOrRange: "1.1.1.1" }],
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
updateConfigContext({
BACKEND_ENDPOINT: endpoint,
});
let asyncWarningMessageResult: string;
const asyncWarningMessageFunc = (msg: string) => (asyncWarningMessageResult = msg);
await getNetworkSettingsWarningMessage(asyncWarningMessageFunc);
expect(asyncWarningMessageResult).toContain(accessMessagePart);
});
});
// Postgres and vcore mongo account checks basically pass through to CheckFirewallRules so those
// tests are omitted here and included in CheckFirewallRules.test.ts
});
});

View File

@@ -1,7 +1,15 @@
import { configContext } from "ConfigContext";
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { userContext } from "UserContext";
import { PortalBackendIPs } from "Utils/EndpointValidation";
const PortalIPs: { [key: string]: string[] } = {
prod1: ["104.42.195.92", "40.76.54.131"],
prod2: ["104.42.196.69"],
mooncake: ["139.217.8.252"],
blackforest: ["51.4.229.218"],
fairfax: ["52.244.48.71"],
ussec: ["29.26.26.67", "29.26.26.66"],
usnat: ["7.28.202.68"],
};
export const getNetworkSettingsWarningMessage = async (
setStateFunc: (warningMessage: string) => void
@@ -20,7 +28,6 @@ export const getNetworkSettingsWarningMessage = async (
setStateFunc,
accessMessage
);
return;
} else if (userContext.apiType === "VCoreMongo") {
checkFirewallRules(
"2023-03-01-preview",
@@ -31,7 +38,6 @@ export const getNetworkSettingsWarningMessage = async (
setStateFunc,
accessMessage
);
return;
} else if (accountProperties) {
// public network access is disabled
if (
@@ -39,14 +45,13 @@ export const getNetworkSettingsWarningMessage = async (
accountProperties.publicNetworkAccess !== "SecuredByPerimeter"
) {
setStateFunc(publicAccessMessage);
return;
}
const ipRules = accountProperties.ipRules;
// public network access is NOT set to "All networks"
if (ipRules?.length > 0) {
if (ipRules.length > 0) {
if (userContext.apiType === "Cassandra" || userContext.apiType === "Mongo") {
const portalIPs = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
const portalIPs = PortalIPs[userContext.portalEnv];
let numberOfMatches = 0;
ipRules.forEach((ipRule) => {
if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) {

View File

@@ -57,22 +57,4 @@ describe("shouldShowQueryPageOptions()", () => {
});
expect(userContext.apiType).toBe("Mongo");
});
it("should be 'Postgres' for Postgres API", () => {
updateUserContext({
databaseAccount: {
kind: "Postgres",
} as DatabaseAccount,
});
expect(userContext.apiType).toBe("Postgres");
});
it("should be 'VCoreMongo' for vCore Mongo", () => {
updateUserContext({
databaseAccount: {
kind: "VCoreMongo",
} as DatabaseAccount,
});
expect(userContext.apiType).toBe("VCoreMongo");
});
});

View File

@@ -21,7 +21,7 @@
<clear />
<add name="X-Xss-Protection" value="1; mode=block" />
<add name="X-Content-Type-Options" value="nosniff" />
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net" />
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com pbi-rdb-edog.analysis-df.windows.net" />
</customHeaders>
<redirectHeaders>
<clear />