diff --git a/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx b/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx index 1a6493e304..9f231a49bb 100644 --- a/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx +++ b/Composer/packages/client/src/components/ManageQNA/ManageQNA.tsx @@ -12,7 +12,7 @@ import { ManageService } from '../ManageService/ManageService'; import { currentProjectIdState } from '../../recoilModel'; import { rootBotProjectIdSelector } from '../../recoilModel/selectors/project'; -const QNA_REGIONS = [{ key: 'westus', text: 'westus' }]; +const QNA_REGIONS = [{ key: 'westus', text: 'West US' }]; const QNA_TIERS = [ { key: 'free', text: 'Free' }, { key: 'paid', text: 'Paid' }, diff --git a/Composer/packages/client/src/components/ManageService/ManageService.tsx b/Composer/packages/client/src/components/ManageService/ManageService.tsx index bac5e6f1d5..0fb074c414 100644 --- a/Composer/packages/client/src/components/ManageService/ManageService.tsx +++ b/Composer/packages/client/src/components/ManageService/ManageService.tsx @@ -22,11 +22,11 @@ import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/Choi import { ProvisionHandoff } from '@bfc/ui-shared'; import sortBy from 'lodash/sortBy'; import { NeutralColors } from '@uifabric/fluent-theme'; +import { AzureTenant } from '@botframework-composer/types'; import TelemetryClient from '../../telemetry/TelemetryClient'; import { AuthClient } from '../../utils/authClient'; import { AuthDialog } from '../../components/Auth/AuthDialog'; -import { armScopes } from '../../constants'; import { getTokenFromCache, isShowAuthDialog, userShouldProvideTokens } from '../../utils/auth'; import { dispatcherState } from '../../recoilModel/atoms'; @@ -60,12 +60,14 @@ type KeyRec = { key: string; }; +type Step = 'intro' | 'subscription' | 'resourceCreation' | 'outcome'; + 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: 480 }; +const dialogBodyStyles = { height: 400 }; const CREATE_NEW_KEY = 'CREATE_NEW'; export const ManageService = (props: ManageServiceProps) => { @@ -74,13 +76,15 @@ export const ManageService = (props: ManageServiceProps) => { const { setApplicationLevelError } = useRecoilValue(dispatcherState); const [subscriptionId, setSubscription] = useState(''); + const [tenantId, setTenantId] = useState(''); const [resourceGroups, setResourceGroups] = useState([]); const [createResourceGroup, setCreateResourceGroup] = useState(false); const [newResourceGroupName, setNewResourceGroupName] = useState(''); const [resourceGroupKey, setResourceGroupKey] = useState(''); const [resourceGroup, setResourceGroup] = useState(''); const [tier, setTier] = useState(''); - + const [allTenants, setAllTenants] = useState([]); + const [tenantsErrorMessage, setTenantsErrorMessage] = useState(undefined); const [showHandoff, setShowHandoff] = useState(false); const [resourceName, setResourceName] = useState(''); const [loading, setLoading] = useState(undefined); @@ -89,9 +93,10 @@ export const ManageService = (props: ManageServiceProps) => { const [key, setKey] = useState(''); const [region, setRegion] = useState(''); const [availableSubscriptions, setAvailableSubscriptions] = useState([]); + const [subscriptionsErrorMessage, setSubscriptionsErrorMessage] = useState(); const [keys, setKeys] = useState([]); - const [currentPage, setCurrentPage] = useState(1); + const [currentStep, setCurrentStep] = useState('intro'); const [outcomeDescription, setOutcomeDescription] = useState(''); const [outcomeSummary, setOutcomeSummary] = useState(); const [outcomeError, setOutcomeError] = useState(false); @@ -133,6 +138,71 @@ export const ManageService = (props: ManageServiceProps) => { } }; + useEffect(() => { + if (!userShouldProvideTokens()) { + AuthClient.getTenants() + .then((tenants) => { + setAllTenants(tenants); + if (tenants.length === 0) { + setTenantsErrorMessage(formatMessage('No Azure Directories were found.')); + } else if (tenants.length === 1) { + setTenantId(tenants[0].tenantId); + } else { + setTenantsErrorMessage(undefined); + } + }) + .catch((err) => { + setTenantsErrorMessage( + formatMessage('There was a problem loading Azure directories. {errMessage}', { + errMessage: err.message || err.toString(), + }) + ); + }); + } + }, []); + + useEffect(() => { + if (tenantId) { + AuthClient.getARMTokenForTenant(tenantId) + .then((token) => { + setToken(token); + setTenantsErrorMessage(undefined); + }) + .catch((err) => { + setTenantsErrorMessage( + formatMessage( + 'There was a problem getting the access token for the current Azure directory. {errMessage}', + { + errMessage: err.message || err.toString(), + } + ) + ); + setTenantsErrorMessage(err.message || err.toString()); + }); + } + }, [tenantId]); + + useEffect(() => { + if (token) { + setAvailableSubscriptions([]); + setSubscriptionsErrorMessage(undefined); + getSubscriptions(token) + .then((data) => { + setAvailableSubscriptions(data); + if (data.length === 0) { + setSubscriptionsErrorMessage( + formatMessage( + 'Your subscription list is empty, please add your subscription, or login with another account.' + ) + ); + } + }) + .catch((err) => { + setSubscriptionsErrorMessage(err.message); + }); + } + }, [token]); + const hasAuth = async () => { let newtoken = ''; if (userShouldProvideTokens()) { @@ -140,29 +210,18 @@ export const ManageService = (props: ManageServiceProps) => { setShowAuthDialog(true); } newtoken = getTokenFromCache('accessToken'); - } else { - newtoken = await AuthClient.getAccessToken(armScopes); } - - setToken(newtoken); - if (newtoken) { - // reset the list - setAvailableSubscriptions([]); - - // fetch list of available subscriptions - setAvailableSubscriptions(await getSubscriptions(newtoken)); - - // go on to the next page post-auth - setCurrentPage(2); + setToken(newtoken); } + setCurrentStep('subscription'); }; useEffect(() => { // reset the ui setSubscription(''); setKeys([]); - setCurrentPage(1); + setCurrentStep('intro'); }, [props.hidden]); const handleRegionOnChange = (e, value: IDropdownOption | undefined) => { @@ -262,7 +321,7 @@ export const ManageService = (props: ManageServiceProps) => { ); setOutcomeSummary(

{err.message}

); setOutcomeError(true); - setCurrentPage(3); + setCurrentStep('outcome'); setLoading(undefined); return; } @@ -312,7 +371,7 @@ export const ManageService = (props: ManageServiceProps) => { ); setOutcomeSummary(

{err.message}

); setOutcomeError(true); - setCurrentPage(3); + setCurrentStep('outcome'); setLoading(undefined); return; } @@ -346,7 +405,7 @@ export const ManageService = (props: ManageServiceProps) => { ); setOutcomeError(false); - setCurrentPage(3); + setCurrentStep('outcome'); } }; @@ -413,7 +472,7 @@ export const ManageService = (props: ManageServiceProps) => { ); setOutcomeError(false); - setCurrentPage(3); + setCurrentStep('outcome'); }; const performNextAction = () => { @@ -441,14 +500,13 @@ export const ManageService = (props: ManageServiceProps) => { ); }; - const renderPageOne = () => { + const renderIntroStep = () => { return (

{formatMessage( - 'Choose from existing {service} keys, create a new {service} resource, or generate a request to handoff to your Azure admin. ', - { service: props.serviceName } + 'Select your Azure directory, then choose the subscription where your existing resource is located and the keys you want to use. ' )} {props.learnMore ? ( @@ -461,9 +519,9 @@ export const ManageService = (props: ManageServiceProps) => {

- + @@ -472,13 +530,13 @@ export const ManageService = (props: ManageServiceProps) => { ); }; - const renderPageChoose = () => { + const renderChooseResourceStep = () => { return (

{formatMessage( - 'Choose from existing {service} keys, create a new {service} resource, or generate a request to handoff to your Azure admin. ', + 'Select your Azure directory, then choose the subscription where you’d like your new {service} resource. ', { service: props.serviceName } )} {props.learnMore ? ( @@ -489,13 +547,27 @@ export const ManageService = (props: ManageServiceProps) => {

({ key: t.tenantId, text: t.displayName }))} + selectedKey={tenantId} + styles={dropdownStyles} + onChange={(_e, o) => { + setTenantId(o?.key as string); + }} + /> + 0)} - label={formatMessage('Select subscription')} + errorMessage={subscriptionsErrorMessage} + label={formatMessage('Subscription')} options={ availableSubscriptions ?.filter((p) => p.subscriptionId && p.displayName) .map((p) => { - return { key: p.subscriptionId ?? '', text: p.displayName ?? 'Unnamed' }; + return { key: p.subscriptionId ?? '', text: p.displayName ?? formatMessage('Unnamed') }; }) ?? [] } placeholder={formatMessage('Select one')} @@ -535,23 +607,19 @@ export const ManageService = (props: ManageServiceProps) => {
{loading && } - setCurrentPage(1)} - /> + setCurrentStep('intro')} /> - +
); }; - const renderPageCreate = () => { + const renderResourseCreatorStep = () => { return (
@@ -564,22 +632,7 @@ export const ManageService = (props: ManageServiceProps) => {
0)} - label={formatMessage('Select subscription')} - options={ - availableSubscriptions - ?.filter((p) => p.subscriptionId && p.displayName) - .map((p) => { - return { key: p.subscriptionId ?? '', text: p.displayName ?? 'Unnamed' }; - }) ?? [] - } - placeholder={formatMessage('Select one')} - selectedKey={subscriptionId} - styles={dropdownStyles} - onChange={onChangeSubscription} - /> - { @@ -597,7 +650,7 @@ export const ManageService = (props: ManageServiceProps) => { required aria-label={formatMessage('Resource group name')} data-testid={'resourceGroupName'} - disabled={!subscriptionId || loading !== undefined} + disabled={!subscriptionId || !!loading} id={'resourceGroupName'} label={formatMessage('Resource group name')} placeholder={formatMessage('Enter name for new resource group')} @@ -612,7 +665,7 @@ export const ManageService = (props: ManageServiceProps) => { required aria-label={formatMessage('Region')} data-testid={'rootRegion'} - disabled={!subscriptionId || loading !== undefined} + disabled={!subscriptionId || !!loading} id={'region'} label={formatMessage('Region')} options={locationList} @@ -625,7 +678,7 @@ export const ManageService = (props: ManageServiceProps) => { required aria-label={formatMessage('Resource name')} data-testid={'resourceName'} - disabled={!subscriptionId || loading !== undefined} + disabled={!subscriptionId || !!loading} id={'resourceName'} label={formatMessage('Resource name')} placeholder={formatMessage('Enter name for new resources')} @@ -638,7 +691,7 @@ export const ManageService = (props: ManageServiceProps) => { required aria-label={formatMessage('Pricing tier')} data-testid={'tier'} - disabled={!subscriptionId || loading !== undefined} + disabled={!subscriptionId || !!loading} id={'tier'} label={formatMessage('Pricing tier')} options={props.tiers} @@ -653,13 +706,13 @@ export const ManageService = (props: ManageServiceProps) => { {loading && } setCurrentPage(1)} + onClick={() => setCurrentStep('subscription')} /> { text={formatMessage('Next')} onClick={createService} /> - +
); }; - const renderPageThree = () => { + const renderOutcomeStep = () => { return (
@@ -688,13 +741,93 @@ export const ManageService = (props: ManageServiceProps) => { )}
- {outcomeError && setCurrentPage(1)} />} + {outcomeError && setCurrentStep('intro')} />}
); }; + const renderSubscriptionSelectionStep = () => { + return ( +
+
+

+ {formatMessage( + 'Select your Azure directory, then choose the subscription where you’d like your new {service} resource.', + { service: props.serviceName } + )} + {props.learnMore ? ( + + {formatMessage('Learn more')} + + ) : null} +

+
+ ({ key: t.tenantId, text: t.displayName }))} + selectedKey={tenantId} + styles={dropdownStyles} + onChange={(_e, o) => { + setTenantId(o?.key as string); + }} + /> + p.subscriptionId && p.displayName) + .map((p) => { + return { key: p.subscriptionId ?? '', text: p.displayName ?? formatMessage('Unnamed') }; + }) ?? [] + } + placeholder={formatMessage('Select one')} + selectedKey={subscriptionId} + styles={dropdownStyles} + onChange={onChangeSubscription} + /> +
+
+ + {loading && } + setCurrentStep('intro')} /> + setCurrentStep('resourceCreation')} + /> + + +
+ ); + }; + + const renderCurrentStep = () => { + switch (currentStep) { + case 'intro': + return renderIntroStep(); + case 'subscription': { + if (nextAction === 'choose') { + return renderChooseResourceStep(); + } + return renderSubscriptionSelectionStep(); + } + case 'resourceCreation': + return renderResourseCreatorStep(); + case 'outcome': + return renderOutcomeStep(); + default: + return null; + } + }; + return ( {showAuthDialog && ( @@ -726,9 +859,9 @@ export const ManageService = (props: ManageServiceProps) => { dialogContentProps={{ type: DialogType.normal, title: - currentPage === 2 + nextAction === 'create' ? formatMessage('Create new {service} resource', { service: props.serviceName }) - : formatMessage('Select {service} keys', { service: props.serviceName }), + : formatMessage('Select {service} key', { service: props.serviceName }), }} hidden={props.hidden || showAuthDialog} minWidth={480} @@ -737,10 +870,7 @@ export const ManageService = (props: ManageServiceProps) => { }} onDismiss={loading ? () => {} : props.onDismiss} > - {currentPage === 1 && renderPageOne()} - {currentPage === 2 && nextAction === 'choose' && renderPageChoose()} - {currentPage === 2 && nextAction === 'create' && renderPageCreate()} - {currentPage === 3 && renderPageThree()} + {renderCurrentStep()} ); diff --git a/Composer/packages/client/src/constants.ts b/Composer/packages/client/src/constants.ts index cb650526bf..58a856820e 100644 --- a/Composer/packages/client/src/constants.ts +++ b/Composer/packages/client/src/constants.ts @@ -52,15 +52,15 @@ export const Tips = { export const LUIS_REGIONS: IDropdownOption[] = [ { key: 'westus', - text: formatMessage('westus'), + text: formatMessage('West US'), }, { key: 'westeurope', - text: formatMessage('westeurope'), + text: formatMessage('West Europe'), }, { key: 'australiaeast', - text: formatMessage('australiaeast'), + text: formatMessage('Australia East'), }, ]; diff --git a/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx b/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx index 50f06d7447..ee1f9d6bda 100644 --- a/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx +++ b/Composer/packages/client/src/pages/botProject/RootBotExternalService.tsx @@ -429,7 +429,7 @@ export const RootBotExternalService: React.FC = (pr ), a2: ({ children }) => ( diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index 2ca9f8e972..14fa1c1023 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -407,8 +407,8 @@ "audio_card_8587cf83": { "message": "Audio card" }, - "australiaeast_f3227a31": { - "message": "australiaeast" + "australia_east_b7af6cc": { + "message": "Australia East" }, "authentication_error_39e996c5": { "message": "Authentication Error" @@ -431,6 +431,9 @@ "azure_connections_9e63f716": { "message": "Azure connections" }, + "azure_directory_d9065529": { + "message": "Azure directory" + }, "azure_functions_5e23be5c": { "message": "Azure Functions" }, @@ -632,9 +635,6 @@ "choose_from_existing_e85a88c4": { "message": "Choose from existing" }, - "choose_from_existing_service_keys_create_a_new_ser_53728093": { - "message": "Choose from existing { service } keys, create a new { service } resource, or generate a request to handoff to your Azure admin. " - }, "choose_how_to_create_your_bot_a97f7b3e": { "message": "Choose how to create your bot" }, @@ -2501,6 +2501,9 @@ "next_steps_fce2208": { "message": "Next steps" }, + "no_azure_directories_were_found_6dfe6f6f": { + "message": "No Azure Directories were found." + }, "no_editor_for_type_8b5593c5": { "message": "No Editor for { type }" }, @@ -3317,11 +3320,8 @@ "select_runtime_version_to_add_d63d383b": { "message": "Select runtime version to add" }, - "select_service_keys_6d3f0980": { - "message": "Select { service } keys" - }, - "select_subscription_c5678611": { - "message": "Select subscription" + "select_service_key_6ca27e67": { + "message": "Select { service } key" }, "select_the_language_that_bot_will_be_able_to_under_1f2bcb96": { "message": "Select the language that bot will be able to understand (User input) and respond to (Bot responses).\n To make this bot available in other languages, click “Add’ to create a copy of the default language, and translate the content into the new language." @@ -3335,6 +3335,15 @@ "select_which_tasks_this_skill_can_perform_172b0eae": { "message": "Select which tasks this skill can perform" }, + "select_your_azure_directory_then_choose_the_subscr_7034a3c0": { + "message": "Select your Azure directory, then choose the subscription where you’d like your new { service } resource." + }, + "select_your_azure_directory_then_choose_the_subscr_8c3aa61": { + "message": "Select your Azure directory, then choose the subscription where your existing resource is located and the keys you want to use." + }, + "select_your_azure_directory_then_choose_the_subscr_e3d67edf": { + "message": "Select your Azure directory, then choose the subscription where you’d like your new { service } resource. " + }, "selection_field_86d1dc94": { "message": "selection field" }, @@ -3728,6 +3737,12 @@ "there_is_no_thumbnail_view_908fe5cc": { "message": "There is no thumbnail view" }, + "there_was_a_problem_getting_the_access_token_for_t_69f5a5e2": { + "message": "There was a problem getting the access token for the current Azure directory. { errMessage }" + }, + "there_was_a_problem_loading_azure_directories_errm_56e6145d": { + "message": "There was a problem loading Azure directories. { errMessage }" + }, "there_was_an_error_74ed3c58": { "message": "There was an error" }, @@ -3959,6 +3974,9 @@ "unknown_state_23f73afb": { "message": "Unknown State" }, + "unnamed_4c8565a0": { + "message": "Unnamed" + }, "unread_notifications_indicator_e2ca00d5": { "message": "Unread notifications Indicator" }, @@ -4109,11 +4127,11 @@ "welcome_to_composer_7147714a": { "message": "Welcome to Composer!" }, - "westeurope_cabf9688": { - "message": "westeurope" + "west_europe_75ac94f4": { + "message": "West Europe" }, - "westus_dc50d800": { - "message": "westus" + "west_us_51d3fdbb": { + "message": "West US" }, "what_can_the_user_accomplish_through_this_conversa_7ddb03a1": { "message": "What can the user accomplish through this conversation? For example, BookATable, OrderACoffee etc." @@ -4259,6 +4277,9 @@ "your_skill_is_ready_to_be_shared_6376eb3c": { "message": "Your skill is ready to be shared!" }, + "your_subscription_list_is_empty_please_add_your_su_6b229c26": { + "message": "Your subscription list is empty, please add your subscription, or login with another account." + }, "your_teams_adapter_is_configured_for_your_publishe_e84e9275": { "message": "Your Teams adapter is configured for your published bot. Copy the manifest, open App Studio in Teams and add the manifest so you can test your bot in Teams" },