remove arm token dependency

This commit is contained in:
Bikram Choudhury
2025-10-27 16:12:54 +05:30
parent 6483bd146d
commit e002a4505c
17 changed files with 129 additions and 118 deletions

11
.vscode/settings.json vendored
View File

@@ -24,5 +24,14 @@
"source.organizeImports": "explicit"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}

View File

@@ -36,6 +36,14 @@ export interface DatabaseAccountSystemData {
export interface DatabaseAccountBackupPolicy {
type: string;
/* periodicModeProperties?: {
backupIntervalInMinutes: number;
backupRetentionIntervalInHours: number;
backupStorageRedundancy: string;
};
continuousModeProperties?: {
tier: string;
}; */
}
export interface DatabaseAccountExtendedProperties {

View File

@@ -1,7 +1,5 @@
import React from "react";
import { userContext } from "UserContext";
import { useAADAuth } from "../../../hooks/useAADAuth";
import { useConfig } from "../../../hooks/useConfig";
import { CopyJobMigrationType } from "../Enums";
import { CopyJobContextProviderType, CopyJobContextState, CopyJobFlowType } from "../Types";
@@ -39,24 +37,15 @@ const getInitialCopyJobState = (): CopyJobContextState => {
}
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
const config = useConfig();
const { isLoggedIn, armToken, account } = useAADAuth(config);
const principalId = account?.localAccountId ?? "";
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
const [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
if (!isLoggedIn || !armToken) {
// Add a shimmer or loader here
return null;
}
const resetCopyJobState = () => {
setCopyJobState(getInitialCopyJobState());
}
return (
<CopyJobContext.Provider value={{ principalId, armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
<CopyJobContext.Provider value={{ copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
{props.children}
</CopyJobContext.Provider>
);

View File

@@ -79,3 +79,12 @@ export function extractErrorMessage(error: CopyJobErrorType): CopyJobErrorType {
}
}
export function getAccountDetailsFromResourceId(accountId: string | undefined) {
if (!accountId) {
return null;
}
const pattern = new RegExp('/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/Microsoft\\.DocumentDB/databaseAccounts/([^/]+)', 'i');
const matches = accountId.match(pattern);
const [_, subscriptionId, resourceGroup, accountName] = matches || [];
return { subscriptionId, resourceGroup, accountName };
}

View File

@@ -25,7 +25,7 @@ export interface PermissionSectionConfig {
Component: React.ComponentType
disabled: boolean;
completed?: boolean;
validate?: (state: CopyJobContextState, armToken?: string) => boolean | Promise<boolean>;
validate?: (state: CopyJobContextState) => boolean | Promise<boolean>;
}
// Section IDs for maintainability
@@ -66,10 +66,9 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
title: ContainerCopyMessages.readPermissionAssigned.title,
Component: AddReadPermissionToDefaultIdentity,
disabled: true,
validate: async (state: CopyJobContextState, armToken?: string) => {
validate: async (state: CopyJobContextState) => {
const principalId = state?.target?.account?.identity?.principalId;
const rolesAssigned = await fetchRoleAssignments(
armToken,
state.source?.subscription?.subscriptionId,
state.source?.account?.resourceGroup,
state.source?.account?.name,
@@ -77,7 +76,6 @@ const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
);
const roleDefinitions = await fetchRoleDefinitions(
armToken,
rolesAssigned ?? []
);
return checkTargetHasReaderRoleOnSource(roleDefinitions ?? []);
@@ -127,10 +125,7 @@ export function checkTargetHasReaderRoleOnSource(roleDefinitions: RoleDefinition
* Returns the permission sections configuration for the Assign Permissions screen.
* Memoizes derived values for performance and decouples logic for testability.
*/
const usePermissionSections = (
state: CopyJobContextState,
armToken: string,
): PermissionSectionConfig[] => {
const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => {
const { validationCache, setValidationCache } = useCopyJobPrerequisitesCache();
const [permissionSections, setPermissionSections] = useState<PermissionSectionConfig[] | null>(null);
const isValidatingRef = useRef(false);
@@ -169,7 +164,7 @@ const usePermissionSections = (
}
// We've reached the first non-cached section - validate it
if (section.validate) {
const isValid = await section.validate(state, armToken);
const isValid = await section.validate(state);
newValidationCache.set(section.id, isValid);
result.push({ ...section, completed: isValid });
// Stop validation if current section failed
@@ -197,7 +192,7 @@ const usePermissionSections = (
return () => {
isValidatingRef.current = false;
}
}, [state, armToken, sectionToValidate]);
}, [state, sectionToValidate]);
return permissionSections ?? [];
};

View File

@@ -39,8 +39,8 @@ const PermissionSection: React.FC<PermissionSectionConfig> = ({
);
const AssignPermissions = () => {
const { armToken, copyJobState } = useCopyJobContext();
const permissionSections = usePermissionSections(copyJobState, armToken);
const { copyJobState } = useCopyJobContext();
const permissionSections = usePermissionSections(copyJobState);
const [openItems, setOpenItems] = React.useState<string[]>([]);
const indentLevels = React.useMemo<IndentLevel[]>(
@@ -48,10 +48,6 @@ const AssignPermissions = () => {
[]
);
/* const onMoveToNextSection: AccordionToggleEventHandler<string> = useCallback((_event, data) => {
setOpenItems(data.openItems);
}, []); */
useEffect(() => {
const firstIncompleteSection = permissionSections.find(section => !section.completed);
const nextOpenItems = firstIncompleteSection ? [firstIncompleteSection.id] : [];

View File

@@ -15,11 +15,11 @@ interface SelectAccountProps { }
const SelectAccount = React.memo(
(_props: SelectAccountProps) => {
const { armToken, copyJobState, setCopyJobState } = useCopyJobContext();
const { copyJobState, setCopyJobState } = useCopyJobContext();
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const subscriptions: Subscription[] = useSubscriptions(armToken);
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId, armToken);
const subscriptions: Subscription[] = useSubscriptions();
const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId);
const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter(account => account.type === "SQL" || account.kind === "GlobalDocumentDB");
const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts);

View File

@@ -11,7 +11,7 @@ import { useMemoizedSourceAndTargetData } from "./memoizedData";
interface SelectSourceAndTargetContainersProps { }
const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainersProps) => {
const { armToken, copyJobState, setCopyJobState } = useCopyJobContext();
const { copyJobState, setCopyJobState } = useCopyJobContext();
const {
source,
target,
@@ -19,7 +19,7 @@ const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainers
sourceContainerParams,
targetDbParams,
targetContainerParams
} = useMemoizedSourceAndTargetData(copyJobState, armToken);
} = useMemoizedSourceAndTargetData(copyJobState);
// Custom hooks
const sourceDatabases = useDatabases(...sourceDbParams) || [];

View File

@@ -1,57 +1,64 @@
import React from "react";
import { getAccountDetailsFromResourceId } from "../../../CopyJobUtils";
import { CopyJobContextState, DatabaseParams, DataContainerParams } from "../../../Types";
export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState, armToken: string) {
export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState) {
const { source, target } = copyJobState ?? {};
const selectedSourceAccount = source?.account;
const selectedTargetAccount = target?.account;
const {
subscriptionId: sourceSubscriptionId,
resourceGroup: sourceResourceGroup,
accountName: sourceAccountName
} = getAccountDetailsFromResourceId(selectedSourceAccount?.id);
const {
subscriptionId: targetSubscriptionId,
resourceGroup: targetResourceGroup,
accountName: targetAccountName
} = getAccountDetailsFromResourceId(selectedTargetAccount?.id);
const sourceDbParams = React.useMemo(
() =>
[
armToken,
source?.subscription?.subscriptionId,
selectedSourceAccount?.resourceGroup,
selectedSourceAccount?.name,
sourceSubscriptionId,
sourceResourceGroup,
sourceAccountName,
'SQL',
] as DatabaseParams,
[armToken, source?.subscription?.subscriptionId, selectedSourceAccount]
[sourceSubscriptionId, sourceResourceGroup, sourceAccountName]
);
const sourceContainerParams = React.useMemo(
() =>
[
armToken,
source?.subscription?.subscriptionId,
selectedSourceAccount?.resourceGroup,
selectedSourceAccount?.name,
sourceSubscriptionId,
sourceResourceGroup,
sourceAccountName,
source?.databaseId,
'SQL',
] as DataContainerParams,
[armToken, source?.subscription?.subscriptionId, selectedSourceAccount, source?.databaseId]
[sourceSubscriptionId, sourceResourceGroup, sourceAccountName, source?.databaseId]
);
const targetDbParams = React.useMemo(
() => [
armToken,
target?.subscriptionId,
selectedTargetAccount?.resourceGroup,
selectedTargetAccount?.name,
targetSubscriptionId,
targetResourceGroup,
targetAccountName,
'SQL',
] as DatabaseParams,
[armToken, target?.subscriptionId, selectedTargetAccount]
[targetSubscriptionId, targetResourceGroup, targetAccountName]
);
const targetContainerParams = React.useMemo(
() => [
armToken,
target?.subscriptionId,
selectedTargetAccount?.resourceGroup,
selectedTargetAccount?.name,
targetSubscriptionId,
targetResourceGroup,
targetAccountName,
target?.databaseId,
'SQL',
] as DataContainerParams,
[armToken, target?.subscriptionId, selectedTargetAccount, target?.databaseId]
[targetSubscriptionId, targetResourceGroup, targetAccountName, target?.databaseId]
);
return { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams };

View File

@@ -28,14 +28,12 @@ export type DropdownOptionType = {
};
export type DatabaseParams = [
string,
string | undefined,
string | undefined,
string | undefined,
ApiType
];
export type DataContainerParams = [
string,
string | undefined,
string | undefined,
string | undefined,
@@ -80,8 +78,6 @@ export interface CopyJobFlowType {
}
export interface CopyJobContextProviderType {
principalId: string;
armToken: string;
flow: CopyJobFlowType;
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
copyJobState: CopyJobContextState | null;

View File

@@ -78,13 +78,13 @@ const App: React.FunctionComponent = () => {
}
StyleConstants.updateStyles();
const explorer = useKnockoutExplorer(config?.platform);
// console.log("Using config: ", config);
console.log("Using config: ", config);
if (!explorer) {
return <LoadingExplorer />;
}
// console.log("Using explorer: ", explorer);
// console.log("Using userContext: ", userContext);
console.log("Using explorer: ", explorer);
console.log("Using userContext: ", userContext);
return (
<KeyboardShortcutRoot>

View File

@@ -0,0 +1,9 @@
import { userContext } from "UserContext";
export function getCopyJobAuthorizationHeader(token: string = ""): Headers {
const headers = new Headers();
const authToken = token ? `Bearer ${token}` : userContext.authorizationToken;
headers.append("Authorization", authToken);
headers.append("Content-Type", "application/json");
return headers;
}

View File

@@ -1,8 +1,8 @@
import { configContext } from "ConfigContext";
import { armRequest } from "Utils/arm/request";
import { getCopyJobAuthorizationHeader } from "../CopyJobAuthUtils";
export type FetchAccountDetailsParams = {
armToken: string;
subscriptionId: string;
resourceGroupName: string;
accountName: string;
@@ -43,13 +43,6 @@ const getArmBaseUrl = (): string => {
return base.endsWith("/") ? base.slice(0, -1) : base;
};
const createAuthHeaders = (armToken: string): Headers => {
const headers = new Headers();
headers.append("Authorization", `Bearer ${armToken}`);
headers.append("Content-Type", "application/json");
return headers;
};
const buildArmUrl = (path: string): string =>
`${getArmBaseUrl()}${path}?api-version=${apiVersion}`;
@@ -64,7 +57,6 @@ const handleResponse = async (response: Response, context: string) => {
};
export const fetchRoleAssignments = async (
armToken: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
@@ -74,7 +66,7 @@ export const fetchRoleAssignments = async (
`/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/sqlRoleAssignments`
);
const response = await fetch(uri, { method: "GET", headers: createAuthHeaders(armToken) });
const response = await fetch(uri, { method: "GET", headers: getCopyJobAuthorizationHeader() });
const data = await handleResponse(response, "role assignments");
return (data.value || []).filter(
@@ -84,13 +76,12 @@ export const fetchRoleAssignments = async (
};
export const fetchRoleDefinitions = async (
armToken: string,
roleAssignments: RoleAssignmentType[]
): Promise<RoleDefinitionType[]> => {
const roleDefinitionIds = roleAssignments.map(assignment => assignment.properties.roleDefinitionId);
const uniqueRoleDefinitionIds = Array.from(new Set(roleDefinitionIds));
const headers = createAuthHeaders(armToken);
const headers = getCopyJobAuthorizationHeader();
const roleDefinitionUris = uniqueRoleDefinitionIds.map((id) => buildArmUrl(id));
const promises = roleDefinitionUris.map((url) => fetch(url, { method: "GET", headers }));

View File

@@ -3,10 +3,10 @@ import useSWR from "swr";
import { getCollectionEndpoint, getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
import { configContext } from "../ConfigContext";
import { ApiType } from "../UserContext";
import { getCopyJobAuthorizationHeader } from "../Utils/CopyJobAuthUtils";
const apiVersion = "2023-09-15";
export interface FetchDataContainersListParams {
armToken: string;
subscriptionId: string;
resourceGroupName: string;
databaseName: string;
@@ -27,7 +27,6 @@ const buildReadDataContainersListUrl = (params: FetchDataContainersListParams):
}
const fetchDataContainersList = async (
armToken: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
@@ -35,17 +34,13 @@ const fetchDataContainersList = async (
apiType: ApiType
): Promise<DatabaseModel[]> => {
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 headers = getCopyJobAuthorizationHeader();
const response = await fetch(uri, {
method: "GET",
@@ -61,7 +56,6 @@ const fetchDataContainersList = async (
};
export function useDataContainers(
armToken: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
@@ -70,13 +64,12 @@ export function useDataContainers(
): DatabaseModel[] | undefined {
const { data } = useSWR(
() => (
armToken && subscriptionId && resourceGroupName && accountName && databaseName && apiType ? [
subscriptionId && resourceGroupName && accountName && databaseName && apiType ? [
"fetchContainersLinkedToDatabases",
armToken, subscriptionId, resourceGroupName, accountName, databaseName, apiType
subscriptionId, resourceGroupName, accountName, databaseName, apiType
] : undefined
),
(_, armToken, subscriptionId, resourceGroupName, accountName, databaseName, apiType) => fetchDataContainersList(
armToken,
(_, subscriptionId, resourceGroupName, accountName, databaseName, apiType) => fetchDataContainersList(
subscriptionId,
resourceGroupName,
accountName,

View File

@@ -1,6 +1,7 @@
import { HttpHeaders } from "Common/Constants";
import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph";
import useSWR from "swr";
import { userContext } from "UserContext";
import { configContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels";
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -10,9 +11,12 @@ interface AccountListResult {
value: DatabaseAccount[];
}
export async function fetchDatabaseAccounts(subscriptionId: string, accessToken: string): Promise<DatabaseAccount[]> {
export async function fetchDatabaseAccounts(subscriptionId: string, accessToken: string = ""): Promise<DatabaseAccount[]> {
if (!accessToken && !userContext.authorizationToken) {
return [];
}
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
const bearer = accessToken ? `Bearer ${accessToken}` : userContext.authorizationToken;
headers.append("Authorization", bearer);
@@ -35,10 +39,13 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
export async function fetchDatabaseAccountsFromGraph(
subscriptionId: string,
accessToken: string,
accessToken: string = "",
): Promise<DatabaseAccount[]> {
if (!accessToken && !userContext.authorizationToken) {
return [];
}
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
const bearer = accessToken ? `Bearer ${accessToken}` : userContext.authorizationToken;
headers.append("Authorization", bearer);
headers.append(HttpHeaders.contentType, "application/json");
@@ -85,9 +92,9 @@ export async function fetchDatabaseAccountsFromGraph(
return databaseAccounts.sort((a, b) => a.name.localeCompare(b.name));
}
export function useDatabaseAccounts(subscriptionId: string, armToken: string): DatabaseAccount[] | undefined {
export function useDatabaseAccounts(subscriptionId: string, armToken: string = ""): DatabaseAccount[] | undefined {
const { data } = useSWR(
() => (armToken && subscriptionId ? ["databaseAccounts", subscriptionId, armToken] : undefined),
() => (subscriptionId ? ["databaseAccounts", subscriptionId, armToken] : undefined),
(_, subscriptionId, armToken) => fetchDatabaseAccountsFromGraph(subscriptionId, armToken),
);
return data;

View File

@@ -3,10 +3,10 @@ import useSWR from "swr";
import { getDatabaseEndpoint } from "../Common/DatabaseAccountUtility";
import { configContext } from "../ConfigContext";
import { ApiType } from "../UserContext";
import { getCopyJobAuthorizationHeader } from "../Utils/CopyJobAuthUtils";
const apiVersion = "2023-09-15";
export interface FetchDatabasesListParams {
armToken: string;
subscriptionId: string;
resourceGroupName: string;
accountName: string;
@@ -24,12 +24,9 @@ const buildReadDatabasesListUrl = (params: FetchDatabasesListParams): string =>
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<DatabaseModel[]> => {
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 fetchDatabasesList = async (subscriptionId: string, resourceGroupName: string, accountName: string, apiType: ApiType): Promise<DatabaseModel[]> => {
const uri = buildReadDatabasesListUrl({ subscriptionId, resourceGroupName, accountName, apiType });
const headers = getCopyJobAuthorizationHeader();
const response = await fetch(uri, {
method: "GET",
@@ -45,7 +42,6 @@ const fetchDatabasesList = async (armToken: string, subscriptionId: string, reso
};
export function useDatabases(
armToken: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
@@ -53,13 +49,12 @@ export function useDatabases(
): DatabaseModel[] | undefined {
const { data } = useSWR(
() => (
armToken && subscriptionId && resourceGroupName && accountName && apiType ? [
subscriptionId && resourceGroupName && accountName && apiType ? [
"fetchDatabasesLinkedToResource",
armToken, subscriptionId, resourceGroupName, accountName, apiType
subscriptionId, resourceGroupName, accountName, apiType
] : undefined
),
(_, armToken, subscriptionId, resourceGroupName, accountName, apiType) => fetchDatabasesList(
armToken,
(_, subscriptionId, resourceGroupName, accountName, apiType) => fetchDatabasesList(
subscriptionId,
resourceGroupName,
accountName,

View File

@@ -1,6 +1,7 @@
import { HttpHeaders } from "Common/Constants";
import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph";
import useSWR from "swr";
import { userContext } from "UserContext";
import { configContext } from "../ConfigContext";
import { Subscription } from "../Contracts/DataModels";
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -10,9 +11,12 @@ interface SubscriptionListResult {
value: Subscription[];
}
export async function fetchSubscriptions(accessToken: string): Promise<Subscription[]> {
export async function fetchSubscriptions(accessToken: string = ""): Promise<Subscription[]> {
if (!accessToken && !userContext.authorizationToken) {
return [];
}
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
const bearer = accessToken ? `Bearer ${accessToken}` : userContext.authorizationToken;
headers.append("Authorization", bearer);
@@ -35,9 +39,12 @@ export async function fetchSubscriptions(accessToken: string): Promise<Subscript
return subscriptions.sort((a, b) => a.displayName.localeCompare(b.displayName));
}
export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<Subscription[]> {
export async function fetchSubscriptionsFromGraph(accessToken: string = ""): Promise<Subscription[]> {
if (!accessToken && !userContext.authorizationToken) {
return [];
}
const headers = new Headers();
const bearer = `Bearer ${accessToken}`;
const bearer = accessToken ? `Bearer ${accessToken}` : userContext.authorizationToken;
headers.append("Authorization", bearer);
headers.append(HttpHeaders.contentType, "application/json");
@@ -85,9 +92,9 @@ export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<
return subscriptions.sort((a, b) => a.displayName.localeCompare(b.displayName));
}
export function useSubscriptions(armToken: string): Subscription[] | undefined {
export function useSubscriptions(armToken: string = ""): Subscription[] | undefined {
const { data } = useSWR(
() => (armToken ? ["subscriptions", armToken] : undefined),
() => ["subscriptions", armToken],
(_, armToken) => fetchSubscriptionsFromGraph(armToken),
);
return data;