-
-
- {/* Main Command Bar - Start */}
-
- {/* Collections Tree and Tabs - Begin */}
-
- {/* Collections Tree and Tabs - End */}
-
-
-
-
+ {
+ userContext.features.enableContainerCopy && userContext.apiType === "SQL" ? (
+
+ ) : (
+
+ )
+ }
+
{
}
@@ -113,6 +111,27 @@ const App: React.FunctionComponent = () => {
const mainElement = document.getElementById("Main");
ReactDOM.render(
, mainElement);
+function DivExplorer({ explorer }: { explorer: Explorer }): JSX.Element {
+ return (
+
+
+ {/* Main Command Bar - Start */}
+
+ {/* Collections Tree and Tabs - Begin */}
+
+ {/* Collections Tree and Tabs - End */}
+
+
+
+
+ );
+}
+
function LoadingExplorer(): JSX.Element {
return (
diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts
index 685930b43..80a5494a4 100644
--- a/src/Platform/Hosted/extractFeatures.ts
+++ b/src/Platform/Hosted/extractFeatures.ts
@@ -39,6 +39,7 @@ export type Features = {
readonly copilotChatFixedMonacoEditorHeight: boolean;
readonly enablePriorityBasedExecution: boolean;
readonly disableConnectionStringLogin: boolean;
+ readonly enableContainerCopy: boolean;
readonly enableCloudShell: boolean;
// can be set via both flight and feature flag
@@ -111,6 +112,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
+ enableContainerCopy: "true" === get("enablecontainercopy"),
enableCloudShell: true,
};
}
diff --git a/src/hooks/useDataContainers.tsx b/src/hooks/useDataContainers.tsx
new file mode 100644
index 000000000..e8702a7d4
--- /dev/null
+++ b/src/hooks/useDataContainers.tsx
@@ -0,0 +1,82 @@
+import { DatabaseModel } from "Contracts/DataModels";
+import useSWR from "swr";
+import { getCollectionEndpoint, getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
+import { configContext } from "../ConfigContext";
+import { FetchDataContainersListParams } from "../Explorer/ContainerCopy/Types";
+import { ApiType } from "../UserContext";
+
+const apiVersion = "2023-09-15";
+
+const buildReadDataContainersListUrl = (params: FetchDataContainersListParams): string => {
+ const { subscriptionId, resourceGroupName, accountName, databaseName, apiType } = params;
+ const databaseEndpoint = getDatabaseEndpoint(apiType);
+ const collectionEndpoint = getCollectionEndpoint(apiType);
+
+ let armEndpoint = configContext.ARM_ENDPOINT;
+ if (armEndpoint.endsWith("/")) {
+ armEndpoint = armEndpoint.slice(0, -1);
+ }
+ return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}/${databaseName}/${collectionEndpoint}?api-version=${apiVersion}`;
+}
+
+const fetchDataContainersList = async (
+ armToken: string,
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ databaseName: string,
+ apiType: ApiType
+): Promise => {
+ const uri = buildReadDataContainersListUrl({
+ armToken,
+ subscriptionId,
+ resourceGroupName,
+ accountName,
+ databaseName,
+ apiType
+ });
+ const headers = new Headers();
+ const bearer = `Bearer ${armToken}`;
+ headers.append("Authorization", bearer);
+ headers.append("Content-Type", "application/json");
+
+ const response = await fetch(uri, {
+ method: "GET",
+ headers: headers
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to fetch containers");
+ }
+
+ const data = await response.json();
+ return data.value;
+};
+
+export function useDataContainers(
+ armToken: string,
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ databaseName: string,
+ apiType: ApiType
+): DatabaseModel[] | undefined {
+ const { data } = useSWR(
+ () => (
+ armToken && subscriptionId && resourceGroupName && accountName && databaseName && apiType ? [
+ "fetchContainersLinkedToDatabases",
+ armToken, subscriptionId, resourceGroupName, accountName, databaseName, apiType
+ ] : undefined
+ ),
+ (_, armToken, subscriptionId, resourceGroupName, accountName, databaseName, apiType) => fetchDataContainersList(
+ armToken,
+ subscriptionId,
+ resourceGroupName,
+ accountName,
+ databaseName,
+ apiType
+ ),
+ );
+
+ return data;
+}
\ No newline at end of file
diff --git a/src/hooks/useDatabases.tsx b/src/hooks/useDatabases.tsx
new file mode 100644
index 000000000..24ca29bbd
--- /dev/null
+++ b/src/hooks/useDatabases.tsx
@@ -0,0 +1,64 @@
+import { DatabaseModel } from "Contracts/DataModels";
+import useSWR from "swr";
+import { getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
+import { configContext } from "../ConfigContext";
+import { FetchDatabasesListParams } from "../Explorer/ContainerCopy/Types";
+import { ApiType } from "../UserContext";
+
+const apiVersion = "2023-09-15";
+const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string => {
+ const { subscriptionId, resourceGroupName, accountName, apiType } = params;
+ const databaseEndpoint = getDatabaseEndpoint(apiType);
+
+ let armEndpoint = configContext.ARM_ENDPOINT;
+ if (armEndpoint.endsWith("/")) {
+ armEndpoint = armEndpoint.slice(0, -1);
+ }
+ return `${armEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/${databaseEndpoint}?api-version=${apiVersion}`;
+}
+
+const fetchDatabasesList = async (armToken: string, subscriptionId: string, resourceGroupName: string, accountName: string, apiType: ApiType): Promise => {
+ const uri = buildReadDatabasesListUrl({ armToken, subscriptionId, resourceGroupName, accountName, apiType });
+ const headers = new Headers();
+ const bearer = `Bearer ${armToken}`;
+ headers.append("Authorization", bearer);
+ headers.append("Content-Type", "application/json");
+
+ const response = await fetch(uri, {
+ method: "GET",
+ headers: headers
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to fetch databases");
+ }
+
+ const data = await response.json();
+ return data.value;
+};
+
+export function useDatabases(
+ armToken: string,
+ subscriptionId: string,
+ resourceGroupName: string,
+ accountName: string,
+ apiType: ApiType
+): DatabaseModel[] | undefined {
+ const { data } = useSWR(
+ () => (
+ armToken && subscriptionId && resourceGroupName && accountName && apiType ? [
+ "fetchDatabasesLinkedToResource",
+ armToken, subscriptionId, resourceGroupName, accountName, apiType
+ ] : undefined
+ ),
+ (_, armToken, subscriptionId, resourceGroupName, accountName, apiType) => fetchDatabasesList(
+ armToken,
+ subscriptionId,
+ resourceGroupName,
+ accountName,
+ apiType
+ ),
+ );
+
+ return data;
+}
\ No newline at end of file
diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts
index 46e655a87..e5d4160ae 100644
--- a/src/hooks/useKnockoutExplorer.ts
+++ b/src/hooks/useKnockoutExplorer.ts
@@ -959,6 +959,13 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
if (inputs.features) {
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
}
+ // somehow we need to make enableContainerCopy true for platform: portal & apiType: SQL
+ // Ideally we need to pass this as a feature flag from portal or in a query param
+ // Then we will fetch the value from query param and set this to true
+ // For now setting it to true unconditionally
+ if (userContext.apiType === "SQL") {
+ Object.assign(userContext.features, { enableContainerCopy: true });
+ }
if (inputs.flights) {
if (inputs.flights.indexOf(Flights.AutoscaleTest) !== -1) {
diff --git a/src/hooks/useSidePanel.ts b/src/hooks/useSidePanel.ts
index 8f7eab69b..25b87f346 100644
--- a/src/hooks/useSidePanel.ts
+++ b/src/hooks/useSidePanel.ts
@@ -3,15 +3,19 @@ import create, { UseStore } from "zustand";
export interface SidePanelState {
isOpen: boolean;
panelWidth: string;
+ hasConsole: boolean;
panelContent?: JSX.Element;
headerText?: string;
openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void;
closeSidePanel: () => void;
+ setPanelHasConsole: (hasConsole: boolean) => void;
getRef?: React.RefObject; // Optional ref for focusing the last element.
}
export const useSidePanel: UseStore = create((set) => ({
isOpen: false,
panelWidth: "440px",
+ hasConsole: true,
+ setPanelHasConsole: (hasConsole: boolean) => set((state) => ({ ...state, hasConsole })),
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })),
closeSidePanel: () => {