upapi update

This commit is contained in:
Tara Zou 2024-04-24 18:51:09 -04:00
parent 56408a97d7
commit d7718c42da
7 changed files with 172 additions and 27 deletions

View File

@ -248,6 +248,7 @@ export class HttpHeaders {
public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
public static xAPIKey: string = "X-API-Key";
}
export class ContentType {

View File

@ -42,6 +42,9 @@ export interface ConfigContext {
ARM_API_VERSION: string;
GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string;
CATALOG_ENDPOINT: string;
CATALOG_API_VERSION: string;
CATALOG_API_KEY: string;
ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
@ -92,6 +95,9 @@ let configContext: Readonly<ConfigContext> = {
ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.microsoft.com",
GRAPH_API_VERSION: "1.6",
CATALOG_ENDPOINT: "https://catalogapi.azure.com/",
CATALOG_API_VERSION: "2023-05-01-preview",
CATALOG_API_KEY: "",
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
@ -248,3 +254,4 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
}
export { configContext };

View File

@ -419,6 +419,7 @@ export interface SelfServeFrameInputs {
authorizationToken: string;
csmEndpoint: string;
flights?: readonly string[];
catalogAPIKey: string;
}
export class MonacoEditorSettings {

View File

@ -97,6 +97,9 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
}
const inputs = event.data.data.inputs as SelfServeFrameInputs;
// Test
console.log("catalogAPIKey" + inputs.catalogAPIKey);
// End Test
if (!inputs) {
return;
}
@ -110,13 +113,15 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
!inputs.databaseAccount ||
!inputs.authorizationToken ||
!inputs.csmEndpoint ||
!selfServeType
!selfServeType ||
!inputs.catalogAPIKey
) {
return;
}
updateConfigContext({
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
CATALOG_API_KEY: inputs.catalogAPIKey,
});
updateUserContext({

View File

@ -1,11 +1,14 @@
import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext";
import { armRequestWithoutPolling } from "../../Utils/arm/request";
import { armRequestWithoutPolling, getOfferingIdsRequest } from "../../Utils/arm/request";
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
import { RefreshResult } from "../SelfServeTypes";
import SqlX from "./SqlX";
import {
FetchPricesResponse,
GetOfferingIdsResponse,
OfferingIdMap,
OfferingIdRequest,
PriceMapAndCurrencyCode,
RegionItem,
RegionsResponse,
@ -170,7 +173,7 @@ const getFetchPricesPathForRegion = (subscriptionId: string): string => {
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
};
export const getPriceMapAndCurrencyCode = async (regions: Array<RegionItem>): Promise<PriceMapAndCurrencyCode> => {
export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<PriceMapAndCurrencyCode> => {
const telemetryData = {
feature: "Calculate approximate cost",
function: "getPriceMapAndCurrencyCode",
@ -181,39 +184,85 @@ export const getPriceMapAndCurrencyCode = async (regions: Array<RegionItem>): Pr
try {
const priceMap = new Map<string, Map<string, number>>();
let currencyCode;
for (const regionItem of regions) {
let pricingCurrency;
for (const region of map.keys()) {
const regionPriceMap = new Map<string, number>();
const requestBody: OfferingIdRequest = {
location: region,
ids: Array.from(map.get(region).values()),
};
const response = await armRequestWithoutPolling<FetchPricesResponse>({
host: configContext.ARM_ENDPOINT,
path: getFetchPricesPathForRegion(userContext.subscriptionId),
method: "POST",
apiVersion: "2020-01-01-preview",
apiVersion: "2023-04-01-preview",
body: requestBody,
});
for (const item of response.result.Items) {
if (pricingCurrency === undefined) {
pricingCurrency = item.pricingCurrency;
} else if (item.pricingCurrency !== pricingCurrency) {
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
}
const offeringId = item.id;
const skuName = map.get(region).get(offeringId);
const unitPrice = item.prices.find(x => x.type == "Consumption").unitPrice;
regionPriceMap.set(skuName, unitPrice);
}
priceMap.set(region, regionPriceMap);
}
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
return { priceMap: priceMap, pricingCurrency: pricingCurrency };
} catch (err) {
const failureTelemetry = { err, selfServeClassName: SqlX.name };
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
return { priceMap: undefined, pricingCurrency: undefined };
}
};
export const getOfferingIds = async (regions: Array<RegionItem>): Promise<OfferingIdMap> => {
const telemetryData = {
feature: "Get Offering Ids to calculate approximate cost",
function: "getOfferingIds",
description: "fetch offering ids API call",
selfServeClassName: SqlX.name,
};
const getOfferingIdsCodeTimestamp = selfServeTraceStart(telemetryData);
try {
const offeringIdMap = new Map<string, Map<string, string>>();
let currencyCode;
for (const regionItem of regions) {
const regionOfferingIdMap = new Map<string, string>();
const response = await getOfferingIdsRequest<GetOfferingIdsResponse>({
host: configContext.CATALOG_ENDPOINT,
path: `/skus`,
method: "GET",
apiVersion: "2023-05-01-preview",
queryParams: {
filter:
"armRegionNameeq '" +
regionItem.locationName.split(" ").join("").toLowerCase() +
"'andserviceFamilyeq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
"locationseq '" +
regionItem.locationName +
"'andserviceFamilyeq 'Databases' and service eq 'Azure Cosmos DB'",
},
});
for (const item of response.result.Items) {
if (currencyCode === undefined) {
currencyCode = item.currencyCode;
} else if (item.currencyCode !== currencyCode) {
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
}
regionPriceMap.set(item.skuName, item.retailPrice);
regionOfferingIdMap.set(item.skuName, item.offeringProperties.offeringId);
}
priceMap.set(regionItem.locationName, regionPriceMap);
offeringIdMap.set(regionItem.locationName, regionOfferingIdMap);
}
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
return { priceMap: priceMap, currencyCode: currencyCode };
selfServeTraceSuccess(telemetryData, getOfferingIdsCodeTimestamp);
return offeringIdMap;
} catch (err) {
const failureTelemetry = { err, selfServeClassName: SqlX.name };
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
return { priceMap: undefined, currencyCode: undefined };
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
return undefined;
}
};

View File

@ -36,17 +36,44 @@ export type FetchPricesResponse = {
Count: number;
};
export type PriceMapAndCurrencyCode = {
priceMap: Map<string, Map<string, number>>;
currencyCode: string;
export type PriceItem = {
prices: Array<PriceType>;
id: string;
pricingCurrency: string;
};
export type PriceItem = {
retailPrice: number;
skuName: string;
currencyCode: string;
export type PriceType = {
type: string;
unitPrice: number;
}
export type PriceMapAndCurrencyCode = {
priceMap: Map<string, Map<string, number>>;
pricingCurrency: string;
};
export type GetOfferingIdsResponse = {
Items: Array<OfferingIdItem>;
NextPageLink: string | undefined;
Count: number;
};
export type OfferingIdItem = {
skuName: string;
offeringProperties: OfferingProperties;
};
export type OfferingProperties = {
offeringId: string;
}
export type OfferingIdRequest = {
ids: Array<string>;
location: string,
}
export type OfferingIdMap = Map<string, Map<string, string>>;
export type RegionsResponse = {
properties: RegionsProperties;
};

View File

@ -160,3 +160,58 @@ async function getOperationStatus(operationStatusUrl: string) {
}
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
}
export async function getOfferingIdsRequest<T>({
host,
path,
apiVersion,
method,
body: requestBody,
queryParams,
contentType,
}: Options): Promise<{ result: T; operationStatusUrl: string }> {
const url = new URL(path, host);
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
if (queryParams) {
queryParams.filter && url.searchParams.append("$filter", queryParams.filter);
queryParams.metricNames && url.searchParams.append("metricnames", queryParams.metricNames);
}
if (!configContext.CATALOG_API_KEY) {
throw new Error("No catalog API key provided");
}
// TODO: delete after test
// console.log("config CATALOG_API_KEY: " + configContext.CATALOG_API_KEY);
// End Test
const response = await window.fetch(url.href, {
method,
headers: {
// [HttpHeaders.xAPIKey]: configContext.CATALOG_API_KEY,
[HttpHeaders.xAPIKey]: "",
},
body: requestBody ? JSON.stringify(requestBody) : undefined,
});
if (!response.ok) {
let error: ARMError;
try {
const errorResponse = (await response.json()) as ParsedErrorResponse;
if ("error" in errorResponse) {
error = new ARMError(errorResponse.error.message);
error.code = errorResponse.error.code;
} else {
error = new ARMError(errorResponse.message);
error.code = errorResponse.code;
}
} catch (error) {
throw new Error(await response.text());
}
throw error;
}
const operationStatusUrl = (response.headers && response.headers.get("location")) || "";
const responseBody = (await response.json()) as T;
return { result: responseBody, operationStatusUrl: operationStatusUrl };
}