mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-06-05 15:12:04 +01:00
upapi update
This commit is contained in:
parent
56408a97d7
commit
d7718c42da
@ -248,6 +248,7 @@ export class HttpHeaders {
|
|||||||
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
public static partitionKey: string = "x-ms-documentdb-partitionkey";
|
||||||
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
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 migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||||
|
public static xAPIKey: string = "X-API-Key";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContentType {
|
export class ContentType {
|
||||||
|
@ -42,6 +42,9 @@ export interface ConfigContext {
|
|||||||
ARM_API_VERSION: string;
|
ARM_API_VERSION: string;
|
||||||
GRAPH_ENDPOINT: string;
|
GRAPH_ENDPOINT: string;
|
||||||
GRAPH_API_VERSION: string;
|
GRAPH_API_VERSION: string;
|
||||||
|
CATALOG_ENDPOINT: string;
|
||||||
|
CATALOG_API_VERSION: string;
|
||||||
|
CATALOG_API_KEY: string;
|
||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
BACKEND_ENDPOINT?: string;
|
||||||
@ -92,6 +95,9 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
ARM_API_VERSION: "2016-06-01",
|
ARM_API_VERSION: "2016-06-01",
|
||||||
GRAPH_ENDPOINT: "https://graph.microsoft.com",
|
GRAPH_ENDPOINT: "https://graph.microsoft.com",
|
||||||
GRAPH_API_VERSION: "1.6",
|
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_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
|
||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
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 };
|
export { configContext };
|
||||||
|
|
||||||
|
@ -419,6 +419,7 @@ export interface SelfServeFrameInputs {
|
|||||||
authorizationToken: string;
|
authorizationToken: string;
|
||||||
csmEndpoint: string;
|
csmEndpoint: string;
|
||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
|
catalogAPIKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MonacoEditorSettings {
|
export class MonacoEditorSettings {
|
||||||
|
@ -97,6 +97,9 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inputs = event.data.data.inputs as SelfServeFrameInputs;
|
const inputs = event.data.data.inputs as SelfServeFrameInputs;
|
||||||
|
// Test
|
||||||
|
console.log("catalogAPIKey" + inputs.catalogAPIKey);
|
||||||
|
// End Test
|
||||||
if (!inputs) {
|
if (!inputs) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -110,13 +113,15 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
!inputs.databaseAccount ||
|
!inputs.databaseAccount ||
|
||||||
!inputs.authorizationToken ||
|
!inputs.authorizationToken ||
|
||||||
!inputs.csmEndpoint ||
|
!inputs.csmEndpoint ||
|
||||||
!selfServeType
|
!selfServeType ||
|
||||||
|
!inputs.catalogAPIKey
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
|
||||||
|
CATALOG_API_KEY: inputs.catalogAPIKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
import { armRequestWithoutPolling, getOfferingIdsRequest } from "../../Utils/arm/request";
|
||||||
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
import { selfServeTraceFailure, selfServeTraceStart, selfServeTraceSuccess } from "../SelfServeTelemetryProcessor";
|
||||||
import { RefreshResult } from "../SelfServeTypes";
|
import { RefreshResult } from "../SelfServeTypes";
|
||||||
import SqlX from "./SqlX";
|
import SqlX from "./SqlX";
|
||||||
import {
|
import {
|
||||||
FetchPricesResponse,
|
FetchPricesResponse,
|
||||||
|
GetOfferingIdsResponse,
|
||||||
|
OfferingIdMap,
|
||||||
|
OfferingIdRequest,
|
||||||
PriceMapAndCurrencyCode,
|
PriceMapAndCurrencyCode,
|
||||||
RegionItem,
|
RegionItem,
|
||||||
RegionsResponse,
|
RegionsResponse,
|
||||||
@ -170,7 +173,7 @@ const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
|||||||
return `/subscriptions/${subscriptionId}/providers/Microsoft.CostManagement/fetchPrices`;
|
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 = {
|
const telemetryData = {
|
||||||
feature: "Calculate approximate cost",
|
feature: "Calculate approximate cost",
|
||||||
function: "getPriceMapAndCurrencyCode",
|
function: "getPriceMapAndCurrencyCode",
|
||||||
@ -181,39 +184,85 @@ export const getPriceMapAndCurrencyCode = async (regions: Array<RegionItem>): Pr
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const priceMap = new Map<string, Map<string, number>>();
|
const priceMap = new Map<string, Map<string, number>>();
|
||||||
let currencyCode;
|
let pricingCurrency;
|
||||||
for (const regionItem of regions) {
|
for (const region of map.keys()) {
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
|
const requestBody: OfferingIdRequest = {
|
||||||
|
location: region,
|
||||||
|
ids: Array.from(map.get(region).values()),
|
||||||
|
};
|
||||||
|
|
||||||
const response = await armRequestWithoutPolling<FetchPricesResponse>({
|
const response = await armRequestWithoutPolling<FetchPricesResponse>({
|
||||||
host: configContext.ARM_ENDPOINT,
|
host: configContext.ARM_ENDPOINT,
|
||||||
path: getFetchPricesPathForRegion(userContext.subscriptionId),
|
path: getFetchPricesPathForRegion(userContext.subscriptionId),
|
||||||
method: "POST",
|
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: {
|
queryParams: {
|
||||||
filter:
|
filter:
|
||||||
"armRegionName eq '" +
|
"locations eq '" +
|
||||||
regionItem.locationName.split(" ").join("").toLowerCase() +
|
regionItem.locationName +
|
||||||
"' and serviceFamily eq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
|
"' and serviceFamily eq 'Databases' and service eq 'Azure Cosmos DB'",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const item of response.result.Items) {
|
for (const item of response.result.Items) {
|
||||||
if (currencyCode === undefined) {
|
regionOfferingIdMap.set(item.skuName, item.offeringProperties.offeringId);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
priceMap.set(regionItem.locationName, regionPriceMap);
|
offeringIdMap.set(regionItem.locationName, regionOfferingIdMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceSuccess(telemetryData, getOfferingIdsCodeTimestamp);
|
||||||
return { priceMap: priceMap, currencyCode: currencyCode };
|
return offeringIdMap;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
||||||
return { priceMap: undefined, currencyCode: undefined };
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -36,17 +36,44 @@ export type FetchPricesResponse = {
|
|||||||
Count: number;
|
Count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceMapAndCurrencyCode = {
|
export type PriceItem = {
|
||||||
priceMap: Map<string, Map<string, number>>;
|
prices: Array<PriceType>;
|
||||||
currencyCode: string;
|
id: string;
|
||||||
|
pricingCurrency: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceItem = {
|
export type PriceType = {
|
||||||
retailPrice: number;
|
type: string;
|
||||||
skuName: string;
|
unitPrice: number;
|
||||||
currencyCode: string;
|
}
|
||||||
|
|
||||||
|
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 = {
|
export type RegionsResponse = {
|
||||||
properties: RegionsProperties;
|
properties: RegionsProperties;
|
||||||
};
|
};
|
||||||
|
@ -160,3 +160,58 @@ async function getOperationStatus(operationStatusUrl: string) {
|
|||||||
}
|
}
|
||||||
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
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 };
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user