mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-12-01 01:46:57 +00:00
fixed merge conflicts
This commit is contained in:
commit
569167fa10
@ -1,7 +1,9 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
|
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||||
|
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { PriorityLevel } from "../Common/Constants";
|
import { PriorityLevel } from "../Common/Constants";
|
||||||
import { configContext, Platform } from "../ConfigContext";
|
import { Platform, configContext } from "../ConfigContext";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
||||||
@ -25,6 +27,15 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
return decodeURIComponent(headers.authorization);
|
return decodeURIComponent(headers.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configContext.platform === Platform.Fabric) {
|
||||||
|
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(MessageTypes.GetAuthorizationToken, [
|
||||||
|
requestInfo,
|
||||||
|
]);
|
||||||
|
console.log("Response from Fabric: ", authorizationToken);
|
||||||
|
headers[HttpHeaders.msDate] = authorizationToken.XDate;
|
||||||
|
return authorizationToken.PrimaryReadWriteToken;
|
||||||
|
}
|
||||||
|
|
||||||
if (userContext.masterKey) {
|
if (userContext.masterKey) {
|
||||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||||
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||||
@ -55,7 +66,11 @@ export const endpoint = () => {
|
|||||||
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
|
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
|
export async function getTokenFromAuthService(
|
||||||
|
verb: string,
|
||||||
|
resourceType: string,
|
||||||
|
resourceId?: string,
|
||||||
|
): Promise<AuthorizationToken> {
|
||||||
try {
|
try {
|
||||||
const host = configContext.BACKEND_ENDPOINT;
|
const host = configContext.BACKEND_ENDPOINT;
|
||||||
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
|
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
|
||||||
|
@ -22,7 +22,7 @@ export function handleCachedDataMessage(message: any): void {
|
|||||||
if (messageContent.error != null) {
|
if (messageContent.error != null) {
|
||||||
cachedDataPromise.deferred.reject(messageContent.error);
|
cachedDataPromise.deferred.reject(messageContent.error);
|
||||||
} else {
|
} else {
|
||||||
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
|
cachedDataPromise.deferred.resolve(messageContent.data);
|
||||||
}
|
}
|
||||||
runGarbageCollector();
|
runGarbageCollector();
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,6 @@
|
|||||||
|
import { MessageTypes } from "Contracts/MessageTypes";
|
||||||
import * as ActionContracts from "./ActionContracts";
|
import * as ActionContracts from "./ActionContracts";
|
||||||
import * as Diagnostics from "./Diagnostics";
|
import * as Diagnostics from "./Diagnostics";
|
||||||
import * as Versions from "./Versions";
|
import * as Versions from "./Versions";
|
||||||
|
|
||||||
/**
|
export { ActionContracts, Diagnostics, MessageTypes, Versions };
|
||||||
* Messaging types used with Data Explorer <-> Portal communication
|
|
||||||
* and Hosted <-> Explorer communication
|
|
||||||
*/
|
|
||||||
export enum MessageTypes {
|
|
||||||
TelemetryInfo,
|
|
||||||
LogInfo,
|
|
||||||
RefreshResources,
|
|
||||||
AllDatabases,
|
|
||||||
CollectionsForDatabase,
|
|
||||||
RefreshOffers,
|
|
||||||
AllOffers,
|
|
||||||
UpdateLocationHash,
|
|
||||||
SingleOffer,
|
|
||||||
RefreshOffer,
|
|
||||||
UpdateAccountName,
|
|
||||||
ForbiddenError,
|
|
||||||
AadSignIn,
|
|
||||||
GetAccessAadRequest,
|
|
||||||
GetAccessAadResponse,
|
|
||||||
UpdateAccountSwitch,
|
|
||||||
UpdateDirectoryControl,
|
|
||||||
SwitchAccount,
|
|
||||||
SendNotification,
|
|
||||||
ClearNotification,
|
|
||||||
ExplorerClickEvent,
|
|
||||||
LoadingStatus,
|
|
||||||
GetArcadiaToken,
|
|
||||||
CreateWorkspace,
|
|
||||||
CreateSparkPool,
|
|
||||||
RefreshDatabaseAccount,
|
|
||||||
CloseTab,
|
|
||||||
OpenQuickstartBlade,
|
|
||||||
OpenPostgreSQLPasswordReset,
|
|
||||||
OpenPostgresNetworkingBlade,
|
|
||||||
OpenCosmosDBNetworkingBlade,
|
|
||||||
DisplayNPSSurvey,
|
|
||||||
OpenVCoreMongoNetworkingBlade,
|
|
||||||
OpenVCoreMongoConnectionStringsBlade,
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ActionContracts, Diagnostics, Versions };
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { AuthorizationToken, MessageTypes } from "./MessageTypes";
|
||||||
|
|
||||||
export type FabricMessage =
|
export type FabricMessage =
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: "newContainer";
|
||||||
@ -5,21 +7,52 @@ export type FabricMessage =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "initialize";
|
type: "initialize";
|
||||||
connectionString: string | undefined;
|
message: {
|
||||||
|
endpoint: string | undefined;
|
||||||
|
error: string | undefined;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: "openTab";
|
type: "openTab";
|
||||||
databaseName: string;
|
databaseName: string;
|
||||||
collectionName: string | undefined;
|
collectionName: string | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "authorizationToken";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: AuthorizationToken | undefined;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataExploreMessage =
|
export type DataExploreMessage =
|
||||||
| "ready"
|
| "ready"
|
||||||
| {
|
| {
|
||||||
type: number;
|
type: MessageTypes.TelemetryInfo;
|
||||||
data: {
|
data: {
|
||||||
action: "LoadDatabases";
|
action: "LoadDatabases";
|
||||||
actionModifier: "success" | "start";
|
actionModifier: "success" | "start";
|
||||||
defaultExperience: "SQL";
|
defaultExperience: "SQL";
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: MessageTypes.GetAuthorizationToken;
|
||||||
|
id: string;
|
||||||
|
params: GetCosmosTokenMessageOptions[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetCosmosTokenMessageOptions = {
|
||||||
|
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
|
||||||
|
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
|
||||||
|
resourceId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CosmosDBTokenResponse = {
|
||||||
|
token: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CosmosDBConnectionInfoResponse = {
|
||||||
|
endpoint: string;
|
||||||
|
};
|
||||||
|
48
src/Contracts/MessageTypes.ts
Normal file
48
src/Contracts/MessageTypes.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Messaging types used with Data Explorer <-> Portal communication,
|
||||||
|
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
|
||||||
|
*/
|
||||||
|
export enum MessageTypes {
|
||||||
|
TelemetryInfo,
|
||||||
|
LogInfo,
|
||||||
|
RefreshResources,
|
||||||
|
AllDatabases,
|
||||||
|
CollectionsForDatabase,
|
||||||
|
RefreshOffers,
|
||||||
|
AllOffers,
|
||||||
|
UpdateLocationHash,
|
||||||
|
SingleOffer,
|
||||||
|
RefreshOffer,
|
||||||
|
UpdateAccountName,
|
||||||
|
ForbiddenError,
|
||||||
|
AadSignIn,
|
||||||
|
GetAccessAadRequest,
|
||||||
|
GetAccessAadResponse,
|
||||||
|
UpdateAccountSwitch,
|
||||||
|
UpdateDirectoryControl,
|
||||||
|
SwitchAccount,
|
||||||
|
SendNotification,
|
||||||
|
ClearNotification,
|
||||||
|
ExplorerClickEvent,
|
||||||
|
LoadingStatus,
|
||||||
|
GetArcadiaToken,
|
||||||
|
CreateWorkspace,
|
||||||
|
CreateSparkPool,
|
||||||
|
RefreshDatabaseAccount,
|
||||||
|
CloseTab,
|
||||||
|
OpenQuickstartBlade,
|
||||||
|
OpenPostgreSQLPasswordReset,
|
||||||
|
OpenPostgresNetworkingBlade,
|
||||||
|
OpenCosmosDBNetworkingBlade,
|
||||||
|
DisplayNPSSurvey,
|
||||||
|
OpenVCoreMongoNetworkingBlade,
|
||||||
|
OpenVCoreMongoConnectionStringsBlade,
|
||||||
|
|
||||||
|
// Data Explorer -> Fabric communication
|
||||||
|
GetAuthorizationToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthorizationToken {
|
||||||
|
XDate: string;
|
||||||
|
PrimaryReadWriteToken: string;
|
||||||
|
}
|
@ -123,19 +123,6 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
await generator.createSampleContainerAsync();
|
await generator.createSampleContainerAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not create any sample for Mongo API account", async () => {
|
|
||||||
const experience = "Sample generation not supported for this API Mongo";
|
|
||||||
updateUserContext({
|
|
||||||
databaseAccount: {
|
|
||||||
properties: {
|
|
||||||
capabilities: [{ name: "EnableMongo" }],
|
|
||||||
},
|
|
||||||
} as DatabaseAccount,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not create any sample for Table API account", async () => {
|
it("should not create any sample for Table API account", async () => {
|
||||||
const experience = "Sample generation not supported for this API Tables";
|
const experience = "Sample generation not supported for this API Tables";
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
@ -102,7 +102,7 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should submit submission", () => {
|
it("should not submit submission if required description field is null", () => {
|
||||||
const explorer = new Explorer();
|
const explorer = new Explorer();
|
||||||
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
|
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
|
||||||
|
|
||||||
@ -110,12 +110,24 @@ describe("Query Copilot Feedback Modal snapshot test", () => {
|
|||||||
submitButton.simulate("click");
|
submitButton.simulate("click");
|
||||||
wrapper.setProps({});
|
wrapper.setProps({});
|
||||||
|
|
||||||
|
expect(SubmitFeedback).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should submit submission", () => {
|
||||||
|
useQueryCopilot.getState().openFeedbackModal("test query", false, "test prompt");
|
||||||
|
const explorer = new Explorer();
|
||||||
|
const wrapper = shallow(<QueryCopilotFeedbackModal explorer={explorer} />);
|
||||||
|
|
||||||
|
const submitButton = wrapper.find("form");
|
||||||
|
submitButton.simulate("submit");
|
||||||
|
wrapper.setProps({});
|
||||||
|
|
||||||
expect(SubmitFeedback).toHaveBeenCalledTimes(1);
|
expect(SubmitFeedback).toHaveBeenCalledTimes(1);
|
||||||
expect(SubmitFeedback).toHaveBeenCalledWith({
|
expect(SubmitFeedback).toHaveBeenCalledWith({
|
||||||
params: {
|
params: {
|
||||||
likeQuery: false,
|
likeQuery: false,
|
||||||
generatedQuery: "",
|
generatedQuery: "test query",
|
||||||
userPrompt: "",
|
userPrompt: "test prompt",
|
||||||
description: "",
|
description: "",
|
||||||
contact: getUserEmail(),
|
contact: getUserEmail(),
|
||||||
},
|
},
|
||||||
|
@ -25,93 +25,94 @@ export const QueryCopilotFeedbackModal = ({ explorer }: { explorer: Explorer }):
|
|||||||
closeFeedbackModal,
|
closeFeedbackModal,
|
||||||
setHideFeedbackModalForLikedQueries,
|
setHideFeedbackModalForLikedQueries,
|
||||||
} = useQueryCopilot();
|
} = useQueryCopilot();
|
||||||
const [isContactAllowed, setIsContactAllowed] = React.useState<boolean>(true);
|
const [isContactAllowed, setIsContactAllowed] = React.useState<boolean>(false);
|
||||||
const [description, setDescription] = React.useState<string>("");
|
const [description, setDescription] = React.useState<string>("");
|
||||||
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
|
const [doNotShowAgainChecked, setDoNotShowAgainChecked] = React.useState<boolean>(false);
|
||||||
const [contact, setContact] = React.useState<string>(getUserEmail());
|
const [contact, setContact] = React.useState<string>(getUserEmail());
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
closeFeedbackModal();
|
||||||
|
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
|
||||||
|
SubmitFeedback({
|
||||||
|
params: { generatedQuery, likeQuery, description, userPrompt, contact },
|
||||||
|
explorer: explorer,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={showFeedbackModal}>
|
<Modal isOpen={showFeedbackModal}>
|
||||||
<Stack style={{ padding: 24 }}>
|
<form onSubmit={handleSubmit}>
|
||||||
<Stack horizontal horizontalAlign="space-between">
|
<Stack style={{ padding: 24 }}>
|
||||||
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
|
<Stack horizontal horizontalAlign="space-between">
|
||||||
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
|
<Text style={{ fontSize: 20, fontWeight: 600, marginBottom: 20 }}>Send feedback to Microsoft</Text>
|
||||||
</Stack>
|
<IconButton iconProps={{ iconName: "Cancel" }} onClick={() => closeFeedbackModal()} />
|
||||||
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
|
</Stack>
|
||||||
<TextField
|
<Text style={{ fontSize: 14, marginBottom: 14 }}>Your feedback will help improve the experience.</Text>
|
||||||
styles={{ root: { marginBottom: 14 } }}
|
<TextField
|
||||||
label="Description"
|
styles={{ root: { marginBottom: 14 } }}
|
||||||
required
|
label="Description"
|
||||||
placeholder="Provide more details"
|
required
|
||||||
value={description}
|
placeholder="Provide more details"
|
||||||
onChange={(_, newValue) => setDescription(newValue)}
|
value={description}
|
||||||
multiline
|
onChange={(_, newValue) => setDescription(newValue)}
|
||||||
rows={3}
|
multiline
|
||||||
/>
|
rows={3}
|
||||||
<TextField
|
|
||||||
styles={{ root: { marginBottom: 14 } }}
|
|
||||||
label="Query generated"
|
|
||||||
defaultValue={generatedQuery}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
<ChoiceGroup
|
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
marginBottom: 14,
|
|
||||||
},
|
|
||||||
flexContainer: {
|
|
||||||
selectors: {
|
|
||||||
".ms-ChoiceField-field::before": { marginTop: 4 },
|
|
||||||
".ms-ChoiceField-field::after": { marginTop: 4 },
|
|
||||||
".ms-ChoiceFieldLabel": { paddingLeft: 6 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
label="May we contact you about your feedback?"
|
|
||||||
options={[
|
|
||||||
{ key: "yes", text: "Yes, you may contact me." },
|
|
||||||
{ key: "no", text: "No, do not contact me." },
|
|
||||||
]}
|
|
||||||
selectedKey={isContactAllowed ? "yes" : "no"}
|
|
||||||
onChange={(_, option) => {
|
|
||||||
setIsContactAllowed(option.key === "yes");
|
|
||||||
setContact(option.key === "yes" ? getUserEmail() : "");
|
|
||||||
}}
|
|
||||||
></ChoiceGroup>
|
|
||||||
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
|
|
||||||
{
|
|
||||||
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
|
|
||||||
Privacy statement
|
|
||||||
</Link>
|
|
||||||
}{" "}
|
|
||||||
for more information.
|
|
||||||
</Text>
|
|
||||||
{likeQuery && (
|
|
||||||
<Checkbox
|
|
||||||
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
|
|
||||||
label="Don't show me this next time"
|
|
||||||
checked={doNotShowAgainChecked}
|
|
||||||
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<TextField
|
||||||
<Stack horizontal horizontalAlign="end">
|
styles={{ root: { marginBottom: 14 } }}
|
||||||
<PrimaryButton
|
label="Query generated"
|
||||||
styles={{ root: { marginRight: 8 } }}
|
defaultValue={generatedQuery}
|
||||||
onClick={() => {
|
readOnly
|
||||||
closeFeedbackModal();
|
/>
|
||||||
setHideFeedbackModalForLikedQueries(doNotShowAgainChecked);
|
<ChoiceGroup
|
||||||
SubmitFeedback({
|
styles={{
|
||||||
params: { generatedQuery, likeQuery, description, userPrompt, contact },
|
root: {
|
||||||
explorer: explorer,
|
marginBottom: 14,
|
||||||
});
|
},
|
||||||
|
flexContainer: {
|
||||||
|
selectors: {
|
||||||
|
".ms-ChoiceField-field::before": { marginTop: 4 },
|
||||||
|
".ms-ChoiceField-field::after": { marginTop: 4 },
|
||||||
|
".ms-ChoiceFieldLabel": { paddingLeft: 6 },
|
||||||
|
},
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
label="May we contact you about your feedback?"
|
||||||
Submit
|
options={[
|
||||||
</PrimaryButton>
|
{ key: "yes", text: "Yes, you may contact me." },
|
||||||
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
|
{ key: "no", text: "No, do not contact me." },
|
||||||
|
]}
|
||||||
|
selectedKey={isContactAllowed ? "yes" : "no"}
|
||||||
|
onChange={(_, option) => {
|
||||||
|
setIsContactAllowed(option.key === "yes");
|
||||||
|
setContact(option.key === "yes" ? getUserEmail() : "");
|
||||||
|
}}
|
||||||
|
></ChoiceGroup>
|
||||||
|
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||||
|
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
|
||||||
|
{
|
||||||
|
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
|
||||||
|
Privacy statement
|
||||||
|
</Link>
|
||||||
|
}{" "}
|
||||||
|
for more information.
|
||||||
|
</Text>
|
||||||
|
{likeQuery && (
|
||||||
|
<Checkbox
|
||||||
|
styles={{ label: { paddingLeft: 0 }, root: { marginBottom: 14 } }}
|
||||||
|
label="Don't show me this next time"
|
||||||
|
checked={doNotShowAgainChecked}
|
||||||
|
onChange={(_, checked) => setDoNotShowAgainChecked(checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Stack horizontal horizontalAlign="end">
|
||||||
|
<PrimaryButton styles={{ root: { marginRight: 8 } }} type="submit">
|
||||||
|
Submit
|
||||||
|
</PrimaryButton>
|
||||||
|
<DefaultButton onClick={() => closeFeedbackModal()}>Cancel</DefaultButton>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -237,7 +237,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
const showTeachingBubble = (): void => {
|
const showTeachingBubble = (): void => {
|
||||||
if (!inputEdited.current) {
|
if (!inputEdited.current) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!inputEdited.current) {
|
if (!inputEdited.current && !isWelcomModalVisible()) {
|
||||||
toggleCopilotTeachingBubbleVisible();
|
toggleCopilotTeachingBubbleVisible();
|
||||||
inputEdited.current = true;
|
inputEdited.current = true;
|
||||||
}
|
}
|
||||||
@ -245,6 +245,10 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isWelcomModalVisible = (): boolean => {
|
||||||
|
return localStorage.getItem("hideWelcomeModal") !== "true";
|
||||||
|
};
|
||||||
|
|
||||||
const clearFeedback = () => {
|
const clearFeedback = () => {
|
||||||
resetButtonState();
|
resetButtonState();
|
||||||
resetQueryResults();
|
resetQueryResults();
|
||||||
@ -297,7 +301,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
setShowSamplePrompts(true);
|
setShowSamplePrompts(true);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter" && userPrompt) {
|
||||||
inputEdited.current = true;
|
inputEdited.current = true;
|
||||||
startGenerateQueryProcess();
|
startGenerateQueryProcess();
|
||||||
}
|
}
|
||||||
@ -533,7 +537,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
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" }}
|
||||||
>
|
>
|
||||||
Copy code
|
Copy query
|
||||||
</CommandBarButton>
|
</CommandBarButton>
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -542,11 +546,11 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
iconProps={{ iconName: "Delete" }}
|
iconProps={{ iconName: "Delete" }}
|
||||||
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
|
style={{ margin: "0 10px", backgroundColor: "#FFF8F0", transition: "background-color 0.3s ease" }}
|
||||||
>
|
>
|
||||||
Delete code
|
Delete query
|
||||||
</CommandBarButton>
|
</CommandBarButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
<WelcomeModal visible={localStorage.getItem("hideWelcomeModal") !== "true"} />
|
<WelcomeModal visible={isWelcomModalVisible()} />
|
||||||
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
|
{isSamplePromptsOpen && <SamplePrompts sampleProps={sampleProps} />}
|
||||||
{query !== "" && query.trim().length !== 0 && (
|
{query !== "" && query.trim().length !== 0 && (
|
||||||
<DeletePopup
|
<DeletePopup
|
||||||
|
@ -21,8 +21,13 @@ import * as StringUtility from "../../Shared/StringUtility";
|
|||||||
export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: QueryCopilotProps): JSX.Element => {
|
||||||
const { query, setQuery, selectedQuery, setSelectedQuery, isGeneratingQuery } = useQueryCopilot();
|
const { query, setQuery, selectedQuery, setSelectedQuery, isGeneratingQuery } = useQueryCopilot();
|
||||||
|
|
||||||
const cachedCopilotToggleStatus = localStorage.getItem(`${userContext.databaseAccount?.id}-queryCopilotToggleStatus`);
|
const cachedCopilotToggleStatus: string = localStorage.getItem(
|
||||||
const [copilotActive, setCopilotActive] = useState<boolean>(StringUtility.toBoolean(cachedCopilotToggleStatus));
|
`${userContext.databaseAccount?.id}-queryCopilotToggleStatus`,
|
||||||
|
);
|
||||||
|
const copilotInitialActive: boolean = cachedCopilotToggleStatus
|
||||||
|
? StringUtility.toBoolean(cachedCopilotToggleStatus)
|
||||||
|
: true;
|
||||||
|
const [copilotActive, setCopilotActive] = useState<boolean>(copilotInitialActive);
|
||||||
|
|
||||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||||
@ -87,7 +92,7 @@ export const QueryCopilotTab: React.FC<QueryCopilotProps> = ({ explorer }: Query
|
|||||||
<QueryCopilotPromptbar explorer={explorer} toggleCopilot={toggleCopilot}></QueryCopilotPromptbar>
|
<QueryCopilotPromptbar explorer={explorer} toggleCopilot={toggleCopilot}></QueryCopilotPromptbar>
|
||||||
)}
|
)}
|
||||||
<Stack className="tabPaneContentContainer">
|
<Stack className="tabPaneContentContainer">
|
||||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
<SplitterLayout percentage={true} vertical={true} primaryIndex={0} primaryMinSize={30} secondaryMinSize={70}>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
language={"sql"}
|
language={"sql"}
|
||||||
content={query}
|
content={query}
|
||||||
|
@ -17,6 +17,37 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<QueryCopilotPromptbar
|
||||||
|
explorer={
|
||||||
|
Explorer {
|
||||||
|
"_isInitializingNotebooks": false,
|
||||||
|
"_resetNotebookWorkspace": [Function],
|
||||||
|
"isFixedCollectionWithSharedThroughputSupported": [Function],
|
||||||
|
"isTabsContentExpanded": [Function],
|
||||||
|
"onRefreshDatabasesKeyPress": [Function],
|
||||||
|
"onRefreshResourcesClick": [Function],
|
||||||
|
"phoenixClient": PhoenixClient {
|
||||||
|
"armResourceId": undefined,
|
||||||
|
"retryOptions": Object {
|
||||||
|
"maxTimeout": 5000,
|
||||||
|
"minTimeout": 5000,
|
||||||
|
"retries": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"provideFeedbackEmail": [Function],
|
||||||
|
"queriesClient": QueriesClient {
|
||||||
|
"container": [Circular],
|
||||||
|
},
|
||||||
|
"refreshNotebookList": [Function],
|
||||||
|
"resourceTree": ResourceTreeAdapter {
|
||||||
|
"container": [Circular],
|
||||||
|
"copyNotebook": [Function],
|
||||||
|
"parameters": [Function],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleCopilot={[Function]}
|
||||||
|
/>
|
||||||
<Stack
|
<Stack
|
||||||
className="tabPaneContentContainer"
|
className="tabPaneContentContainer"
|
||||||
>
|
>
|
||||||
@ -25,10 +56,10 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] =
|
|||||||
onDragEnd={null}
|
onDragEnd={null}
|
||||||
onDragStart={null}
|
onDragStart={null}
|
||||||
onSecondaryPaneSizeChange={null}
|
onSecondaryPaneSizeChange={null}
|
||||||
percentage={false}
|
percentage={true}
|
||||||
primaryIndex={0}
|
primaryIndex={0}
|
||||||
primaryMinSize={100}
|
primaryMinSize={30}
|
||||||
secondaryMinSize={200}
|
secondaryMinSize={70}
|
||||||
vertical={true}
|
vertical={true}
|
||||||
>
|
>
|
||||||
<EditorReact
|
<EditorReact
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
jest.mock("../../../hooks/useDirectories");
|
jest.mock("../../../hooks/useDirectories");
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { fireEvent, render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { extractFeatures } from "Platform/Hosted/extractFeatures";
|
||||||
|
import { updateUserContext, userContext } from "UserContext";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConnectExplorer } from "./ConnectExplorer";
|
import { ConnectExplorer } from "./ConnectExplorer";
|
||||||
|
|
||||||
@ -16,3 +18,24 @@ it("shows the connect form", () => {
|
|||||||
fireEvent.click(screen.getByText("Connect to your account with connection string"));
|
fireEvent.click(screen.getByText("Connect to your account with connection string"));
|
||||||
expect(screen.queryByPlaceholderText("Please enter a connection string")).toBeDefined();
|
expect(screen.queryByPlaceholderText("Please enter a connection string")).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hides the connection string link when feature.disableConnectionStringLogin is true", () => {
|
||||||
|
const connectionString = "fakeConnectionString";
|
||||||
|
const login = jest.fn();
|
||||||
|
const setConnectionString = jest.fn();
|
||||||
|
const setEncryptedToken = jest.fn();
|
||||||
|
const setAuthType = jest.fn();
|
||||||
|
const oldFeatures = userContext.features;
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
"feature.disableConnectionStringLogin": "true",
|
||||||
|
});
|
||||||
|
|
||||||
|
const testFeatures = extractFeatures(params);
|
||||||
|
updateUserContext({ features: testFeatures });
|
||||||
|
|
||||||
|
render(<ConnectExplorer {...{ login, setEncryptedToken, setAuthType, connectionString, setConnectionString }} />);
|
||||||
|
expect(screen.queryByPlaceholderText("Connect to your account with connection string")).toBeNull();
|
||||||
|
|
||||||
|
updateUserContext({ features: oldFeatures });
|
||||||
|
});
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
|
import { userContext } from "UserContext";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
||||||
import ErrorImage from "../../../../images/error.svg";
|
import ErrorImage from "../../../../images/error.svg";
|
||||||
@ -37,6 +38,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
|||||||
setConnectionString,
|
setConnectionString,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isFormVisible, { setTrue: showForm }] = useBoolean(false);
|
const [isFormVisible, { setTrue: showForm }] = useBoolean(false);
|
||||||
|
const enableConnectionStringLogin = !userContext.features.disableConnectionStringLogin;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
||||||
@ -46,7 +48,7 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
|||||||
<img src={ConnectImage} alt="Azure Cosmos DB" />
|
<img src={ConnectImage} alt="Azure Cosmos DB" />
|
||||||
</p>
|
</p>
|
||||||
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
|
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
|
||||||
{isFormVisible ? (
|
{isFormVisible && enableConnectionStringLogin ? (
|
||||||
<form
|
<form
|
||||||
id="connectWithConnectionString"
|
id="connectWithConnectionString"
|
||||||
onSubmit={async (event) => {
|
onSubmit={async (event) => {
|
||||||
@ -89,9 +91,11 @@ export const ConnectExplorer: React.FunctionComponent<Props> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div id="connectWithAad">
|
<div id="connectWithAad">
|
||||||
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
|
||||||
<p className="switchConnectTypeText" onClick={showForm}>
|
{enableConnectionStringLogin && (
|
||||||
Connect to your account with connection string
|
<p className="switchConnectTypeText" onClick={showForm}>
|
||||||
</p>
|
Connect to your account with connection string
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,6 +41,7 @@ export type Features = {
|
|||||||
readonly enableCopilotFullSchema: boolean;
|
readonly enableCopilotFullSchema: boolean;
|
||||||
readonly copilotChatFixedMonacoEditorHeight: boolean;
|
readonly copilotChatFixedMonacoEditorHeight: boolean;
|
||||||
readonly enablePriorityBasedExecution: boolean;
|
readonly enablePriorityBasedExecution: boolean;
|
||||||
|
readonly disableConnectionStringLogin: boolean;
|
||||||
|
|
||||||
// can be set via both flight and feature flag
|
// can be set via both flight and feature flag
|
||||||
autoscaleDefault: boolean;
|
autoscaleDefault: boolean;
|
||||||
@ -114,6 +115,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
enableCopilotFullSchema: "true" === get("enablecopilotfullschema", "true"),
|
enableCopilotFullSchema: "true" === get("enablecopilotfullschema", "true"),
|
||||||
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
|
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
|
||||||
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
|
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
|
||||||
|
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,15 +2,13 @@ import { createUri } from "Common/UrlUtility";
|
|||||||
import { FabricMessage } from "Contracts/FabricContract";
|
import { FabricMessage } from "Contracts/FabricContract";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { fetchEncryptedToken } from "Platform/Hosted/Components/ConnectExplorer";
|
|
||||||
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
|
||||||
import { fetchAccessData } from "hooks/usePortalAccessToken";
|
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { AccountKind, Flights } from "../Common/Constants";
|
import { AccountKind, Flights } from "../Common/Constants";
|
||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import { sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
import { handleCachedDataMessage, sendMessage, sendReadyMessage } from "../Common/MessageHandler";
|
||||||
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
|
import { Platform, configContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
|
import { ActionType, DataExplorerAction, TabKind } from "../Contracts/ActionContracts";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
@ -107,23 +105,7 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "initialize": {
|
case "initialize": {
|
||||||
// TODO For now, retrieve info from session storage. Replace with info injected into Data Explorer
|
explorer = await configureWithFabric(data.message.endpoint);
|
||||||
const connectionString = data.connectionString ?? sessionStorage.getItem("connectionString");
|
|
||||||
if (!connectionString) {
|
|
||||||
console.error("No connection string found in session storage");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const encryptedToken = await fetchEncryptedToken(connectionString);
|
|
||||||
// TODO Duplicated from useTokenMetadata
|
|
||||||
const encryptedTokenMetadata = await fetchAccessData(encryptedToken);
|
|
||||||
|
|
||||||
const hostedConfig: EncryptedToken = {
|
|
||||||
authType: AuthType.EncryptedToken,
|
|
||||||
encryptedToken,
|
|
||||||
encryptedTokenMetadata,
|
|
||||||
};
|
|
||||||
|
|
||||||
explorer = await configureWithEncryptedToken(hostedConfig);
|
|
||||||
resolve(explorer);
|
resolve(explorer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -166,6 +148,10 @@ async function configureFabric(): Promise<Explorer> {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "authorizationToken": {
|
||||||
|
handleCachedDataMessage(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
|
console.error(`Unknown Fabric message type: ${JSON.stringify(data)}`);
|
||||||
break;
|
break;
|
||||||
@ -315,6 +301,25 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer {
|
|||||||
return explorer;
|
return explorer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function configureWithFabric(documentEndpoint: string): Explorer {
|
||||||
|
updateUserContext({
|
||||||
|
authType: AuthType.ConnectionString,
|
||||||
|
databaseAccount: {
|
||||||
|
id: "",
|
||||||
|
location: "",
|
||||||
|
type: "",
|
||||||
|
name: "Mounted",
|
||||||
|
kind: AccountKind.Default,
|
||||||
|
properties: {
|
||||||
|
documentEndpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const explorer = new Explorer();
|
||||||
|
setTimeout(() => explorer.refreshAllDatabases(), 0);
|
||||||
|
return explorer;
|
||||||
|
}
|
||||||
|
|
||||||
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
function configureWithEncryptedToken(config: EncryptedToken): Explorer {
|
||||||
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
const apiExperience = DefaultExperienceUtility.getDefaultExperienceFromApiKind(config.encryptedTokenMetadata.apiKind);
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
@ -128,7 +128,7 @@ export const useQueryCopilot: QueryCopilotStore = create((set) => ({
|
|||||||
|
|
||||||
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
openFeedbackModal: (generatedQuery: string, likeQuery: boolean, userPrompt: string) =>
|
||||||
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
set({ generatedQuery, likeQuery, userPrompt, showFeedbackModal: true }),
|
||||||
closeFeedbackModal: () => set({ generatedQuery: "", likeQuery: false, userPrompt: "", showFeedbackModal: false }),
|
closeFeedbackModal: () => set({ showFeedbackModal: false }),
|
||||||
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
setHideFeedbackModalForLikedQueries: (hideFeedbackModalForLikedQueries: boolean) =>
|
||||||
set({ hideFeedbackModalForLikedQueries }),
|
set({ hideFeedbackModalForLikedQueries }),
|
||||||
refreshCorrelationId: () => set({ correlationId: guid() }),
|
refreshCorrelationId: () => set({ correlationId: guid() }),
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<conditions>
|
<conditions>
|
||||||
<add input="{HTTP_HOST}" pattern="^cosmos.azure.com" />
|
<add input="{HTTP_HOST}" pattern="^cosmos.azure.com" />
|
||||||
</conditions>
|
</conditions>
|
||||||
<action type="Redirect" url="/?feature.enableAadDataPlane=true" redirectType="Permanent" />
|
<action type="Redirect" url="/?feature.enableAadDataPlane=true&feature.disableConnectionStringLogin=true" redirectType="Permanent" />
|
||||||
</rule>
|
</rule>
|
||||||
</rules>
|
</rules>
|
||||||
<outboundRules>
|
<outboundRules>
|
||||||
|
Loading…
Reference in New Issue
Block a user