diff --git a/Composer/packages/client/src/recoilModel/dispatchers/provision.ts b/Composer/packages/client/src/recoilModel/dispatchers/provision.ts index 89cad0c849..35b341969d 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/provision.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/provision.ts @@ -27,6 +27,13 @@ export const provisionDispatcher = () => { type: 'success', }; }; + const getProvisionPartialSuccessNotification = (value: string): CardProps => { + return { + title: formatMessage('Provision partially completed'), + description: formatMessage('{msg}', { msg: value }), + type: 'warning', + }; + }; const getProvisionFailureNotification = (value: string): CardProps => { return { title: formatMessage('Provision failure'), @@ -115,7 +122,11 @@ export const provisionDispatcher = () => { isCleanTimer = false; try { const response = await httpClient.get(`/provision/${projectId}/status/${targetType}/${targetName}/${jobId}`); - if (response.data?.status === 200 && response.data.config && response.data.config != {}) { + if ( + (response.data?.status === 200 || response.data?.status === 206) && + response.data.config && + response.data.config != {} + ) { // delete provisionStatus callbackHelpers.set(provisionStatusState(projectId), (status) => { const newStatus = { ...status }; @@ -143,7 +154,11 @@ export const provisionDispatcher = () => { subscriptionId: response.data.config.subscriptionId, }); - notification = getProvisionSuccessNotification(response.data.message); + if (response.data?.status === 200) { + notification = getProvisionSuccessNotification(response.data.message); + } else { + notification = getProvisionPartialSuccessNotification(response.data.message); + } isCleanTimer = true; } else { if (response.data.status !== 500) { diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index e314109a3b..6ba5383c40 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -2,9 +2,6 @@ "0_bytes_a1e1cdb3": { "message": "0 Bytes" }, - "5_minute_intro_7ea06d2b": { - "message": "5 Minute Intro" - }, "ErrorInfo_part1": { "message": "An error occurred in the form editor!" }, @@ -317,9 +314,6 @@ "application_updates_bdf5f8b6": { "message": "Application Updates" }, - "apr_9_2020_3c8b47d7": { - "message": "Apr 9, 2020" - }, "are_you_sure_you_want_to_delete_your_bot_this_acti_214a9e11": { "message": "Are you sure you want to delete your bot? This action cannot be undone and your bot and all related files in the bot project folder will be permanently deleted. Your Azure resources will remain unchanged." }, @@ -440,9 +434,6 @@ "begindialog_a5594562": { "message": "BeginDialog" }, - "ben_brown_99c12d19": { - "message": "Ben Brown" - }, "boolean_6000988a": { "message": "Boolean" }, @@ -503,11 +494,8 @@ "bot_framework_composer_is_a_visual_authoring_canva_c3947d91": { "message": "Bot Framework Composer is a visual authoring canvas for building bots and other types of conversational application with the Microsoft Bot Framework technology stack. With Composer you will find everything you need to build a modern, state-of-the-art conversational experience." }, - "bot_framework_composer_is_an_open_source_visual_au_2be2e02b": { - "message": "Bot Framework Composer is an open-source visual authoring canvas for developers and multi-disciplinary teams to build bots. Composer integrates LUIS and QnA Maker, and allows sophisticated composition of bot replies using language generation." - }, - "bot_framework_provides_the_most_comprehensive_expe_e34a7f5d": { - "message": "Bot Framework provides the most comprehensive experience for building conversational applications." + "bot_framework_emulator_fefd4a59": { + "message": "Bot Framework Emulator" }, "bot_is_botname_c5af0c89": { "message": "Bot is { botName }" @@ -569,9 +557,6 @@ "break_out_of_loop_ab30157c": { "message": "Break out of loop" }, - "build_your_first_bot_f9c3e427": { - "message": "Build your first bot" - }, "building_5e8a3c1d": { "message": "Building" }, @@ -602,9 +587,6 @@ "cannot_parse_attachment_c3e552a5": { "message": "Cannot parse attachment." }, - "cannot_post_activity_conversation_not_found_c1e26d2d": { - "message": "Cannot post activity. Conversation not found." - }, "cannot_upload_file_conversation_not_found_8a983504": { "message": "Cannot upload file. Conversation not found." }, @@ -617,6 +599,9 @@ "check_for_updates_and_install_them_automatically_50337340": { "message": "Check for updates and install them automatically." }, + "check_out_the_resources_on_github_4d73792a": { + "message": "Check out the resources on GitHub." + }, "choice_input_f75a2353": { "message": "Choice Input" }, @@ -638,9 +623,6 @@ "choose_subscription_3d78d10d": { "message": "Choose subscription" }, - "chris_whitten_11df1f35": { - "message": "Chris Whitten" - }, "clear_all_da755751": { "message": "Clear all" }, @@ -686,12 +668,12 @@ "composer_cannot_yet_translate_your_bot_automatical_2d54081b": { "message": "Composer cannot yet translate your bot automatically.\nTo create a translation manually, Composer will create a copy of your bot’s content with the name of the additional language. This content can then be translated without affecting the original bot logic or flow and you can switch between languages to ensure the responses are correctly and appropriately translated." }, + "composer_documentation_60a27d37": { + "message": "Composer documentation" + }, "composer_includes_a_telemetry_feature_that_collect_8fd7bfbf": { "message": "Composer includes a telemetry feature that collects usage information. It is important that the Composer team understands how the tool is being used so that it can be improved." }, - "composer_introduction_98a93701": { - "message": "Composer introduction" - }, "composer_is_up_to_date_9118257d": { "message": "Composer is up to date." }, @@ -704,6 +686,9 @@ "composer_needs_net_core_sdk_46e2a8ae": { "message": "Composer needs .NET Core SDK" }, + "composer_on_github_1e3782ef": { + "message": "Composer on GitHub" + }, "composer_settings_31b04099": { "message": "Composer Settings" }, @@ -932,9 +917,6 @@ "create_new_e0946c49": { "message": "Create new" }, - "create_new_empty_bot_21cf0ea3": { - "message": "Create new empty bot" - }, "create_new_folder_19d3faa4": { "message": "Create new folder" }, @@ -1226,6 +1208,9 @@ "done_54e3d4b6": { "message": "Done" }, + "download_emulator_c8fb3403": { + "message": "Download Emulator" + }, "download_icon_2e0d10": { "message": "Download Icon" }, @@ -1235,9 +1220,6 @@ "downloading_bb6fb34b": { "message": "Downloading..." }, - "downloading_language_model_9d40c817": { - "message": "Downloading Language Model" - }, "due_to_the_following_error_we_were_unable_to_succe_5ca033c7": { "message": "Due to the following error, we were unable to successfully add your selected QnA keys to your bot project:" }, @@ -1379,6 +1361,9 @@ "endpoints_ff946539": { "message": "Endpoints" }, + "engage_with_other_bot_builders_ask_and_answer_ques_550d86bc": { + "message": "Engage with other bot builders. Ask and answer questions." + }, "ensure_your_bot_can_understand_your_users_by_frequ_c3f5c722": { "message": "Ensure your bot can understand your users by frequently training your LUIS model." }, @@ -1610,6 +1595,9 @@ "find_pre_built_adaptive_expressions_b106308e": { "message": "Find pre-built Adaptive expressions" }, + "find_tutorials_step_by_step_guides_discover_what_y_8f3e3863": { + "message": "Find tutorials, step-by-step guides. Discover what you can build with Composer." + }, "firstselector_a3daca5d": { "message": "FirstSelector" }, @@ -1721,15 +1709,18 @@ "getting_help_ab6811b0": { "message": "Getting Help" }, - "getting_started_f45a7e87": { - "message": "Getting Started" - }, "getting_template_910a4116": { "message": "Getting template" }, + "go_to_composer_repository_cba5d3d2": { + "message": "Go to Composer repository" + }, "go_to_qna_all_up_view_page_d475333d": { "message": "Go to QnA all-up view page." }, + "go_to_stack_overflow_e525148": { + "message": "Go to Stack Overflow" + }, "got_it_2c06b54a": { "message": "Got it!" }, @@ -1958,9 +1949,6 @@ "itemcount_plural_0_no_schemas_1_one_schema_other_s_e1aea7f": { "message": "{ itemCount, plural,\n =0 {No schemas}\n =1 {One schema}\n other {# schemas}\n} have been found.\n { itemCount, select,\n 0 {}\n other {Press down arrow key to navigate the search results}\n}" }, - "jan_28_2020_8beb36dc": { - "message": "Jan 28, 2020" - }, "just_add_a_qna_key_and_you_ll_be_ready_to_talk_to__d18758bb": { "message": "Just add a QnA key and you’ll be ready to talk to your bot." }, @@ -2348,9 +2336,6 @@ "ms_teams_15993b97": { "message": "MS Teams" }, - "msft_ignite_ai_show_e131edef": { - "message": "MSFT Ignite AI Show" - }, "msg_bf173fef": { "message": "{ msg }" }, @@ -2471,6 +2456,9 @@ "no_qna_file_with_name_id_7cb89755": { "message": "NO QNA FILE WITH NAME { id }" }, + "no_recent_bots_f4cf7d0a": { + "message": "No recent bots" + }, "no_search_results_1ba50423": { "message": "No search results" }, @@ -2483,6 +2471,9 @@ "no_uploads_were_attached_as_a_part_of_the_request_63e92f54": { "message": "No uploads were attached as a part of the request." }, + "no_web_chat_activity_yet_4227dc1a": { + "message": "No Web Chat activity yet." + }, "no_wildcard_ff439e76": { "message": "no wildcard" }, @@ -2501,9 +2492,6 @@ "notifications_cbfa7704": { "message": "Notifications" }, - "nov_12_2019_96ec5473": { - "message": "Nov 12, 2019" - }, "number_a6dc44e": { "message": "Number" }, @@ -2618,6 +2606,9 @@ "or_4f7d4edb": { "message": "Or: " }, + "orchestrator_downloading_language_model_e785be44": { + "message": "Orchestrator: Downloading language model" + }, "orchestrator_recognizer_cf38b65a": { "message": "Orchestrator recognizer" }, @@ -2804,6 +2795,9 @@ "provision_failure_983d3844": { "message": "Provision failure" }, + "provision_partially_completed_b0120a72": { + "message": "Provision partially completed" + }, "provision_success_d6a6e437": { "message": "Provision success" }, @@ -2918,6 +2912,9 @@ "re_prompt_for_input_reprompt_dialog_event_ba028f7": { "message": "Re-prompt for input (Reprompt dialog event)" }, + "read_documentation_d5df5d59": { + "message": "Read documentation" + }, "read_me_4734ca1": { "message": "Read Me" }, @@ -3044,6 +3041,9 @@ "resource_name_817b6e75": { "message": "Resource name" }, + "resources_ccefab27": { + "message": "Resources" + }, "response_is_response_3cd62f8f": { "message": "Response is { response }" }, @@ -3383,6 +3383,9 @@ "specify_an_attachment_layout_when_there_are_more_t_28ffc0c2": { "message": "Specify an attachment layout when there are more than one." }, + "specify_an_existing_bot_to_connect_to_your_azure_b_3c632ffa": { + "message": "Specify an existing bot to connect to your Azure Bot resource." + }, "speech_16063aed": { "message": "Speech" }, @@ -3392,6 +3395,9 @@ "ssml_tag_981a8aac": { "message": "SSML tag" }, + "stack_overflow_de80008e": { + "message": "Stack Overflow" + }, "start_and_stop_local_bot_runtimes_individually_901c8d7d": { "message": "Start and stop local bot runtimes individually." }, @@ -3503,6 +3509,9 @@ "terms_of_use_6542769b": { "message": "Terms of Use" }, + "test_and_debug_bots_built_using_the_bot_framework__fa382385": { + "message": "Test and debug bots built using the Bot Framework SDK. Available on GitHub." + }, "test_in_emulator_b1b3c278": { "message": "Test in Emulator" }, @@ -3908,9 +3917,6 @@ "video_card_cda18e03": { "message": "Video card" }, - "video_tutorials_79eb26ca": { - "message": "Video tutorials:" - }, "view_dialog_f5151228": { "message": "View dialog" }, @@ -3926,9 +3932,6 @@ "view_project_readme_ff079d2e": { "message": "View project readme" }, - "vishwac_sena_45910bf0": { - "message": "Vishwac Sena" - }, "visit_a_this_page_a_to_learn_more_about_entity_def_c7c862a9": { "message": "Visit this page to learn more about entity definition." }, @@ -3950,15 +3953,9 @@ "we_need_to_define_the_endpoints_for_the_skill_to_a_5dc98d90": { "message": "We need to define the endpoints for the skill to allow other bots to interact with it." }, - "weather_bot_c38920cd": { - "message": "Weather Bot" - }, "web_chat_c5ca7ab6": { "message": "Web Chat" }, - "webchat_inspector_4d0dfeb7": { - "message": "Webchat Inspector" - }, "webchat_log_b7213a9e": { "message": "Webchat log." }, @@ -3995,6 +3992,12 @@ "what_is_the_type_of_this_trigger_d2701744": { "message": "What is the type of this trigger?" }, + "what_s_new_a9752a8e": { + "message": "What''s new" + }, + "what_s_new_list_6fe719cb": { + "message": "What''s new list" + }, "what_you_need_to_know_to_get_started_e2ab837a": { "message": "What you need to know to get started" }, @@ -4067,6 +4070,9 @@ "you_do_not_have_permission_to_save_bots_here_56cc10c7": { "message": "You do not have permission to save bots here" }, + "you_don_t_have_any_bot_yet_start_to_link_create_a__ccacc2d6": { + "message": "You don’t have any bot yet. Start to create a new bot" + }, "you_have_successfully_published_name_to_publishtar_bc81d3c1": { "message": "You have successfully published { name } to { publishTarget }" }, diff --git a/extensions/azurePublish/src/node/index.ts b/extensions/azurePublish/src/node/index.ts index bcd3e9805f..87543cee36 100644 --- a/extensions/azurePublish/src/node/index.ts +++ b/extensions/azurePublish/src/node/index.ts @@ -411,72 +411,73 @@ export default async (composer: IExtensionRegistration): Promise => { // perform the provision using azureProvisioner.create. // this will start the process, then return. // However, the process will continue in the background - try { - const provisionResults = await azureProvisioner.create(config); - // GOT PROVISION RESULTS! - // cast this into the right form for a publish profile + const provisionResults = await azureProvisioner.create(config); - let currentProfile = null; - if (config.currentProfile) { - currentProfile = JSON.parse(config.currentProfile.configuration); - } - const currentSettings = currentProfile?.settings; - - const publishProfile = { - name: currentProfile?.name ?? config.hostname, - environment: currentProfile?.environment ?? 'composer', - tenantId: provisionResults?.tenantId ?? currentProfile?.tenantId, - subscriptionId: provisionResults.subscriptionId ?? currentProfile?.subscriptionId, - resourceGroup: currentProfile?.resourceGroup ?? provisionResults.resourceGroup?.name, - botName: currentProfile?.botName ?? provisionResults.botName, - hostname: config.hostname ?? currentProfile?.hostname, - luisResource: provisionResults.luisPrediction ? `${config.hostname}-luis` : currentProfile?.luisResource, - runtimeIdentifier: currentProfile?.runtimeIdentifier ?? 'win-x64', - region: config.location, - settings: { - applicationInsights: { - InstrumentationKey: - provisionResults.appInsights?.instrumentationKey ?? - currentSettings?.applicationInsights?.InstrumentationKey, - }, - cosmosDb: provisionResults.cosmosDB ?? currentSettings?.cosmosDb, - blobStorage: provisionResults.blobStorage ?? currentSettings?.blobStorage, - luis: { - authoringKey: provisionResults.luisAuthoring?.authoringKey ?? currentSettings?.luis?.authoringKey, - authoringEndpoint: - provisionResults.luisAuthoring?.authoringEndpoint ?? currentSettings?.luis?.authoringEndpoint, - endpointKey: provisionResults.luisPrediction?.endpointKey ?? currentSettings?.luis?.endpointKey, - endpoint: provisionResults.luisPrediction?.endpoint ?? currentSettings?.luis?.endpoint, - region: provisionResults.luisPrediction?.location ?? currentSettings?.luis?.region, - }, - qna: { - subscriptionKey: provisionResults.qna?.subscriptionKey ?? currentSettings?.qna?.subscriptionKey, - qnaRegion: provisionResults.qna?.region ?? currentSettings?.qna?.qnaRegion, - }, - MicrosoftAppId: provisionResults.appId ?? currentSettings?.MicrosoftAppId, - MicrosoftAppPassword: provisionResults.appPassword ?? currentSettings?.MicrosoftAppPassword, + // cast this into the right form for a publish profile + let currentProfile = null; + if (config.currentProfile) { + currentProfile = JSON.parse(config.currentProfile.configuration); + } + const currentSettings = currentProfile?.settings; + + const publishProfile = { + name: currentProfile?.name ?? config.hostname, + environment: currentProfile?.environment ?? 'composer', + tenantId: provisionResults?.tenantId ?? currentProfile?.tenantId, + subscriptionId: provisionResults.subscriptionId ?? currentProfile?.subscriptionId, + resourceGroup: currentProfile?.resourceGroup ?? provisionResults.resourceGroup?.name, + botName: currentProfile?.botName ?? provisionResults.botName, + hostname: config.hostname ?? currentProfile?.hostname, + luisResource: provisionResults.luisPrediction ? `${config.hostname}-luis` : currentProfile?.luisResource, + runtimeIdentifier: currentProfile?.runtimeIdentifier ?? 'win-x64', + region: config.location, + settings: { + applicationInsights: { + InstrumentationKey: + provisionResults.appInsights?.instrumentationKey ?? + currentSettings?.applicationInsights?.InstrumentationKey, }, - }; - for (const configUnit in currentProfile) { - if (!(configUnit in publishProfile)) { - publishProfile[configUnit] = currentProfile[configUnit]; - } + cosmosDb: provisionResults.cosmosDB ?? currentSettings?.cosmosDb, + blobStorage: provisionResults.blobStorage ?? currentSettings?.blobStorage, + luis: { + authoringKey: provisionResults.luisAuthoring?.authoringKey ?? currentSettings?.luis?.authoringKey, + authoringEndpoint: + provisionResults.luisAuthoring?.authoringEndpoint ?? currentSettings?.luis?.authoringEndpoint, + endpointKey: provisionResults.luisPrediction?.endpointKey ?? currentSettings?.luis?.endpointKey, + endpoint: provisionResults.luisPrediction?.endpoint ?? currentSettings?.luis?.endpoint, + region: provisionResults.luisPrediction?.location ?? currentSettings?.luis?.region, + }, + qna: { + subscriptionKey: provisionResults.qna?.subscriptionKey ?? currentSettings?.qna?.subscriptionKey, + qnaRegion: provisionResults.qna?.region ?? currentSettings?.qna?.qnaRegion, + }, + MicrosoftAppId: provisionResults.appId ?? currentSettings?.MicrosoftAppId, + MicrosoftAppPassword: provisionResults.appPassword ?? currentSettings?.MicrosoftAppPassword, + }, + }; + for (const configUnit in currentProfile) { + if (!(configUnit in publishProfile)) { + publishProfile[configUnit] = currentProfile[configUnit]; } + } - this.logger(publishProfile); + this.logger(publishProfile); - BackgroundProcessManager.updateProcess(jobId, 200, 'Provision completed successfully!', publishProfile); - } catch (error) { - BackgroundProcessManager.updateProcess( - jobId, - 500, - `${stringifyError(error)} - detail message can see ${getProvisionLogName(name)} in your bot folder` - ); - // save provision history to log file. - const provisionHistoryPath = path.resolve(project.dataDir, getProvisionLogName(name)); - await this.persistProvisionHistory(jobId, name, provisionHistoryPath); + if (provisionResults.success) { + BackgroundProcessManager.updateProcess(jobId, 200, 'Provisioning completed successfully!', publishProfile); + } else { + const partialSuccess = provisionResults.provisionedCount > 0; + const errorCode = partialSuccess ? 206 : 500; + let message = `${provisionResults.errorMessage}. See ${getProvisionLogName(name)} in your bot folder`; + if (partialSuccess) { + message = `Provisioning completed ${provisionResults.provisionedCount} items before encountering a problem. ${message}`; + } + BackgroundProcessManager.updateProcess(jobId, errorCode, message, partialSuccess ? publishProfile : undefined); } + // save provision history to log file. + const provisionHistoryPath = path.resolve(project.dataDir, getProvisionLogName(name)); + await this.persistProvisionHistory(jobId, name, provisionHistoryPath); + // add in history this.addProvisionHistory(project.id, config.name, BackgroundProcessManager.getStatus(jobId)); BackgroundProcessManager.removeProcess(jobId); diff --git a/extensions/azurePublish/src/node/provision.ts b/extensions/azurePublish/src/node/provision.ts index 8825b09baa..fb46fddfb8 100644 --- a/extensions/azurePublish/src/node/provision.ts +++ b/extensions/azurePublish/src/node/provision.ts @@ -219,6 +219,26 @@ export class BotProjectProvision { * Provision a set of Azure resources for use with a bot */ public async create(config: ProvisionConfig) { + // this object collects all of the various configuration output + const provisionResults = { + success: false, + provisionedCount: 0, + errorMessage: null, + subscriptionId: null, + appId: null, + appPassword: null, + resourceGroup: null, + webApp: null, + luisPrediction: null, + luisAuthoring: null, + blobStorage: null, + cosmosDB: null, + appInsights: null, + qna: null, + botName: null, + tenantId: this.tenantId, + }; + try { // ensure a tenantId is available. if (!this.tenantId) { @@ -228,23 +248,6 @@ export class BotProjectProvision { // tokenCredentials is used for authentication across the API calls const tokenCredentials = new TokenCredentials(this.accessToken); - // this object collects all of the various configuration output - const provisionResults = { - subscriptionId: null, - appId: null, - appPassword: null, - resourceGroup: null, - webApp: null, - luisPrediction: null, - luisAuthoring: null, - blobStorage: null, - cosmosDB: null, - appInsights: null, - qna: null, - botName: null, - tenantId: this.tenantId, - }; - const resourceGroupName = config.resourceGroup ?? config.hostname; // azure resource manager class config @@ -404,22 +407,30 @@ export class BotProjectProvision { name: `${config.hostname}-qna`, }); break; + + default: + continue; } + + provisionResults.provisionedCount += 1; } + provisionResults.success = true; + // TODO: NOT SURE WHAT THIS DOES! Something about tracking what deployments happen because of composer? // await this.azureResourceManagementClient.deployDeploymentCounter({ // resourceGroupName: resourceGroupName, // name: '1d41002f-62a1-49f3-bd43-2f3f32a19cbb', // WHAT IS THIS CONSTANT??? // }); - - return provisionResults; } catch (err) { + const errorMessage = JSON.stringify(err, Object.getOwnPropertyNames(err)); this.logger({ status: BotProjectDeployLoggerType.PROVISION_ERROR, - message: JSON.stringify(err, Object.getOwnPropertyNames(err)), + message: errorMessage, }); - throw stringifyError(err); + provisionResults.errorMessage = errorMessage; } + + return provisionResults; } }