mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 09:20:16 +00:00
Implement query copilot UI (#1452)
This commit is contained in:
11
src/Explorer/QueryCopilot/CopilotCarousel.test.tsx
Normal file
11
src/Explorer/QueryCopilot/CopilotCarousel.test.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotCarousel } from "./CopilotCarousel";
|
||||
|
||||
describe("Query Copilot Carousel snapshot test", () => {
|
||||
it("should render when isOpen is true", () => {
|
||||
const wrapper = shallow(<QueryCopilotCarousel isOpen={true} explorer={new Explorer()} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
275
src/Explorer/QueryCopilot/CopilotCarousel.tsx
Normal file
275
src/Explorer/QueryCopilot/CopilotCarousel.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import {
|
||||
DefaultButton,
|
||||
ISeparatorStyles,
|
||||
IconButton,
|
||||
Image,
|
||||
Link,
|
||||
Modal,
|
||||
PrimaryButton,
|
||||
Separator,
|
||||
Spinner,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@fluentui/react";
|
||||
import { QueryCopilotSampleDatabaseId, StyleConstants } from "Common/Constants";
|
||||
import { handleError } from "Common/ErrorHandlingUtils";
|
||||
import { createCollection } from "Common/dataAccess/createCollection";
|
||||
import * as DataModels from "Contracts/DataModels";
|
||||
import { ContainerSampleGenerator } from "Explorer/DataSamples/ContainerSampleGenerator";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { AllPropertiesIndexed } from "Explorer/Panes/AddCollectionPanel";
|
||||
import { PromptCard } from "Explorer/QueryCopilot/PromptCard";
|
||||
import { useDatabases } from "Explorer/useDatabases";
|
||||
import { useCarousel } from "hooks/useCarousel";
|
||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||
import React, { useState } from "react";
|
||||
import YoutubePlaceholder from "../../../images/YoutubePlaceholder.svg";
|
||||
|
||||
interface QueryCopilotCarouselProps {
|
||||
isOpen: boolean;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
const separatorStyles: Partial<ISeparatorStyles> = {
|
||||
root: {
|
||||
selectors: {
|
||||
"::before": {
|
||||
background: StyleConstants.BaseMedium,
|
||||
},
|
||||
},
|
||||
padding: "16px 0",
|
||||
height: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const QueryCopilotCarousel: React.FC<QueryCopilotCarouselProps> = ({
|
||||
isOpen,
|
||||
explorer,
|
||||
}: QueryCopilotCarouselProps): JSX.Element => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isCreatingDatabase, setIsCreatingDatabase] = useState<boolean>(false);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<number>(1);
|
||||
|
||||
const getHeaderText = (): string => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return "What exactly is copilot?";
|
||||
case 2:
|
||||
return "Setting up your Sample database";
|
||||
case 3:
|
||||
return "Sample prompts to help you";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const getQueryCopilotInitialInput = (): string => {
|
||||
switch (selectedPrompt) {
|
||||
case 1:
|
||||
return "Write a query to return all records in this table";
|
||||
case 2:
|
||||
return "Write a query to return all records in this table created in the last thirty days";
|
||||
case 3:
|
||||
return 'Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"';
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const createSampleDatabase = async (): Promise<void> => {
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
if (database) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreatingDatabase(true);
|
||||
const params: DataModels.CreateCollectionParams = {
|
||||
createNewDatabase: true,
|
||||
collectionId: "SampleContainer",
|
||||
databaseId: QueryCopilotSampleDatabaseId,
|
||||
databaseLevelThroughput: true,
|
||||
autoPilotMaxThroughput: 1000,
|
||||
offerThroughput: undefined,
|
||||
indexingPolicy: AllPropertiesIndexed,
|
||||
partitionKey: {
|
||||
paths: ["/CategoryId"],
|
||||
kind: "Hash",
|
||||
version: 2,
|
||||
},
|
||||
};
|
||||
await createCollection(params);
|
||||
await explorer.refreshAllDatabases();
|
||||
const database = useDatabases.getState().findDatabaseWithId(QueryCopilotSampleDatabaseId);
|
||||
// populate sample container with sample data
|
||||
await database.loadCollections();
|
||||
const collection = database.findCollectionWithId("SampleContainer");
|
||||
collection.isSampleCollection = true;
|
||||
const sampleGenerator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorer);
|
||||
await sampleGenerator.populateContainerAsync(collection, "/CategoryId");
|
||||
// auto-expand sample database + container and show teaching bubble
|
||||
await database.expandDatabase();
|
||||
collection.expandCollection();
|
||||
useDatabases.getState().updateDatabase(database);
|
||||
} catch (error) {
|
||||
//TODO: show error in UI
|
||||
handleError(error, "Query copilot quickstart");
|
||||
throw error;
|
||||
} finally {
|
||||
setIsCreatingDatabase(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getContent = (): JSX.Element => {
|
||||
switch (page) {
|
||||
case 1:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context
|
||||
to the user.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>How do you use copilot</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch
|
||||
this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image src={YoutubePlaceholder} style={{ margin: "16px auto" }} />
|
||||
<Text style={{ fontSize: 14, fontWeight: 600 }}>What is copilot good at</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot can do and its capablites with a link to{" "}
|
||||
<Link href="" target="_blank">
|
||||
documentation
|
||||
</Link>{" "}
|
||||
if possible.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>What are its limitations</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
A couple of lines about what copilot cant do and its limitations.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Link to documentation
|
||||
</Link>
|
||||
</Text>
|
||||
<Text style={{ fontSize: 14, fontWeight: 600, marginTop: 16 }}>Disclaimer</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 8 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Stack style={{ marginTop: 8 }}>
|
||||
<Text style={{ fontSize: 13 }}>
|
||||
Before you get started, we need to configure your sample database for you. Here is a summary of the
|
||||
database being created for your reference. Configuration values can be updated using the settings icon in
|
||||
the query builder.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 24 }}>Database Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>CopilotSampleDb</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database throughput (autoscale)</Text>
|
||||
<Text style={{ fontSize: 13 }}>Autoscale</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Database Max RU/s</Text>
|
||||
<Text>1000</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Your database throughput will automatically scale from{" "}
|
||||
<strong>100 RU/s (10% of max RU/s) - 1000 RU/s</strong> based on usage.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 10, marginTop: 8 }}>
|
||||
Estimated monthly cost (USD): <strong>$8.76 - $87.60</strong> (1 region, 100 - 1000 RU/s, $0.00012/RU)
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Container Id</Text>
|
||||
<Text style={{ fontSize: 13 }}>SampleContainer</Text>
|
||||
<Text style={{ fontSize: 13, fontWeight: 600, marginTop: 16 }}>Partition key</Text>
|
||||
<Text style={{ fontSize: 13 }}>CategoryId</Text>
|
||||
</Stack>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Stack>
|
||||
<Text>To help you get started, here are some sample prompts to get you started</Text>
|
||||
<Stack tokens={{ childrenGap: 12 }} style={{ marginTop: 16 }}>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table"
|
||||
description="This is a basic query which returns all records in the table "
|
||||
onSelect={() => setSelectedPrompt(1)}
|
||||
isSelected={selectedPrompt === 1}
|
||||
/>
|
||||
<PromptCard
|
||||
header="Write a query to return all records in this table created in the last thirty days"
|
||||
description="This builds on the previous query which returns all records in the table which were inserted in the last thirty days. You can also modify this query to return records based upon creation date"
|
||||
onSelect={() => setSelectedPrompt(2)}
|
||||
isSelected={selectedPrompt === 2}
|
||||
/>
|
||||
<PromptCard
|
||||
header='Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"'
|
||||
description='This builds on the previous query which returns all records in the table which were inserted in the last thirty days but which has the record owner as "contoso"'
|
||||
onSelect={() => setSelectedPrompt(3)}
|
||||
isSelected={selectedPrompt === 3}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 13, marginTop: 32 }}>
|
||||
Interested in learning more about how to write effective prompts. Please read this article for more
|
||||
information.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
You can also access these prompts by selecting the Samples prompts button in the query builder page.
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, marginTop: 16 }}>
|
||||
Don't like any of the prompts? Just click Get Started and write your own prompt.
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal styles={{ main: { width: 880 } }} isOpen={isOpen && page < 4}>
|
||||
<Stack style={{ padding: 16 }}>
|
||||
<Stack horizontal horizontalAlign="space-between">
|
||||
<Text variant="xLarge">{getHeaderText()}</Text>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
onClick={() => useCarousel.getState().setShowCopilotCarousel(false)}
|
||||
/>
|
||||
</Stack>
|
||||
{getContent()}
|
||||
<Separator styles={separatorStyles} />
|
||||
<Stack horizontal horizontalAlign="start" verticalAlign="center">
|
||||
{page !== 1 && (
|
||||
<DefaultButton
|
||||
text="Previous"
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
)}
|
||||
<PrimaryButton
|
||||
text={page === 3 ? "Get started" : "Next"}
|
||||
onClick={async () => {
|
||||
if (page === 3) {
|
||||
useCarousel.getState().setShowCopilotCarousel(false);
|
||||
useTabs.getState().setQueryCopilotTabInitialInput(getQueryCopilotInitialInput());
|
||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (page === 2) {
|
||||
await createSampleDatabase();
|
||||
}
|
||||
|
||||
setPage(page + 1);
|
||||
}}
|
||||
disabled={isCreatingDatabase}
|
||||
/>
|
||||
{isCreatingDatabase && <Spinner style={{ marginLeft: 8 }} />}
|
||||
{isCreatingDatabase && <Text style={{ marginLeft: 8, color: "#0078D4" }}>Setting up your database...</Text>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
19
src/Explorer/QueryCopilot/PromptCard.test.tsx
Normal file
19
src/Explorer/QueryCopilot/PromptCard.test.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import { PromptCard } from "./PromptCard";
|
||||
|
||||
describe("Prompt card snapshot test", () => {
|
||||
it("should render properly if isSelected is true", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={true} onSelect={() => undefined} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render properly if isSelected is false", () => {
|
||||
const wrapper = shallow(
|
||||
<PromptCard header="TestHeader" description="TestDescription" isSelected={false} onSelect={() => undefined} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
48
src/Explorer/QueryCopilot/PromptCard.tsx
Normal file
48
src/Explorer/QueryCopilot/PromptCard.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ChoiceGroup, Stack, Text } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
interface PromptCardProps {
|
||||
header: string;
|
||||
description: string;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
}
|
||||
|
||||
export const PromptCard: React.FC<PromptCardProps> = ({
|
||||
header,
|
||||
description,
|
||||
isSelected,
|
||||
onSelect,
|
||||
}: PromptCardProps): JSX.Element => {
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
style={{
|
||||
padding: "16px 0 16px 16px ",
|
||||
boxSizing: "border-box",
|
||||
width: 650,
|
||||
height: 100,
|
||||
border: "1px solid #F3F2F1",
|
||||
boxShadow: "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
}}
|
||||
>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack horizontal>
|
||||
<div>
|
||||
<Text style={{ fontSize: 13, color: "#00A2AD", background: "#F8FFF0" }}>Prompt</Text>
|
||||
</div>
|
||||
<Text style={{ fontSize: 13, marginLeft: 16 }}>{header}</Text>
|
||||
</Stack>
|
||||
<Text style={{ fontSize: 10, marginTop: 16 }}>{description}</Text>
|
||||
</Stack.Item>
|
||||
<Stack.Item style={{ marginLeft: 16 }}>
|
||||
<ChoiceGroup
|
||||
styles={{ flexContainer: { width: 36 } }}
|
||||
options={[{ key: "selected", text: "" }]}
|
||||
selectedKey={isSelected ? "selected" : ""}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
13
src/Explorer/QueryCopilot/QueryCopilotTab.test.tsx
Normal file
13
src/Explorer/QueryCopilot/QueryCopilotTab.test.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Explorer from "../Explorer";
|
||||
import { QueryCopilotTab } from "./QueryCopilotTab";
|
||||
|
||||
describe("Query copilot tab snapshot test", () => {
|
||||
it("should render with initial input", () => {
|
||||
const wrapper = shallow(
|
||||
<QueryCopilotTab initialInput="Write a query to return all records in this table" explorer={new Explorer()} />
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
164
src/Explorer/QueryCopilot/QueryCopilotTab.tsx
Normal file
164
src/Explorer/QueryCopilot/QueryCopilotTab.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/* eslint-disable no-console */
|
||||
import { FeedOptions } from "@azure/cosmos";
|
||||
import { IconButton, Image, Link, Stack, Text, TextField } from "@fluentui/react";
|
||||
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||
import { getErrorMessage, handleError } from "Common/ErrorHandlingUtils";
|
||||
import { shouldEnableCrossPartitionKey } from "Common/HeadersUtility";
|
||||
import { MinimalQueryIterator } from "Common/IteratorUtilities";
|
||||
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
||||
import { queryDocumentsPage } from "Common/dataAccess/queryDocumentsPage";
|
||||
import { QueryResults } from "Contracts/ViewModels";
|
||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
||||
import Explorer from "Explorer/Explorer";
|
||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||
import { SaveQueryPane } from "Explorer/Panes/SaveQueryPane/SaveQueryPane";
|
||||
import { QueryResultSection } from "Explorer/Tabs/QueryTab/QueryResultSection";
|
||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||
import { useSidePanel } from "hooks/useSidePanel";
|
||||
import React, { useState } from "react";
|
||||
import SplitterLayout from "react-splitter-layout";
|
||||
import CopilotIcon from "../../../images/Copilot.svg";
|
||||
import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
|
||||
import SaveQueryIcon from "../../../images/save-cosmos.svg";
|
||||
|
||||
interface QueryCopilotTabProps {
|
||||
initialInput: string;
|
||||
explorer: Explorer;
|
||||
}
|
||||
|
||||
export const QueryCopilotTab: React.FC<QueryCopilotTabProps> = ({
|
||||
initialInput,
|
||||
explorer,
|
||||
}: QueryCopilotTabProps): JSX.Element => {
|
||||
const [userInput, setUserInput] = useState<string>(initialInput || "");
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [selectedQuery, setSelectedQuery] = useState<string>("");
|
||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||
const [queryIterator, setQueryIterator] = useState<MinimalQueryIterator>();
|
||||
const [queryResults, setQueryResults] = useState<QueryResults>();
|
||||
const [errorMessage, setErrorMessage] = useState<string>("");
|
||||
|
||||
const generateQuery = (): string => {
|
||||
switch (userInput) {
|
||||
case "Write a query to return all records in this table":
|
||||
return "SELECT * FROM c";
|
||||
case "Write a query to return all records in this table created in the last thirty days":
|
||||
return "SELECT * FROM c WHERE c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000";
|
||||
case `Write a query to return all records in this table created in the last thirty days which also have the record owner as "Contoso"`:
|
||||
return `SELECT * FROM c WHERE c.owner = "Contoso" AND c._ts > (DATEDIFF(s, '1970-01-01T00:00:00Z', GETUTCDATE()) - 2592000) * 1000`;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
const onExecuteQueryClick = async (): Promise<void> => {
|
||||
const queryToExecute = selectedQuery || query;
|
||||
const queryIterator = queryDocuments(QueryCopilotSampleDatabaseId, QueryCopilotSampleContainerId, queryToExecute, {
|
||||
enableCrossPartitionQuery: shouldEnableCrossPartitionKey(),
|
||||
} as FeedOptions);
|
||||
setQueryIterator(queryIterator);
|
||||
|
||||
setTimeout(async () => {
|
||||
await queryDocumentsPerPage(0, queryIterator);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const queryDocumentsPerPage = async (firstItemIndex: number, queryIterator: MinimalQueryIterator): Promise<void> => {
|
||||
try {
|
||||
setIsExecuting(true);
|
||||
const queryResults: QueryResults = await queryPagesUntilContentPresent(
|
||||
firstItemIndex,
|
||||
async (firstItemIndex: number) =>
|
||||
queryDocumentsPage(QueryCopilotSampleContainerId, queryIterator, firstItemIndex)
|
||||
);
|
||||
|
||||
setQueryResults(queryResults);
|
||||
setErrorMessage("");
|
||||
} catch (error) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
setErrorMessage(errorMessage);
|
||||
handleError(errorMessage, "executeQueryCopilotTab");
|
||||
} finally {
|
||||
setIsExecuting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getCommandbarButtons = (): CommandButtonComponentProps[] => {
|
||||
const executeQueryBtnLabel = selectedQuery ? "Execute Selection" : "Execute Query";
|
||||
const executeQueryBtn = {
|
||||
iconSrc: ExecuteQueryIcon,
|
||||
iconAlt: executeQueryBtnLabel,
|
||||
onCommandClick: () => onExecuteQueryClick(),
|
||||
commandButtonLabel: executeQueryBtnLabel,
|
||||
ariaLabel: executeQueryBtnLabel,
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
const saveQueryBtn = {
|
||||
iconSrc: SaveQueryIcon,
|
||||
iconAlt: "Save Query",
|
||||
onCommandClick: () =>
|
||||
useSidePanel.getState().openSidePanel("Save Query", <SaveQueryPane explorer={explorer} queryToSave={query} />),
|
||||
commandButtonLabel: "Save Query",
|
||||
ariaLabel: "Save Query",
|
||||
hasPopup: false,
|
||||
};
|
||||
|
||||
return [executeQueryBtn, saveQueryBtn];
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
useCommandBar.getState().setContextButtons(getCommandbarButtons());
|
||||
}, [query]);
|
||||
|
||||
return (
|
||||
<Stack className="tab-pane" style={{ padding: 24, width: "100%", height: "100%" }}>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Image src={CopilotIcon} />
|
||||
<Text style={{ marginLeft: 8, fontWeight: 600, fontSize: 16 }}>Copilot</Text>
|
||||
</Stack>
|
||||
<Stack horizontal verticalAlign="center" style={{ marginTop: 16, width: "100%" }}>
|
||||
<TextField
|
||||
value={userInput}
|
||||
onChange={(_, newValue) => setUserInput(newValue)}
|
||||
style={{ lineHeight: 30 }}
|
||||
styles={{ root: { width: "90%" } }}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "Send" }}
|
||||
style={{ marginLeft: 8 }}
|
||||
onClick={() => setQuery(generateQuery())}
|
||||
/>
|
||||
</Stack>
|
||||
<Text style={{ marginTop: 8, marginBottom: 24, fontSize: 12 }}>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.{" "}
|
||||
<Link href="" target="_blank">
|
||||
Read preview terms
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
<Stack className="tabPaneContentContainer">
|
||||
<SplitterLayout vertical={true} primaryIndex={0} primaryMinSize={100} secondaryMinSize={200}>
|
||||
<EditorReact
|
||||
language={"sql"}
|
||||
content={query}
|
||||
isReadOnly={false}
|
||||
ariaLabel={"Editing Query"}
|
||||
lineNumbers={"on"}
|
||||
onContentChanged={(newQuery: string) => setQuery(newQuery)}
|
||||
onContentSelected={(selectedQuery: string) => setSelectedQuery(selectedQuery)}
|
||||
/>
|
||||
<QueryResultSection
|
||||
isMongoDB={false}
|
||||
queryEditorContent={selectedQuery || query}
|
||||
error={errorMessage}
|
||||
queryResults={queryResults}
|
||||
isExecuting={isExecuting}
|
||||
executeQueryDocumentsPage={(firstItemIndex: number) => queryDocumentsPerPage(firstItemIndex, queryIterator)}
|
||||
/>
|
||||
</SplitterLayout>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query Copilot Carousel snapshot test should render when isOpen is true 1`] = `
|
||||
<Modal
|
||||
isOpen={true}
|
||||
styles={
|
||||
Object {
|
||||
"main": Object {
|
||||
"width": 880,
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"padding": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<Text
|
||||
variant="xLarge"
|
||||
>
|
||||
What exactly is copilot?
|
||||
</Text>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Cancel",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about copilot and the background about it. The idea is to have some text to give context to the user.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
How do you use copilot
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
To generate queries , just describe the query you want and copilot will generate the query for you.Watch this video to learn more about how to use copilot.
|
||||
</Text>
|
||||
<Image
|
||||
src=""
|
||||
style={
|
||||
Object {
|
||||
"margin": "16px auto",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
}
|
||||
}
|
||||
>
|
||||
What is copilot good at
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot can do and its capablites with a link to
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
documentation
|
||||
</StyledLinkBase>
|
||||
|
||||
if possible.
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
What are its limitations
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
A couple of lines about what copilot cant do and its limitations.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Link to documentation
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 14,
|
||||
"fontWeight": 600,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
Disclaimer
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Separator
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"height": 1,
|
||||
"padding": "16px 0",
|
||||
"selectors": Object {
|
||||
"::before": Object {
|
||||
"background": undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="start"
|
||||
verticalAlign="center"
|
||||
>
|
||||
<CustomizedPrimaryButton
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
text="Next"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
`;
|
||||
171
src/Explorer/QueryCopilot/__snapshots__/PromptCard.test.tsx.snap
Normal file
171
src/Explorer/QueryCopilot/__snapshots__/PromptCard.test.tsx.snap
Normal file
@@ -0,0 +1,171 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is false 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey=""
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Object {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
|
||||
exports[`Prompt card snapshot test should render properly if isSelected is true 1`] = `
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"border": "1px solid #F3F2F1",
|
||||
"boxShadow": "0px 1.6px 3.6px rgba(0, 0, 0, 0.132), 0px 0.3px 0.9px rgba(0, 0, 0, 0.108)",
|
||||
"boxSizing": "border-box",
|
||||
"height": 100,
|
||||
"padding": "16px 0 16px 16px ",
|
||||
"width": 650,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StackItem
|
||||
grow={1}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"background": "#F8FFF0",
|
||||
"color": "#00A2AD",
|
||||
"fontSize": 13,
|
||||
}
|
||||
}
|
||||
>
|
||||
Prompt
|
||||
</Text>
|
||||
</div>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 13,
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestHeader
|
||||
</Text>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 10,
|
||||
"marginTop": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
TestDescription
|
||||
</Text>
|
||||
</StackItem>
|
||||
<StackItem
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 16,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledChoiceGroup
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "selected",
|
||||
"text": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="selected"
|
||||
styles={
|
||||
Object {
|
||||
"flexContainer": Object {
|
||||
"width": 36,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
`;
|
||||
@@ -0,0 +1,124 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Query copilot tab snapshot test should render with initial input 1`] = `
|
||||
<Stack
|
||||
className="tab-pane"
|
||||
style={
|
||||
Object {
|
||||
"height": "100%",
|
||||
"padding": 24,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<Image
|
||||
src=""
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 16,
|
||||
"fontWeight": 600,
|
||||
"marginLeft": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
Copilot
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": 16,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
verticalAlign="center"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"lineHeight": 30,
|
||||
}
|
||||
}
|
||||
styles={
|
||||
Object {
|
||||
"root": Object {
|
||||
"width": "90%",
|
||||
},
|
||||
}
|
||||
}
|
||||
value="Write a query to return all records in this table"
|
||||
/>
|
||||
<CustomizedIconButton
|
||||
iconProps={
|
||||
Object {
|
||||
"iconName": "Send",
|
||||
}
|
||||
}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginLeft": 8,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"fontSize": 12,
|
||||
"marginBottom": 24,
|
||||
"marginTop": 8,
|
||||
}
|
||||
}
|
||||
>
|
||||
AI-generated content can have mistakes. Make sure it's accurate and appropriate before using it.
|
||||
|
||||
<StyledLinkBase
|
||||
href=""
|
||||
target="_blank"
|
||||
>
|
||||
Read preview terms
|
||||
</StyledLinkBase>
|
||||
</Text>
|
||||
<Stack
|
||||
className="tabPaneContentContainer"
|
||||
>
|
||||
<t
|
||||
customClassName=""
|
||||
onDragEnd={null}
|
||||
onDragStart={null}
|
||||
onSecondaryPaneSizeChange={null}
|
||||
percentage={false}
|
||||
primaryIndex={0}
|
||||
primaryMinSize={100}
|
||||
secondaryMinSize={200}
|
||||
vertical={true}
|
||||
>
|
||||
<EditorReact
|
||||
ariaLabel="Editing Query"
|
||||
content=""
|
||||
isReadOnly={false}
|
||||
language="sql"
|
||||
lineNumbers="on"
|
||||
onContentChanged={[Function]}
|
||||
onContentSelected={[Function]}
|
||||
/>
|
||||
<QueryResultSection
|
||||
error=""
|
||||
executeQueryDocumentsPage={[Function]}
|
||||
isExecuting={false}
|
||||
isMongoDB={false}
|
||||
queryEditorContent=""
|
||||
/>
|
||||
</t>
|
||||
</Stack>
|
||||
</Stack>
|
||||
`;
|
||||
Reference in New Issue
Block a user