diff --git a/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx b/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx index 769308d470..1a6493e304 100644 --- a/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx +++ b/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx @@ -13,6 +13,10 @@ import { currentProjectIdState } from '../../recoilModel'; import { rootBotProjectIdSelector } from '../../recoilModel/selectors/project'; const QNA_REGIONS = [{ key: 'westus', text: 'westus' }]; +const QNA_TIERS = [ + { key: 'free', text: 'Free' }, + { key: 'paid', text: 'Paid' }, +]; type ManageQNAProps = { hidden: boolean; @@ -32,13 +36,22 @@ export const ManageQNA = (props: ManageQNAProps) => { subscriptionId: string, resourceGroupName: string, resourceName: string, - region: string + region: string, + tier?: string ): Promise => { // hide modal props.onToggleVisibility(false); // start background QNA - createQNA(rootBotProjectId, tokenCredentials, subscriptionId, resourceGroupName, resourceName, region); + createQNA( + rootBotProjectId, + tokenCredentials, + subscriptionId, + resourceGroupName, + resourceName, + region, + tier || 'free' + ); return ''; }; @@ -54,6 +67,7 @@ export const ManageQNA = (props: ManageQNAProps) => { regions={QNA_REGIONS} serviceKeyType={'QnAMaker'} serviceName={'QnA Maker'} + tiers={QNA_TIERS} onDismiss={props.onDismiss} onGetKey={props.onGetKey} onNext={props.onNext} diff --git a/Composer/packages/client/src/components/ManageService/ManageService.tsx b/Composer/packages/client/src/components/ManageService/ManageService.tsx index 4c73afad24..bac5e6f1d5 100644 --- a/Composer/packages/client/src/components/ManageService/ManageService.tsx +++ b/Composer/packages/client/src/components/ManageService/ManageService.tsx @@ -36,7 +36,8 @@ type ManageServiceProps = { subscriptionId: string, resourceGroupName: string, resourceName: string, - region: string + region: string, + tier?: string ) => Promise; createServiceInBackground?: boolean; handoffInstructions: string; @@ -48,6 +49,7 @@ type ManageServiceProps = { serviceName: string; regions?: IDropdownOption[]; serviceKeyType: string; + tiers?: IDropdownOption[]; onToggleVisibility: (visible: boolean) => void; }; @@ -58,11 +60,12 @@ type KeyRec = { key: string; }; -const dropdownStyles = { dropdown: { width: '100%', marginBottom: 20 } }; +const dropdownStyles = { dropdown: { width: '100%', marginBottom: 10 } }; +const inputStyles = { root: { width: '100%', marginBottom: 10 } }; const summaryLabelStyles = { display: 'block', color: '#605E5C', fontSize: 14 }; const summaryStyles = { background: '#F3F2F1', padding: '1px 1rem' }; const mainElementStyle = { marginBottom: 20 }; -const dialogBodyStyles = { height: 360 }; +const dialogBodyStyles = { height: 480 }; const CREATE_NEW_KEY = 'CREATE_NEW'; export const ManageService = (props: ManageServiceProps) => { @@ -76,6 +79,7 @@ export const ManageService = (props: ManageServiceProps) => { const [newResourceGroupName, setNewResourceGroupName] = useState(''); const [resourceGroupKey, setResourceGroupKey] = useState(''); const [resourceGroup, setResourceGroup] = useState(''); + const [tier, setTier] = useState(''); const [showHandoff, setShowHandoff] = useState(false); const [resourceName, setResourceName] = useState(''); @@ -169,6 +173,14 @@ export const ManageService = (props: ManageServiceProps) => { } }; + const handleTierOnChange = (e, value: IDropdownOption | undefined) => { + if (value != null) { + setTier(value.key as string); + } else { + setTier(''); + } + }; + const fetchKeys = async (cognitiveServicesManagementClient, accounts) => { const keyList: KeyRec[] = []; for (const account in accounts) { @@ -265,14 +277,15 @@ export const ManageService = (props: ManageServiceProps) => { try { if (props.createServiceInBackground) { - props.createService(tokenCredentials, subscriptionId, resourceGroupName, resourceName, region); + props.createService(tokenCredentials, subscriptionId, resourceGroupName, resourceName, region, tier); } else { const newKey = await props.createService( tokenCredentials, subscriptionId, resourceGroupName, resourceName, - region + region, + tier ); TelemetryClient.track('SettingsGetKeysCreateNewResourceCompleted', { @@ -542,7 +555,7 @@ export const ManageService = (props: ManageServiceProps) => { return (
-

+

{formatMessage( 'Input your details below to create a new {service} resource. You will be able to manage your new resource in the Azure portal.', { service: props.serviceName } @@ -588,7 +601,7 @@ export const ManageService = (props: ManageServiceProps) => { id={'resourceGroupName'} label={formatMessage('Resource group name')} placeholder={formatMessage('Enter name for new resource group')} - styles={{ root: { marginTop: 10 } }} + styles={inputStyles} value={newResourceGroupName} onChange={(e, val) => { setNewResourceGroupName(val || ''); @@ -616,10 +629,25 @@ export const ManageService = (props: ManageServiceProps) => { id={'resourceName'} label={formatMessage('Resource name')} placeholder={formatMessage('Enter name for new resources')} - styles={{ root: { marginTop: 10 } }} + styles={inputStyles} value={resourceName} onChange={(e, val) => setResourceName(val || '')} /> + {props.tiers && ( + + )}

@@ -635,6 +663,7 @@ export const ManageService = (props: ManageServiceProps) => { !resourceName || !region || !resourceGroupKey || + (props.tiers && !tier) || (resourceGroupKey == CREATE_NEW_KEY && !newResourceGroupName) } text={formatMessage('Next')} diff --git a/Composer/packages/client/src/recoilModel/dispatchers/provisionQNA.ts b/Composer/packages/client/src/recoilModel/dispatchers/provisionQNA.ts index 09387a67dd..2863ed62fe 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/provisionQNA.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/provisionQNA.ts @@ -6,6 +6,7 @@ import { CallbackInterface, useRecoilCallback } from 'recoil'; import { CognitiveServicesManagementClient } from '@azure/arm-cognitiveservices'; import { TokenCredentials } from '@azure/ms-rest-js'; import { SearchManagementClient } from '@azure/arm-search'; +import { SkuName } from '@azure/arm-search/lib/models'; import { WebSiteManagementClient } from '@azure/arm-appservice'; import { ApplicationInsightsManagementClient } from '@azure/arm-appinsights'; @@ -20,6 +21,57 @@ import { dispatcherState, settingsState } from '../atoms'; import { setError } from './shared'; import httpClient from './../../utils/httpUtil'; import { createNotification, addNotificationInternal, deleteNotificationInternal } from './notification'; + +type SkuList = { + search: { + name: SkuName; + }; + qna: { + name: string; + }; + appservice: { + name: string; + tier: string; + size: string; + family: string; + capacity: number; + }; + appinsights?: boolean; +}; + +const FREE_SKUS: SkuList = { + search: { + name: 'free', + }, + appservice: { + name: 'F1', + tier: 'Free', + size: 'F1', + family: 'F', + capacity: 1, + }, + qna: { + name: 'F0', + }, +}; + +const PAID_SKUS: SkuList = { + search: { + name: 'standard', + }, + appservice: { + name: 'S1', + tier: 'Standard', + size: 'S1', + family: 'S', + capacity: 1, + }, + qna: { + name: 'S0', + }, + appinsights: true, +}; + // poll for the endpoint key til it is available const fetchEndpointKey = async (projectId: string, subscriptionKey: string): Promise => { return new Promise((resolve, reject) => { @@ -50,11 +102,12 @@ export const provisionQNADispatcher = () => { subscriptionId: string, resourceGroupName: string, resourceName: string, - region: string + region: string, + tier: string ) => { const { snapshot } = callbackHelpers; - const startTime = new Date().getTime(); + const skus: SkuList = tier === 'free' ? FREE_SKUS : PAID_SKUS; const notification = createNotification(getPendingQNANotificationCardProps()); // add that notification @@ -68,9 +121,7 @@ export const provisionQNADispatcher = () => { const searchManagementClient = new SearchManagementClient(tokenCredentials as any, subscriptionId); await searchManagementClient.services.createOrUpdate(resourceGroupName, qnaMakerSearchName, { location: region, - sku: { - name: 'standard', - }, + sku: skus.search, replicaCount: 1, partitionCount: 1, hostingMode: 'default', @@ -79,40 +130,41 @@ export const provisionQNADispatcher = () => { const webSiteManagementClient = new WebSiteManagementClient(tokenCredentials, subscriptionId); await webSiteManagementClient.appServicePlans.createOrUpdate(resourceGroupName, resourceGroupName, { location: region, - sku: { - name: 'S1', - tier: 'Standard', - size: 'S1', - family: 'S', - capacity: 1, - }, - }); - // deploy or update exisiting app insights component - const applicationInsightsManagementClient = new ApplicationInsightsManagementClient( - tokenCredentials, - subscriptionId - ); - await applicationInsightsManagementClient.components.createOrUpdate(resourceGroupName, resourceGroupName, { - location: region, - applicationType: 'web', - kind: 'web', + sku: skus.appservice, }); // add web config for websites - const azureSearchAdminKey = (await searchManagementClient.adminKeys.get(resourceGroupName, qnaMakerSearchName)) - .primaryKey; - const appInsightsComponent = await applicationInsightsManagementClient.components.get( - resourceGroupName, - resourceGroupName - ); - const userAppInsightsKey = appInsightsComponent.instrumentationKey; - const userAppInsightsName = resourceGroupName; - const userAppInsightsAppId = appInsightsComponent.appId; const primaryEndpointKey = `${qnaMakerWebAppName}-PrimaryEndpointKey`; const secondaryEndpointKey = `${qnaMakerWebAppName}-SecondaryEndpointKey`; const defaultAnswer = 'No good match found in KB.'; const QNAMAKER_EXTENSION_VERSION = 'latest'; + const azureSearchAdminKey = (await searchManagementClient.adminKeys.get(resourceGroupName, qnaMakerSearchName)) + .primaryKey; + + // if app insights is included, set this up too + let userAppInsightsAppId, userAppInsightsKey, userAppInsightsName; + if (skus.appinsights) { + // deploy or update exisiting app insights component + const applicationInsightsManagementClient = new ApplicationInsightsManagementClient( + tokenCredentials, + subscriptionId + ); + await applicationInsightsManagementClient.components.createOrUpdate(resourceGroupName, resourceGroupName, { + location: region, + applicationType: 'web', + kind: 'web', + }); + + const appInsightsComponent = await applicationInsightsManagementClient.components.get( + resourceGroupName, + resourceGroupName + ); + userAppInsightsKey = appInsightsComponent.instrumentationKey; + userAppInsightsName = resourceGroupName; + userAppInsightsAppId = appInsightsComponent.appId; + } + // deploy qna host webapp const webAppResult = await webSiteManagementClient.webApps.createOrUpdate( resourceGroupName, @@ -179,9 +231,7 @@ export const provisionQNADispatcher = () => { ); await cognitiveServicesManagementClient.accounts.create(resourceGroupName, qnaMakerServiceName, { kind: 'QnAMaker', - sku: { - name: 'F0', - }, + sku: skus.qna, location: region, properties: { apiProperties: {