mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-17 01:36:57 +00:00
Merge branch 'master' into users/languy/save-documentstab-prefs
This commit is contained in:
commit
55df5cb121
@ -2366,9 +2366,9 @@ a:link {
|
|||||||
.tabsManagerContainer {
|
.tabsManagerContainer {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
@ -2671,7 +2671,7 @@ a:link {
|
|||||||
width: @ActiveTabWidth;
|
width: @ActiveTabWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper> .tabNavText {
|
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
border-bottom: 2px solid rgba(0, 120, 212, 1);
|
border-bottom: 2px solid rgba(0, 120, 212, 1);
|
||||||
}
|
}
|
||||||
@ -2708,68 +2708,68 @@ a:link {
|
|||||||
border-right: @ButtonBorderWidth solid @BaseMedium;
|
border-right: @ButtonBorderWidth solid @BaseMedium;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
.contentWrapper {
|
.contentWrapper {
|
||||||
.flex-display();
|
.flex-display();
|
||||||
width: @ContentWrapper;
|
width: @ContentWrapper;
|
||||||
|
|
||||||
.statusIconContainer {
|
.statusIconContainer {
|
||||||
width: @StatusIconContainerSize;
|
width: @StatusIconContainerSize;
|
||||||
height: @StatusIconContainerSize;
|
height: @StatusIconContainerSize;
|
||||||
margin-left: @SmallSpace;
|
margin-left: @SmallSpace;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
||||||
.errorIconContainer {
|
.errorIconContainer {
|
||||||
width: @ErrorIconContainer;
|
width: @ErrorIconContainer;
|
||||||
height: @ErrorIconContainer;
|
height: @ErrorIconContainer;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
|
|
||||||
.errorIcon {
|
.errorIcon {
|
||||||
width: @ErrorIconWidth;
|
width: @ErrorIconWidth;
|
||||||
height: @LoadingErrorIconSize;
|
|
||||||
background-image: url(../images/error_no_outline.svg);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: 3px;
|
|
||||||
display: block;
|
|
||||||
margin: 1px 0px 0px 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorIconContainer.actionsEnabled {
|
|
||||||
&:hover {
|
|
||||||
.hover();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
.active();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorIconContainer[tabindex]:active {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadingIcon {
|
|
||||||
width: @LoadingErrorIconSize;
|
|
||||||
height: @LoadingErrorIconSize;
|
height: @LoadingErrorIconSize;
|
||||||
margin: 0px 0px @SmallSpace @SmallSpace;
|
background-image: url(../images/error_no_outline.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: 3px;
|
||||||
|
display: block;
|
||||||
|
margin: 1px 0px 0px 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabNavText {
|
.errorIconContainer.actionsEnabled {
|
||||||
margin-left: @SmallSpace;
|
&:hover {
|
||||||
margin-right: 2px;
|
.hover();
|
||||||
color: @BaseDark;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
&:focus {
|
||||||
white-space: nowrap;
|
.focus();
|
||||||
flex-grow: 1;
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
.active();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorIconContainer[tabindex]:active {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingIcon {
|
||||||
|
width: @LoadingErrorIconSize;
|
||||||
|
height: @LoadingErrorIconSize;
|
||||||
|
margin: 0px 0px @SmallSpace @SmallSpace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabNavText {
|
||||||
|
margin-left: @SmallSpace;
|
||||||
|
margin-right: 2px;
|
||||||
|
color: @BaseDark;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tabIconSection {
|
.tabIconSection {
|
||||||
width: 29px;
|
width: 29px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -255,6 +255,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,10 @@ 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;
|
||||||
|
// This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP
|
||||||
|
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;
|
||||||
@ -93,6 +97,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
|
||||||
@ -192,6 +199,9 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
PROXY_PATH: "/proxy",
|
PROXY_PATH: "/proxy",
|
||||||
EMULATOR_ENDPOINT: "https://localhost:8081",
|
EMULATOR_ENDPOINT: "https://localhost:8081",
|
||||||
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
|
||||||
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
|
||||||
|
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,6 +425,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 {
|
||||||
|
@ -1157,21 +1157,25 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refreshSampleData(): Promise<void> {
|
public async refreshSampleData(): Promise<void> {
|
||||||
if (!userContext.sampleDataConnectionInfo) {
|
try {
|
||||||
|
if (!userContext.sampleDataConnectionInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const collection: DataModels.Collection = await readSampleCollection();
|
||||||
|
if (!collection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
|
||||||
|
if (!databaseId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
|
||||||
|
useDatabases.setState({ sampleDataResourceTokenCollection });
|
||||||
|
} catch (error) {
|
||||||
|
Logger.logError(getErrorMessage(error), "Explorer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const collection: DataModels.Collection = await readSampleCollection();
|
|
||||||
if (!collection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
|
|
||||||
if (!databaseId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
|
|
||||||
useDatabases.setState({ sampleDataResourceTokenCollection });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import * as Constants from "Common/Constants";
|
|||||||
import { SplitterDirection } from "Common/Splitter";
|
import { SplitterDirection } from "Common/Splitter";
|
||||||
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
|
||||||
import { configContext } from "ConfigContext";
|
import { configContext } from "ConfigContext";
|
||||||
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import {
|
import {
|
||||||
DefaultRUThreshold,
|
DefaultRUThreshold,
|
||||||
LocalStorageUtility,
|
LocalStorageUtility,
|
||||||
@ -107,7 +108,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
||||||
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
||||||
const shouldShowCopilotSampleDBOption = userContext.apiType === "SQL" && useQueryCopilot.getState().copilotEnabled;
|
const shouldShowCopilotSampleDBOption =
|
||||||
|
userContext.apiType === "SQL" &&
|
||||||
|
useQueryCopilot.getState().copilotEnabled &&
|
||||||
|
useDatabases.getState().sampleDataResourceTokenCollection;
|
||||||
const handlerOnSubmit = async () => {
|
const handlerOnSubmit = async () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
@ -117,6 +117,7 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
|
|
||||||
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,15 @@
|
|||||||
import { configContext } from "../../ConfigContext";
|
import { configContext } from "../../ConfigContext";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { armRequestWithoutPolling } from "../../Utils/arm/request";
|
import { get } from "../../Utils/arm/generatedClients/cosmos/locations";
|
||||||
|
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,
|
||||||
@ -166,11 +170,21 @@ export const getRegions = async (): Promise<Array<RegionItem>> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getRegionShortName = async (regionDisplayName: string): Promise<string> => {
|
||||||
|
const locationsList = await get(userContext.subscriptionId, regionDisplayName);
|
||||||
|
|
||||||
|
if ("id" in locationsList) {
|
||||||
|
const locationId = locationsList.id;
|
||||||
|
return locationId.substring(locationId.lastIndexOf("/") + 1);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const getFetchPricesPathForRegion = (subscriptionId: string): string => {
|
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 +195,94 @@ 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 billingCurrency;
|
||||||
for (const regionItem of regions) {
|
for (const region of map.keys()) {
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
|
const regionShortName = await getRegionShortName(region);
|
||||||
|
const requestBody: OfferingIdRequest = {
|
||||||
|
location: regionShortName,
|
||||||
|
ids: Array.from(map.get(region).keys()),
|
||||||
|
};
|
||||||
|
|
||||||
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",
|
||||||
queryParams: {
|
body: requestBody,
|
||||||
filter:
|
|
||||||
"armRegionName eq '" +
|
|
||||||
regionItem.locationName.split(" ").join("").toLowerCase() +
|
|
||||||
"' and serviceFamily eq 'Databases' and productName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const item of response.result.Items) {
|
for (const item of response.result) {
|
||||||
if (currencyCode === undefined) {
|
if (item.error) {
|
||||||
currencyCode = item.currencyCode;
|
continue;
|
||||||
} else if (item.currencyCode !== currencyCode) {
|
}
|
||||||
|
|
||||||
|
if (billingCurrency === undefined) {
|
||||||
|
billingCurrency = item.billingCurrency;
|
||||||
|
} else if (item.billingCurrency !== billingCurrency) {
|
||||||
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
throw Error("Currency Code Mismatch: Currency code not same for all regions / skus.");
|
||||||
}
|
}
|
||||||
regionPriceMap.set(item.skuName, item.retailPrice);
|
|
||||||
|
const offeringId = item.id;
|
||||||
|
const skuName = map.get(region).get(offeringId);
|
||||||
|
const unitPriceinBillingCurrency = item.prices.find((x) => x.type === "Consumption")
|
||||||
|
?.unitPriceinBillingCurrency;
|
||||||
|
regionPriceMap.set(skuName, unitPriceinBillingCurrency);
|
||||||
}
|
}
|
||||||
priceMap.set(regionItem.locationName, regionPriceMap);
|
priceMap.set(region, regionPriceMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceSuccess(telemetryData, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return { priceMap: priceMap, currencyCode: currencyCode };
|
return { priceMap: priceMap, billingCurrency: billingCurrency };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return { priceMap: undefined, currencyCode: undefined };
|
return { priceMap: undefined, billingCurrency: undefined };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOfferingIdPathForRegion = (): string => {
|
||||||
|
return `/skus?serviceFamily=Databases&service=Azure Cosmos DB`;
|
||||||
|
};
|
||||||
|
|
||||||
|
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>>();
|
||||||
|
for (const regionItem of regions) {
|
||||||
|
const regionOfferingIdMap = new Map<string, string>();
|
||||||
|
const regionShortName = await getRegionShortName(regionItem.locationName);
|
||||||
|
|
||||||
|
const response = await getOfferingIdsRequest<GetOfferingIdsResponse>({
|
||||||
|
host: configContext.CATALOG_ENDPOINT,
|
||||||
|
path: getOfferingIdPathForRegion(),
|
||||||
|
method: "GET",
|
||||||
|
apiVersion: "2023-05-01-preview",
|
||||||
|
queryParams: {
|
||||||
|
filter: "armRegionName eq '" + regionShortName + "'",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const item of response.result.items) {
|
||||||
|
if (item.offeringProperties?.length > 0) {
|
||||||
|
regionOfferingIdMap.set(item.offeringProperties[0].offeringId, item.skuName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offeringIdMap.set(regionItem.locationName, regionOfferingIdMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
selfServeTraceSuccess(telemetryData, getOfferingIdsCodeTimestamp);
|
||||||
|
return offeringIdMap;
|
||||||
|
} catch (err) {
|
||||||
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
|
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,7 @@ import { BladeType, generateBladeLink } from "../SelfServeUtils";
|
|||||||
import {
|
import {
|
||||||
deleteDedicatedGatewayResource,
|
deleteDedicatedGatewayResource,
|
||||||
getCurrentProvisioningState,
|
getCurrentProvisioningState,
|
||||||
|
getOfferingIds,
|
||||||
getPriceMapAndCurrencyCode,
|
getPriceMapAndCurrencyCode,
|
||||||
getRegions,
|
getRegions,
|
||||||
refreshDedicatedGatewayProvisioning,
|
refreshDedicatedGatewayProvisioning,
|
||||||
@ -370,9 +371,10 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
});
|
});
|
||||||
|
|
||||||
regions = await getRegions();
|
regions = await getRegions();
|
||||||
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(regions);
|
const offeringIdMap = await getOfferingIds(regions);
|
||||||
|
const priceMapAndCurrencyCode = await getPriceMapAndCurrencyCode(offeringIdMap);
|
||||||
priceMap = priceMapAndCurrencyCode.priceMap;
|
priceMap = priceMapAndCurrencyCode.priceMap;
|
||||||
currencyCode = priceMapAndCurrencyCode.currencyCode;
|
currencyCode = priceMapAndCurrencyCode.billingCurrency;
|
||||||
|
|
||||||
const response = await getCurrentProvisioningState();
|
const response = await getCurrentProvisioningState();
|
||||||
if (response.status && response.status !== "Deleting") {
|
if (response.status && response.status !== "Deleting") {
|
||||||
|
@ -30,23 +30,51 @@ export type UpdateDedicatedGatewayRequestProperties = {
|
|||||||
serviceType: string;
|
serviceType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FetchPricesResponse = {
|
export type FetchPricesResponse = Array<PriceItem>;
|
||||||
Items: Array<PriceItem>;
|
|
||||||
NextPageLink: string | undefined;
|
export type PriceItem = {
|
||||||
Count: number;
|
prices: Array<PriceType>;
|
||||||
|
id: string;
|
||||||
|
billingCurrency: string;
|
||||||
|
error: PriceError;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PriceType = {
|
||||||
|
type: string;
|
||||||
|
unitPriceinBillingCurrency: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PriceError = {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceMapAndCurrencyCode = {
|
export type PriceMapAndCurrencyCode = {
|
||||||
priceMap: Map<string, Map<string, number>>;
|
priceMap: Map<string, Map<string, number>>;
|
||||||
currencyCode: string;
|
billingCurrency: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PriceItem = {
|
export type GetOfferingIdsResponse = {
|
||||||
retailPrice: number;
|
items: Array<OfferingIdItem>;
|
||||||
skuName: string;
|
nextPageLink: string | undefined;
|
||||||
currencyCode: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OfferingIdItem = {
|
||||||
|
skuName: string;
|
||||||
|
offeringProperties: Array<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,52 @@ 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,
|
||||||
|
}: 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await window.fetch(url.href, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
[HttpHeaders.xAPIKey]: configContext.CATALOG_API_KEY,
|
||||||
|
},
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
@ -33,9 +33,11 @@ All you need to provide is a resource group to deploy in to.
|
|||||||
|
|
||||||
To use this script, there are a few prerequisites that must be done at least once:
|
To use this script, there are a few prerequisites that must be done at least once:
|
||||||
|
|
||||||
1. [Install Azure PowerShell](https://learn.microsoft.com/en-us/powershell/azure/install-azps-windows?view=azps-12.0.0&tabs=powershell&pivots=windows-psgallery) if you don't already have it.
|
1. This script requires Powershell 7+. Install it [here](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows).
|
||||||
2. Connect to your Azure account using `Connect-AzAccount`.
|
2. [Install Azure PowerShell](https://learn.microsoft.com/en-us/powershell/azure/install-azps-windows?view=azps-12.0.0&tabs=powershell&pivots=windows-psgallery) if you don't already have it.
|
||||||
3. Ensure you have a Resource Group _ready_ to deploy into, the deploy script requires an existing resource group. This resource group should be named `[username]-e2e-testing`, where `[username]` is your Windows username, (**Microsoft employees:** This should be your alias). The easiest way to do this is by running the `create-resource-group.ps1` script, specifying the Subscription (Name or ID) and Location in which you want to create the Resource Group. For example:
|
3. [Install Bicep CLI](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install#install-manually) if it is not already installed.
|
||||||
|
4. Connect to your Azure account using `Connect-AzAccount`.
|
||||||
|
5. Ensure you have a Resource Group _ready_ to deploy into, the deploy script requires an existing resource group. This resource group should be named `[username]-e2e-testing`, where `[username]` is your Windows username, (**Microsoft employees:** This should be your alias). The easiest way to do this is by running the `create-resource-group.ps1` script, specifying the Subscription (Name or ID) and Location in which you want to create the Resource Group. For example:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\test\resources\create-resource-group.ps1 -SubscriptionName "My Subscription" -Location "West US 3"
|
.\test\resources\create-resource-group.ps1 -SubscriptionName "My Subscription" -Location "West US 3"
|
||||||
@ -120,6 +122,14 @@ Configuring for E2E Testing
|
|||||||
|
|
||||||
## Running the tests
|
## Running the tests
|
||||||
|
|
||||||
|
If Azure CLI is not installed, please [install it](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).
|
||||||
|
|
||||||
|
Log into Az CLI with the following command:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
az login --scope https://management.core.windows.net//.default
|
||||||
|
```
|
||||||
|
|
||||||
To run all tests in a headless browser, run the following command from the root of the repo:
|
To run all tests in a headless browser, run the following command from the root of the repo:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@ -140,4 +150,4 @@ npx playwright test --ui
|
|||||||
|
|
||||||
The UI allows you to select a specific test to run and to see the results of the test in the browser.
|
The UI allows you to select a specific test to run and to see the results of the test in the browser.
|
||||||
|
|
||||||
See the [Playwright docs](https://playwright.dev/docs/running-tests) for more information on running tests.
|
See the [Playwright docs](https://playwright.dev/docs/running-tests) for more information on running tests.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user