diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreateBot.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreateBot.tsx index 4add06b0c6..8578971755 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreateBot.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreateBot.tsx @@ -278,7 +278,14 @@ export function CreateBotV2(props: CreateBotProps) { - + { + TelemetryClient.track('NeedAnotherTemplateClicked'); + }} + > {formatMessage('Need another template? Send us a request')} diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx index cfe546777d..0f01399bca 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx @@ -19,8 +19,9 @@ import { useRecoilValue } from 'recoil'; import { DialogCreationCopy } from '../../../constants'; import { getAliasFromPayload, isElectron } from '../../../utils/electronUtil'; -import { userHasNodeInstalledState } from '../../../recoilModel'; +import { creationFlowTypeState, userHasNodeInstalledState } from '../../../recoilModel'; import { InstallDepModal } from '../../InstallDepModal'; +import TelemetryClient from '../../../telemetry/TelemetryClient'; import { CreateBotV2 } from './CreateBot'; @@ -40,6 +41,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) { const { templates, onDismiss, onNext, onJumpToOpenModal, fetchReadMe } = props; const [showNodeModal, setShowNodeModal] = useState(false); const userHasNode = useRecoilValue(userHasNodeInstalledState); + const creationFlowType = useRecoilValue(creationFlowTypeState); useEffect(() => { // open bot directly if alias exist. @@ -65,6 +67,10 @@ export function CreateOptionsV2(props: CreateOptionsProps) { return; } } + TelemetryClient.track('NewBotDialogOpened', { + isSkillBot: creationFlowType === 'Skill', + fromAbsHandoff: false, + }); setIsOpenCreateModal(true); }, [props.location?.search]); const dialogWrapperProps = DialogCreationCopy.CREATE_OPTIONS; @@ -100,6 +106,10 @@ export function CreateOptionsV2(props: CreateOptionsProps) { const handleJumpToNext = () => { if (option === 'Create') { + TelemetryClient.track('NewBotDialogOpened', { + isSkillBot: false, + fromAbsHandoff: true, + }); setIsOpenCreateModal(true); } else { onJumpToOpenModal(props.location?.search); @@ -121,7 +131,7 @@ export function CreateOptionsV2(props: CreateOptionsProps) { dialogType={DialogTypes.Customer} onDismiss={onDismiss} > - + diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx index b4f6197c15..93d20d6a32 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/CreationFlow.tsx @@ -181,7 +181,10 @@ const CreationFlowV2: React.FC = () => { path="create/:runtimeLanguage/:templateId" updateFolder={updateFolder} onCurrentPathUpdate={updateCurrentPath} - onDismiss={handleDismiss} + onDismiss={() => { + TelemetryClient.track('CreationCancelled'); + handleDismiss(); + }} onSubmit={handleSubmit} /> = () => { path="create/:templateId" updateFolder={updateFolder} onCurrentPathUpdate={updateCurrentPath} - onDismiss={handleDismiss} + onDismiss={() => { + TelemetryClient.track('CreationCancelled'); + handleDismiss(); + }} onSubmit={handleSubmit} /> { + TelemetryClient.track('CreationCancelled'); + handleDismiss(); + }} onJumpToOpenModal={handleJumpToOpenModal} onNext={handleCreateNext} /> diff --git a/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx b/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx index 47593d5440..d15f9381e8 100644 --- a/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx +++ b/Composer/packages/client/src/components/CreationFlow/v2/DefineConversation.tsx @@ -15,7 +15,7 @@ import querystring from 'query-string'; import { FontWeights } from '@uifabric/styling'; import { DialogWrapper, DialogTypes } from '@bfc/ui-shared'; import { useRecoilValue } from 'recoil'; -import { csharpFeedKey, functionsRuntimeKey, nodeFeedKey, QnABotTemplateId } from '@bfc/shared'; +import { csharpFeedKey, FeedType, functionsRuntimeKey, nodeFeedKey, QnABotTemplateId } from '@bfc/shared'; import { RuntimeType, webAppRuntimeKey } from '@bfc/shared'; import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown'; import camelCase from 'lodash/camelCase'; @@ -29,6 +29,7 @@ import { ImportSuccessNotificationWrapper } from '../../ImportModal/ImportSucces import { dispatcherState, templateProjectsState } from '../../../recoilModel'; import { LocationSelectContent } from '../LocationSelectContent'; import { getAliasFromPayload, Profile } from '../../../utils/electronUtil'; +import TelemetryClient from '../../../telemetry/TelemetryClient'; // -------------------- Styles -------------------- // @@ -293,6 +294,12 @@ const DefineConversationV2: React.FC = (props) => { dataToSubmit.alias = await getAliasFromPayload(source, payload); } } + TelemetryClient.track('CreationExecuted', { + runtimeChoice: dataToSubmit?.runtimeType, + runtimeLanguage: dataToSubmit?.runtimeLanguage as FeedType, + isPva: isImported, + isAbs: !!dataToSubmit?.source, + }); onSubmit({ ...dataToSubmit }, templateId || ''); }, [hasErrors, formData] diff --git a/Composer/packages/client/src/pages/botProject/create-publish-profile/PublishProfileDialog.tsx b/Composer/packages/client/src/pages/botProject/create-publish-profile/PublishProfileDialog.tsx index 6fee5f553b..ecd9d3f695 100644 --- a/Composer/packages/client/src/pages/botProject/create-publish-profile/PublishProfileDialog.tsx +++ b/Composer/packages/client/src/pages/botProject/create-publish-profile/PublishProfileDialog.tsx @@ -184,6 +184,7 @@ export const PublishProfileDialog: React.FC = (props) // require tenant id to be set by plugin (handles multiple tenant scenario) if (!tenantId) { + TelemetryClient.track('ProvisioningProfileCreateFailure', { message: 'azure tenant not set' }); const notification = createNotification({ type: 'error', title: formatMessage('Error provisioning.'), diff --git a/Composer/packages/client/src/pages/publish/PublishDialog.tsx b/Composer/packages/client/src/pages/publish/PublishDialog.tsx index 5a3cdd8431..b4259d4729 100644 --- a/Composer/packages/client/src/pages/publish/PublishDialog.tsx +++ b/Composer/packages/client/src/pages/publish/PublishDialog.tsx @@ -10,6 +10,8 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField'; import formatMessage from 'format-message'; import { CheckboxVisibility, DetailsList } from 'office-ui-fabric-react/lib/DetailsList'; +import TelemetryClient from '../../telemetry/TelemetryClient'; + import { BotStatus } from './type'; export const PublishDialog = (props) => { @@ -86,6 +88,7 @@ export const PublishDialog = (props) => { setShowItems(cleanedItems); }; const submit = async () => { + TelemetryClient.track('PublishStartBtnClick'); props.onDismiss(); await props.onSubmit(showItems); cleanComments(); diff --git a/Composer/packages/client/src/recoilModel/dispatchers/provision.ts b/Composer/packages/client/src/recoilModel/dispatchers/provision.ts index 5046bc0c0e..b237450cfd 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/provision.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/provision.ts @@ -99,6 +99,10 @@ export const provisionDispatcher = () => { notification.id ); } catch (error) { + TelemetryClient.track('ProvisioningProfileCreateFailure', { + message: error.response?.data || 'Error when provision target', + }); + // set notification const notification = createNotification( getProvisionFailureNotification(error.response?.data || 'Error when provision target') @@ -164,6 +168,9 @@ export const provisionDispatcher = () => { if (response.data.status !== 500) { notification = getProvisionPendingNotification(response.data.message); } else { + TelemetryClient.track('ProvisioningProfileCreateFailure', { + message: response.data.message, + }); notification = getProvisionFailureNotification(response.data.message); isCleanTimer = true; } diff --git a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts index 4fb78cc5c1..41c8f6997d 100644 --- a/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts +++ b/Composer/packages/client/src/recoilModel/dispatchers/publisher.ts @@ -29,6 +29,7 @@ import * as qnaUtil from '../../utils/qnaUtil'; import { ClientStorage } from '../../utils/storage'; import { RuntimeOutputData } from '../types'; import { checkIfFunctionsMissing, missingFunctionsError } from '../../utils/runtimeErrors'; +import TelemetryClient from '../../telemetry/TelemetryClient'; import { BotStatus, Text } from './../../constants'; import httpClient from './../../utils/httpUtil'; @@ -43,6 +44,7 @@ export const publishStorage = new ClientStorage(window.sessionStorage, 'publish' export const publisherDispatcher = () => { const publishFailure = async ({ set }: CallbackInterface, title: string, error, target, projectId: string) => { + TelemetryClient.track('PublishFailure', { message: typeof error === 'string' ? error : JSON.stringify(error) }); if (target.name === defaultPublishConfig.name) { set(botStatusState(projectId), BotStatus.failed); set(botBuildTimeErrorState(projectId), { ...error, title }); @@ -59,6 +61,7 @@ export const publisherDispatcher = () => { }; const publishSuccess = async ({ set }: CallbackInterface, projectId: string, data: PublishResult, target) => { + TelemetryClient.track('PublishSuccess'); const { endpointURL, status } = data; if (target.name === defaultPublishConfig.name) { if (status === PUBLISH_SUCCESS && endpointURL) { diff --git a/Composer/packages/server/src/locales/en-US.json b/Composer/packages/server/src/locales/en-US.json index a523a8d646..6845817af4 100644 --- a/Composer/packages/server/src/locales/en-US.json +++ b/Composer/packages/server/src/locales/en-US.json @@ -182,6 +182,9 @@ "add_alternative_phrasing_17e0304c": { "message": "+ Add alternative phrasing" }, + "add_an_existing_bot_5a9cc5b1": { + "message": "Add an existing bot" + }, "add_app_id_and_password_164e0fdb": { "message": "Add App ID and Password" }, @@ -431,6 +434,12 @@ "azure_functions_5e23be5c": { "message": "Azure Functions" }, + "azure_functions_required_2a035b48": { + "message": "Azure Functions required" + }, + "azure_functions_runtime_not_installed_bc24e100": { + "message": "Azure Functions runtime not installed." + }, "azure_web_app_d834cb4c": { "message": "Azure Web App" }, @@ -698,6 +707,9 @@ "composer_logo_ba2048a0": { "message": "Composer Logo" }, + "composer_needs_azure_functions_36138382": { + "message": "Composer needs Azure Functions" + }, "composer_needs_net_core_sdk_46e2a8ae": { "message": "Composer needs .NET Core SDK" }, @@ -1910,6 +1922,9 @@ "insert_template_reference_bb33720e": { "message": "Insert template reference" }, + "install_azure_functions_d607f182": { + "message": "Install Azure Functions" + }, "install_error_a9319839": { "message": "Install Error" }, @@ -2621,6 +2636,9 @@ "onboarding_8407871c": { "message": "Onboarding" }, + "once_you_publish_your_bot_to_azure_you_will_be_rea_93048067": { + "message": "Once you publish your bot to Azure you will be ready to add connections." + }, "ondialogevents_types_3dc569b5": { "message": "OnDialogEvents Types" }, @@ -2894,6 +2912,9 @@ "publish_to_dev_ops_3b8f5a2f": { "message": "Publish to Dev Ops" }, + "publish_your_bot_9099e323": { + "message": "Publish your bot" + }, "publish_your_bot_to_azure_and_manage_published_bot_67751ca9": { "message": "Publish your bot to Azure and manage published bots here." }, @@ -3191,9 +3212,6 @@ "save_11a80ec3": { "message": "Save" }, - "save_as_9e0cf70b": { - "message": "Save as" - }, "save_your_skill_manifest_63bf5f26": { "message": "Save your skill manifest" }, @@ -3833,6 +3851,9 @@ "to_perform_provisioning_and_publishing_actions_com_a2c54389": { "message": "To perform provisioning and publishing actions, Composer requires access to your Azure and MS Graph accounts. Paste access tokens from the az command line tool using the commands highlighted below." }, + "to_run_this_bot_composer_needs_azure_functions_cor_bbbd0e7": { + "message": "To run this bot, Composer needs Azure Functions Core Tools." + }, "to_run_this_bot_composer_needs_net_core_sdk_d1551038": { "message": "To run this bot, Composer needs .NET Core SDK." }, diff --git a/Composer/packages/types/src/telemetry.ts b/Composer/packages/types/src/telemetry.ts index 87f8110869..7860f2d3e2 100644 --- a/Composer/packages/types/src/telemetry.ts +++ b/Composer/packages/types/src/telemetry.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { FeedType, RuntimeType } from './creation'; import { TelemetrySettings } from './settings'; export type ServerSettings = Partial<{ telemetry: TelemetrySettings }>; @@ -120,6 +121,10 @@ type ResourcesItem = { }; type PublishingEvents = { + CreateProvisionStarted: { newResourceGroup: boolean }; + PublishStartBtnClick: undefined; + PublishSuccess: undefined; + PublishFailure: { message: string }; NewPublishingProfileStarted: undefined; NewPublishingProfileSaved: { type: string; msAppId?: string; subscriptionId?: string }; PublishingProfileStarted: { target: string; projectId: string; msAppId?: string; subscriptionId?: string }; @@ -132,6 +137,14 @@ type PublishingEvents = { ProvisionCancel: undefined; ProvisionShowHandoff: undefined; ProvisionAddResourcesCancel: undefined; + ProvisioningProfileCreateFailure: { message: string }; +}; + +type CreationEvents = { + NewBotDialogOpened: { fromAbsHandoff: boolean; isSkillBot: boolean }; + CreationCancelled: undefined; + NeedAnotherTemplateClicked: undefined; + CreationExecuted: { runtimeChoice: RuntimeType; runtimeLanguage: FeedType; isPva: boolean; isAbs: boolean }; }; type AppSettingsEvents = { @@ -238,7 +251,8 @@ export type TelemetryEvents = ApplicationEvents & WebChatEvents & LuEditorEvents & OrchestratorEvents & - PropertyEditorEvents; + PropertyEditorEvents & + CreationEvents; export type TelemetryEventName = keyof TelemetryEvents; diff --git a/extensions/azurePublish/src/components/azureProvisionDialog.tsx b/extensions/azurePublish/src/components/azureProvisionDialog.tsx index b3c1150153..e80d99207a 100644 --- a/extensions/azurePublish/src/components/azureProvisionDialog.tsx +++ b/extensions/azurePublish/src/components/azureProvisionDialog.tsx @@ -1195,6 +1195,7 @@ export const AzureProvisionDialog: React.FC = () => { text={formatMessage('Create')} onClick={() => { const selectedResources = formData.requiredResources.concat(formData.enabledResources); + telemetryClient?.track('CreateProvisionStarted', { newResourceGroup: isNewResourceGroup }); onSubmit({ tenantId: formData.tenantId, subscription: formData.subscriptionId,