mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 15:06:55 +00:00
Added support for self serve telemetry + Localization fixes (#580)
* initial telemetry commit * Added localization changes * moved telemetrymessage types to selfservetypes * fixed compile errors * fixed failing test * changed translation file format * Addressed PR comments * modified test
This commit is contained in:
parent
63e13cdabe
commit
6cdac3c53b
@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
|
import { DescriptionType, NumberUiType, SmartUiInput } from "../../../SelfServe/SelfServeTypes";
|
||||||
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
|
import { SmartUiComponent, SmartUiDescriptor } from "./SmartUiComponent";
|
||||||
import { NumberUiType, SmartUiInput, DescriptionType } from "../../../SelfServe/SelfServeTypes";
|
|
||||||
|
|
||||||
describe("SmartUiComponent", () => {
|
describe("SmartUiComponent", () => {
|
||||||
const exampleData: SmartUiDescriptor = {
|
const exampleData: SmartUiDescriptor = {
|
||||||
@ -97,9 +97,9 @@ describe("SmartUiComponent", () => {
|
|||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "object",
|
type: "object",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "Database 1", key: "db1" },
|
{ labelTKey: "Database 1", key: "db1" },
|
||||||
{ label: "Database 2", key: "db2" },
|
{ labelTKey: "Database 2", key: "db2" },
|
||||||
{ label: "Database 3", key: "db3" },
|
{ labelTKey: "Database 3", key: "db3" },
|
||||||
],
|
],
|
||||||
defaultKey: "db2",
|
defaultKey: "db2",
|
||||||
},
|
},
|
||||||
|
@ -334,7 +334,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
|||||||
dropdownWidth="auto"
|
dropdownWidth="auto"
|
||||||
options={choices.map((c) => ({
|
options={choices.map((c) => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
text: this.props.getTranslation(c.label),
|
text: this.props.getTranslation(c.labelTKey),
|
||||||
}))}
|
}))}
|
||||||
styles={{
|
styles={{
|
||||||
root: { width: 400 },
|
root: { width: 400 },
|
||||||
|
5
src/Localization/en/Common.json
Normal file
5
src/Localization/en/Common.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Save": "Save",
|
||||||
|
"Discard": "Discard",
|
||||||
|
"Refresh": "Refesh"
|
||||||
|
}
|
31
src/Localization/en/SelfServeExample.json
Normal file
31
src/Localization/en/SelfServeExample.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"NorthCentralUS": "North Central US",
|
||||||
|
"WestUS": "West US",
|
||||||
|
"EastUS2": "East US 2",
|
||||||
|
"Current Region": "Current Region",
|
||||||
|
"RegionDropdownInfo": "More regions can be added in the future.",
|
||||||
|
"RegionsAndAccountNameValidationError": "Regions and account name should not be empty.",
|
||||||
|
"DbThroughputValidationError": "Please update throughput for database.",
|
||||||
|
"DescriptionLabel": "Description",
|
||||||
|
"DescriptionText": "This class sets collection and database throughput.",
|
||||||
|
"DecriptionLinkText": "Click here for more information",
|
||||||
|
"Regions": "Regions",
|
||||||
|
"RegionsPlaceholder": "Select a region",
|
||||||
|
"Enable Logging": "Enable Logging",
|
||||||
|
"Enable": "Enable",
|
||||||
|
"Disable": "Disable",
|
||||||
|
"Account Name": "Account Name",
|
||||||
|
"AccountNamePlaceHolder": "Enter the account name",
|
||||||
|
"Collection Throughput": "Collection Throughput",
|
||||||
|
"Enable DB level throughput": "Enable Database Level Throughput",
|
||||||
|
"Database Throughput": "Database Throughput",
|
||||||
|
"UpdateInProgressMessage": "Data is being updated",
|
||||||
|
"UpdateCompletedMessageTitle": "Update succeeded",
|
||||||
|
"UpdateCompletedMessageText": "Data update completed.",
|
||||||
|
"SubmissionMessageSuccessTitle": "Update started",
|
||||||
|
"SubmissionMessageForNewRegionText": "Data update started. Region changed.",
|
||||||
|
"SubmissionMessageForSameRegionText": "Data update started. Region not changed.",
|
||||||
|
"SubmissionMessageErrorTitle": "Data update failed",
|
||||||
|
"SubmissionMessageErrorText": "Data update failed because of errors.",
|
||||||
|
"OnSaveFailureMessage": "Data save operation not currently permitted."
|
||||||
|
}
|
52
src/Localization/en/SqlX.json
Normal file
52
src/Localization/en/SqlX.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance.",
|
||||||
|
"DedicatedGateway": "Dedicated Gateway",
|
||||||
|
"Enable": "Enable",
|
||||||
|
"Disable": "Disable",
|
||||||
|
"LearnAboutDedicatedGateway": "Learn more about dedicated gateway.",
|
||||||
|
"DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.",
|
||||||
|
"DedicatedGatewayPricing": "Learn more about dedicated gateway pricing.",
|
||||||
|
"SKUs": "SKUs",
|
||||||
|
"SKUsPlaceHolder": "Select SKUs",
|
||||||
|
"NumberOfInstances": "Number of instances",
|
||||||
|
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
|
||||||
|
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
|
||||||
|
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
|
||||||
|
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
|
||||||
|
"CreateMessage": "Dedicated gateway resource is being created.",
|
||||||
|
"CreateInitializeTitle": "Provisioning resource",
|
||||||
|
"CreateInitializeMessage": "Dedicated gateway resource will be provisioned.",
|
||||||
|
"CreateSuccessTitle": "Resource provisioned",
|
||||||
|
"CreateSuccesseMessage": "Dedicated gateway resource provisioned.",
|
||||||
|
"CreateFailureTitle": "Failed to provision resource",
|
||||||
|
"CreateFailureMessage": "Dedicated gateway resource provisioning failed.",
|
||||||
|
"UpdateMessage": "Dedicated gateway resource is being updated.",
|
||||||
|
"UpdateInitializeTitle": "Updating resource",
|
||||||
|
"UpdateInitializeMessage": "Dedicated gateway resource will be updated.",
|
||||||
|
"UpdateSuccessTitle": "Resource updated",
|
||||||
|
"UpdateSuccesseMessage": "Dedicated gateway resource updated.",
|
||||||
|
"UpdateFailureTitle": "Failed to update resource",
|
||||||
|
"UpdateFailureMessage": "Dedicated gateway resource updation failed.",
|
||||||
|
"DeleteMessage": "Dedicated gateway resource is being deleted.",
|
||||||
|
"DeleteInitializeTitle": "Deleting resource",
|
||||||
|
"DeleteInitializeMessage": "Dedicated gateway resource will be deleted.",
|
||||||
|
"DeleteSuccessTitle": "Resource deleted",
|
||||||
|
"DeleteSuccesseMessage": "Dedicated gateway resource deleted.",
|
||||||
|
"DeleteFailureTitle": "Failed to delete resource",
|
||||||
|
"DeleteFailureMessage": "Dedicated gateway resource deletion failed.",
|
||||||
|
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment.",
|
||||||
|
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
|
||||||
|
"NoValue": "",
|
||||||
|
"SKUDetails": "SKU Details:",
|
||||||
|
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
|
||||||
|
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
|
||||||
|
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
|
||||||
|
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
|
||||||
|
"Cost": "Cost",
|
||||||
|
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
|
||||||
|
"ConnectionString": "Connection String",
|
||||||
|
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
|
||||||
|
"KeysBlade": "the keys blade",
|
||||||
|
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
|
||||||
|
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
|
||||||
|
}
|
@ -1,92 +0,0 @@
|
|||||||
{
|
|
||||||
"translations": {
|
|
||||||
"Common": {
|
|
||||||
"Save": "Save",
|
|
||||||
"Discard": "Discard",
|
|
||||||
"Refresh": "Refesh"
|
|
||||||
},
|
|
||||||
"SelfServeExample": {
|
|
||||||
"North Central US": "North Central US",
|
|
||||||
"West US": "West US",
|
|
||||||
"East US 2": "East US 2",
|
|
||||||
"Current Region": "Current Region",
|
|
||||||
"RegionDropdownInfo": "More regions can be added in the future.",
|
|
||||||
"RegionsAndAccountNameValidationError": "Regions and account name should not be empty.",
|
|
||||||
"DbThroughputValidationError": "Please update throughput for database.",
|
|
||||||
"DescriptionLabel": "Description",
|
|
||||||
"DescriptionText": "This class sets collection and database throughput.",
|
|
||||||
"DecriptionLinkText": "Click here for more information",
|
|
||||||
"Regions": "Regions",
|
|
||||||
"RegionsPlaceholder": "Select a region",
|
|
||||||
"Enable Logging": "Enable Logging",
|
|
||||||
"Enable": "Enable",
|
|
||||||
"Disable": "Disable",
|
|
||||||
"Account Name": "Account Name",
|
|
||||||
"AccountNamePlaceHolder": "Enter the account name",
|
|
||||||
"Collection Throughput": "Collection Throughput",
|
|
||||||
"Enable DB level throughput": "Enable Database Level Throughput",
|
|
||||||
"Database Throughput": "Database Throughput",
|
|
||||||
"UpdateInProgressMessage": "Data is being updated",
|
|
||||||
"UpdateCompletedMessageTitle": "Update succeeded",
|
|
||||||
"UpdateCompletedMessageText": "Data update completed.",
|
|
||||||
"SubmissionMessageSuccessTitle": "Update started",
|
|
||||||
"SubmissionMessageForNewRegionText": "Data update started. Region changed.",
|
|
||||||
"SubmissionMessageForSameRegionText": "Data update started. Region not changed.",
|
|
||||||
"SubmissionMessageErrorTitle": "Data update failed",
|
|
||||||
"SubmissionMessageErrorText": "Data update failed because of errors.",
|
|
||||||
"OnSaveFailureMessage": "Data save operation not currently permitted."
|
|
||||||
},
|
|
||||||
"SqlX": {
|
|
||||||
"DedicatedGatewayDescription": "Provision a dedicated gateway cluster for your Azure Cosmos DB account. A dedicated gateway is compute that is a front-end to data in your Azure Cosmos DB account. Your dedicated gateway automatically includes the integrated cache, which can improve read performance. ",
|
|
||||||
"DedicatedGateway": "Dedicated Gateway",
|
|
||||||
"Enable": "Enable",
|
|
||||||
"Disable": "Disable",
|
|
||||||
"LearnAboutDedicatedGateway": "Learn more about dedicated gateway.",
|
|
||||||
"DeprovisioningDetailsText": "Learn more about deprovisioning the dedicated gateway.",
|
|
||||||
"DedicatedGatewayPricing": "Learn more about dedicated gateway pricing",
|
|
||||||
"SKUs": "SKUs",
|
|
||||||
"SKUsPlaceHolder": "Select SKUs",
|
|
||||||
"NumberOfInstances": "Number of instances",
|
|
||||||
"CosmosD4s": "Cosmos.D4s (General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory)",
|
|
||||||
"CosmosD8s": "Cosmos.D8s (General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory)",
|
|
||||||
"CosmosD16s": "Cosmos.D16s (General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory)",
|
|
||||||
"CosmosD32s": "Cosmos.D32s (General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory)",
|
|
||||||
"CreateMessage": "Dedicated gateway resource is being created.",
|
|
||||||
"CreateInitializeTitle": "Provisioning resource",
|
|
||||||
"CreateInitializeMessage": "Dedicated gateway resource will be provisioned.",
|
|
||||||
"CreateSuccessTitle": "Resource provisioned",
|
|
||||||
"CreateSuccesseMessage": "Dedicated gateway resource provisioned.",
|
|
||||||
"CreateFailureTitle": "Failed to provision resource",
|
|
||||||
"CreateFailureMessage": "Dedicated gateway resource provisioning failed.",
|
|
||||||
"UpdateMessage": "Dedicated gateway resource is being updated.",
|
|
||||||
"UpdateInitializeTitle": "Updating resource",
|
|
||||||
"UpdateInitializeMessage": "Dedicated gateway resource will be updated.",
|
|
||||||
"UpdateSuccessTitle": "Resource updated",
|
|
||||||
"UpdateSuccesseMessage": "Dedicated gateway resource updated.",
|
|
||||||
"UpdateFailureTitle": "Failed to update resource",
|
|
||||||
"UpdateFailureMessage": "Dedicated gateway resource updation failed.",
|
|
||||||
"DeleteMessage": "Dedicated gateway resource is being deleted.",
|
|
||||||
"DeleteInitializeTitle": "Deleting resource",
|
|
||||||
"DeleteInitializeMessage": "Dedicated gateway resource will be deleted.",
|
|
||||||
"DeleteSuccessTitle": "Resource deleted",
|
|
||||||
"DeleteSuccesseMessage": "Dedicated gateway resource deleted.",
|
|
||||||
"DeleteFailureTitle": "Failed to delete resource",
|
|
||||||
"DeleteFailureMessage": "Dedicated gateway resource deletion failed.",
|
|
||||||
"CannotSave": "Cannot save the changes to the Dedicated gateway resource at the moment",
|
|
||||||
"DedicatedGatewayEndpoint": "Dedicated gatewayEndpoint",
|
|
||||||
"NoValue": "",
|
|
||||||
"SKUDetails": "SKU Details: ",
|
|
||||||
"CosmosD4Details": "General Purpose Cosmos Compute with 4 vCPUs, 16 GB Memory",
|
|
||||||
"CosmosD8Details": "General Purpose Cosmos Compute with 8 vCPUs, 32 GB Memory",
|
|
||||||
"CosmosD16Details": "General Purpose Cosmos Compute with 16 vCPUs, 64 GB Memory",
|
|
||||||
"CosmosD32Details": "General Purpose Cosmos Compute with 32 vCPUs, 128 GB Memory",
|
|
||||||
"Cost": "Cost",
|
|
||||||
"CostText": "Hourly cost of the dedicated gateway resource depends on the SKU selection, number of instances per region, and number of regions.",
|
|
||||||
"ConnectionString": "Connection String",
|
|
||||||
"ConnectionStringText": "To use the dedicated gateway, use the connection string shown in ",
|
|
||||||
"KeysBlade": "the keys blade",
|
|
||||||
"WarningBannerOnUpdate": "Adding or modifying dedicated gateway instances may affect your bill.",
|
|
||||||
"WarningBannerOnDelete": "After deprovisioning the dedicated gateway, you must update any applications using the old dedicated gateway connection string."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,9 +23,9 @@ import {
|
|||||||
} from "./SelfServeExample.rp";
|
} from "./SelfServeExample.rp";
|
||||||
|
|
||||||
const regionDropdownItems: ChoiceItem[] = [
|
const regionDropdownItems: ChoiceItem[] = [
|
||||||
{ label: "North Central US", key: Regions.NorthCentralUS },
|
{ labelTKey: "NorthCentralUS", key: Regions.NorthCentralUS },
|
||||||
{ label: "West US", key: Regions.WestUS },
|
{ labelTKey: "WestUS", key: Regions.WestUS },
|
||||||
{ label: "East US 2", key: Regions.EastUS2 },
|
{ labelTKey: "EastUS2", key: Regions.EastUS2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const regionDropdownInfo: Info = {
|
const regionDropdownInfo: Info = {
|
||||||
|
@ -2,10 +2,12 @@ import { Spinner, SpinnerSize } from "office-ui-fabric-react";
|
|||||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
import { withTranslation } from "react-i18next";
|
||||||
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
import { normalizeArmEndpoint } from "../Common/EnvironmentUtility";
|
||||||
import { sendReadyMessage } from "../Common/MessageHandler";
|
import { sendReadyMessage } from "../Common/MessageHandler";
|
||||||
import { configContext, updateConfigContext } from "../ConfigContext";
|
import { configContext, updateConfigContext } from "../ConfigContext";
|
||||||
import { SelfServeFrameInputs } from "../Contracts/ViewModels";
|
import { SelfServeFrameInputs } from "../Contracts/ViewModels";
|
||||||
|
import i18n from "../i18n";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
|
||||||
import "./SelfServe.less";
|
import "./SelfServe.less";
|
||||||
@ -14,14 +16,35 @@ import { SelfServeDescriptor } from "./SelfServeTypes";
|
|||||||
import { SelfServeType } from "./SelfServeUtils";
|
import { SelfServeType } from "./SelfServeUtils";
|
||||||
initializeIcons();
|
initializeIcons();
|
||||||
|
|
||||||
|
const loadTranslationFile = async (className: string): Promise<void> => {
|
||||||
|
const language = i18n.languages[0];
|
||||||
|
const fileName = `${className}.json`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let translations: any;
|
||||||
|
try {
|
||||||
|
translations = await import(`../Localization/${language}/${fileName}`);
|
||||||
|
} catch (e) {
|
||||||
|
translations = await import(`../Localization/en/${fileName}`);
|
||||||
|
}
|
||||||
|
i18n.addResourceBundle(language, className, translations.default, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadTranslations = async (className: string): Promise<void> => {
|
||||||
|
await loadTranslationFile("Common");
|
||||||
|
await loadTranslationFile(className);
|
||||||
|
};
|
||||||
|
|
||||||
const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDescriptor> => {
|
const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDescriptor> => {
|
||||||
switch (selfServeType) {
|
switch (selfServeType) {
|
||||||
case SelfServeType.example: {
|
case SelfServeType.example: {
|
||||||
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
||||||
|
await loadTranslations(SelfServeExample.default.name);
|
||||||
return new SelfServeExample.default().toSelfServeDescriptor();
|
return new SelfServeExample.default().toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.sqlx: {
|
case SelfServeType.sqlx: {
|
||||||
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
||||||
|
await loadTranslations(SqlX.default.name);
|
||||||
return new SqlX.default().toSelfServeDescriptor();
|
return new SqlX.default().toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -33,7 +56,8 @@ const renderComponent = (selfServeDescriptor: SelfServeDescriptor): JSX.Element
|
|||||||
if (!selfServeDescriptor) {
|
if (!selfServeDescriptor) {
|
||||||
return <h1>Invalid self serve type!</h1>;
|
return <h1>Invalid self serve type!</h1>;
|
||||||
}
|
}
|
||||||
return <SelfServeComponent descriptor={selfServeDescriptor} />;
|
const SelfServeComponentTranslated = withTranslation()(SelfServeComponent);
|
||||||
|
return <SelfServeComponentTranslated descriptor={selfServeDescriptor} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSpinner = (): JSX.Element => {
|
const renderSpinner = (): JSX.Element => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
import { SelfServeComponent, SelfServeComponentState } from "./SelfServeComponent";
|
import { SelfServeComponent, SelfServeComponentState } from "./SelfServeComponent";
|
||||||
import { NumberUiType, OnSaveResult, SelfServeDescriptor, SmartUiInput } from "./SelfServeTypes";
|
import { NumberUiType, OnSaveResult, SelfServeDescriptor, SmartUiInput } from "./SelfServeTypes";
|
||||||
|
|
||||||
@ -87,9 +87,9 @@ describe("SelfServeComponent", () => {
|
|||||||
dataFieldName: "database",
|
dataFieldName: "database",
|
||||||
type: "object",
|
type: "object",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "Database 1", key: "db1" },
|
{ labelTKey: "Database 1", key: "db1" },
|
||||||
{ label: "Database 2", key: "db2" },
|
{ labelTKey: "Database 2", key: "db2" },
|
||||||
{ label: "Database 3", key: "db3" },
|
{ labelTKey: "Database 3", key: "db3" },
|
||||||
],
|
],
|
||||||
defaultKey: "db2",
|
defaultKey: "db2",
|
||||||
},
|
},
|
||||||
@ -106,7 +106,9 @@ describe("SelfServeComponent", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("should render and honor save, discard, refresh actions", async () => {
|
it("should render and honor save, discard, refresh actions", async () => {
|
||||||
const wrapper = shallow(<SelfServeComponent descriptor={exampleData} />);
|
const wrapper = shallow(
|
||||||
|
<SelfServeComponent descriptor={exampleData} t={undefined} i18n={undefined} tReady={undefined} />
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
@ -158,7 +160,9 @@ describe("SelfServeComponent", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("getResolvedValue", async () => {
|
it("getResolvedValue", async () => {
|
||||||
const wrapper = shallow(<SelfServeComponent descriptor={exampleData} />);
|
const wrapper = shallow(
|
||||||
|
<SelfServeComponent descriptor={exampleData} t={undefined} i18n={undefined} tReady={undefined} />
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
const selfServeComponent = wrapper.instance() as SelfServeComponent;
|
const selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||||
|
|
||||||
@ -179,7 +183,9 @@ describe("SelfServeComponent", () => {
|
|||||||
|
|
||||||
it("message bar and spinner snapshots", async () => {
|
it("message bar and spinner snapshots", async () => {
|
||||||
const newDescriptor = { ...exampleData, onRefresh: onRefreshIsUpdatingMock };
|
const newDescriptor = { ...exampleData, onRefresh: onRefreshIsUpdatingMock };
|
||||||
let wrapper = shallow(<SelfServeComponent descriptor={newDescriptor} />);
|
let wrapper = shallow(
|
||||||
|
<SelfServeComponent descriptor={newDescriptor} t={undefined} i18n={undefined} tReady={undefined} />
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
let selfServeComponent = wrapper.instance() as SelfServeComponent;
|
let selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||||
selfServeComponent.onSaveButtonClick();
|
selfServeComponent.onSaveButtonClick();
|
||||||
@ -187,7 +193,9 @@ describe("SelfServeComponent", () => {
|
|||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
|
||||||
newDescriptor.onRefresh = onRefreshMock;
|
newDescriptor.onRefresh = onRefreshMock;
|
||||||
wrapper = shallow(<SelfServeComponent descriptor={newDescriptor} />);
|
wrapper = shallow(
|
||||||
|
<SelfServeComponent descriptor={newDescriptor} t={undefined} i18n={undefined} tReady={undefined} />
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
selfServeComponent = wrapper.instance() as SelfServeComponent;
|
selfServeComponent = wrapper.instance() as SelfServeComponent;
|
||||||
selfServeComponent.onSaveButtonClick();
|
selfServeComponent.onSaveButtonClick();
|
||||||
|
@ -8,15 +8,15 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
SpinnerSize,
|
SpinnerSize,
|
||||||
Stack,
|
Stack,
|
||||||
|
Text,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Translation } from "react-i18next";
|
import { WithTranslation } from "react-i18next";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import { sendMessage } from "../Common/MessageHandler";
|
import { sendMessage } from "../Common/MessageHandler";
|
||||||
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
|
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
|
||||||
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
import { SmartUiComponent, SmartUiDescriptor } from "../Explorer/Controls/SmartUi/SmartUiComponent";
|
||||||
import "../i18n";
|
|
||||||
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
|
import { commandBarItemStyles, commandBarStyles, containerStackTokens, separatorStyles } from "./SelfServeStyles";
|
||||||
import {
|
import {
|
||||||
AnyDisplay,
|
AnyDisplay,
|
||||||
@ -57,7 +57,7 @@ interface PortalNotificationContent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelfServeComponentProps {
|
export interface SelfServeComponentProps extends WithTranslation {
|
||||||
descriptor: SelfServeDescriptor;
|
descriptor: SelfServeDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +108,9 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
this.retryIntervalInMs = SelfServeComponent.defaultRetryIntervalInMs;
|
this.retryIntervalInMs = SelfServeComponent.defaultRetryIntervalInMs;
|
||||||
}
|
}
|
||||||
this.retryOptions = { forever: true, maxTimeout: this.retryIntervalInMs, minTimeout: this.retryIntervalInMs };
|
this.retryOptions = { forever: true, maxTimeout: this.retryIntervalInMs, minTimeout: this.retryIntervalInMs };
|
||||||
|
|
||||||
|
// translation function passed to SelfServeComponent
|
||||||
|
this.translationFunction = this.props.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onError = (hasErrors: boolean): void => {
|
private onError = (hasErrors: boolean): void => {
|
||||||
@ -391,8 +394,8 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
return this.getTranslation(key, "Common");
|
return this.getTranslation(key, "Common");
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTranslation = (messageKey: string, prefix = `${this.smartUiGeneratorClassName}`): string => {
|
private getTranslation = (messageKey: string, namespace = `${this.smartUiGeneratorClassName}`): string => {
|
||||||
const translationKey = `${prefix}.${messageKey}`;
|
const translationKey = `${namespace}:${messageKey}`;
|
||||||
const translation = this.translationFunction ? this.translationFunction(translationKey) : messageKey;
|
const translation = this.translationFunction ? this.translationFunction(translationKey) : messageKey;
|
||||||
if (translation === translationKey) {
|
if (translation === translationKey) {
|
||||||
return messageKey;
|
return messageKey;
|
||||||
@ -441,53 +444,45 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
if (this.state.compileErrorMessage) {
|
if (this.state.compileErrorMessage) {
|
||||||
return <MessageBar messageBarType={MessageBarType.error}>{this.state.compileErrorMessage}</MessageBar>;
|
return (
|
||||||
|
<MessageBar messageBarType={MessageBarType.error}>
|
||||||
|
<Text>{this.state.compileErrorMessage}</Text>
|
||||||
|
</MessageBar>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Translation>
|
<div style={{ overflowX: "auto" }}>
|
||||||
{(translate) => {
|
<Stack tokens={containerStackTokens}>
|
||||||
if (!this.translationFunction) {
|
<Stack.Item>
|
||||||
this.translationFunction = translate;
|
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
||||||
}
|
<Separator styles={separatorStyles} />
|
||||||
|
</Stack.Item>
|
||||||
return (
|
{this.state.isInitializing ? (
|
||||||
<div style={{ overflowX: "auto" }}>
|
<Spinner size={SpinnerSize.large} />
|
||||||
<Stack tokens={containerStackTokens}>
|
) : (
|
||||||
<Stack.Item>
|
<>
|
||||||
<CommandBar styles={commandBarStyles} items={this.getCommandBarItems()} />
|
{this.state.notification && (
|
||||||
<Separator styles={separatorStyles} />
|
<MessageBar
|
||||||
</Stack.Item>
|
messageBarType={this.state.notification.type}
|
||||||
{this.state.isInitializing ? (
|
onDismiss={
|
||||||
<Spinner size={SpinnerSize.large} />
|
this.state.notification.isCancellable ? () => this.setState({ notification: undefined }) : undefined
|
||||||
) : (
|
}
|
||||||
<>
|
>
|
||||||
{this.state.notification && (
|
<Text>{this.state.notification.message}</Text>
|
||||||
<MessageBar
|
</MessageBar>
|
||||||
messageBarType={this.state.notification.type}
|
)}
|
||||||
onDismiss={
|
<SmartUiComponent
|
||||||
this.state.notification.isCancellable
|
disabled={this.state.refreshResult?.isUpdateInProgress || this.state.isSaving}
|
||||||
? () => this.setState({ notification: undefined })
|
descriptor={this.state.root as SmartUiDescriptor}
|
||||||
: undefined
|
currentValues={this.state.currentValues}
|
||||||
}
|
onInputChange={this.onInputChange}
|
||||||
>
|
onError={this.onError}
|
||||||
{this.state.notification.message}
|
getTranslation={this.getTranslation}
|
||||||
</MessageBar>
|
/>
|
||||||
)}
|
</>
|
||||||
<SmartUiComponent
|
)}
|
||||||
disabled={this.state.refreshResult?.isUpdateInProgress || this.state.isSaving}
|
</Stack>
|
||||||
descriptor={this.state.root as SmartUiDescriptor}
|
</div>
|
||||||
currentValues={this.state.currentValues}
|
|
||||||
onInputChange={this.onInputChange}
|
|
||||||
onError={this.onError}
|
|
||||||
getTranslation={this.getTranslation}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Translation>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
src/SelfServe/SelfServeTelemetryProcessor.ts
Normal file
69
src/SelfServe/SelfServeTelemetryProcessor.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { sendMessage } from "../Common/MessageHandler";
|
||||||
|
import { configContext } from "../ConfigContext";
|
||||||
|
import { SelfServeMessageTypes } from "../Contracts/SelfServeContracts";
|
||||||
|
import { appInsights } from "../Shared/appInsights";
|
||||||
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { SelfServeTelemetryMessage } from "./SelfServeTypes";
|
||||||
|
|
||||||
|
const action = Action.SelfServe;
|
||||||
|
|
||||||
|
export const trace = (data: SelfServeTelemetryMessage): void => {
|
||||||
|
sendSelfServeTelemetryMessage(ActionModifiers.Mark, data);
|
||||||
|
appInsights.trackEvent({ name: Action[action] }, decorateData(data, ActionModifiers.Mark));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const traceStart = (data: SelfServeTelemetryMessage): number => {
|
||||||
|
const timestamp: number = Date.now();
|
||||||
|
sendSelfServeTelemetryMessage(ActionModifiers.Start, data);
|
||||||
|
appInsights.startTrackEvent(Action[action]);
|
||||||
|
return timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const traceSuccess = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
|
||||||
|
sendSelfServeTelemetryMessage(ActionModifiers.Success, data, timestamp || Date.now());
|
||||||
|
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Success));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const traceFailure = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
|
||||||
|
sendSelfServeTelemetryMessage(ActionModifiers.Failed, data, timestamp || Date.now());
|
||||||
|
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Failed));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const traceCancel = (data: SelfServeTelemetryMessage, timestamp?: number): void => {
|
||||||
|
sendSelfServeTelemetryMessage(ActionModifiers.Cancel, data, timestamp || Date.now());
|
||||||
|
appInsights.stopTrackEvent(Action[action], decorateData(data, ActionModifiers.Cancel));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendSelfServeTelemetryMessage = (
|
||||||
|
actionModifier: string,
|
||||||
|
data: SelfServeTelemetryMessage,
|
||||||
|
timeStamp?: number
|
||||||
|
): void => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const dataToSend: any = {
|
||||||
|
type: SelfServeMessageTypes.TelemetryInfo,
|
||||||
|
data: {
|
||||||
|
action: Action[action],
|
||||||
|
actionModifier: actionModifier,
|
||||||
|
data: JSON.stringify(decorateData(data)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (timeStamp) {
|
||||||
|
dataToSend.data.timeStamp = timeStamp;
|
||||||
|
}
|
||||||
|
sendMessage(dataToSend);
|
||||||
|
};
|
||||||
|
|
||||||
|
const decorateData = (data: SelfServeTelemetryMessage, actionModifier?: string) => {
|
||||||
|
return {
|
||||||
|
databaseAccountName: userContext.databaseAccount?.name,
|
||||||
|
defaultExperience: userContext.defaultExperience,
|
||||||
|
authType: userContext.authType,
|
||||||
|
subscriptionId: userContext.subscriptionId,
|
||||||
|
platform: configContext.platform,
|
||||||
|
env: process.env.NODE_ENV,
|
||||||
|
actionModifier,
|
||||||
|
...data,
|
||||||
|
} as { [key: string]: string };
|
||||||
|
};
|
@ -98,7 +98,7 @@ export enum NumberUiType {
|
|||||||
Slider = "Slider",
|
Slider = "Slider",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChoiceItem = { label: string; key: string };
|
export type ChoiceItem = { labelTKey: string; key: string };
|
||||||
|
|
||||||
export type InputType = number | string | boolean | ChoiceItem | Description;
|
export type InputType = number | string | boolean | ChoiceItem | Description;
|
||||||
|
|
||||||
@ -157,3 +157,9 @@ export interface RefreshResult {
|
|||||||
export interface RefreshParams {
|
export interface RefreshParams {
|
||||||
retryIntervalInMs: number;
|
retryIntervalInMs: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SelfServeTelemetryMessage {
|
||||||
|
selfServeClassName: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
@ -131,9 +131,9 @@ describe("SelfServeUtils", () => {
|
|||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Regions",
|
labelTKey: "Regions",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "South West US", key: "SWUS" },
|
{ labelTKey: "South West US", key: "SWUS" },
|
||||||
{ label: "North Central US", key: "NCUS" },
|
{ labelTKey: "North Central US", key: "NCUS" },
|
||||||
{ label: "East US 2", key: "EUS2" },
|
{ labelTKey: "East US 2", key: "EUS2" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -238,9 +238,9 @@ describe("SelfServeUtils", () => {
|
|||||||
type: "object",
|
type: "object",
|
||||||
labelTKey: "Regions",
|
labelTKey: "Regions",
|
||||||
choices: [
|
choices: [
|
||||||
{ label: "South West US", key: "SWUS" },
|
{ labelTKey: "South West US", key: "SWUS" },
|
||||||
{ label: "North Central US", key: "NCUS" },
|
{ labelTKey: "North Central US", key: "NCUS" },
|
||||||
{ label: "East US 2", key: "EUS2" },
|
{ labelTKey: "East US 2", key: "EUS2" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
children: [] as Node[],
|
children: [] as Node[],
|
||||||
|
@ -195,5 +195,5 @@ export const generateBladeLink = (blade: BladeType): string => {
|
|||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const resourceGroupName = userContext.resourceGroup;
|
const resourceGroupName = userContext.resourceGroup;
|
||||||
const databaseAccountName = userContext.databaseAccount.name;
|
const databaseAccountName = userContext.databaseAccount.name;
|
||||||
return `https://portal.azure.com/#@microsoft.onmicrosoft.com/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}/${blade}`;
|
return `${document.referrer}#@microsoft.onmicrosoft.com/resource/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}/${blade}`;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators";
|
import { IsDisplayable, OnChange, RefreshOptions, Values } from "../Decorators";
|
||||||
|
import { trace } from "../SelfServeTelemetryProcessor";
|
||||||
import {
|
import {
|
||||||
ChoiceItem,
|
ChoiceItem,
|
||||||
Description,
|
Description,
|
||||||
@ -147,10 +148,10 @@ const onEnableDedicatedGatewayChange = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const skuDropDownItems: ChoiceItem[] = [
|
const skuDropDownItems: ChoiceItem[] = [
|
||||||
{ label: "CosmosD4s", key: CosmosD4s },
|
{ labelTKey: "CosmosD4s", key: CosmosD4s },
|
||||||
{ label: "CosmosD8s", key: CosmosD8s },
|
{ labelTKey: "CosmosD8s", key: CosmosD8s },
|
||||||
{ label: "CosmosD16s", key: CosmosD16s },
|
{ labelTKey: "CosmosD16s", key: CosmosD16s },
|
||||||
{ label: "CosmosD32s", key: CosmosD32s },
|
{ labelTKey: "CosmosD32s", key: CosmosD32s },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getSkus = async (): Promise<ChoiceItem[]> => {
|
const getSkus = async (): Promise<ChoiceItem[]> => {
|
||||||
@ -176,6 +177,8 @@ export default class SqlX extends SelfServeBaseClass {
|
|||||||
currentValues: Map<string, SmartUiInput>,
|
currentValues: Map<string, SmartUiInput>,
|
||||||
baselineValues: Map<string, SmartUiInput>
|
baselineValues: Map<string, SmartUiInput>
|
||||||
): Promise<OnSaveResult> => {
|
): Promise<OnSaveResult> => {
|
||||||
|
trace({ selfServeClassName: "SqlX" });
|
||||||
|
|
||||||
const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean;
|
const dedicatedGatewayCurrentlyEnabled = currentValues.get("enableDedicatedGateway")?.value as boolean;
|
||||||
const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;
|
const dedicatedGatewayOriginallyEnabled = baselineValues.get("enableDedicatedGateway")?.value as boolean;
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -114,6 +114,7 @@ export enum Action {
|
|||||||
NotebooksGalleryPublicGalleryCount,
|
NotebooksGalleryPublicGalleryCount,
|
||||||
NotebooksGalleryFavoritesCount,
|
NotebooksGalleryFavoritesCount,
|
||||||
NotebooksGalleryPublishedCount,
|
NotebooksGalleryPublishedCount,
|
||||||
|
SelfServe,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionModifiers = {
|
export const ActionModifiers = {
|
||||||
|
10
src/i18n.ts
10
src/i18n.ts
@ -1,22 +1,14 @@
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import XHR from "i18next-http-backend";
|
|
||||||
import EnglishTranslations from "./Localization/en/translations.json";
|
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(XHR)
|
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
resources: {
|
|
||||||
en: EnglishTranslations,
|
|
||||||
},
|
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
|
detection: { order: ["navigator", "cookie", "localStorage", "sessionStorage", "querystring", "htmlTag"] },
|
||||||
debug: process.env.NODE_ENV === "development",
|
debug: process.env.NODE_ENV === "development",
|
||||||
ns: ["translations"],
|
|
||||||
defaultNS: "translations",
|
|
||||||
keySeparator: ".",
|
keySeparator: ".",
|
||||||
interpolation: {
|
interpolation: {
|
||||||
formatSeparator: ",",
|
formatSeparator: ",",
|
||||||
@ -29,3 +21,5 @@ i18n
|
|||||||
useSuspense: false,
|
useSuspense: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
|
Loading…
Reference in New Issue
Block a user