diff --git a/Deployment/GCC/otherresourcesazuredeploy.json b/Deployment/GCC/otherresourcesazuredeploy.json index ba3021da..bd50f85f 100644 --- a/Deployment/GCC/otherresourcesazuredeploy.json +++ b/Deployment/GCC/otherresourcesazuredeploy.json @@ -85,15 +85,15 @@ "description": "Location for all resources." } }, - "qnaMakerSku": { + "questionAnswerServiceSku": { "type": "string", "allowedValues": [ - "F0 (3 managed documents per month, 3 transactions per second, 100 transactions per minute, 50K transactions per month)", - "S0 ($10 per month for unlimited documents, 3 transactions per second, 100 transactions per minute)" + "F0", + "S" ], - "defaultValue": "S0 ($10 per month for unlimited documents, 3 transactions per second, 100 transactions per minute)", + "defaultValue": "F0", "metadata": { - "description": "The pricing tier for the QnAMaker service." + "description": "The pricing tier for the Question Answering service." } }, "searchServiceSku": { @@ -122,6 +122,19 @@ "description": "The branch of the GitHub repository to deploy." }, "defaultValue": "master" + }, + "questionAnswerProjectName": { + "type": "string", + "metadata": { + "description": "The Question Answering service project name." + } + }, + "questionAnswerDeploymentName": { + "type": "string", + "metadata": { + "description": "The Question Answering service deployment name." + }, + "defaultValue": "production" } }, "variables": { @@ -133,10 +146,10 @@ "configAppName": "[concat(parameters('baseResourceName'), '-config')]", "configAppUrl": "[concat('https://', variables('configAppName'), '.azurewebsites.us')]", "configAppInsightsName": "[concat(parameters('baseResourceName'), '-config')]", - "qnaMakerAccountName": "[parameters('baseResourceName')]", - "qnaMakerAppServiceName": "[concat(parameters('baseResourceName'), '-qnamaker')]", - "qnaMakerAppInsightsName": "[concat(parameters('baseResourceName'), '-qnamaker')]", - "qnaMakerSkuValue": "[substring(parameters('qnaMakerSku'), 0, 2)]", + "questionAnswerAccountName": "[parameters('baseResourceName')]", + "questionAnswerAppServiceName": "[concat(parameters('baseResourceName'), '-qnamaker')]", + "questionAnswerAppInsightsName": "[concat(parameters('baseResourceName'), '-qnamaker')]", + "questionAnswerSkuValue": "[parameters('questionAnswerServiceSku')]", "azureSearchName": "[concat(uniquestring(concat(resourceGroup().id, parameters('baseResourceName'))), '-search')]", "azureSearchSkus": { "F ": "free", @@ -150,7 +163,8 @@ "Shared" ], "isSharedPlan": "[contains(variables('sharedSkus'), parameters('sku'))]", - "skuFamily": "[if(equals(parameters('sku'), 'Shared'), 'D', take(parameters('sku'), 1))]" + "skuFamily": "[if(equals(parameters('sku'), 'Shared'), 'D', take(parameters('sku'), 1))]", + "questionAnswerLocation": "westus" }, "resources": [ { @@ -218,8 +232,8 @@ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')),'2015-05-01-preview').key1, ';EndpointSuffix=','core.usgovcloudapi.net')]" }, { - "name": "QnAMakerHostUrl", - "value": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))).hostNames[0])]" + "name": "QuestionAnswerHostUrl", + "value": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('questionAnswerAppServiceName'))).hostNames[0])]" }, { "name": "TenantId", @@ -258,12 +272,12 @@ "value": "10" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { - "name": "QnAMakerApiEndpointUrl", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').endpoint]" + "name": "QuestionAnswerApiEndpointUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { "name": "ApplicationInsightsLogLevel", @@ -272,6 +286,14 @@ { "name": "IsGCCHybridDeployment", "value": true + }, + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -279,8 +301,8 @@ "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", - "[resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName'))]", + "[resourceId('Microsoft.Web/sites', variables('questionAnswerAppServiceName'))]", + "[resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]" ], "resources": [ @@ -308,7 +330,7 @@ "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName'))]", + "[resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName'))]", "[resourceId('Microsoft.Insights/components/', variables('configAppInsightsName'))]" ], "kind": "app", @@ -336,12 +358,12 @@ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')),'2015-05-01-preview').key1, ';EndpointSuffix=','core.usgovcloudapi.net')]" }, { - "name": "QnAMakerApiEndpointUrl", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').endpoint]" + "name": "QuestionAnswerApiEndpointUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", @@ -370,6 +392,15 @@ { "name": "ValidUpns", "value": "[parameters('configAdminUPNList')]" + }, + + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -406,22 +437,32 @@ }, { "type": "Microsoft.CognitiveServices/accounts", - "kind": "QnAMaker", - "name": "[variables('qnaMakerAccountName')]", - "apiVersion": "2017-04-18", - "location": "[parameters('location')]", + "kind": "TextAnalytics", + "name": "[variables('questionAnswerAccountName')]", + "apiVersion": "2022-03-01", + "location": "[variables('questionAnswerLocation')]", + "identity": { + "type": "SystemAssigned" + }, "sku": { - "name": "[variables('qnaMakerSkuValue')]" + "name": "[variables('questionAnswerSkuValue')]" }, "properties": { "apiProperties": { - "qnaRuntimeEndpoint": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))).hostNames[0])]" - } + "qnaAzureSearchEndpointId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, 'providers/Microsoft.Search/searchServices/', variables('questionAnswerAccountName'), '-migration')]" + }, + "customSubDomainName": "[concat(variables('questionAnswerAccountName'), '-migration')]", + "networkAcls": { + "defaultAction": "Allow", + "virtualNetworkRules": [], + "ipRules": [] + }, + "publicNetworkAccess": "Enabled" }, "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', variables('qnaMakerAppServiceName'))]", + "[resourceId('Microsoft.Web/Sites', variables('questionAnswerAppServiceName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]", - "[resourceId('microsoft.insights/components/', variables('qnaMakerAppInsightsName'))]" + "[resourceId('microsoft.insights/components/', variables('questionAnswerAppInsightsName'))]" ] }, { @@ -484,12 +525,12 @@ "value": "dotnet" }, { - "name": "QnAMakerApiUrl", - "value": "https://virginia.api.cognitive.microsoft.us" + "name": "QuestionAnswerApiUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { "name": "SearchServiceName", @@ -522,6 +563,14 @@ { "name": "IsGCCHybridDeployment", "value": true + }, + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -546,7 +595,7 @@ { "type": "Microsoft.Web/sites", "apiVersion": "2016-08-01", - "name": "[variables('qnaMakerAppServiceName')]", + "name": "[variables('questionAnswerAppServiceName')]", "location": "[parameters('location')]", "properties": { "enabled": true, @@ -557,7 +606,7 @@ ] } }, - "name": "[variables('qnaMakerAppServiceName')]", + "name": "[variables('questionAnswerAppServiceName')]", "serverFarmId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]", "hostingEnvironment": "" }, @@ -570,19 +619,19 @@ ], "resources": [ { - "name": "[variables('qnaMakerAppInsightsName')]", + "name": "[variables('questionAnswerAppInsightsName')]", "type": "microsoft.insights/components", "kind": "web", "apiVersion": "2015-05-01", "location": "[parameters('location')]", "tags": { - "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', variables('qnaMakerAppServiceName')))]": "Resource" + "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', variables('questionAnswerAppServiceName')))]": "Resource" }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('qnaMakerAppServiceName'))]" + "[resourceId('Microsoft.Web/sites/', variables('questionAnswerAppServiceName'))]" ], "properties": { - "ApplicationId": "[variables('qnaMakerAppServiceName')]" + "ApplicationId": "[variables('questionAnswerAppServiceName')]" } }, { @@ -590,19 +639,18 @@ "name": "appsettings", "type": "config", "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', variables('qnaMakerAppServiceName'))]", + "[resourceId('Microsoft.Web/Sites', variables('questionAnswerAppServiceName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]" ], "properties": { "AzureSearchName": "[variables('azureSearchName')]", "AzureSearchAdminKey": "[listAdminKeys(resourceId('Microsoft.Search/searchServices/', variables('azureSearchName')), '2015-08-19').primaryKey]", - "UserAppInsightsKey": "[reference(resourceId('Microsoft.Insights/components/', variables('qnaMakerAppInsightsName')), '2015-05-01').InstrumentationKey]", - "UserAppInsightsName": "[variables('qnaMakerAppInsightsName')]", - "UserAppInsightsAppId": "[reference(resourceId('Microsoft.Insights/components/', variables('qnaMakerAppInsightsName')), '2015-05-01').AppId]", - "PrimaryEndpointKey": "[concat(variables('qnaMakerAppServiceName'), '-PrimaryEndpointKey')]", - "SecondaryEndpointKey": "[concat(variables('qnaMakerAppServiceName'), '-SecondaryEndpointKey')]", - "DefaultAnswer": "No good match found in KB.", - "QNAMAKER_EXTENSION_VERSION": "latest" + "UserAppInsightsKey": "[reference(resourceId('Microsoft.Insights/components/', variables('questionAnswerAppInsightsName')), '2015-05-01').InstrumentationKey]", + "UserAppInsightsName": "[variables('questionAnswerAppInsightsName')]", + "UserAppInsightsAppId": "[reference(resourceId('Microsoft.Insights/components/', variables('questionAnswerAppInsightsName')), '2015-05-01').AppId]", + "PrimaryEndpointKey": "[concat(variables('questionAnswerAppServiceName'), '-PrimaryEndpointKey')]", + "SecondaryEndpointKey": "[concat(variables('questionAnswerAppServiceName'), '-SecondaryEndpointKey')]", + "DefaultAnswer": "No good match found in KB." } } ] diff --git a/Deployment/azuredeploy.json b/Deployment/azuredeploy.json index 9c02b30f..9b685b0f 100644 --- a/Deployment/azuredeploy.json +++ b/Deployment/azuredeploy.json @@ -118,15 +118,15 @@ "description": "Location for all resources." } }, - "qnaMakerSku": { + "questionAnswerServiceSku": { "type": "string", "allowedValues": [ - "F0 (3 managed documents per month, 3 transactions per second, 100 transactions per minute, 50K transactions per month)", - "S0 ($10 per month for unlimited documents, 3 transactions per second, 100 transactions per minute)" + "F0", + "S" ], - "defaultValue": "S0 ($10 per month for unlimited documents, 3 transactions per second, 100 transactions per minute)", + "defaultValue": "F0", "metadata": { - "description": "The pricing tier for the QnAMaker service." + "description": "The pricing tier for the Question Answering service." } }, "defaultCulture": { @@ -176,8 +176,21 @@ "description": "The branch of the GitHub repository to deploy." }, "defaultValue": "master" - } - }, + }, + "questionAnswerProjectName": { + "type": "string", + "metadata": { + "description": "The Question Answering service project name." + } + }, + "questionAnswerDeploymentName": { + "type": "string", + "metadata": { + "description": "The Question Answering service deployment name." + }, + "defaultValue": "production" + } + }, "variables": { "expertBotName": "[concat(parameters('baseResourceName'), '-expert')]", "expertBotDisplayName": "[concat(parameters('appDisplayName'), ' Expert')]", @@ -193,11 +206,11 @@ "configAppName": "[concat(parameters('baseResourceName'), '-config')]", "configAppUrl": "[concat('https://', variables('configAppName'), '.azurewebsites.net')]", "configAppInsightsName": "[concat(parameters('baseResourceName'), '-config')]", - "qnaMakerAccountName": "[parameters('baseResourceName')]", - "qnaMakerLocation": "westus", - "qnaMakerAppServiceName": "[concat(parameters('baseResourceName'), '-qnamaker')]", - "qnaMakerAppInsightsName": "[concat(parameters('baseResourceName'), '-qnamaker')]", - "qnaMakerSkuValue": "[substring(parameters('qnaMakerSku'), 0, 2)]", + "questionAnswerAccountName": "[parameters('baseResourceName')]", + "questionAnswerLocation": "westus", + "questionAnswerAppServiceName": "[concat(parameters('baseResourceName'), '-questionAnswer')]", + "questionAnswerAppInsightsName": "[concat(parameters('baseResourceName'), '-questionAnswer')]", + "questionAnswerServiceSkuValue": "[parameters('questionAnswerServiceSku')]", "azureSearchName": "[concat(uniquestring(concat(resourceGroup().id, parameters('baseResourceName'))), '-search')]", "azureSearchSkus": { "F ": "free", @@ -287,8 +300,8 @@ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')),'2015-05-01-preview').key1)]" }, { - "name": "QnAMakerHostUrl", - "value": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))).hostNames[0])]" + "name": "QuestionAnswerHostUrl", + "value": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('questionAnswerAppServiceName'))).hostNames[0])]" }, { "name": "TenantId", @@ -327,12 +340,12 @@ "value": "10" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { - "name": "QnAMakerApiEndpointUrl", - "value": "https://westus.api.cognitive.microsoft.com" + "name": "QuestionAnswerApiEndpointUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { "name": "ApplicationInsightsLogLevel", @@ -349,6 +362,14 @@ { "name": "IsGCCHybridDeployment", "value": false + }, + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -356,8 +377,8 @@ "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", - "[resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName'))]", + "[resourceId('Microsoft.Web/sites', variables('questionAnswerAppServiceName'))]", + "[resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]", "[resourceId('Microsoft.Insights/components/', variables('botAppInsightsName'))]" ], @@ -485,7 +506,7 @@ "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName'))]", + "[resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName'))]", "[resourceId('Microsoft.Insights/components/', variables('configAppInsightsName'))]" ], "kind": "app", @@ -513,12 +534,12 @@ "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')),'2015-05-01-preview').key1)]" }, { - "name": "QnAMakerApiEndpointUrl", - "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').endpoint]" + "name": "QuestionAnswerApiEndpointUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { "name": "APPINSIGHTS_INSTRUMENTATIONKEY", @@ -547,6 +568,14 @@ { "name": "ValidUpns", "value": "[parameters('configAdminUPNList')]" + }, + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -583,22 +612,32 @@ }, { "type": "Microsoft.CognitiveServices/accounts", - "kind": "QnAMaker", - "name": "[variables('qnaMakerAccountName')]", - "apiVersion": "2017-04-18", - "location": "[variables('qnaMakerLocation')]", + "kind": "TextAnalytics", + "name": "[variables('questionAnswerAccountName')]", + "apiVersion": "2022-03-01", + "location": "[variables('questionAnswerLocation')]", + "identity": { + "type": "SystemAssigned" + }, "sku": { - "name": "[variables('qnaMakerSkuValue')]" + "name": "[variables('questionAnswerServiceSkuValue')]" }, "properties": { "apiProperties": { - "qnaRuntimeEndpoint": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('qnaMakerAppServiceName'))).hostNames[0])]" - } + "qnaAzureSearchEndpointId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, 'providers/Microsoft.Search/searchServices/', variables('questionAnswerAccountName'), '-migration')]" + }, + "customSubDomainName": "[concat(variables('questionAnswerAccountName'), '-migration')]", + "networkAcls": { + "defaultAction": "Allow", + "virtualNetworkRules": [], + "ipRules": [] + }, + "publicNetworkAccess": "Enabled" }, "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', variables('qnaMakerAppServiceName'))]", + "[resourceId('Microsoft.Web/Sites', variables('questionAnswerAppServiceName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]", - "[resourceId('microsoft.insights/components/', variables('qnaMakerAppInsightsName'))]" + "[resourceId('microsoft.insights/components/', variables('questionAnswerAppInsightsName'))]" ] }, { @@ -661,12 +700,12 @@ "value": "dotnet" }, { - "name": "QnAMakerApiUrl", - "value": "https://westus.api.cognitive.microsoft.com" + "name": "QuestionAnswerApiUrl", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').endpoint]" }, { - "name": "QnAMakerSubscriptionKey", - "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('qnaMakerAccountName')), '2017-04-18').key1]" + "name": "QuestionAnswerSubscriptionKey", + "value": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts/', variables('questionAnswerAccountName')), '2017-04-18').key1]" }, { "name": "SearchServiceName", @@ -699,6 +738,14 @@ { "name": "IsGCCHybridDeployment", "value": false + }, + { + "name": "QuestionAnswerProjectName", + "value": "[parameters('questionAnswerProjectName')]" + }, + { + "name": "DeploymentName", + "value": "[parameters('questionAnswerDeploymentName')]" } ] } @@ -723,7 +770,7 @@ { "type": "Microsoft.Web/sites", "apiVersion": "2016-08-01", - "name": "[variables('qnaMakerAppServiceName')]", + "name": "[variables('questionAnswerAppServiceName')]", "location": "[parameters('location')]", "properties": { "enabled": true, @@ -734,7 +781,7 @@ ] } }, - "name": "[variables('qnaMakerAppServiceName')]", + "name": "[variables('questionAnswerAppServiceName')]", "serverFarmId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]", "hostingEnvironment": "" }, @@ -747,19 +794,19 @@ ], "resources": [ { - "name": "[variables('qnaMakerAppInsightsName')]", + "name": "[variables('questionAnswerAppInsightsName')]", "type": "microsoft.insights/components", "kind": "web", "apiVersion": "2015-05-01", "location": "[parameters('location')]", "tags": { - "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', variables('qnaMakerAppServiceName')))]": "Resource" + "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', variables('questionAnswerAppServiceName')))]": "Resource" }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('qnaMakerAppServiceName'))]" + "[resourceId('Microsoft.Web/sites/', variables('questionAnswerAppServiceName'))]" ], "properties": { - "ApplicationId": "[variables('qnaMakerAppServiceName')]" + "ApplicationId": "[variables('questionAnswerAppServiceName')]" } }, { @@ -767,19 +814,18 @@ "name": "appsettings", "type": "config", "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', variables('qnaMakerAppServiceName'))]", + "[resourceId('Microsoft.Web/Sites', variables('questionAnswerAppServiceName'))]", "[resourceId('Microsoft.Search/searchServices/', variables('azureSearchName'))]" ], "properties": { "AzureSearchName": "[variables('azureSearchName')]", "AzureSearchAdminKey": "[listAdminKeys(resourceId('Microsoft.Search/searchServices/', variables('azureSearchName')), '2015-08-19').primaryKey]", - "UserAppInsightsKey": "[reference(resourceId('Microsoft.Insights/components/', variables('qnaMakerAppInsightsName')), '2015-05-01').InstrumentationKey]", - "UserAppInsightsName": "[variables('qnaMakerAppInsightsName')]", - "UserAppInsightsAppId": "[reference(resourceId('Microsoft.Insights/components/', variables('qnaMakerAppInsightsName')), '2015-05-01').AppId]", - "PrimaryEndpointKey": "[concat(variables('qnaMakerAppServiceName'), '-PrimaryEndpointKey')]", - "SecondaryEndpointKey": "[concat(variables('qnaMakerAppServiceName'), '-SecondaryEndpointKey')]", - "DefaultAnswer": "No good match found in KB.", - "QNAMAKER_EXTENSION_VERSION": "latest" + "UserAppInsightsKey": "[reference(resourceId('Microsoft.Insights/components/', variables('questionAnswerAppInsightsName')), '2015-05-01').InstrumentationKey]", + "UserAppInsightsName": "[variables('questionAnswerAppInsightsName')]", + "UserAppInsightsAppId": "[reference(resourceId('Microsoft.Insights/components/', variables('questionAnswerAppInsightsName')), '2015-05-01').AppId]", + "PrimaryEndpointKey": "[concat(variables('questionAnswerAppServiceName'), '-PrimaryEndpointKey')]", + "SecondaryEndpointKey": "[concat(variables('questionAnswerAppServiceName'), '-SecondaryEndpointKey')]", + "DefaultAnswer": "No good match found in KB." } } ] diff --git a/Deployment/deploy.ps1 b/Deployment/deploy.ps1 index d9487591..604f83c0 100644 --- a/Deployment/deploy.ps1 +++ b/Deployment/deploy.ps1 @@ -192,7 +192,7 @@ https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic AuthorizationToken = $authorizationToken }, @{ - Name = $parameters.BaseResourceName.Value + '-qnamaker' + Name = $parameters.BaseResourceName.Value + '-questionAnswer' ServiceType = 'WebApp' AuthorizationToken = $authorizationToken }, @@ -210,7 +210,7 @@ https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic ServiceType = 'ApplicationInsights' }, @{ - Name = $parameters.BaseResourceName.Value + '-qnamaker' + Name = $parameters.BaseResourceName.Value + '-questionAnswer' ServiceType = 'ApplicationInsights' }) @@ -420,7 +420,7 @@ https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic # Deploy ARM templates Write-Host "Deploying app services, storage accounts, bot service, and cognitive services..." -ForegroundColor Yellow - az deployment group create --resource-group $parameters.ResourceGroupName.Value --subscription $parameters.SubscriptionId.Value --template-file 'azuredeploy.json' --parameters "baseResourceName=$($parameters.BaseResourceName.Value)" "userBotClientId=$userBotAppId" "userBotClientSecret=$userBotClientSecret" "expertBotClientId=$botAppId" "expertBotClientSecret=$appSecret" "configAppClientId=$configAppId" "configAdminUPNList=$($parameters.ConfigAdminUPNList.Value)" "appDisplayName=$($parameters.AppDisplayName.Value)" "appDescription=$($parameters.AppDescription.Value)" "tenantId=$($parameters.tenantId.Value)" "appIconUrl=$($parameters.AppIconUrl.Value)" "sku=$($parameters.Sku.Value)" "planSize=$($parameters.PlanSize.Value)" "qnaMakerSku=$($parameters.QnaMakerSku.Value)" "searchServiceSku=$($parameters.SearchServiceSku.Value)" "gitRepoUrl=$($parameters.GitRepoUrl.Value)" "gitBranch=$($parameters.GitBranch.Value)" "defaultCulture=$($parameters.DefaultCulture.Value)" + az deployment group create --resource-group $parameters.ResourceGroupName.Value --subscription $parameters.SubscriptionId.Value --template-file 'azuredeploy.json' --parameters "baseResourceName=$($parameters.BaseResourceName.Value)" "userBotClientId=$userBotAppId" "userBotClientSecret=$userBotClientSecret" "expertBotClientId=$botAppId" "expertBotClientSecret=$appSecret" "configAppClientId=$configAppId" "configAdminUPNList=$($parameters.ConfigAdminUPNList.Value)" "appDisplayName=$($parameters.AppDisplayName.Value)" "appDescription=$($parameters.AppDescription.Value)" "tenantId=$($parameters.tenantId.Value)" "appIconUrl=$($parameters.AppIconUrl.Value)" "sku=$($parameters.Sku.Value)" "planSize=$($parameters.PlanSize.Value)" "questionAnswerServiceSku=$($parameters.QuestionAnswerServiceSku.Value)" "searchServiceSku=$($parameters.SearchServiceSku.Value)" "gitRepoUrl=$($parameters.GitRepoUrl.Value)" "gitBranch=$($parameters.GitBranch.Value)" "defaultCulture=$($parameters.DefaultCulture.Value)" "questionAnswerDeploymentName=$($parameters.QuestionAnswerDeploymentName.Value)" "questionAnswerProjectName=$($parameters.QuestionAnswerProjectName.Value)" if($LASTEXITCODE -ne 0){ # CollectARMDeploymentLogs Throw "ERROR: ARM template deployment error." @@ -504,7 +504,7 @@ https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic # Check for presence of Azure CLI If (-not (Test-Path -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2")) { - Write-Host "AZURE CLI NOT INSTALLED!`nPLEASE INSTALL THE CLI FROM https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest and re-run this script in a new PowerShell session" -ForegroundColor Red + Write-Host "AZURE CLI NOT INSTALLED!`nPLEASE INSTALL THE CLI FROM https://azcliprod.blob.core.windows.net/msi/azure-cli-2.30.0.msi and re-run this script in a new PowerShell session" -ForegroundColor Red break } diff --git a/Deployment/parameters.json b/Deployment/parameters.json index 19fe4e61..407b5899 100644 --- a/Deployment/parameters.json +++ b/Deployment/parameters.json @@ -67,9 +67,9 @@ "Value": "2", "Description": "The size of the hosting plan (small, medium, or large)." }, - "QnaMakerSku": { - "Value": "S0 ($10 per month for unlimited documents, 3 transactions per second, 100 transactions per minute)", - "Description": "The pricing tier for the QnAMaker service." + "QuestionAnswerServiceSku": { + "Value": "F0", + "Description": "The pricing tier for the QuestionAnswer service." }, "SearchServiceSku": { "Value": "B (15 indexes)", @@ -90,5 +90,13 @@ "DefaultCulture": { "Value": "en", "Description": "Default culture." + }, + "QuestionAnswerProjectName": { + "Value": "<>", + "description": "The Question Answering service project name." + }, + "QuestionAnswerDeploymentName": { + "value": "production", + "description": "The Question Answering service deployment name." } } \ No newline at end of file diff --git a/Manifest/EndUser/ar.json b/Manifest/EndUser/ar.json index ae94d06d..49bd19ba 100644 --- a/Manifest/EndUser/ar.json +++ b/Manifest/EndUser/ar.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "الأسئلة المتداولة Plus", + "name.full": "الأسئلة المتداولة Plus", "description.short": "روبوت الأسئلة المتداولة سهل الاستخدام الذي يجيب عن الأسئلة ويوصلك بالخبراء.", "description.full": "روبوت الأسئلة والإجابات سهل الاستخدام الذي يجيب عن الأسئلة الشائعة المطروحة. إذا لم يستطع الإجابة، فسيوصلك بأحد الخبراء فور توفره.", "bots[0].commandLists[0].commands[0].title": "القيام بجولة", diff --git a/Manifest/EndUser/de.json b/Manifest/EndUser/de.json index 9433761d..2220fab5 100644 --- a/Manifest/EndUser/de.json +++ b/Manifest/EndUser/de.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus", + "name.full": "FAQ Plus", "description.short": "Ein freundlicher FAQ-Bot, der Fragen beantworten und Sie mit Experten verbindet.", "description.full": "Ein freundlicher Frage- und Antwort-Bot, der häufig gestellte Fragen beantwortet. Wenn er keine Antwort liefern kann, wird er Sie in Verbindung mit einem Experten bringen, sobald einer verfügbar ist.", "bots[0].commandLists[0].commands[0].title": "Einen Rundgang machen", diff --git a/Manifest/EndUser/en.json b/Manifest/EndUser/en.json index 5175052f..6017312b 100644 --- a/Manifest/EndUser/en.json +++ b/Manifest/EndUser/en.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus", + "name.full": "FAQ Plus", "description.short": "A friendly FAQ bot that answers questions and connects you to experts.", "description.full": "A friendly question and answer bot that answers commonly asked questions. If it can't answer, it will put you in touch with an expert as soon as they are available.", "bots[0].commandLists[0].commands[0].title": "Take a tour", diff --git a/Manifest/EndUser/es.json b/Manifest/EndUser/es.json index b94357f4..2b8c69e0 100644 --- a/Manifest/EndUser/es.json +++ b/Manifest/EndUser/es.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "Preguntas más frecuentes Plus", + "name.full": "Preguntas más frecuentes Plus", "description.short": "Un bot de preguntas frecuentes que responde preguntas y le conecta con expertos.", "description.full": "Un bot de preguntas y respuestas descriptiva que responde a las preguntas más frecuentes. Si no puede responder, le pondrá en contacto con un experto tan pronto como esté disponible.", "bots[0].commandLists[0].commands[0].title": "Visita guiada", diff --git a/Manifest/EndUser/fr.json b/Manifest/EndUser/fr.json index bb56417c..5cb36c64 100644 --- a/Manifest/EndUser/fr.json +++ b/Manifest/EndUser/fr.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "Forum aux questions Plus", + "name.full": "Forum aux questions Plus", "description.short": "Réponses et rencontre d’experts à l’aide d’un bot de FAQ amical.", "description.full": "Un robot de question/réponses amical qui répond aux questions fréquemment posées. S’il ne peut pas y répondre, il vous mettra en contact avec un expert dès que celui ou celle-ci sera disponible.", "bots[0].commandLists[0].commands[0].title": "Suivre la visite guidée", diff --git a/Manifest/EndUser/he.json b/Manifest/EndUser/he.json index 44083fe7..c1f2aa6f 100644 --- a/Manifest/EndUser/he.json +++ b/Manifest/EndUser/he.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "שאלות נפוצות פלוס", + "name.full": "שאלות נפוצות פלוס", "description.short": "FAQ bot ידידותי שעונה על שאלות נפוצות ומקשר אותך למומחים.", "description.full": "bot ידידותי לניהול דיאלוג שאלות ותשובות עונה על שאלות נפוצות. אם הוא לא יודע לענות, הוא מקשר אותך למומחה ברגע שהוא זמין.", "bots[0].commandLists[0].commands[0].title": "צא לסיור", diff --git a/Manifest/EndUser/ja.json b/Manifest/EndUser/ja.json index e99314bd..2d1e2809 100644 --- a/Manifest/EndUser/ja.json +++ b/Manifest/EndUser/ja.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ プラス", + "name.full": "FAQ プラス", "description.short": "質問に答えて専門家に接続する、フレンドリーな FAQ ボットです。", "description.full": "よくある質問に答えるフレンドリーな質問と回答用のボットです。 回答できない場合は、専門家が対応できるようになり次第、専門家につなぎます。", "bots[0].commandLists[0].commands[0].title": "ツアーを開始", diff --git a/Manifest/EndUser/ko.json b/Manifest/EndUser/ko.json index f6b3895b..4c7dcc6a 100644 --- a/Manifest/EndUser/ko.json +++ b/Manifest/EndUser/ko.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus", + "name.full": "FAQ Plus", "description.short": "질문에 답변하고 전문가에게 연결하는 친숙한 FAQ 봇입니다.", "description.full": "흔히 묻는 질문에 답변하는 친근한 질문과 대답 봇입니다. 봇이 답변할 수 없는 경우, 가능한 한 빨리 전문가와 연락할 수 있도록 연결합니다.", "bots[0].commandLists[0].commands[0].title": "둘러보기", diff --git a/Manifest/EndUser/manifest_enduser.json b/Manifest/EndUser/manifest_enduser.json index 77bccce7..1cd49991 100644 --- a/Manifest/EndUser/manifest_enduser.json +++ b/Manifest/EndUser/manifest_enduser.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.8/MicrosoftTeams.schema.json", "manifestVersion": "1.8", - "version": "4.0.0", + "version": "5.0.0", "id": "aba30cfc-05df-4002-b956-a70996b12a54", "packageName": "com.microsoft.teams.faqplus", "developer": { diff --git a/Manifest/EndUser/pt-BR.json b/Manifest/EndUser/pt-BR.json index 396ed73c..03ef22b1 100644 --- a/Manifest/EndUser/pt-BR.json +++ b/Manifest/EndUser/pt-BR.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "Perguntas Mais Frequentes", + "name.full": "Perguntas Mais Frequentes", "description.short": "Um bot amigo de FAQ que responde a perguntas e conecta você a especialistas.", "description.full": "Um bot de perguntas e respostas amigáveis que responde às perguntas mais frequentes. Se ele não puder responder, você entrará em contato com um especialista assim que estiver disponível.", "bots[0].commandLists[0].commands[0].title": "Faça um tour", diff --git a/Manifest/EndUser/ru.json b/Manifest/EndUser/ru.json index 20d8803a..7cabe428 100644 --- a/Manifest/EndUser/ru.json +++ b/Manifest/EndUser/ru.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "Вопросы и ответы Плюс", + "name.full": "Вопросы и ответы Плюс", "description.short": "Бот вопросов и ответов, отвечающий на вопросы и связывающий со специалистами.", "description.full": "Простой в использовании бот вопросов и ответов, который отвечает на часто задаваемые вопросы. Если бот не сможет ответить на вопрос, он свяжет вас с первым освободившимся специалистом.", "bots[0].commandLists[0].commands[0].title": "Обзор", diff --git a/Manifest/EndUser/zh-CN.json b/Manifest/EndUser/zh-CN.json index 58d3fe37..7624a758 100644 --- a/Manifest/EndUser/zh-CN.json +++ b/Manifest/EndUser/zh-CN.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "常见问题解答增强版", + "name.full": "常见问题解答增强版", "description.short": "友好的常见问题解答机器人,可解答问题并帮你联系专家。", "description.full": "友好的问与答机器人,可解答常见问题。如果无法解答,它会在专家有空时尽快为你联系专家。", "bots[0].commandLists[0].commands[0].title": "浏览", diff --git a/Manifest/EndUser/zh-TW.json b/Manifest/EndUser/zh-TW.json index 822aa463..b7a982a0 100644 --- a/Manifest/EndUser/zh-TW.json +++ b/Manifest/EndUser/zh-TW.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "常見問題集增強版", + "name.full": "常見問題集增強版", "description.short": "友善的常見問題集 Bot,能回答問題並讓您與專家聯繫。", "description.full": "友善的問答 Bot,可回答常見問題。若無法回答,它會協助您聯繫有空的專家。", "bots[0].commandLists[0].commands[0].title": "導覽", diff --git a/Manifest/SME/ar.json b/Manifest/SME/ar.json index 66772843..fe21af43 100644 --- a/Manifest/SME/ar.json +++ b/Manifest/SME/ar.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "أسئلة متداولة Plus فريق خبراء", + "name.full": "أسئلة متداولة Plus فريق خبراء", "description.short": "روبوت الأسئلة المتداولة سهل الاستخدام الذي يجيب عن الأسئلة ويوصلك بالخبراء.", "description.full": "روبوت الأسئلة والإجابات سهل الاستخدام الذي يجيب عن الأسئلة الشائعة المطروحة. إذا لم يستطع الإجابة، فسيوصلك بأحد الخبراء فور توفره.", "bots[0].commandLists[0].commands[0].title": "القيام بجولة", diff --git a/Manifest/SME/de.json b/Manifest/SME/de.json index f7b6bb53..7d69c924 100644 --- a/Manifest/SME/de.json +++ b/Manifest/SME/de.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus (Expertenteam)", + "name.full": "FAQ Plus (Expertenteam)", "description.short": "Ein freundlicher FAQ-Bot, der Fragen beantworten und Sie mit Experten verbindet.", "description.full": "Ein freundlicher Frage- und Antwort-Bot, der häufig gestellte Fragen beantwortet. Wenn er keine Antwort liefern kann, wird er Sie in Verbindung mit einem Experten bringen, sobald einer verfügbar ist.", "bots[0].commandLists[0].commands[0].title": "Einen Rundgang machen", diff --git a/Manifest/SME/en.json b/Manifest/SME/en.json index e162cd4c..34c0678b 100644 --- a/Manifest/SME/en.json +++ b/Manifest/SME/en.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus (Experts Team)", + "name.full": "FAQ Plus (Experts Team)", "description.short": "A friendly FAQ bot that answers questions and connects you to experts.", "description.full": "A friendly question and answer bot that answers commonly asked questions. If it can't answer, it will put you in touch with an expert as soon as they are available.", "bots[0].commandLists[0].commands[0].title": "Take a tour", diff --git a/Manifest/SME/es.json b/Manifest/SME/es.json index 71f2396d..11daed2a 100644 --- a/Manifest/SME/es.json +++ b/Manifest/SME/es.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "P+F Plus (equipo de expertos)", + "name.full": "P+F Plus (equipo de expertos)", "description.short": "Un bot de preguntas frecuentes que responde preguntas y le conecta con expertos.", "description.full": "Un bot de preguntas y respuestas descriptiva que responde a las preguntas más frecuentes. Si no puede responder, le pondrá en contacto con un experto tan pronto como esté disponible.", "bots[0].commandLists[0].commands[0].title": "Visita guiada", diff --git a/Manifest/SME/fr.json b/Manifest/SME/fr.json index 75170f3c..0489aa43 100644 --- a/Manifest/SME/fr.json +++ b/Manifest/SME/fr.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus (équipe d’experts)", + "name.full": "FAQ Plus (équipe d’experts)", "description.short": "Réponses et rencontre d’experts à l’aide d’un bot de FAQ amical.", "description.full": "Un robot de question/réponses amical qui répond aux questions fréquemment posées. S’il ne peut pas y répondre, il vous mettra en contact avec un expert dès que celui ou celle-ci sera disponible.", "bots[0].commandLists[0].commands[0].title": "Suivre la visite guidée", diff --git a/Manifest/SME/he.json b/Manifest/SME/he.json index 7281ca81..fc79c48e 100644 --- a/Manifest/SME/he.json +++ b/Manifest/SME/he.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "שאלות נפוצות פלוס -צוות מומחים", + "name.full": "שאלות נפוצות פלוס -צוות מומחים", "description.short": "FAQ bot ידידותי שעונה על שאלות נפוצות ומקשר אותך למומחים.", "description.full": "bot ידידותי לניהול דיאלוג שאלות ותשובות עונה על שאלות נפוצות. אם הוא לא יודע לענות, הוא מקשר אותך למומחה ברגע שהוא זמין.", "bots[0].commandLists[0].commands[0].title": "צא לסיור", diff --git a/Manifest/SME/ja.json b/Manifest/SME/ja.json index abf76c72..e476b80f 100644 --- a/Manifest/SME/ja.json +++ b/Manifest/SME/ja.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ プラス (専門家チーム)", + "name.full": "FAQ プラス (専門家チーム)", "description.short": "質問に答えて専門家に接続する、フレンドリーな FAQ ボットです。", "description.full": "よくある質問に答えるフレンドリーな質問と回答用のボットです。 回答できない場合は、専門家が対応できるようになり次第、専門家につなぎます。", "bots[0].commandLists[0].commands[0].title": "ツアーを開始", diff --git a/Manifest/SME/ko.json b/Manifest/SME/ko.json index 8ccb216c..fecb1e41 100644 --- a/Manifest/SME/ko.json +++ b/Manifest/SME/ko.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Plus(전문가 팀)", + "name.full": "FAQ Plus(전문가 팀)", "description.short": "질문에 답변하고 전문가에게 연결하는 친숙한 FAQ 봇입니다.", "description.full": "흔히 묻는 질문에 답변하는 친근한 질문과 대답 봇입니다. 봇이 답변할 수 없는 경우, 가능한 한 빨리 전문가와 연락할 수 있도록 연결합니다.", "bots[0].commandLists[0].commands[0].title": "둘러보기", diff --git a/Manifest/SME/manifest_sme.json b/Manifest/SME/manifest_sme.json index f7741648..50eca162 100644 --- a/Manifest/SME/manifest_sme.json +++ b/Manifest/SME/manifest_sme.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.8/MicrosoftTeams.schema.json", "manifestVersion": "1.8", - "version": "4.0.0", + "version": "5.0.0", "id": "97ee4132-77a2-4101-9aa8-aaa17a44f87c", "packageName": "com.microsoft.teams.faqplus", "developer": { diff --git a/Manifest/SME/pt-BR.json b/Manifest/SME/pt-BR.json index 9797af1c..f00775ee 100644 --- a/Manifest/SME/pt-BR.json +++ b/Manifest/SME/pt-BR.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "Perg.+ Freq. (Equipe de Esp.)", + "name.full": "Perg.+ Freq. (Equipe de Esp.)", "description.short": "Um bot amigo de FAQ que responde a perguntas e conecta você a especialistas.", "description.full": "Um bot de perguntas e respostas amigáveis que responde às perguntas mais frequentes. Se ele não puder responder, você entrará em contato com um especialista assim que estiver disponível.", "bots[0].commandLists[0].commands[0].title": "Faça um tour", diff --git a/Manifest/SME/ru.json b/Manifest/SME/ru.json index c3ea99d6..437d4464 100644 --- a/Manifest/SME/ru.json +++ b/Manifest/SME/ru.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "FAQ Плюс (команда экспертов)", + "name.full": "FAQ Плюс (команда экспертов)", "description.short": "Бот вопросов и ответов, отвечающий на вопросы и связывающий со специалистами.", "description.full": "Простой в использовании бот вопросов и ответов, который отвечает на часто задаваемые вопросы. Если бот не сможет ответить на вопрос, он свяжет вас с первым освободившимся специалистом.", "bots[0].commandLists[0].commands[0].title": "Обзор", diff --git a/Manifest/SME/zh-CN.json b/Manifest/SME/zh-CN.json index 38e45e32..238c1401 100644 --- a/Manifest/SME/zh-CN.json +++ b/Manifest/SME/zh-CN.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "常见问题解答增强版(专家团队)", + "name.full": "常见问题解答增强版(专家团队)", "description.short": "友好的常见问题解答机器人,可解答问题并帮你联系专家。", "description.full": "友好的问与答机器人,可解答常见问题。如果无法解答,它会在专家有空时尽快为你联系专家。", "bots[0].commandLists[0].commands[0].title": "浏览", diff --git a/Manifest/SME/zh-TW.json b/Manifest/SME/zh-TW.json index 9c1f18a5..087a691c 100644 --- a/Manifest/SME/zh-TW.json +++ b/Manifest/SME/zh-TW.json @@ -1,6 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "name.short": "常見問題集增強版 (專家團隊)", + "name.full": "常見問題集增強版 (專家團隊)", "description.short": "友善的常見問題集 Bot,能回答問題並讓您與專家聯繫。", "description.full": "友善的問答 Bot,可回答常見問題。若無法回答,它會協助您聯繫有空的專家。", "bots[0].commandLists[0].commands[0].title": "導覽", diff --git a/README.md b/README.md index 905b897f..f5655c77 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,11 @@ Chatbots on Microsoft Teams are an easy way to provide answers to frequently ask FAQ Plus bot is a friendly Q&A bot that brings a human in the loop when it is unable to help. A user can ask the bot a question and the bot responds with an answer if it's in the knowledge base. If not, the bot offers the user an option to "Ask an expert", which posts the question to a pre-configured team of experts to provide support. An expert can assign the question to themself, chat with the user to gain more context and add the question to the knowledge base from using a messaging extention so that the next user to ask that same question will get an answer from the chatbot! -**The July 2020 (version 3) release of FAQ Plus includes a multi-turn feature to the end user experience. With the multi-turn feature, users will be presented with follow-up options along with an answer to their question. This enables the FAQ Plus bot to answer the user's question with more relevance. Multi-turn follow-up options are programmed directly into the QnA Maker when the tenant admin uploads the Q&A pairs into the knowledge base.** +**The November 2022 (version 5) release of FAQ Plus includes [Question Answering](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview), an Azure Cognitive Service for Language feature. Question answering provides cloud-based Natural Language Processing (NLP) that allows you to create a natural conversational layer over your data. It is used to find the most appropriate answer for any input from your custom knowledge base (KB) of information. +Build a knowledge base by adding unstructured documents or extracting questions and answers from your semi-structured content, including FAQ, manuals, and documents. Get the best answers from the questions and answers in your knowledge base—automatically. Question Answering supports a multi-turn feature to the end user experience. With the multi-turn feature, users will be presented with follow-up options along with an answer to their question. This enables the FAQ Plus bot to answer the user's question with more relevance. Multi-turn follow-up options are programmed directly into the Question Answering when the tenant admin uploads the Q&A pairs into the knowledge base. +The latest (version 5) release of FAQ Plus separates the end-user and the sme bot. With splitting the bot and having different bot registrations, users can now setup different permission policies for these two bots.** + +**** **FAQ Plus provides features to the expert team such as:** * Adding/editing/deleting/previewing QnA @@ -73,17 +77,17 @@ Begin with the [Solution overview](https://github.com/OfficeDev/microsoft-teams- When you're ready to try out FAQ Plus, or to use it in your own organization, you can choose to follow one of the below guides. * [Deployment guide powershell](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Deployment-Guide-manual). - * **Recommended** Use this option to deploy the FAQ Plus v4.0 using powershell script. The entire set-up is done by the powershell script. + * **Recommended** Use this option to deploy the FAQ Plus v5.0 using powershell script. The entire set-up is done by the powershell script. * [Deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Deployment-Guide). - * Use this option to deploy the FAQ+ v4.0 manually. + * Use this option to deploy the FAQ+ v5.0 manually. ## Migration -If you already have older version of FAQ Plus installed, then please use this [v4 migration guide](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Migration-Guide-manual). Please note that deploying the major version update, like FAQ Plus version 4.0 involves more than syncing the App Service and Azure Functions, so plan to review the migration guide before migrating to latest. +If you already have older version of FAQ Plus installed, then please use this [migration guide](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Migration-Guide-manual). Please note that deploying the major version update, like FAQ Plus version 5.0 involves more than syncing the App Service and Azure Functions, so plan to review the migration guide before migrating to latest. ## Feedback -Thoughts? Questions? Ideas? Share them with us on [Teams UserVoice](https://microsoftteams.uservoice.com/forums/555103-public)! +Thoughts? Questions? Ideas? Share them with us [here](https://aka.ms/fqbappfeedback)! Please report bugs and other code issues [here](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/issues/new). diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.csproj b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.csproj index 853a3689..9d99bf47 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.csproj +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.csproj @@ -16,7 +16,7 @@ - + all diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.xml b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.xml index b3332a9d..54bbc524 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.xml +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction.xml @@ -6,7 +6,7 @@ - Azure Function to publish QnA Maker knowledge base. + Azure Function to publish Question Answering knowledge base. @@ -20,7 +20,7 @@ - Function to get and publish QnA Maker knowledge base. + Function to get and publish Question Answering knowledge base. Duration of publish operations. Log. diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/PublishFunction.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/PublishFunction.cs index 39635c99..512e7315 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/PublishFunction.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/PublishFunction.cs @@ -13,11 +13,11 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; /// - /// Azure Function to publish QnA Maker knowledge base. + /// Azure Function to publish Question Answering knowledge base. /// public class PublishFunction { - private readonly IQnaServiceProvider qnaServiceProvider; + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; private readonly IConfigurationDataProvider configurationProvider; private readonly ISearchServiceDataProvider searchServiceDataProvider; private readonly IKnowledgeBaseSearchService knowledgeBaseSearchService; @@ -25,20 +25,20 @@ public class PublishFunction /// /// Initializes a new instance of the class. /// - /// Qna service provider. + /// Question Answering service provider. /// Configuration service provider. /// Search service data provider. /// Knowledgebase search service. - public PublishFunction(IQnaServiceProvider qnaServiceProvider, IConfigurationDataProvider configurationProvider, ISearchServiceDataProvider searchServiceDataProvider, IKnowledgeBaseSearchService knowledgeBaseSearchService) + public PublishFunction(IQuestionAnswerServiceProvider questionAnswerServiceProvider, IConfigurationDataProvider configurationProvider, ISearchServiceDataProvider searchServiceDataProvider, IKnowledgeBaseSearchService knowledgeBaseSearchService) { - this.qnaServiceProvider = qnaServiceProvider; + this.questionAnswerServiceProvider = questionAnswerServiceProvider; this.configurationProvider = configurationProvider; this.searchServiceDataProvider = searchServiceDataProvider; this.knowledgeBaseSearchService = knowledgeBaseSearchService; } /// - /// Function to get and publish QnA Maker knowledge base. + /// Function to get and publish Question Answering knowledge base. /// /// Duration of publish operations. /// Log. @@ -49,25 +49,29 @@ public async Task Run([TimerTrigger("0 */15 * * * *")]TimerInfo myTimer, ILogger try { var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(Constants.KnowledgeBaseEntityId).ConfigureAwait(false); - bool toBePublished = await this.qnaServiceProvider.GetPublishStatusAsync(knowledgeBaseId).ConfigureAwait(false); + bool toBePublished = await this.questionAnswerServiceProvider.GetPublishStatusAsync().ConfigureAwait(false); log.LogInformation("To be published - " + toBePublished); log.LogInformation("knowledge base id - " + knowledgeBaseId); if (toBePublished) { log.LogInformation("Publishing knowledge base"); - await this.qnaServiceProvider.PublishKnowledgebaseAsync(knowledgeBaseId).ConfigureAwait(false); + await this.questionAnswerServiceProvider.PublishKnowledgebaseAsync().ConfigureAwait(false); + log.LogInformation("Successfully published the knowledge base" + knowledgeBaseId); } log.LogInformation("Setup azure search data"); await this.searchServiceDataProvider.SetupAzureSearchDataAsync(knowledgeBaseId).ConfigureAwait(false); + log.LogInformation("Successfully setup the azure search data"); log.LogInformation("Update azure search service"); await this.knowledgeBaseSearchService.InitializeSearchServiceDependencyAsync().ConfigureAwait(false); + log.LogInformation("Successfully updated azure search service"); + } catch (Exception ex) { - log.LogError(ex, "Exception occured while publishing knowledge base in QnA Maker.", SeverityLevel.Error); + log.LogError(ex, "Exception occured while publishing knowledge base.", SeverityLevel.Error); throw; } } diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Startup.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Startup.cs index 835b9394..23e4f124 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Startup.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/Startup.cs @@ -3,7 +3,7 @@ // using System; -using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker; +using Azure; using Microsoft.Azure.Functions.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -21,18 +21,36 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction /// public class Startup : FunctionsStartup { + private Uri endpoint; + private AzureKeyCredential credential; + private string projectName; + private string deploymentName; + private string qnAServicerSubscriptionKey; + /// /// Application startup configuration. /// /// Webjobs builder. public override void Configure(IFunctionsHostBuilder builder) { - IQnAMakerClient qnaMakerClient = new QnAMakerClient(new ApiKeyServiceClientCredentials(Environment.GetEnvironmentVariable("QnAMakerSubscriptionKey"))) { Endpoint = Environment.GetEnvironmentVariable("QnAMakerApiUrl") }; - builder.Services.AddSingleton((provider) => new QnaServiceProvider( - provider.GetRequiredService(), provider.GetRequiredService>(), qnaMakerClient)); - builder.Services.AddSingleton(); - builder.Services.AddSingleton((provider) => new SearchServiceDataProvider(provider.GetRequiredService(), Environment.GetEnvironmentVariable("StorageConnectionString"))); - builder.Services.AddSingleton(new Common.Providers.ConfigurationDataProvider(Environment.GetEnvironmentVariable("StorageConnectionString"))); + this.endpoint = new Uri(Environment.GetEnvironmentVariable("QuestionAnswerApiUrl")); + this.credential = new AzureKeyCredential(Environment.GetEnvironmentVariable("QuestionAnswerSubscriptionKey")); + this.projectName = Environment.GetEnvironmentVariable("QuestionAnswerProjectName"); + this.deploymentName = Environment.GetEnvironmentVariable("DeploymentName"); + this.qnAServicerSubscriptionKey = Environment.GetEnvironmentVariable("QuestionAnswerSubscriptionKey"); + + builder.Services.AddSingleton((provider) => new QuestionAnswerServiceProvider( + provider.GetRequiredService(), + provider.GetRequiredService>(), + this.endpoint, + this.credential, + this.projectName, + this.deploymentName, + this.qnAServicerSubscriptionKey)); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton((provider) => new SearchServiceDataProvider(provider.GetRequiredService(), Environment.GetEnvironmentVariable("StorageConnectionString"))); + builder.Services.AddSingleton(new ConfigurationDataProvider(Environment.GetEnvironmentVariable("StorageConnectionString"))); builder.Services.AddSingleton(); var isGCCHybridDeployment = Convert.ToBoolean(Environment.GetEnvironmentVariable("IsGCCHybridDeployment")); builder.Services.AddSingleton((provider) => new KnowledgeBaseSearchService(Environment.GetEnvironmentVariable("SearchServiceName"), Environment.GetEnvironmentVariable("SearchServiceQueryApiKey"), Environment.GetEnvironmentVariable("SearchServiceAdminApiKey"), Environment.GetEnvironmentVariable("StorageConnectionString"), isGCCHybridDeployment)); diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/local.settings.json b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/local.settings.json index ea80e065..6393d847 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/local.settings.json +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.AzureFunction/local.settings.json @@ -3,12 +3,14 @@ "Values": { "AzureWebJobsStorage": "", "AzureWebJobsDashboard": "", - "QnAMakerSubscriptionKey": "", - "QnAMakerApiUrl": "", + "QuestionAnswerSubscriptionKey": "", + "QuestionAnswerApiUrl": "", "StorageConnectionString": "", "SearchServiceName": "", "SearchServiceAdminApiKey": "", "SearchServiceQueryApiKey": "", - "IsGCCHybridDeployment": false + "IsGCCHybridDeployment": false, + "QuestionAnswerProjectName": "", + "DeploymentName": "" } } \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/MessagingExtensionQnaCard.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/MessagingExtensionQnaCard.cs index f4fdbc81..d3add937 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/MessagingExtensionQnaCard.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/MessagingExtensionQnaCard.cs @@ -507,11 +507,6 @@ public static IList GetAllKbQuestionsCard(IList 1) - { - var createdAtvalue = qnaDocument.Metadata.FirstOrDefault(metadata => metadata.Name == Constants.MetadataCreatedAt)?.Value; - createdAt = createdAtvalue != null ? new DateTime(long.Parse(createdAtvalue, CultureInfo.InvariantCulture)) : default; - } string conversationId = string.Empty; string activityId = string.Empty; @@ -529,20 +524,8 @@ public static IList GetAllKbQuestionsCard(IList 1) - { - activityReferenceId = qnaDocument.Metadata.FirstOrDefault(metadata => metadata.Name == Constants.MetadataActivityReferenceId)?.Value; - conversationId = qnaDocument.Metadata.FirstOrDefault(metadata => metadata.Name == Constants.MetadataConversationId)?.Value; - activityId = activitiesData?.FirstOrDefault(activity => activity.ActivityReferenceId == activityReferenceId)?.ActivityId; - dateString = string.Format(CultureInfo.InvariantCulture, Strings.DateFormat, "{{DATE(" + createdAt.ToString(Rfc3339DateTimeFormat, CultureInfo.InvariantCulture) + ", SHORT)}}", "{{TIME(" + createdAt.ToString(Rfc3339DateTimeFormat, CultureInfo.InvariantCulture) + ")}}"); - metadataCreatedAt = qnaDocument.Metadata.FirstOrDefault(metadata => metadata.Name == Constants.MetadataCreatedAt)?.Value; - } - else - { - customMessage = string.IsNullOrEmpty(metadataCreatedAt) ? Strings.ManuallyAddedQuestionMessage : string.Empty; - } - + customMessage = string.IsNullOrEmpty(metadataCreatedAt) ? Strings.ManuallyAddedQuestionMessage : string.Empty; + var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0)) { Body = new List diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/ResponseCard.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/ResponseCard.cs index d20253c1..9645f042 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/ResponseCard.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/ResponseCard.cs @@ -9,7 +9,7 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Cards using System.Globalization; using System.Linq; using AdaptiveCards; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; + using global::Azure.AI.Language.QuestionAnswering; using Microsoft.Bot.Schema; using Microsoft.Teams.Apps.FAQPlusPlus.Common; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Helpers; @@ -33,14 +33,14 @@ public static class ResponseCard private const uint IconHeight = 32; /// - /// Construct the response card - when user asks a question to the QnA Maker through the bot. + /// Construct the response card - when user asks a question to the Question Answering through the bot. /// /// The response model. /// Actual question that the user has asked the bot. /// The base URI where the app is hosted. /// The response card payload. /// The response card to append to a message as an attachment. - public static Attachment GetCard(QnASearchResult response, string userQuestion, string appBaseUri, ResponseCardPayload payload) + public static Attachment GetCard(KnowledgeBaseAnswer response, string userQuestion, string appBaseUri, ResponseCardPayload payload) { bool isRichCard = false; AdaptiveSubmitActionData answerModel = new AdaptiveSubmitActionData(); @@ -84,7 +84,7 @@ public static Attachment GetCard(QnASearchResult response, string userQuestion, /// The response card payload. /// Boolean value where true represent it is a rich card while false represent it is a normal card. /// A list of adaptive elements which makes up the body of the adaptive card. - private static List BuildResponseCardBody(QnASearchResult response, string userQuestion, string answer, string appBaseUri, ResponseCardPayload payload, bool isRichCard) + private static List BuildResponseCardBody(KnowledgeBaseAnswer response, string userQuestion, string answer, string appBaseUri, ResponseCardPayload payload, bool isRichCard) { var textAlignment = CultureInfo.CurrentCulture.TextInfo.IsRightToLeft ? AdaptiveHorizontalAlignment.Right : AdaptiveHorizontalAlignment.Left; var answerModel = isRichCard ? JsonConvert.DeserializeObject(response?.Answer) : new AnswerModel(); @@ -144,11 +144,11 @@ private static List BuildResponseCardBody(QnASearchResult respo }); // If there follow up prompts, then the follow up prompts will render accordingly. - if (response?.Context.Prompts.Count > 0) + if (response?.Dialog.Prompts.Count > 0) { - List previousQuestions = BuildListOfPreviousQuestions((int)response.Id, userQuestion, answer, payload); + List previousQuestions = BuildListOfPreviousQuestions((int)response.QnaId, userQuestion, answer, payload); - foreach (var item in response.Context.Prompts) + foreach (var item in response.Dialog.Prompts) { var container = new AdaptiveContainer { @@ -203,7 +203,7 @@ private static List BuildResponseCardBody(QnASearchResult respo DisplayText = item.DisplayText, Text = item.DisplayText, }, - PreviousQuestions = new List { previousQuestions.Last() }, + PreviousQuestions = new List { previousQuestions.Last() }, IsPrompt = true, }, }, @@ -265,6 +265,8 @@ private static List BuildListOfActions(string userQuestion, stri return actionsList; } + // TOD :: Grey area :: Update the logic + /// /// This method will build the list of previous questions. /// @@ -273,13 +275,13 @@ private static List BuildListOfActions(string userQuestion, stri /// The knowledge base answer. /// The response card payload. /// A list of previous questions. - private static List BuildListOfPreviousQuestions(int id, string userQuestion, string answer, ResponseCardPayload payload) + private static List BuildListOfPreviousQuestions(int id, string userQuestion, string answer, ResponseCardPayload payload) { - var previousQuestions = payload.PreviousQuestions ?? new List(); + var previousQuestions = payload.PreviousQuestions ?? new List(); - previousQuestions.Add(new QnADTO + previousQuestions.Add(new KnowledgeBaseAnswerDTO { - Id = id, + QnaId = id, Questions = new List() { userQuestion, diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/SmeFeedbackCard.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/SmeFeedbackCard.cs index 670cd0c6..4f694af8 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/SmeFeedbackCard.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Cards/SmeFeedbackCard.cs @@ -93,7 +93,7 @@ public static Attachment GetCard(ShareFeedbackCardPayload data, TeamsChannelAcco }); } - // Question asked fact and view article show card is available when feedback is on QnA Maker response. + // Question asked fact and view article show card is available when feedback is on Question Answering response. if (!string.IsNullOrWhiteSpace(data.KnowledgeBaseAnswer) && !string.IsNullOrWhiteSpace(data.UserQuestion)) { smeFeedbackCard.Body.Add(new AdaptiveFactSet diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/BotCommandResolver.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/BotCommandResolver.cs index d55b4e57..12f7a571 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/BotCommandResolver.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/BotCommandResolver.cs @@ -33,7 +33,7 @@ public class BotCommandResolver : IBotCommandResolver private const string ChangeStatus = "change status"; private readonly IConfigurationDataProvider configurationProvider; - private readonly IQnaServiceProvider qnaServiceProvider; + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; private readonly IActivityStorageProvider activityStorageProvider; private readonly ILogger logger; private readonly IQnAPairServiceFacade qnaPairService; @@ -45,14 +45,14 @@ public class BotCommandResolver : IBotCommandResolver /// /// Configuration Provider. /// Activity storage provider. - /// QnA service provider. + /// Question answer service provider. /// Instance to send logs to the Application Insights service. /// Instance of QnA pair service class to call add/update/get QnA pair. /// Represents a set of key/value application configuration properties for FaqPlusPlus bot. /// Conversation service to send adaptive card in personal and teams chat. public BotCommandResolver( Common.Providers.IConfigurationDataProvider configurationProvider, - IQnaServiceProvider qnaServiceProvider, + IQuestionAnswerServiceProvider questionAnswerServiceProvider, IActivityStorageProvider activityStorageProvider, IQnAPairServiceFacade qnaPairService, IOptionsMonitor botSettings, @@ -60,7 +60,7 @@ public BotCommandResolver( IConversationService conversationService) { this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - this.qnaServiceProvider = qnaServiceProvider ?? throw new ArgumentNullException(nameof(qnaServiceProvider)); + this.questionAnswerServiceProvider = questionAnswerServiceProvider ?? throw new ArgumentNullException(nameof(questionAnswerServiceProvider)); this.activityStorageProvider = activityStorageProvider ?? throw new ArgumentNullException(nameof(activityStorageProvider)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.qnaPairService = qnaPairService ?? throw new ArgumentNullException(nameof(qnaPairService)); @@ -116,7 +116,7 @@ public async Task ResolveBotCommandInPersonalChatAsync( } else { - this.logger.LogInformation("Sending input to QnAMaker"); + this.logger.LogInformation("Sending input to QuestionAnswer"); await this.qnaPairService.GetReplyToQnAAsync(turnContext, message).ConfigureAwait(false); } } @@ -162,7 +162,7 @@ public async Task ResolveBotCommandInTeamChatAsync( case Constants.DeleteCommand: this.logger.LogInformation($"Delete card submit in channel {message.Value?.ToString()}"); - await QnaHelper.DeleteQnaPair(turnContext, this.qnaServiceProvider, this.activityStorageProvider, this.logger, cancellationToken).ConfigureAwait(false); + await QnaHelper.DeleteQnaPair(turnContext, this.questionAnswerServiceProvider, this.activityStorageProvider, this.logger, cancellationToken).ConfigureAwait(false); break; case Constants.NoCommand: @@ -180,7 +180,7 @@ public async Task ResolveBotCommandInTeamChatAsync( if (((ErrorResponseException)ex).Response.StatusCode == HttpStatusCode.BadRequest) { var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(Constants.KnowledgeBaseEntityId).ConfigureAwait(false); - var hasPublished = await this.qnaServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); + var hasPublished = await this.questionAnswerServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); // Check if knowledge base has not published yet. if (!hasPublished) diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/ConversationService.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/ConversationService.cs index 03c1d2f1..cbeaaab9 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/ConversationService.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/ConversationService.cs @@ -166,7 +166,7 @@ public async Task SendAdaptiveCardInPersonalChatAsync( if (payload.IsPrompt) { - this.logger.LogInformation("Sending input to QnAMaker for prompt"); + this.logger.LogInformation("Sending input to QuestionAnswer for prompt"); await this.qnaPairServiceFacade.GetReplyToQnAAsync(turnContext, message).ConfigureAwait(false); } else diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/QnAPairServiceFacade.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/QnAPairServiceFacade.cs index 295fc83e..4ccf6ce6 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/QnAPairServiceFacade.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Components/QnAPairServiceFacade.cs @@ -8,7 +8,7 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Components using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; + using global::Azure.AI.Language.QuestionAnswering; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; @@ -23,7 +23,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Components using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; using Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity; using Newtonsoft.Json.Linq; - using ErrorResponseException = Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models.ErrorResponseException; /// /// Class that handles get/add/update of QnA pairs. @@ -32,7 +31,7 @@ public class QnAPairServiceFacade : IQnAPairServiceFacade { private readonly IConfigurationDataProvider configurationProvider; private readonly IActivityStorageProvider activityStorageProvider; - private readonly IQnaServiceProvider qnaServiceProvider; + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; private readonly ILogger logger; private readonly string appBaseUri; private readonly BotSettings options; @@ -42,18 +41,18 @@ public class QnAPairServiceFacade : IQnAPairServiceFacade /// /// Configuration Provider. /// Activity storage provider. - /// QnA service provider. + /// Question answer service provider. /// Represents a set of key/value application configuration properties for FaqPlusPlus bot.ram> /// Instance to send logs to the Application Insights service. public QnAPairServiceFacade( Common.Providers.IConfigurationDataProvider configurationProvider, - IQnaServiceProvider qnaServiceProvider, + IQuestionAnswerServiceProvider questionAnswerServiceProvider, IActivityStorageProvider activityStorageProvider, IOptionsMonitor botSettings, ILogger logger) { this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - this.qnaServiceProvider = qnaServiceProvider ?? throw new ArgumentNullException(nameof(qnaServiceProvider)); + this.questionAnswerServiceProvider = questionAnswerServiceProvider ?? throw new ArgumentNullException(nameof(questionAnswerServiceProvider)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.activityStorageProvider = activityStorageProvider ?? throw new ArgumentNullException(nameof(activityStorageProvider)); if (botSettings == null) @@ -79,8 +78,6 @@ public async Task GetReplyToQnAAsync( try { - var queryResult = new QnASearchResultList(); - ResponseCardPayload payload = new ResponseCardPayload(); if (!string.IsNullOrEmpty(message.ReplyToId) && (message.Value != null)) @@ -88,13 +85,13 @@ public async Task GetReplyToQnAAsync( payload = ((JObject)message.Value).ToObject(); } - queryResult = await this.qnaServiceProvider.GenerateAnswerAsync(question: text, isTestKnowledgeBase: false, payload.PreviousQuestions?.Last().Id.ToString(), payload.PreviousQuestions?.Last().Questions.First()).ConfigureAwait(false); + var queryResult = await this.questionAnswerServiceProvider.GenerateAnswerAsync(question: text, isTestKnowledgeBase: false, payload.PreviousQuestions?.Last().QnaId.ToString(), payload.PreviousQuestions?.Last().Questions.First()).ConfigureAwait(false); bool answerFound = false; - foreach (QnASearchResult answerData in queryResult.Answers) + foreach (KnowledgeBaseAnswer answerData in queryResult.Answers) { - bool isContextOnly = answerData.Context?.IsContextOnly ?? false; - if (answerData.Id != -1 && + bool isContextOnly = answerData.Dialog?.IsContextOnly ?? false; + if (answerData.QnaId != -1 && ((!isContextOnly && payload.PreviousQuestions == null) || (isContextOnly && payload.PreviousQuestions != null))) { @@ -116,7 +113,7 @@ public async Task GetReplyToQnAAsync( if (((ErrorResponseException)ex).Response.StatusCode == System.Net.HttpStatusCode.BadRequest) { var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(Constants.KnowledgeBaseEntityId).ConfigureAwait(false); - var hasPublished = await this.qnaServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); + var hasPublished = await this.questionAnswerServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); // Check if knowledge base has not published yet. if (!hasPublished) @@ -141,8 +138,8 @@ public async Task GetReplyToQnAAsync( /// A of type bool where true represents question and answer pair updated successfully while false indicates failure in updating the question and answer pair. public async Task SaveQnAPairAsync(ITurnContext turnContext, string answer, AdaptiveSubmitActionData qnaPairEntity) { - QnASearchResult searchResult; - var qnaAnswerResponse = await this.qnaServiceProvider.GenerateAnswerAsync(qnaPairEntity.OriginalQuestion, qnaPairEntity.IsTestKnowledgeBase).ConfigureAwait(false); + KnowledgeBaseAnswer searchResult; + var qnaAnswerResponse = await this.questionAnswerServiceProvider.GenerateAnswerAsync(qnaPairEntity.OriginalQuestion, qnaPairEntity.IsTestKnowledgeBase).ConfigureAwait(false); searchResult = qnaAnswerResponse.Answers.FirstOrDefault(); bool isSameQuestion = false; @@ -154,11 +151,14 @@ public async Task SaveQnAPairAsync(ITurnContext turnContext, string answer } // Edit the QnA pair if the question is exist in the knowledgebase & exactly the same question on which we are performing the action. - if (searchResult.Id != -1 && isSameQuestion) + if (searchResult.QnaId != -1 && isSameQuestion) { - int qnaPairId = searchResult.Id.Value; - await this.qnaServiceProvider.UpdateQnaAsync(qnaPairId, answer, turnContext.Activity.From.AadObjectId, qnaPairEntity.UpdatedQuestion, qnaPairEntity.OriginalQuestion).ConfigureAwait(false); - this.logger.LogInformation($"Question updated by: {turnContext.Activity.Conversation.AadObjectId}"); + int qnaPairId = searchResult.QnaId.Value; + + this.logger.LogInformation($"Started : Question updated by: {turnContext.Activity.Conversation.AadObjectId} QnA Pair Id : {qnaPairId}"); + await this.questionAnswerServiceProvider.UpdateQnaAsync(qnaPairId, answer, turnContext.Activity.From.AadObjectId, qnaPairEntity.UpdatedQuestion, qnaPairEntity.OriginalQuestion, searchResult.Metadata).ConfigureAwait(false); + this.logger.LogInformation($"Completed : Question updated by: {turnContext.Activity.Conversation.AadObjectId} QnA Pair Id : {qnaPairId}"); + Attachment attachment = new Attachment(); if (qnaPairEntity.IsRichCard) { @@ -173,7 +173,7 @@ public async Task SaveQnAPairAsync(ITurnContext turnContext, string answer attachment = MessagingExtensionQnaCard.ShowNormalCard(qnaPairEntity, turnContext.Activity.From.Name, actionPerformed: Strings.LastEditedText); } - var activityId = this.activityStorageProvider.GetAsync(qnaAnswerResponse.Answers.First().Metadata.FirstOrDefault(x => x.Name == Constants.MetadataActivityReferenceId)?.Value).Result.FirstOrDefault().ActivityId; + var activityId = this.activityStorageProvider.GetAsync(qnaAnswerResponse.Answers.First().Metadata.FirstOrDefault(x => x.Key == Constants.MetadataActivityReferenceId).Value).Result.FirstOrDefault().ActivityId; var updateCardActivity = new Activity(ActivityTypes.Message) { Id = activityId ?? throw new ArgumentNullException(nameof(activityId)), @@ -231,7 +231,7 @@ await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync( } else { - var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); + var hasQuestionExist = await this.questionAnswerServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); if (hasQuestionExist) { // Shows the error message on task module, if question already exist. @@ -265,7 +265,7 @@ await TaskModuleActivity.GetTaskModuleResponseAsync(this.CardResponseAsync( } else { - var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); + var hasQuestionExist = await this.questionAnswerServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); if (hasQuestionExist) { // Shows the error message on task module, if question already exist. @@ -315,7 +315,7 @@ private async Task CardResponseAsync( else { // Check if question exist in the production/test knowledgebase & exactly the same question. - var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); + var hasQuestionExist = await this.questionAnswerServiceProvider.QuestionExistsInKbAsync(postedQnaPairEntity.UpdatedQuestion).ConfigureAwait(false); // Edit the question if it doesn't exist in the test knowledgebse. if (hasQuestionExist) diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Helpers/QnaHelper.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Helpers/QnaHelper.cs index cb6e3320..10c4315e 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Helpers/QnaHelper.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Helpers/QnaHelper.cs @@ -9,7 +9,7 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Helpers using System.Linq; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; + using global::Azure.AI.Language.QuestionAnswering; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; using Microsoft.Extensions.Logging; @@ -59,24 +59,25 @@ public static string BuildCombinedDescriptionAsync(AdaptiveSubmitActionData ques /// Delete qna pair. /// /// Turn context. - /// Qna Service provider. + /// Question answer service provider. /// Activity Storage Provider. /// Logger. /// Cancellation Token. /// A task that represents the work queued to execute. public static async Task DeleteQnaPair( ITurnContext turnContext, - IQnaServiceProvider qnaServiceProvider, + IQuestionAnswerServiceProvider questionAnswerServiceProvider, IActivityStorageProvider activityStorageProvider, ILogger logger, CancellationToken cancellationToken) { - QnASearchResult searchResult; + KnowledgeBaseAnswer searchResult; Attachment attachment; var activity = (Activity)turnContext.Activity; var activityValue = ((JObject)activity.Value).ToObject(); - QnASearchResultList qnaAnswerResponse = await qnaServiceProvider.GenerateAnswerAsync(activityValue?.OriginalQuestion, isTestKnowledgeBase: false).ConfigureAwait(false); + + AnswersResult qnaAnswerResponse = await questionAnswerServiceProvider.GenerateAnswerAsync(activityValue?.OriginalQuestion, isTestKnowledgeBase: false).ConfigureAwait(false); bool isSameQuestion = false; searchResult = qnaAnswerResponse.Answers.First(); @@ -89,12 +90,12 @@ public static async Task DeleteQnaPair( } // Delete the QnA pair if question exist in the knowledgebase & exactly the same question user wants to delete. - if (searchResult.Id != -1 && isSameQuestion) + if (searchResult.QnaId != -1 && isSameQuestion) { - await qnaServiceProvider.DeleteQnaAsync(searchResult.Id.Value).ConfigureAwait(false); + await questionAnswerServiceProvider.DeleteQnaAsync(searchResult.QnaId.Value).ConfigureAwait(false); logger.LogInformation($"Question deleted by: {activity.Conversation.AadObjectId}"); attachment = MessagingExtensionQnaCard.DeletedEntry(activityValue?.OriginalQuestion, searchResult.Answer, activity.From.Name, activityValue?.UpdateHistoryData); - ActivityEntity activityEntity = new ActivityEntity { ActivityReferenceId = searchResult.Metadata.FirstOrDefault(x => x.Name == Constants.MetadataActivityReferenceId)?.Value }; + ActivityEntity activityEntity = new ActivityEntity { ActivityReferenceId = searchResult.Metadata.FirstOrDefault(x => x.Key == Constants.MetadataActivityReferenceId).Value }; bool operationStatus = await activityStorageProvider.DeleteActivityEntityAsync(activityEntity).ConfigureAwait(false); if (!operationStatus) @@ -115,9 +116,9 @@ public static async Task DeleteQnaPair( else { // check if question and answer is present in unpublished version. - qnaAnswerResponse = await qnaServiceProvider.GenerateAnswerAsync(activityValue?.OriginalQuestion, isTestKnowledgeBase: true).ConfigureAwait(false); + qnaAnswerResponse = await questionAnswerServiceProvider.GenerateAnswerAsync(activityValue?.OriginalQuestion, isTestKnowledgeBase: true).ConfigureAwait(false); - if (qnaAnswerResponse?.Answers?.First().Id != -1) + if (qnaAnswerResponse?.Answers?.First().QnaId != -1) { await turnContext.SendActivityAsync(MessageFactory.Text(string.Format(CultureInfo.InvariantCulture, Strings.WaitMessage, activityValue?.OriginalQuestion))).ConfigureAwait(false); } @@ -132,7 +133,7 @@ public static async Task DeleteQnaPair( /// Qna service provider. /// Question. /// A of type bool where true represents question is already exist in knowledgebase while false indicates the question does not exist in knowledgebase. - public static async Task QuestionExistsInKbAsync(this IQnaServiceProvider provider, string question) + public static async Task QuestionExistsInKbAsync(this IQuestionAnswerServiceProvider provider, string question) { var prodHasQuestion = await provider.HasQuestionAsync(question, isTestKnowledgeBase: false).ConfigureAwait(false); if (prodHasQuestion) @@ -150,7 +151,7 @@ public static async Task QuestionExistsInKbAsync(this IQnaServiceProvider /// Question. /// Knowledgebase. /// A of type bool where true represents question is already exist in knowledgebase while false indicates the question does not exist in knowledgebase. - private static async Task HasQuestionAsync(this IQnaServiceProvider provider, string question, bool isTestKnowledgeBase) + private static async Task HasQuestionAsync(this IQuestionAnswerServiceProvider provider, string question, bool isTestKnowledgeBase) { var qnaPreviewAnswerResponse = await provider.GenerateAnswerAsync(question, isTestKnowledgeBase).ConfigureAwait(false); var questionAnswerResponse = qnaPreviewAnswerResponse.Answers.FirstOrDefault(); diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Microsoft.Teams.Apps.FAQPlusPlus.Common.csproj b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Microsoft.Teams.Apps.FAQPlusPlus.Common.csproj index 59268032..effc1634 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Microsoft.Teams.Apps.FAQPlusPlus.Common.csproj +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Microsoft.Teams.Apps.FAQPlusPlus.Common.csproj @@ -10,8 +10,9 @@ + + - diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AnswerModel.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AnswerModel.cs index fed949eb..08a3d0a6 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AnswerModel.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AnswerModel.cs @@ -7,7 +7,7 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Models using Newtonsoft.Json; /// - /// Represents the FAQPlus QnA Maker answer response model. + /// Represents the FAQPlus Question Answering answer response model. /// public class AnswerModel { diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AskAnExpertCardPayload.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AskAnExpertCardPayload.cs index 47fb0b67..5d209ebf 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AskAnExpertCardPayload.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AskAnExpertCardPayload.cs @@ -21,13 +21,13 @@ public class AskAnExpertCardPayload : TeamsAdaptiveSubmitActionData /// /// Gets or sets the question for the expert being asked by the user through Response card- - /// Response Card: Response generated by the bot to user question by calling QnA Maker service. + /// Response Card: Response generated by the bot to user question by calling Question Answering service. /// public string UserQuestion { get; set; } /// /// Gets or sets the answer for the expert- Answer sent to the SME team along with feedback - /// provided by the user on response given by bot calling QnA Maker service. + /// provided by the user on response given by bot calling Question Answering service. /// public string KnowledgeBaseAnswer { get; set; } } diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AzureSearchEntity.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AzureSearchEntity.cs index db4c85fe..2c797ac4 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AzureSearchEntity.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/AzureSearchEntity.cs @@ -6,7 +6,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Models { using System; using System.Collections.Generic; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; using Microsoft.Azure.Search; using Microsoft.Azure.Search.Models; using Newtonsoft.Json; @@ -50,12 +49,6 @@ public class AzureSearchEntity [JsonProperty("questions")] public IList Questions { get; set; } - /// - /// Gets or sets Metadata. - /// - [JsonProperty("metadata")] - public IList Metadata { get; set; } - /// /// Gets or sets CreatedDate. /// diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/Configuration/QuestionAnswerSettings.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/Configuration/QuestionAnswerSettings.cs new file mode 100644 index 00000000..3f6f3819 --- /dev/null +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/Configuration/QuestionAnswerSettings.cs @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// +namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Models.Configuration +{ + /// + /// Provides app settings related to QuestionAnswerServiceProvider. + /// + public class QuestionAnswerSettings + { + /// + /// Gets or Sets scorethreshold. + /// + public string ScoreThreshold { get; set; } + } +} diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ConfigurationEntityTypes.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ConfigurationEntityTypes.cs index f468ee5d..2edca4e6 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ConfigurationEntityTypes.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ConfigurationEntityTypes.cs @@ -29,8 +29,8 @@ public static class ConfigurationEntityTypes public const string HelpTabText = "HelpTabText"; /// - /// QnaMaker endpoint key entity. + /// QuestionAnswer endpoint key entity. /// - public const string QnAMakerEndpointKey = "QnaMakerEndpointKey"; + public const string QuestionAnswerEndpointKey = "QuestionAnswerEndpointKey"; } } diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/KnowledgeBaseAnswerDTO.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/KnowledgeBaseAnswerDTO.cs new file mode 100644 index 00000000..02bba7bd --- /dev/null +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/KnowledgeBaseAnswerDTO.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Models +{ + + /// + /// This KnowledgeBaseAnswerDTO entity is used for azure search. + /// + public class KnowledgeBaseAnswerDTO + { + /// + /// Gets or sets the questions. + /// + public IList Questions { get; set; } + + /// + /// Gets or sets the Answer. + /// + [JsonProperty("Answer")] + public string Answer { get; set; } + + /// + /// Gets or sets the Confidence. + /// + public double? Confidence { get; set; } + + /// + /// Gets or sets QnaId. + /// + [JsonProperty("Id")] + public int? QnaId { get; set; } + + /// + /// Gets or sets the CreatedDate. + /// + [JsonProperty("CpdatedDateTime")] + public string CreatedDate { get; set; } + + /// + /// Gets or sets the UpdatedDate. + /// + [JsonProperty("LastUpdatedDateTime")] + public string UpdatedDate { get; set; } + + /// + /// Gets or sets the Source. + /// + public string Source { get; set; } + + /// + /// Gets or sets the Metadata. + /// + public IDictionary Metadata { get; set; } + + } +} diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ResponseCardPayload.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ResponseCardPayload.cs index 6fb23a75..0ec4068b 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ResponseCardPayload.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ResponseCardPayload.cs @@ -5,7 +5,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Models { using System.Collections.Generic; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; /// /// Represents the payload of a response card. @@ -25,7 +24,7 @@ public class ResponseCardPayload : TeamsAdaptiveSubmitActionData /// /// Gets or sets list of previous questions when a follow up prompt is selected. /// - public List PreviousQuestions { get; set; } + public List PreviousQuestions { get; set; } /// /// Gets or sets a value indicating whether question is from prompt. diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ShareFeedbackCardPayload.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ShareFeedbackCardPayload.cs index 69af91fa..f4d42d9c 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ShareFeedbackCardPayload.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Models/ShareFeedbackCardPayload.cs @@ -22,13 +22,13 @@ public class ShareFeedbackCardPayload : TeamsAdaptiveSubmitActionData /// /// Gets or sets the question for the expert being asked by the user through Response card- - /// Response Card: Response generated by the bot to user question by calling QnA Maker service. + /// Response Card: Response generated by the bot to user question by calling Question Answering service. /// public string UserQuestion { get; set; } /// /// Gets or sets the answer for the expert- Answer sent to the SME team along with feedback - /// provided by the user on response given by bot calling QnA Maker service. + /// provided by the user on response given by bot calling Question Answering service. /// public string KnowledgeBaseAnswer { get; set; } } diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ar.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ar.resx index 16860dc6..452c5d96 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ar.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ar.resx @@ -229,7 +229,7 @@ إليك ما عثرت عليه: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. مغلق: @@ -587,7 +587,7 @@ سؤال مضاف يدويًا - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering التاريخ diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.de.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.de.resx index 69e80ada..8ec037db 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.de.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.de.resx @@ -229,7 +229,7 @@ ich bin Ihr freundlicher Bot für Fragen und Antworten, mit dem Sie Personen unt Das habe ich gefunden: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Geschlossen: @@ -587,7 +587,7 @@ ich bin Ihr freundlicher Bot für Fragen und Antworten, mit dem Sie Personen unt Frage manuell hinzugefügt - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Datum diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.es.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.es.resx index d26f4746..8c8314ed 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.es.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.es.resx @@ -229,7 +229,7 @@ Soy su bot de Preguntas y respuestas que proporciona soporte a gente que interac Esto es lo que he encontrado: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Cerrado: @@ -587,7 +587,7 @@ Soy su bot de Preguntas y respuestas que proporciona soporte a gente que interac Pregunta agregada manualmente - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Fecha diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.fr.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.fr.resx index 6d658989..57aa0ecc 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.fr.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.fr.resx @@ -229,7 +229,7 @@ Je suis votre robot Questions et réponses convivial qui vous aide à prendre en Voici ce que j’ai trouvé : - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Fermé : @@ -587,7 +587,7 @@ Je suis votre robot Questions et réponses convivial qui vous aide à prendre en Question ajoutée manuellement - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Date diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.he.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.he.resx index df1949b7..0e591155 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.he.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.he.resx @@ -229,7 +229,7 @@ הנה מה שמצאתי: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. נסגר: @@ -587,7 +587,7 @@ שאלה שנוספה ידנית - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering תאריך diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ja.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ja.resx index 3e074e38..6c14d117 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ja.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ja.resx @@ -229,7 +229,7 @@ 以下の問題が見つかりました: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. 終了日時: @@ -587,7 +587,7 @@ 手動で追加された質問 - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering 日付 diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ko.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ko.resx index f458b58f..a119618c 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ko.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ko.resx @@ -229,7 +229,7 @@ 검색 결과: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. 닫힘: @@ -587,7 +587,7 @@ 수동으로 추가된 질문 - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering 날짜 diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.pt-BR.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.pt-BR.resx index 624b6716..1b3ca8f1 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.pt-BR.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.pt-BR.resx @@ -229,7 +229,7 @@ Sou o seu bot de QA que ajuda você a fornecer suporte a pessoas que interagem c Veja o que encontrei: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Fechado: @@ -587,7 +587,7 @@ Sou o seu bot de QA que ajuda você a fornecer suporte a pessoas que interagem c Pergunta manualmente adicionada - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Data diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.resx index 627504b3..901e71d4 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.resx @@ -229,7 +229,7 @@ I am your friendly Q&A bot that helps you provide support to people who inte Here's what I found: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Closed: @@ -587,7 +587,7 @@ I am your friendly Q&A bot that helps you provide support to people who inte Manually added question - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Date diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ru.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ru.resx index c5483c78..aa97f2e9 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ru.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.ru.resx @@ -229,7 +229,7 @@ Вот что мне удалось найти: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. Закрыт: @@ -587,7 +587,7 @@ Вопрос, добавленный вручную - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering Дата diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-CN.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-CN.resx index df3cb0c6..149a7816 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-CN.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-CN.resx @@ -229,7 +229,7 @@ 下面是我找到的内容: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. 已关闭: @@ -587,7 +587,7 @@ 已手动添加问题 - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering 日期 diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-TW.resx b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-TW.resx index 8606e0b7..c0b0b444 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-TW.resx +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Properties/Strings.zh-TW.resx @@ -229,7 +229,7 @@ 以下是我找到的: - Response header-letting the user know that a response is generated to their request from QnA Maker. + Response header-letting the user know that a response is generated to their request from Question Answering. 結案時間: @@ -587,7 +587,7 @@ 已手動新增問題 - Question answer pair added manually in QnA Maker + Question answer pair added manually in Question Answering 日期 diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQnaServiceProvider.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQuestionAnswerServiceProvider.cs similarity index 69% rename from Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQnaServiceProvider.cs rename to Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQuestionAnswerServiceProvider.cs index 5003fe73..08cb199f 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQnaServiceProvider.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/IQuestionAnswerServiceProvider.cs @@ -1,18 +1,20 @@ -// +// // Copyright (c) Microsoft. All rights reserved. // namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers { + using System; using System.Collections.Generic; using System.Threading.Tasks; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; + using global::Azure; + using global::Azure.AI.Language.QuestionAnswering; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; /// - /// Qna maker service provider interface. + /// Question Answering service provider interface. /// - public interface IQnaServiceProvider + public interface IQuestionAnswerServiceProvider { /// /// This method is used to add QnA pair in Kb. @@ -23,21 +25,33 @@ public interface IQnaServiceProvider /// Conversation id. /// Activity reference id. /// Operation status of performed action. - Task AddQnaAsync(string question, string combinedDescription, string createdBy, string conversationId, string activityReferenceId); + Task>> AddQnaAsync(string question, string combinedDescription, string createdBy, string conversationId, string activityReferenceId); + + /// + /// Update Qna pair in knowledge base. + /// + /// Question id. + /// Answer text. + /// Updated by user. + /// Updated question text. + /// Original question text. + /// The metadata. + /// Perfomed action task. + Task>> UpdateQnaAsync(int questionId, string answer, string updatedBy, string updatedQuestion, string question, IReadOnlyDictionary metadata); /// /// This method is used to delete Qna pair from KB. /// /// Question id. /// Delete response. - Task DeleteQnaAsync(int questionId); + Task>> DeleteQnaAsync(int questionId); /// /// This method returns the downloaded knowledgebase documents. /// /// Knowledgebase Id. /// Json string. - Task> DownloadKnowledgebaseAsync(string knowledgeBaseId); + Task> DownloadKnowledgebaseAsync(string knowledgeBaseId); /// /// Get answer from knowledgebase for a given question. @@ -47,32 +61,19 @@ public interface IQnaServiceProvider /// Id of previous question. /// Previous question information. /// QnaSearchResult object as response. - Task GenerateAnswerAsync(string question, bool isTestKnowledgeBase, string previousQnAId = null, string previousUserQuery = null); - - /// - /// This method is used to update Qna pair in Kb. - /// - /// Question id. - /// Answer text. - /// Updated by user. - /// Updated question text. - /// Original question text. - /// Task of updated action. - Task UpdateQnaAsync(int questionId, string answer, string updatedBy, string updatedQuestion, string question); + Task GenerateAnswerAsync(string question, bool isTestKnowledgeBase, string previousQnAId = null, string previousUserQuery = null); /// /// Checks whether knowledgebase need to be published. /// - /// Knowledgebase id. /// A of type bool where true represents knowledgebase need to be published while false indicates knowledgebase not need to be published. - Task GetPublishStatusAsync(string knowledgeBaseId); + Task GetPublishStatusAsync(); /// /// Method is used to publish knowledgebase. /// - /// Knowledgebase Id. /// Task for published data. - Task PublishKnowledgebaseAsync(string knowledgeBaseId); + Task> PublishKnowledgebaseAsync(); /// /// Get knowledgebase published information. diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QnaServiceProvider.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QnaServiceProvider.cs deleted file mode 100644 index 0f1d594d..00000000 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QnaServiceProvider.cs +++ /dev/null @@ -1,266 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Threading.Tasks; - using System.Web; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; - using Microsoft.Extensions.Options; - using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; - using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models.Configuration; - - /// - /// Qna maker service provider class. - /// - public class QnaServiceProvider : IQnaServiceProvider - { - /// - /// Environment type. - /// - private const string EnvironmentType = "Prod"; - - /// - /// Maximum number of answers to be returned by the QnA maker for a given question. - /// - private const int MaxNumberOfAnswersToFetch = 3; - - private readonly IConfigurationDataProvider configurationProvider; - private readonly IQnAMakerClient qnaMakerClient; - private readonly IQnAMakerRuntimeClient qnaMakerRuntimeClient; - - /// - /// Represents a set of key/value application configuration properties. - /// - private readonly QnAMakerSettings options; - - /// - /// Initializes a new instance of the class. - /// - /// ConfigurationProvider fetch and store information in storage table. - /// A set of key/value application configuration properties. - /// Qna service client. - /// Qna service runtime client. - public QnaServiceProvider(IConfigurationDataProvider configurationProvider, IOptionsMonitor optionsAccessor, IQnAMakerClient qnaMakerClient, IQnAMakerRuntimeClient qnaMakerRuntimeClient) - { - this.configurationProvider = configurationProvider; - this.qnaMakerClient = qnaMakerClient; - this.options = optionsAccessor.CurrentValue; - this.qnaMakerRuntimeClient = qnaMakerRuntimeClient; - } - - /// - /// Initializes a new instance of the class. - /// - /// ConfigurationProvider fetch and store information in storage table. - /// A set of key/value application configuration properties. - /// Qna service client. - public QnaServiceProvider(IConfigurationDataProvider configurationProvider, IOptionsMonitor optionsAccessor, IQnAMakerClient qnaMakerClient) - { - this.configurationProvider = configurationProvider; - this.qnaMakerClient = qnaMakerClient; - this.options = optionsAccessor.CurrentValue; - } - - /// - /// This method is used to add QnA pair in Kb. - /// - /// Question text. - /// Answer text. - /// Created by user. - /// Conversation id. - /// Activity reference id refer to activityid in storage table. - /// Operation state as task. - public async Task AddQnaAsync(string question, string combinedDescription, string createdBy, string conversationId, string activityReferenceId) - { - var knowledgeBase = await this.configurationProvider.GetSavedEntityDetailAsync(ConfigurationEntityTypes.KnowledgeBaseId).ConfigureAwait(false); - - // Update knowledgebase. - return await this.qnaMakerClient.Knowledgebase.UpdateAsync(knowledgeBase, new UpdateKbOperationDTO - { - // Create JSON of changes. - Add = new UpdateKbOperationDTOAdd - { - QnaList = new List - { - new QnADTO - { - Questions = new List { question?.Trim() }, - Answer = combinedDescription?.Trim(), - Metadata = new List() - { - new MetadataDTO() { Name = Constants.MetadataCreatedAt, Value = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture) }, - new MetadataDTO() { Name = Constants.MetadataCreatedBy, Value = createdBy }, - new MetadataDTO() { Name = Constants.MetadataConversationId, Value = HttpUtility.UrlEncode(conversationId) }, - new MetadataDTO() { Name = Constants.MetadataActivityReferenceId, Value = activityReferenceId }, - }, - }, - }, - }, - Update = null, - Delete = null, - }).ConfigureAwait(false); - } - - /// - /// Update Qna pair in knowledge base. - /// - /// Question id. - /// Answer text. - /// Updated by user. - /// Updated question text. - /// Original question text. - /// Perfomed action task. - public async Task UpdateQnaAsync(int questionId, string answer, string updatedBy, string updatedQuestion, string question) - { - var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(ConfigurationEntityTypes.KnowledgeBaseId).ConfigureAwait(false); - var questions = default(UpdateQnaDTOQuestions); - if (!string.IsNullOrEmpty(updatedQuestion?.Trim())) - { - questions = (updatedQuestion?.ToUpperInvariant().Trim() == question?.ToUpperInvariant().Trim()) ? null - : new UpdateQnaDTOQuestions() - { - Add = new List { updatedQuestion.Trim() }, - Delete = new List { question.Trim() }, - }; - } - - // Update knowledgebase. - await this.qnaMakerClient.Knowledgebase.UpdateAsync(knowledgeBaseId, new UpdateKbOperationDTO - { - // Create JSON of changes. - Add = null, - Update = new UpdateKbOperationDTOUpdate() - { - QnaList = new List() - { - new UpdateQnaDTO() - { - Id = questionId, - Source = Constants.Source, - Answer = answer?.Trim(), - Questions = questions, - Metadata = new UpdateQnaDTOMetadata() - { - Add = new List() - { - new MetadataDTO() { Name = Constants.MetadataUpdatedAt, Value = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture) }, - new MetadataDTO() { Name = Constants.MetadataUpdatedBy, Value = updatedBy }, - }, - }, - }, - }, - }, - Delete = null, - }).ConfigureAwait(false); - } - - /// - /// This method is used to delete Qna pair from KB. - /// - /// Question id. - /// Perfomed action task. - public async Task DeleteQnaAsync(int questionId) - { - var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(ConfigurationEntityTypes.KnowledgeBaseId).ConfigureAwait(false); - - // to delete a question and answer based on id. - await this.qnaMakerClient.Knowledgebase.UpdateAsync(knowledgeBaseId, new UpdateKbOperationDTO - { - // Create JSON of changes. - Add = null, - Update = null, - Delete = new UpdateKbOperationDTODelete() - { - Ids = new List() { questionId }, - }, - }).ConfigureAwait(false); - } - - /// - /// Get answer from knowledgebase for a given question. - /// - /// Question text. - /// Prod or test. - /// Id of previous question. - /// Previous question information. - /// QnaSearchResultList result as response. - public async Task GenerateAnswerAsync(string question, bool isTestKnowledgeBase, string previousQnAId = null, string previousUserQuery = null) - { - var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(ConfigurationEntityTypes.KnowledgeBaseId).ConfigureAwait(false); - - QueryDTO queryDTO = new QueryDTO() - { - IsTest = isTestKnowledgeBase, - Question = question?.Trim(), - ScoreThreshold = Convert.ToDouble(this.options.ScoreThreshold, CultureInfo.InvariantCulture), - Top = MaxNumberOfAnswersToFetch, - }; - - if (previousQnAId != null && previousUserQuery != null) - { - queryDTO.Context = new QueryDTOContext - { - PreviousQnaId = previousQnAId, - PreviousUserQuery = previousUserQuery, - }; - } - - return await this.qnaMakerRuntimeClient.Runtime.GenerateAnswerAsync(knowledgeBaseId, queryDTO).ConfigureAwait(false); - } - - /// - /// This method returns the downloaded knowledgebase documents. - /// - /// Knowledgebase Id. - /// List of question and answer document object. - public async Task> DownloadKnowledgebaseAsync(string knowledgeBaseId) - { - var qnaDocuments = await this.qnaMakerClient.Knowledgebase.DownloadAsync(knowledgeBaseId, environment: EnvironmentType).ConfigureAwait(false); - return qnaDocuments.QnaDocuments; - } - - /// - /// Checks whether knowledgebase need to be published. - /// - /// Knowledgebase id. - /// A of type bool where true represents knowledgebase need to be published while false indicates knowledgebase not need to be published. - public async Task GetPublishStatusAsync(string knowledgeBaseId) - { - var qnaDocuments = await this.qnaMakerClient.Knowledgebase.GetDetailsAsync(knowledgeBaseId).ConfigureAwait(false); - if (qnaDocuments != null && qnaDocuments.LastChangedTimestamp != null && qnaDocuments.LastPublishedTimestamp != null) - { - return Convert.ToDateTime(qnaDocuments.LastChangedTimestamp) > Convert.ToDateTime(qnaDocuments.LastPublishedTimestamp); - } - - return true; - } - - /// - /// Method is used to publish knowledgebase. - /// - /// Knowledgebase Id. - /// Task for published data. - public async Task PublishKnowledgebaseAsync(string knowledgeBaseId) - { - await this.qnaMakerClient.Knowledgebase.PublishAsync(knowledgeBaseId).ConfigureAwait(false); - } - - /// - /// Get knowledgebase published information. - /// - /// Knowledgebase id. - /// A of type bool where true represents knowledgebase has published atleast once while false indicates that knowledgebase has not published yet. - public async Task GetInitialPublishedStatusAsync(string knowledgeBaseId) - { - KnowledgebaseDTO qnaDocuments = await this.qnaMakerClient.Knowledgebase.GetDetailsAsync(knowledgeBaseId).ConfigureAwait(false); - return !string.IsNullOrEmpty(qnaDocuments.LastPublishedTimestamp); - } - } -} diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QuestionAnswerServiceProvider.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QuestionAnswerServiceProvider.cs new file mode 100644 index 00000000..42f1053a --- /dev/null +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/QuestionAnswerServiceProvider.cs @@ -0,0 +1,302 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text.Json; + using System.Threading.Tasks; + using System.Web; + using global::Azure; + using global::Azure.AI.Language.QuestionAnswering; + using global::Azure.AI.Language.QuestionAnswering.Authoring; + using global::Azure.Core; + using Microsoft.Extensions.Options; + using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; + using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models.Configuration; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// Question Answering service provider class. + /// + public class QuestionAnswerServiceProvider : IQuestionAnswerServiceProvider + { + /// + /// Maximum number of answers to be returned by the Question Answering for a given question. + /// + private const int MaxNumberOfAnswersToFetch = 3; + + private readonly string projectName; + private readonly string deploymentName = "production"; + private readonly string testDeploymentName = "test"; + + private readonly Uri endpoint; + private readonly AzureKeyCredential credential; + private readonly IConfigurationDataProvider configurationProvider; + private readonly string qnaServiceSubscriptionKey; + private readonly QuestionAnsweringClient client; + private readonly QuestionAnsweringProject project; + private readonly QuestionAnsweringProject unPublishedProject; + + private readonly QuestionAnsweringAuthoringClient questionAnsweringAuthoringClient; + + /// + /// Represents a set of key/value application configuration properties. + /// + private readonly QuestionAnswerSettings options; + + /// + /// Initializes a new instance of the class. + /// + /// ConfigurationProvider fetch and store information in storage table. + /// A set of key/value application configuration properties. + /// The end point. + /// The credential. + /// The project name. + /// The deployment name. + /// The qna service subscription key. + public QuestionAnswerServiceProvider( + IConfigurationDataProvider configurationProvider, + IOptionsMonitor optionsAccessor, + Uri endpoint, + AzureKeyCredential credential, + string projectName, + string deploymentName, + string qnaServiceSubscriptionKey) + { + this.endpoint = endpoint ?? throw new ArgumentNullException(nameof(endpoint)); + this.credential = credential ?? throw new ArgumentNullException(nameof(credential)); + this.projectName = projectName ?? throw new ArgumentNullException(nameof(projectName)); + this.deploymentName = deploymentName ?? throw new ArgumentNullException(nameof(deploymentName)); + this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); + this.options = optionsAccessor.CurrentValue ?? throw new ArgumentNullException(nameof(optionsAccessor)); + this.qnaServiceSubscriptionKey = qnaServiceSubscriptionKey ?? throw new ArgumentNullException(nameof(qnaServiceSubscriptionKey)); ; + + this.endpoint = endpoint; + this.credential = credential; + this.projectName = projectName; + this.deploymentName = deploymentName; + this.qnaServiceSubscriptionKey = qnaServiceSubscriptionKey; + + this.configurationProvider = configurationProvider; + this.options = optionsAccessor.CurrentValue; + + this.client = new QuestionAnsweringClient(this.endpoint, this.credential); + this.project = new QuestionAnsweringProject(this.projectName, this.deploymentName); + this.unPublishedProject = new QuestionAnsweringProject(this.projectName, this.testDeploymentName); + this.questionAnsweringAuthoringClient = new QuestionAnsweringAuthoringClient(this.endpoint, this.credential); + } + + /// + /// This method is used to add QnA pair in Kb. + /// + /// Question text. + /// Answer text. + /// Created by user. + /// Conversation id. + /// Activity reference id refer to activityid in storage table. + /// Operation state as task. + public async Task>> AddQnaAsync(string question, string combinedDescription, string createdBy, string conversationId, string activityReferenceId) + { + RequestContent updateQnasRequestContent = RequestContent.Create( + new[] + { + new + { + op = "add", + value = new + { + questions = new[] {question }, + answer = combinedDescription?.Trim(), + metadata = new + { + createdat = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture), + createdby = createdBy, + conversationid = HttpUtility.UrlEncode(conversationId), + activityreferenceid = activityReferenceId, + }, + }, + }, + }); + + return await this.questionAnsweringAuthoringClient.UpdateQnasAsync(WaitUntil.Started, this.projectName, updateQnasRequestContent); + } + + /// + /// Update Qna pair in knowledge base. + /// + /// Question id. + /// Answer text. + /// Updated by user. + /// Updated question text. + /// Original question text. + /// The metadata. + /// Perfomed action task. + public async Task>> UpdateQnaAsync(int questionId, string answer, string updatedBy, string updatedQuestion, string question, IReadOnlyDictionary metadata) + { + string[] questions = null; + if (!string.IsNullOrEmpty(updatedQuestion?.Trim())) + { + questions = (updatedQuestion?.ToUpperInvariant().Trim() == question?.ToUpperInvariant().Trim()) ? new[] { question } + : new[] { updatedQuestion }; + } + + RequestContent updateQnasRequestContent = RequestContent.Create( + new[] + { + new + { + op = "replace", + value = new + { + id = questionId, + Source = Constants.Source, + questions = questions, + answer = answer?.Trim(), + metadata = new + { + createdat = metadata.TryGetValue("createdat", out string createdat) ? createdat : DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture), + createdby = metadata.TryGetValue("createdby", out string createdby) ? createdby : string.Empty, + conversationid = metadata.TryGetValue("conversationid", out string conversationid) ? conversationid : string.Empty, + activityreferenceid = metadata.TryGetValue("activityreferenceid", out string activityreferenceid) ? activityreferenceid : string.Empty, + updatedat = DateTime.UtcNow.Ticks.ToString(CultureInfo.InvariantCulture), + updatedby = updatedBy, + }, + }, + }, + }); + + return await this.questionAnsweringAuthoringClient.UpdateQnasAsync(WaitUntil.Started, this.projectName, updateQnasRequestContent); + } + + /// + /// This method is used to delete Qna pair from KB. + /// + /// Question id. + /// Perfomed action task. + public async Task>> DeleteQnaAsync(int questionId) + { + RequestContent updateQnasRequestContent = RequestContent.Create( + new[] + { + new + { + op = "delete", + value = new + { + id = questionId, + }, + }, + }); + + return await this.questionAnsweringAuthoringClient.UpdateQnasAsync(WaitUntil.Started, this.projectName, updateQnasRequestContent).ConfigureAwait(false); ; + } + + /// + /// Get answer from knowledgebase for a given question. + /// + /// Question text. + /// Prod or test. + /// Id of previous question. + /// Previous question information. + /// QnaSearchResultList result as response. + public async Task GenerateAnswerAsync(string question, bool isTestKnowledgeBase, string previousQnAId = null, string previousUserQuery = null) + { + string questions = question?.Trim(); + int previousQnAIds = Convert.ToInt32(previousQnAId); + var context = new KnowledgeBaseAnswerContext(previousQnAIds); + var project = isTestKnowledgeBase ? this.unPublishedProject : this.project; + context.PreviousQuestion = previousUserQuery; + + AnswersOptions options = new AnswersOptions + { + ConfidenceThreshold = Convert.ToDouble(this.options.ScoreThreshold, CultureInfo.InvariantCulture), + Size = MaxNumberOfAnswersToFetch, + AnswerContext = context, + }; + + Response responseFollowUp = await this.client.GetAnswersAsync(questions, project, options).ConfigureAwait(false); + + return responseFollowUp.Value; + + } + + /// + /// This method returns the downloaded knowledgebase documents. + /// + /// Knowledgebase Id. + /// List of question and answer document object. + public async Task> DownloadKnowledgebaseAsync(string knowledgeBaseId) + { + IEnumerable knowledgebaseList = null; + + var qnaDocuments = await this.questionAnsweringAuthoringClient.ExportAsync(WaitUntil.Completed, this.projectName, "json").ConfigureAwait(false); + JsonDocument operationValueJson = JsonDocument.Parse(qnaDocuments.Value); + string exportedFileUrl = operationValueJson.RootElement.GetProperty("resultUrl").ToString(); + + using (var client = new HttpClient()) + { + client.BaseAddress = new Uri(exportedFileUrl); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", this.qnaServiceSubscriptionKey); + HttpResponseMessage response = client.GetAsync(exportedFileUrl).Result; + if (response.IsSuccessStatusCode) + { + JsonDocument exportedFileResult = JsonDocument.Parse(response.Content.ReadAsStringAsync().Result); + var exportedAssests = exportedFileResult.RootElement.GetProperty("Assets").GetProperty("Qnas").ToString(); + + knowledgebaseList = JsonConvert.DeserializeObject>(exportedAssests.ToString()); + } + } + + return knowledgebaseList; + } + + /// + /// Checks whether knowledgebase need to be published. + /// + /// A of type bool where true represents knowledgebase need to be published while false indicates knowledgebase not need to be published. + public async Task GetPublishStatusAsync() + { + var qnaDocuments = await this.questionAnsweringAuthoringClient.GetProjectDetailsAsync(this.projectName).ConfigureAwait(false); + var formatter = new BinaryData(qnaDocuments.Content); + var responseJson = JObject.Parse(formatter.ToString()); + + if (qnaDocuments != null && qnaDocuments != null && responseJson["lastDeployedDateTime"] != null) + { + return Convert.ToDateTime(responseJson["lastModifiedDateTime"]) > Convert.ToDateTime(responseJson["lastDeployedDateTime"]); + } + + return true; + } + + /// + /// Method is used to publish knowledgebase. + /// + /// Task for published data. + public async Task> PublishKnowledgebaseAsync() + { + return await this.questionAnsweringAuthoringClient.DeployProjectAsync(WaitUntil.Completed, this.projectName, this.deploymentName).ConfigureAwait(false); + } + + /// + /// Get knowledgebase published information. + /// + /// Knowledgebase id. + /// A of type bool where true represents knowledgebase has published atleast once while false indicates that knowledgebase has not published yet. + public async Task GetInitialPublishedStatusAsync(string knowledgeBaseId) + { + var qnaDocuments = await this.questionAnsweringAuthoringClient.GetProjectDetailsAsync(this.projectName).ConfigureAwait(false); + var formatter = new BinaryData(qnaDocuments.Content); + var responseJson = JObject.Parse(formatter.ToString()); + + return !string.IsNullOrEmpty(responseJson["lastDeployedDateTime"].ToString()); + } + } +} diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/SearchServiceDataProvider.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/SearchServiceDataProvider.cs index ef1bfd70..e51a8fe2 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/SearchServiceDataProvider.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/Providers/SearchServiceDataProvider.cs @@ -6,10 +6,9 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers { using System; using System.Collections.Generic; - using System.Globalization; using System.Linq; using System.Threading.Tasks; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; + using global::Azure.AI.Language.QuestionAnswering; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; @@ -25,17 +24,18 @@ public class SearchServiceDataProvider : ISearchServiceDataProvider /// private const string FaqPlusQnAFile = "/faqplusqnadata.json"; - private readonly IQnaServiceProvider qnaServiceProvider; + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; + private readonly string storageConnectionString; /// /// Initializes a new instance of the class. /// - /// question and answer ServiceProvider. + /// question and answer ServiceProvider. /// Azure web job storage. - public SearchServiceDataProvider(IQnaServiceProvider qnaServiceProvider, string storageConnectionString) + public SearchServiceDataProvider(IQuestionAnswerServiceProvider questionAnswerServiceProvider, string storageConnectionString) { - this.qnaServiceProvider = qnaServiceProvider; + this.questionAnswerServiceProvider = questionAnswerServiceProvider; this.storageConnectionString = storageConnectionString; } @@ -46,8 +46,9 @@ public SearchServiceDataProvider(IQnaServiceProvider qnaServiceProvider, string /// Task of downloaded data. public async Task SetupAzureSearchDataAsync(string knowledgeBaseId) { - IEnumerable qnaDocuments = await this.qnaServiceProvider.DownloadKnowledgebaseAsync(knowledgeBaseId).ConfigureAwait(false); - string azureJson = this.GenerateFormattedJson(qnaDocuments); + var downloadedQnaDocuments = await this.questionAnswerServiceProvider.DownloadKnowledgebaseAsync(knowledgeBaseId).ConfigureAwait(false); + + string azureJson = this.GenerateFormattedJson(downloadedQnaDocuments); await this.AddDataToBlobStorageAsync(azureJson).ConfigureAwait(false); } @@ -56,24 +57,27 @@ public async Task SetupAzureSearchDataAsync(string knowledgeBaseId) /// /// Qna documents. /// Create json format for search. - private string GenerateFormattedJson(IEnumerable qnaDocuments) + private string GenerateFormattedJson(IEnumerable qnaDocuments) { IList searchEntityList = new List(); foreach (var item in qnaDocuments) { - var createdDate = item.Metadata.FirstOrDefault(prop => prop.Name == Constants.MetadataCreatedAt); - var updatedDate = item.Metadata.FirstOrDefault(prop => prop.Name == Constants.MetadataUpdatedAt); + var createdDate = item.Metadata.FirstOrDefault(prop => prop.Key == Constants.MetadataCreatedAt).Value; + var updatedDate = item.Metadata.FirstOrDefault(prop => prop.Key == Constants.MetadataUpdatedAt).Value; searchEntityList.Add( new AzureSearchEntity() { - Id = item.Id.ToString(), + Id = item.QnaId.ToString(), Source = item.Source, Questions = item.Questions, Answer = item.Answer, - CreatedDate = createdDate != null ? new DateTimeOffset(new DateTime(Convert.ToInt64(createdDate.Value))) : new DateTimeOffset(DateTime.MinValue, TimeSpan.Zero), - UpdatedDate = updatedDate != null ? new DateTimeOffset(new DateTime(Convert.ToInt64(updatedDate.Value))) : new DateTimeOffset(DateTime.MinValue, TimeSpan.Zero), - Metadata = item.Metadata, + CreatedDate = createdDate != null ? new DateTimeOffset(new DateTime(Convert.ToInt64(createdDate))) : new DateTimeOffset(DateTime.MinValue, TimeSpan.Zero), + UpdatedDate = updatedDate != null ? new DateTimeOffset(new DateTime(Convert.ToInt64(updatedDate))) : new DateTimeOffset(DateTime.MinValue, TimeSpan.Zero), + + // Create the serach service without MetaData. + // MetaData in AzureSerach if of collection(Complex type) whereas the Json from KB is of Dictionary. Its throws error while serialization. + // Metadata = item.Metadata }); } diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/MessagingExtensionActivity.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/MessagingExtensionActivity.cs index 045fb16f..0bbf6a3b 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/MessagingExtensionActivity.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/MessagingExtensionActivity.cs @@ -12,7 +12,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights.DataContracts; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models; using Microsoft.Bot.Builder; using Microsoft.Bot.Schema; using Microsoft.Bot.Schema.Teams; @@ -30,7 +29,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - using ErrorResponseException = Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.Models.ErrorResponseException; /// /// Class that handles messaging extension query, fetch, submit activity in expert's team chat. @@ -79,7 +77,8 @@ public class MessagingExtensionActivity : IMessagingExtensionActivity private readonly string appBaseUri; private readonly IConfigurationDataProvider configurationProvider; - private readonly IQnaServiceProvider qnaServiceProvider; + + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; private readonly ILogger logger; private readonly IActivityStorageProvider activityStorageProvider; private readonly ISearchService searchService; @@ -96,7 +95,7 @@ public class MessagingExtensionActivity : IMessagingExtensionActivity /// /// Configuration Provider. /// Activity storage provider. - /// Question and answer maker service provider. + /// Question and answer service provider. /// SearchService dependency injection. /// Bot adapter dependency injection. /// IMemoryCache dependency injection. @@ -107,7 +106,7 @@ public class MessagingExtensionActivity : IMessagingExtensionActivity /// Notifies in expert's Team chat. public MessagingExtensionActivity( Common.Providers.IConfigurationDataProvider configurationProvider, - IQnaServiceProvider qnaServiceProvider, + IQuestionAnswerServiceProvider questionAnswerServiceProvider, IActivityStorageProvider activityStorageProvider, ISearchService searchService, BotFrameworkAdapter botAdapter, @@ -119,7 +118,9 @@ public MessagingExtensionActivity( INotificationService notificationService) { this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - this.qnaServiceProvider = qnaServiceProvider ?? throw new ArgumentNullException(nameof(qnaServiceProvider)); + + this.questionAnswerServiceProvider = questionAnswerServiceProvider ?? throw new ArgumentNullException(nameof(questionAnswerServiceProvider)); + this.activityStorageProvider = activityStorageProvider ?? throw new ArgumentNullException(nameof(activityStorageProvider)); this.searchService = searchService ?? throw new ArgumentNullException(nameof(searchService)); this.botAdapter = botAdapter ?? throw new ArgumentNullException(nameof(botAdapter)); @@ -318,13 +319,6 @@ public async Task SubmitActionAsync( } catch (Exception ex) { - if (((ErrorResponseException)ex).Body?.Error?.Code == ErrorCodeType.QuotaExceeded) - { - this.logger.LogError(ex, "QnA storage limit exceeded and is not able to save the qna pair. Please contact your system administrator to provision additional storage space."); - await turnContext.SendActivityAsync("QnA storage limit exceeded and is not able to save the qna pair. Please contact your system administrator to provision additional storage space.").ConfigureAwait(false); - return null; - } - this.logger.LogError(ex, "Error while submitting new question via messaging extension"); await turnContext.SendActivityAsync(Strings.ErrorMessage).ConfigureAwait(false); throw ex; @@ -567,7 +561,7 @@ private async Task AddQuestionCardResponseAsyn try { // Check if question exist in the production/test knowledgebase & exactly the same question. - var hasQuestionExist = await this.qnaServiceProvider.QuestionExistsInKbAsync(qnaPairEntity.UpdatedQuestion).ConfigureAwait(false); + var hasQuestionExist = await this.questionAnswerServiceProvider.QuestionExistsInKbAsync(qnaPairEntity.UpdatedQuestion).ConfigureAwait(false); // Question already exist in knowledgebase. if (hasQuestionExist) @@ -586,7 +580,7 @@ private async Task AddQuestionCardResponseAsyn if (((ErrorResponseException)ex).Response.StatusCode == HttpStatusCode.BadRequest) { var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(Constants.KnowledgeBaseEntityId).ConfigureAwait(false); - var hasPublished = await this.qnaServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); + var hasPublished = await this.questionAnswerServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); // Check if knowledge base has not published yet. // If knowledge base has published then throw the error otherwise contiue to add the question & answer pair. @@ -600,7 +594,11 @@ private async Task AddQuestionCardResponseAsyn // Save the question in the knowledgebase. var activityReferenceId = Guid.NewGuid().ToString(); - await this.qnaServiceProvider.AddQnaAsync(qnaPairEntity.UpdatedQuestion?.Trim(), combinedDescription, turnContext.Activity.From.AadObjectId, turnContext.Activity.Conversation.Id, activityReferenceId).ConfigureAwait(false); + + this.logger.LogInformation($"Started : Save the question in the knowledgebase by: {turnContext.Activity.From.AadObjectId}"); + await this.questionAnswerServiceProvider.AddQnaAsync(qnaPairEntity.UpdatedQuestion?.Trim(), combinedDescription, turnContext.Activity.From.AadObjectId, turnContext.Activity.Conversation.Id, activityReferenceId).ConfigureAwait(false); + this.logger.LogInformation($"Completed : Save the question in the knowledgebase by : {turnContext.Activity.From.AadObjectId}"); + qnaPairEntity.IsTestKnowledgeBase = true; ResourceResponse activityResponse; diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/TaskModuleActivity.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/TaskModuleActivity.cs index 202ca57b..2e9454a5 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/TaskModuleActivity.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Common/TeamsActivity/TaskModuleActivity.cs @@ -17,7 +17,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Properties; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; - using Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -37,7 +36,9 @@ public class TaskModuleActivity : ITaskModuleActivity private const int TaskModuleWidth = 500; private readonly IConfigurationDataProvider configurationProvider; - private readonly IQnaServiceProvider qnaServiceProvider; + + private readonly IQuestionAnswerServiceProvider questionAnswerServiceProvider; + private readonly ILogger logger; private readonly IQnAPairServiceFacade qnaPairServiceFacade; @@ -45,17 +46,17 @@ public class TaskModuleActivity : ITaskModuleActivity /// Initializes a new instance of the class. /// /// Configuration Provider. - /// QnA service provider. + /// Question answer service provider. /// Instance to send logs to the Application Insights service. /// Instance of QnA pair service class to call add/update/get QnA pair. public TaskModuleActivity( Common.Providers.IConfigurationDataProvider configurationProvider, - IQnaServiceProvider qnaServiceProvider, + IQuestionAnswerServiceProvider questionAnswerServiceProvider, ILogger logger, IQnAPairServiceFacade qnaPairServiceFacade) { this.configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider)); - this.qnaServiceProvider = qnaServiceProvider ?? throw new ArgumentNullException(nameof(qnaServiceProvider)); + this.questionAnswerServiceProvider = questionAnswerServiceProvider ?? throw new ArgumentNullException(nameof(questionAnswerServiceProvider)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.qnaPairServiceFacade = qnaPairServiceFacade ?? throw new ArgumentNullException(nameof(qnaPairServiceFacade)); } @@ -145,7 +146,7 @@ public async Task OnSubmitAsync( if (((ErrorResponseException)ex?.InnerException)?.Response.StatusCode == HttpStatusCode.BadRequest) { var knowledgeBaseId = await this.configurationProvider.GetSavedEntityDetailAsync(Constants.KnowledgeBaseEntityId).ConfigureAwait(false); - var hasPublished = await this.qnaServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); + var hasPublished = await this.questionAnswerServiceProvider.GetInitialPublishedStatusAsync(knowledgeBaseId).ConfigureAwait(false); // Check if knowledge base has not published yet. if (!hasPublished) diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/App_Start/AutofacConfig.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/App_Start/AutofacConfig.cs index 3edebe26..0edafda0 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/App_Start/AutofacConfig.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/App_Start/AutofacConfig.cs @@ -9,7 +9,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Configuration using System.Web.Mvc; using Autofac; using Autofac.Integration.Mvc; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; /// @@ -31,32 +30,10 @@ public static IContainer RegisterDependencies() .As() .SingleInstance(); - var qnaMakerClient = new QnAMakerClient( - new ApiKeyServiceClientCredentials( - ConfigurationManager.AppSettings["QnAMakerSubscriptionKey"])) - { Endpoint = StripRouteFromQnAMakerEndpoint(ConfigurationManager.AppSettings["QnAMakerApiEndpointUrl"]) }; - - builder.Register(c => qnaMakerClient) - .As() - .SingleInstance(); - var container = builder.Build(); DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); return container; } - - // Strip the route suffix from the endpoint - private static string StripRouteFromQnAMakerEndpoint(string endpoint) - { - const string apiRoute = "/qnamaker/v4.0"; - - if (endpoint.EndsWith(apiRoute, System.StringComparison.OrdinalIgnoreCase)) - { - endpoint = endpoint.Substring(0, endpoint.Length - apiRoute.Length); - } - - return endpoint; - } } } \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Controllers/HomeController.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Controllers/HomeController.cs index ef110ca3..ff1a64a0 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Controllers/HomeController.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Controllers/HomeController.cs @@ -9,7 +9,6 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Configuration.Controllers using System.Threading.Tasks; using System.Web; using System.Web.Mvc; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Models; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Providers; @@ -20,17 +19,14 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus.Configuration.Controllers public class HomeController : Controller { private readonly IConfigurationDataProvider configurationPovider; - private readonly IQnAMakerClient qnaMakerClient; /// /// Initializes a new instance of the class. /// /// configurationPovider dependency injection. - /// qnaMakerClient dependency injection. - public HomeController(IConfigurationDataProvider configurationPovider, IQnAMakerClient qnaMakerClient) + public HomeController(IConfigurationDataProvider configurationPovider) { this.configurationPovider = configurationPovider; - this.qnaMakerClient = qnaMakerClient; } /// @@ -113,8 +109,8 @@ public async Task UpsertKnowledgeBaseIdAsync(string knowledgeBaseI } /// - /// Validate knowledgebase id from QnA Maker service first and then proceed to save it on success. - /// The QnA Maker endpoint key is also refreshed as part of this process. + /// Validate knowledgebase id from Question Answering service first and then proceed to save it on success. + /// The Question Answering endpoint key is also refreshed as part of this process. /// /// knowledgeBaseId is the unique string to identify knowledgebase. /// View. @@ -122,21 +118,13 @@ public async Task UpsertKnowledgeBaseIdAsync(string knowledgeBaseI [ValidateAntiForgeryToken] public async Task ValidateAndSaveKnowledgeBaseIdAsync(string knowledgeBaseId) { - bool isValidKnowledgeBaseId = await this.IsKnowledgeBaseIdValid(knowledgeBaseId).ConfigureAwait(false); - if (isValidKnowledgeBaseId) + var endpointRefreshStatus = true; + if (!endpointRefreshStatus) { - var endpointRefreshStatus = await this.RefreshQnAMakerEndpointKeyAsync().ConfigureAwait(false); - if (!endpointRefreshStatus) - { - return new HttpStatusCodeResult(HttpStatusCode.InternalServerError, "Sorry, unable to save the QnAMaker endpoint key due to an internal error. Try again."); - } - - return await this.UpsertKnowledgeBaseIdAsync(knowledgeBaseId).ConfigureAwait(false); - } - else - { - return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "The provided knowledgebase id is not valid."); + return new HttpStatusCodeResult(HttpStatusCode.InternalServerError, "Sorry, unable to save the QuestionAnswer endpoint key due to an internal error. Try again."); } + + return await this.UpsertKnowledgeBaseIdAsync(knowledgeBaseId).ConfigureAwait(false); } /// @@ -238,45 +226,5 @@ private static string ParseTeamIdFromDeepLink(string teamIdDeepLink) return HttpUtility.UrlDecode(match.Groups[1].Value); } - - /// - /// Check if provided knowledgebase id is valid or not. - /// - /// knowledgebase id. - /// A of type bool where true represents provided knowledgebase id is valid while false indicates provided knowledgebase id is not valid. - private async Task IsKnowledgeBaseIdValid(string knowledgeBaseId) - { - try - { - var knowledgebaseDetail = await this.qnaMakerClient.Knowledgebase.GetDetailsAsync(knowledgeBaseId).ConfigureAwait(false); - return knowledgebaseDetail.Id == knowledgeBaseId; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch -#pragma warning restore CA1031 // Do not catch general exception types - { - return false; - } - } - - /// - /// Update the saved endpoint key. - /// - /// A of type bool where true represents updated data is saved or updated successfully while false indicates failure in saving or updating the updated data. - private async Task RefreshQnAMakerEndpointKeyAsync() - { - try - { - var endpointKeys = await this.qnaMakerClient.EndpointKeys.GetKeysAsync().ConfigureAwait(false); - await this.configurationPovider.UpsertEntityAsync(endpointKeys.PrimaryEndpointKey, ConfigurationEntityTypes.QnAMakerEndpointKey).ConfigureAwait(false); - return true; - } -#pragma warning disable CA1031 // Do not catch general exception types - catch -#pragma warning restore CA1031 // Do not catch general exception types - { - return false; - } - } } } \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Microsoft.Teams.Apps.FAQPlusPlus.Configuration.csproj b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Microsoft.Teams.Apps.FAQPlusPlus.Configuration.csproj index 882d4b01..c5016572 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Microsoft.Teams.Apps.FAQPlusPlus.Configuration.csproj +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Microsoft.Teams.Apps.FAQPlusPlus.Configuration.csproj @@ -80,9 +80,6 @@ ..\packages\Microsoft.AspNet.TelemetryCorrelation.1.0.8\lib\net45\Microsoft.AspNet.TelemetryCorrelation.dll - - ..\packages\Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.2.0.1\lib\net461\Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker.dll - ..\packages\Microsoft.Azure.KeyVault.Core.3.0.5\lib\net461\Microsoft.Azure.KeyVault.Core.dll diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Models/KnowledgeBaseViewModel.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Models/KnowledgeBaseViewModel.cs index d1d960b1..b3748122 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Models/KnowledgeBaseViewModel.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Models/KnowledgeBaseViewModel.cs @@ -13,11 +13,11 @@ public class KnowledgeBaseViewModel /// /// Gets or sets knowledge base id text box to be used in View. /// - [Required(ErrorMessage = "Enter knowledge base id.")] + [Required(ErrorMessage = "Enter project name of QA service.")] [MinLength(1)] [DataType(DataType.Text)] - [Display(Name = "Knowledge base ID")] - [RegularExpression(@"(\S)+", ErrorMessage = "Enter knowledge base ID which should not contain any whitespace.")] + [Display(Name = "Project Name")] + [RegularExpression(@"(\S)+", ErrorMessage = "Enter project name which should not contain any whitespace.")] public string KnowledgeBaseId { get; set; } } } \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Views/Home/_KnowledgeBase.cshtml b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Views/Home/_KnowledgeBase.cshtml index 19ee14f0..9eccf48a 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Views/Home/_KnowledgeBase.cshtml +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Views/Home/_KnowledgeBase.cshtml @@ -9,7 +9,7 @@ *
- @Html.TextBoxFor(model => model.KnowledgeBaseId, htmlAttributes: new { @class = "form-control knowledgeBaseIdTextBox toggleTextBox", @placeholder = "Enter knowledge base ID." }) + @Html.TextBoxFor(model => model.KnowledgeBaseId, htmlAttributes: new { @class = "form-control knowledgeBaseIdTextBox toggleTextBox", @placeholder = "Enter project name of Question and Answer service." }) @Html.ValidationMessageFor(model => model.KnowledgeBaseId, "", htmlAttributes: new { @class = "error-message" })
diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Web.config b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Web.config index fe519f99..a68a081d 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Web.config +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/Web.config @@ -1,48 +1,50 @@ - + - - - - - - + + + + + + - - - - - - + + + + + + - + + + - - + + - - + + + - - + + @@ -50,335 +52,335 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + - + - + - - + + \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/libman.json b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/libman.json index 3a3f4726..ad87160d 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/libman.json +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/libman.json @@ -3,7 +3,7 @@ "defaultProvider": "cdnjs", "libraries": [ { - "library": "jquery@3.4.1", + "library": "jquery@3.6.0", "destination": "lib/jquery/", "files": [ "jquery.min.js", @@ -61,5 +61,5 @@ "library": "jquery-ajax-unobtrusive@3.2.6", "destination": "lib/jquery-ajax-unobtrusive/" } -] + ] } \ No newline at end of file diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/packages.config b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/packages.config index 7cf5dff0..bd907576 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/packages.config +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus.Configuration/packages.config @@ -16,7 +16,6 @@ - diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus/Microsoft.Teams.Apps.FAQPlusPlus.csproj b/Source/Microsoft.Teams.Apps.FAQPlusPlus/Microsoft.Teams.Apps.FAQPlusPlus.csproj index 12afe25f..01ea3a72 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus/Microsoft.Teams.Apps.FAQPlusPlus.csproj +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus/Microsoft.Teams.Apps.FAQPlusPlus.csproj @@ -9,7 +9,6 @@ - @@ -48,4 +47,5 @@ + diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus/Startup.cs b/Source/Microsoft.Teams.Apps.FAQPlusPlus/Startup.cs index 2a5eb01b..ac424cba 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus/Startup.cs +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus/Startup.cs @@ -8,11 +8,9 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus using System.Collections.Generic; using System.Globalization; using System.Linq; - using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Localization; - using Microsoft.Azure.CognitiveServices.Knowledge.QnAMaker; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Integration.AspNet.Core; using Microsoft.Bot.Connector.Authentication; @@ -28,12 +26,19 @@ namespace Microsoft.Teams.Apps.FAQPlusPlus using Microsoft.Teams.Apps.FAQPlusPlus.Common.Components; using Microsoft.Teams.Apps.FAQPlusPlus.Common.Extensions; using Microsoft.Teams.Apps.FAQPlusPlus.Common.TeamsActivity; + using global::Azure; /// /// This a Startup class for this Bot. /// public class Startup { + private readonly Uri endpoint; + private readonly AzureKeyCredential credential; + private readonly string projectName; + private readonly string deploymentName; + private readonly string qnAServicerSubscriptionKey; + /// /// Initializes a new instance of the class. /// @@ -41,7 +46,12 @@ public class Startup public Startup(IConfiguration configuration) { this.Configuration = configuration; - } + this.qnAServicerSubscriptionKey = this.Configuration.GetValue("QuestionAnswerSubscriptionKey"); + this.endpoint = new Uri(this.Configuration.GetValue("QuestionAnswerApiEndpointUrl")); + this.credential = new AzureKeyCredential(this.Configuration.GetValue("QuestionAnswerSubscriptionKey")); + this.projectName = this.Configuration.GetValue("QuestionAnswerProjectName"); + this.deploymentName = this.Configuration.GetValue("DeploymentName"); + } /// /// Gets Configurations Interfaces. @@ -95,9 +105,9 @@ public void ConfigureServices(IServiceCollection services) knowledgeBaseSettings.IsGCCHybridDeployment = this.Configuration.GetValue("IsGCCHybridDeployment"); }); - services.Configure(qnAMakerSettings => + services.Configure(questionAnswerSettings => { - qnAMakerSettings.ScoreThreshold = this.Configuration["ScoreThreshold"]; + questionAnswerSettings.ScoreThreshold = this.Configuration["ScoreThreshold"]; }); services.Configure(botSettings => @@ -118,16 +128,22 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - IQnAMakerClient qnaMakerClient = new QnAMakerClient(new ApiKeyServiceClientCredentials(this.Configuration["QnAMakerSubscriptionKey"])) { Endpoint = this.Configuration["QnAMakerApiEndpointUrl"] }; - string endpointKey = Task.Run(() => qnaMakerClient.EndpointKeys.GetKeysAsync()).Result.PrimaryEndpointKey; + services.AddSingleton((provider) => new QuestionAnswerServiceProvider( + provider.GetRequiredService(), + provider.GetRequiredService>(), + this.endpoint, + this.credential, + this.projectName, + this.deploymentName, + this.qnAServicerSubscriptionKey)); - services.AddSingleton((provider) => new QnaServiceProvider( - provider.GetRequiredService(), - provider.GetRequiredService>(), - qnaMakerClient, - new QnAMakerRuntimeClient(new EndpointKeyServiceClientCredentials(endpointKey)) { RuntimeEndpoint = this.Configuration["QnAMakerHostUrl"] })); services.AddSingleton((provider) => new ActivityStorageProvider(provider.GetRequiredService>())); - services.AddSingleton((provider) => new KnowledgeBaseSearchService(this.Configuration["SearchServiceName"], this.Configuration["SearchServiceQueryApiKey"], this.Configuration["SearchServiceAdminApiKey"], this.Configuration["StorageConnectionString"], this.Configuration.GetValue("IsGCCHybridDeployment"))); + services.AddSingleton((provider) => new KnowledgeBaseSearchService( + this.Configuration["SearchServiceName"], + this.Configuration["SearchServiceQueryApiKey"], + this.Configuration["SearchServiceAdminApiKey"], + this.Configuration["StorageConnectionString"], + this.Configuration.GetValue("IsGCCHybridDeployment"))); services.AddSingleton(); services.AddSingleton(); @@ -164,4 +180,4 @@ public void ConfigureServices(IServiceCollection services) }); } } -} \ No newline at end of file +} diff --git a/Source/Microsoft.Teams.Apps.FAQPlusPlus/appsettings.json b/Source/Microsoft.Teams.Apps.FAQPlusPlus/appsettings.json index 8388a67c..e46cf16a 100644 --- a/Source/Microsoft.Teams.Apps.FAQPlusPlus/appsettings.json +++ b/Source/Microsoft.Teams.Apps.FAQPlusPlus/appsettings.json @@ -4,9 +4,9 @@ "UserAppPassword": "", "ExpertAppId": "", "ExpertAppPassword": "", - "QnAMakerHostUrl": "", - "QnAMakerSubscriptionKey": "", - "QnAMakerApiEndpointUrl": "", + "QuestionAnswerHostUrl": "", + "QuestionAnswerSubscriptionKey": "", + "QuestionAnswerApiEndpointUrl": "", "ScoreThreshold": "0.5", "StorageConnectionString": "", "AppBaseUri": "", @@ -23,5 +23,7 @@ "ApplicationInsights": { "InstrumentationKey": "" }, - "ApplicationInsightsLogLevel": "" + "ApplicationInsightsLogLevel": "", + "QustionAnswerProjectName": "", + "DeploymentName": "" } \ No newline at end of file diff --git a/Wiki/Best-Practices.md b/Wiki/Best-Practices.md new file mode 100644 index 00000000..73aba286 --- /dev/null +++ b/Wiki/Best-Practices.md @@ -0,0 +1,27 @@ +* **Knowledge base quality:** A good bot experience for your end-users is critically dependent upon the quality of your knowledge base. Please refer to these links on helpful on how to set up useful knowledge bases. + + * https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/how-to/manage-knowledge-base + + +* **Multi-Turn Enablement:** + - With the new updates to the FAQ Plus app template, the knowledge base can now support multi-turn conversations. To understand the basics of multi-turn conversations, navigate to the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview#multi-turn-conversations) to understand about multi-turn conversations. + + - To enable QnA pairs to appear in the multi-turn follow-up prompts please check the 'Show in contextual flow only' checkbox while creating the QnA pairs as multi-turn works only for contextual flow QnA pairs. + ![multi_turn_contextual_flow](./Images/multi_turn_contextual_flow.png) + +* **Managing user expectations:** + + - The bot always keeps the end-user apprised of the status of their requests. For example, the bot will notify end-user about the status changes (e.g. from unassigned to assigned). You should also leverage the help tab text to define and set expectations for requestors. For example, if you goal is to answer all queries within 24 hours, mention that within the help tab. + - Members of the Experts team will be able to edit and delete only those QnA pairs that are added through the app. Existing QnA pairs or the ones directly added in the Question Answering service portal will not be editable. + +* **Setting your expert team for success:** Similar to knowledge base quality, the success of this app in your organization also depends hugely on the experts team you have. You would want to make sure they can reap the full benefits of this app in helping end-users. Following are the items you should consider: + + * Involve them in the installation process, show them how the full app works - there are three big components - end-user bot; bot notifications in the general channel of the team; Messaging extension that enables experts to quickly shift through assigned/unassigned request. + + * Set up expectations with end-users and empower the specialists to deliver those expectations. For example, ensure that all queries do get resolved within 24 hours or the end-user is apprised of what's taking longer than usual. This will build confidence and you'd have returning end-users. + + * Leverage the 'Help Tab'. Use this tab to document process you'd like your end-users to follow. For example, giving them a lay of the land on what kind of questions they can ask and under what conditions they should escalate to asking an expert are good candidates for such content. + +* **Configuring answers as rich cards instead of usual text based answers:** The knowledge base quality can be enhanced by configuring answers as rich cards to provide additional information such as title, image, supporting article link etc. Text based fields in the rich card support markdown to emphasize the message for end-users. + + ![Rich card field mapping](/Wiki/Images/FAQPlus_QnA_fieldmapping.png) diff --git a/Wiki/Continuous-Deployment.md b/Wiki/Continuous-Deployment.md new file mode 100644 index 00000000..24570854 --- /dev/null +++ b/Wiki/Continuous-Deployment.md @@ -0,0 +1,40 @@ +Continuous Deployment service in Azure App Services and Azure Function Apps offers merging latest code changes to Azure hosted environment from GitHub. It helps to seamlessly update the Azure services without need of new deployment. + +### Continuous deployment in Azure App Services + +Please follow below steps to deploy latest changes to the app service: + +1. Log in to the Azure Portal for your subscription. + +1. Select App Services from left menu blade + + [[https://github.com/OfficeDev/microsoft-teams-apps-bookaroom/wiki/Images/Azure-appservice-menu.png|Azure app service menu blade]] + +1. Search and select the app service name (search for the `base resource name`) which is created during first deployment. For e.g. `constoso-faqplus`.azurewebsites.net + +1. Select Deployment Center under menu blade + + [[https://github.com/OfficeDev/microsoft-teams-apps-bookaroom/wiki/Images/Deployment-center.png|Deployment center in Azure app service]] + +1. Click on Sync to synchronize the latest bits from GitHub master branch + + [[https://github.com/OfficeDev/microsoft-teams-apps-bookaroom/wiki/Images/sync-github.png|Sync GitHub deployment]] + + _note_: please make sure that `Repository` name is pointing to correct OfficeDev repo git path. + +1. Once the deployment is successful, please restart the app service and check the application is working. + +### Continuous deployment in Azure Function Apps + +Please follow below steps to deploy latest changes to the app service: + +1. Search Function App in Azure portal search box and select. + +1. Filter the app name which is created during first deployment. For e.g. `constoso-faqplus`-function.azurewebsites.net + [[https://github.com/OfficeDev/microsoft-teams-apps-bookaroom/wiki/Images/Azure-function-menu.png|Azure Function App Overview menu]] + +1. Under Overview section, select `Deployment options configured with ExternalGit` + +1. Under Deployment Center, click on Sync to synchronize the latest bits from GitHub master branch + +_note_: please make sure that `Repository` name is pointing to correct OfficeDev repo git path. \ No newline at end of file diff --git a/Wiki/Cost-Estimates.md b/Wiki/Cost-Estimates.md new file mode 100644 index 00000000..544d5cff --- /dev/null +++ b/Wiki/Cost-Estimates.md @@ -0,0 +1,61 @@ +## Assumptions + +The estimate below assumes: + +- 500 users in the tenant +- Each user performs 5 add, update or delete operations per day. +- Each user uses messaging extension 25 times/week. + +## [](/wiki/costestimate#sku-recommendations)SKU recommendations + +The recommended SKUs for a production environment are: + +- Congnitive Service for Language (Custom Question Answering): Standard (S) +- App Service: Standard (S2) +- Azure Search: Basic + - Create up to 14 knowledge bases + - The Azure Search service cannot be upgraded once it is provisioned, so select a tier that will meet your anticipated needs. + + +## Estimated load + +**Number of QnA queries**: 500 users * 5 questions/user/day * 30 (number of days in a month) = 75000 questions/month + +**Data storage**: 1 GB max + +**Table data operations**: +* Configuration + * (2 reads/question * 75000 questions) + (1 read/escalation * 75000 escalations) + (1 read/update * 2 updates/ticket * 75000 tickets) = 375000 reads + * (1 write/update * 3 updates/ticket * 75000 tickets) = 225000 writes + +**Blob data operations**: + +* Blob storage is called when messaging extension is used. +* Total number of read calls in storage = 4 calls/hour(Azure Function) * 24 hours/day * 30 days = 2880 +* Total number of write calls in storage = 4 calls/hour(Azure Function) * 24 hours/day * 30 days = 2880 + +## Estimated cost + +**IMPORTANT:** This is only an estimate, based on the assumptions above. Your actual costs may vary. + +Prices were taken from the [Azure Pricing Overview](https://azure.microsoft.com/en-us/pricing/) on 09 November 2022, for the West US 2 region. + +Use the [Azure Pricing Calculator](https://azure.com/e/595930b9653945a2870a339a5ea8bce2) to model different service tiers and usage patterns. + +Resource | Tier | Load | Monthly price +--- | --- | --- | --- +Storage account (Table) | Standard_LRS | < 1GB data, 75,000 operations | $0.05 + $0.01 = $0.06 +Storage account (Blob) | Standard_LRS | < 1GB data, 5,000 write operations, 5,000 read operations|$0.05| +Bot Channels Registration | F0 | N/A | Free +App Service Plan | S2 | 744 hours | $148.8 +App Service (Messaging Extension) | - | | (charged to App Service Plan) +Application Insights (Messaging Extension) | - | < 5GB data | (free up to 5 GB) +App Service (Configuration) | - | | (charged to App Service Plan) +Application Insights (Configuration) | - | < 5GB data | (free up to 5 GB) +Question answering Cognitive Service | S | 0-2.5M text records | $1.50 per 1,000 text records +Azure Search | B | | $75.14 +App Service (Question Answering) | F0 | | (charged to App Service Plan) +Application Insights (Question Answering) | - | < 5GB data | (free up to 5 GB) +Azure Function | Dedicated | 4 executions/hour * 24 hours/day * 30 days = 2880 executions|(free up to 1 million executions) +**Total** | | | **$225.55** + diff --git a/Wiki/Data-Stores.md b/Wiki/Data-Stores.md new file mode 100644 index 00000000..ccfa7541 --- /dev/null +++ b/Wiki/Data-Stores.md @@ -0,0 +1,88 @@ +The app uses the following data stores: + +1. Azure Storage Account + + * [Table] Storage for bot related configurations (welcome message, KbId, TeamId, and static tab text). + * [Table] For tracking all of the requests and the necessary actions that impact a request - i.e. request is assigned to an expert. + * [Table] For maintaining card conversation state which is used to refresh cards when edited or deleted. + * [Blob] Storage for knowledge base QnA pairs with metadata information in JSON format. + +2. Azure Search service list item index, created and maintained by the Question Answering cognitive service. + + All these resources are created in your Azure subscription. None are hosted directly by Microsoft. + +## Storage account + ### ConfigurationInfo Table + +The **ConfigurationInfo** table stores data about the necessary configurations that are required for the bot. Each row in the table has the following columns: + +|Attribute | Comment| +-----------|--------- +KnowledgeBaseId | This is the Project name for which the bot can return answers from the Question Answering service. +MSTeamId | The team Id which the bot can be able to post messages whenever the end-user asks for an expert's assistance with a query. +StaticTabText | The static tab text, would be the standard text that is displayed in the help tab which will be installed along with the bot in a personal scope. The static tab text will be configured by the configurator application, and is publically accessible with no authentication. +WelcomeMessage | The welcome message is a configurable text that the bot would send to the end-user the very first time that end-user installs the bot in a personal scope. +LastModifiedByUPN | The user principal name (UPN) of the Admin who modified the setting. +LastModifiedByObjectId | The Azure Active Directory Object Id of the Admin who modified the setting. +LastModifiedOn | The date/time on which the Admin modified the setting. + +### TicketInfo Table + +The **TicketInfo** table stores data about tickets (or requests) that are posted to the expert team by the bot on behalf of end-user. Each row in the table has the following columns: + +|Attribute | Comment| +-----------|--------- +|TicketId|The ticket ID. +|Status|An integer value. +|Title|The title provided by the end-user. +|DateCreated|The date when a new ticket is created. +|Description|The description text that is written by the end-user. +|RequesterName|The name of the end-user when a new ticket is created. +|RequesterUserPrincipalName|The email address of the end-user. +|RequesterGivenName|The first name of the end-user +|RequesterConversationId|The conversationId of the 1:1 chat between the end-user and the FAQPlus bot. +|SmeCardActivityId|The activityId when the new ticket adaptive card is posted in the General channel of the experts team. +|SmeThreadConversationId|The conversationId in the experts team General channel at the time a new ticket is created. +|DateAssigned|The date when a expert self-assigns a ticket. +|AssignedToName|The name of the expert who self-assigns a ticket. +|AssignedToObjectId|The AAD Object ID of the expert who self-assigns a ticket. +|DateClosed|The date when a ticket is updated to the closed status. +|LastModifiedByName|The name of the expert who recently updated the ticket. +|LastModifiedByObjectId|The AAD Object ID of the expert who recently updated the ticket. +|LastModifiedOn|The date/time on which the expert has updated the ticket. +|UserQuestion|The original question that has been asked by the end-user. +|KbEntryAnswer|The answer that is stored in the knowledge base. + +### ActivityEntity Table + +The **ActivityEntity** table is used to maintain card state which is further used to refresh cards when edited or deleted. Each row in the table has the following columns: + +|Attribute | Comment| +-----------|--------- +TimeStamp | The date and time when the card gets posted. +ActivityID | The ID of the card that gets posted in the channel when the question is added for the first time. + +### Blob Storage + +The **Blob Storage** stores knowledge base with QnA and metadata in JSON format. +The same JSON is used by messaging extension search results for knowledge base tab. + +``` +[ + { + "id":"<>", + "answer":"<>", + "source":"Bot", + "questions":[ + "<>" + ], + "metadata": { + "createdby""<>", + "conversationid""<>", + "updatedby""<>", + }, + "createddate":"<>", + "updateddate":"<>" + } +] +``` \ No newline at end of file diff --git a/Wiki/Deployment-Guide-for-GCC.md b/Wiki/Deployment-Guide-for-GCC.md new file mode 100644 index 00000000..4049ad60 --- /dev/null +++ b/Wiki/Deployment-Guide-for-GCC.md @@ -0,0 +1,403 @@ +# How to choose the deployment procedure appropriate for your configuration? + +> **Note** : This guide is intended for customers requiring the deployment of FAQ Plus of Office 365 Government Community Cloud (aka GCC) only - The standard deployment for Azure AD Commercial tenant is located [here](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Deployment-Guide) + +Microsoft provides different cloud offering across Office 365, Azure or the Power Platform to meet customers compliance and regulation requirements. While the global Office 365 and Azure flavors, referred as “Commercial” cloud, are the default and most used environments, customers can choose other alternatives that we’ll refer as Microsoft Cloud for Government. + +Microsoft Cloud for Government provides a comprehensive cloud platform designed expressly for U.S. Federal, State, and Local Governments to meet the U.S. Government’s thorough security and compliance regulations. Microsoft Cloud for Government meets the standards of many government agencies, including FedRAMP, HIPAA, and CJIS, to name a few. + +Depending on the cloud offerings that are used, there are differences to consider in terms of service availability and URLs to access and manage those services. Here is a global representation of these different configuration across Office 365, Azure and Power Platform for Commercial and US Gov cloud offerings. + + [[/Images/GCCHigh1.png|GCCHigh1]] + +If you need more information on these offerings for US Sovereign Cloud, please refer to the following article - https://aka.ms/AA632wo + +## Here are the tests to run to identify you current GCC setup : + +### Check 1 - Identify your Office 365 configuration. + +- From a web browser, go to https://gettenantpartitionweb.azurewebsites.net/ +- Enter the name of your Azure AD tenant – e.g. contoso.onmicrosoft.com. +- Press “Lookup tenant” and check the results. + +|Lookup Results|Mapping| +|-----------|---------| +|Azure AD Instance: Azure AD Global, Tenant Scope: Not applicable |O365 Commercial| +|Azure AD Instance: Azure AD Global, Tenant Scope: GCC (Moderate)|O365 GCC| +|Azure AD Instance: Azure AD Government, Tenant Scope: GCC High|O365 GCC-H| + +Save this value as the result for Check 1 + +### Check 2a - Identify your Azure configuration (for templates based on Azure resources only) + +There are only two options here based on the URL used to access the Azure portal and deploy your resources. + +|Azure portal URL|Mapping| +|-----------|--------- +|https://portal.azure.com|Azure Commercial| +|https://portal.azure.us|Azure Government| + +Save this value as the result for Check 2a + +More info on Azure services and URL patterns for Commercial vs Gov clouds + +[Compare Azure Government and global Azure - Azure Government | Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-government/compare-azure-government-global-azure) + +### Check 2b - Identify your Power Platform configuration (for templates based on Power Platform only) + +Here are the URL patterns for Power Platform to determine the cloud environment. + +|Power App portal URL|Mapping| +|-----------|--------- +|https://*.powerapps.com|Power App Commercial| +|https://*.gov.powerapps.us|Power App GCC| +|https://*.high.powerapps.us|Power App GCC-H| +|Not supported|Power App DoD| + +Save this value as the result for Check 2b + +More info on Power Platform services and URL patterns for Commercial vs Gov clouds + +[Microsoft Power Apps US Government - Power Platform | Microsoft Docs](https://docs.microsoft.com/en-us/power-platform/admin/powerapps-us-government) + +### Check 3 - Are you running in multi or single-tenancy mode (for templates based on Azure resources only)? + +This question is to determine if your Azure subscription is linked to the same Azure AD as the one where your Office 365 users are deployed. + +If you use the same domain to access the Azure portal than your O365 services, the answer is **single-tenancy**. + e.g. you use your O365 credentials to access the Azure portal. + +Otherwise, the answer is **multi-tenancy**. +e.g. you have different email addresses or use different Azure AD to access O365 and the Azure. + +Based on these different checks, you can determine your configuration and which deployment model to use. + +Save this value as the result for Check 3 + +## Your Microsoft Teams app templates is based on Azure resources. + +|Check 1|Check 2a|Check 3|Supported|Deployment scenario| +|-----------|---------|-----------|---------|----------- +|O365 Commercial|Azure Commercial|Single|Yes|Standard| +|O365 Commercial|Azure Commercial|Multi|Yes|Standard (1)| +|O365 GCC|Azure Commercial|Single|Yes|Standard (2)| +|O365 GCC|Azure Commercial|Multi|Yes|Standard (1) (2)| +|O365 GCC|Azure Government|Single|Not Supported|Not Available (3)| +|O365 GCC|Azure Government|Multi|Yes|Hybrid| +|O365 GCC-H/DoD|Any|Any|Not Supported|Not Available (4)| + +(1) Use the standard deployment procedure – Pay attention to the tenantID provided in the input parameters of the ARM template: you should change the default value (the Azure AD linked to your Azure subscription) to the Azure AD tenantID where your O365 users and services are located. + +(2) Use the standard deployment procedure – Microsoft Teams admin portal for GCC now supports “Manage Apps” as well as “App setup policies” – Also, Teams for GCC now supports “Message Extensions” so there is nothing specific to do to deploy on O365 GCC compared to O365 Commercial with Azure Commercial. + +(3) By design, O365 GCC and Azure Gov are connected to different Azure AD tenants – It’s not possible to be in a single tenant configuration with this setup. + +(4) Most the Microsoft App templates rely on the Azure Bot Service to enable bot / Message Extensions in Teams. Microsoft Teams channel is not yet enabled for Azure Bot Service on Azure Government. + +Before deploying a Teams app template to your Azure Government subscription, make sure that all the required services and pre-requisites are met. This means that the list of required Azure services are available and registered on the targeted region. This page explains how to verify that a resource provider is available and how to register it for your subscription - + +[Resource providers and resource types - Azure Resource Manager | Microsoft Docs](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-providers-and-types) + +## Your Microsoft Teams app templates is based on Power Platform. + +|Check 1|Check 2b|Supported|Deployment scenario| +|-----------|---------|-----------|--------- +|O365 Commercial|Power Platform Commercial|Yes|Standard +|O365 GCC|Power Platform GCC|Yes|Standard (5) (6) +|O365 GCC-H|Power Platform GCC-H|Yes|Standard (5) (6) +|O365 DoD|Power Platform DoD|Not Supported|Not Available (7) + +(5) Use the standard deployment procedure – Depending on the pre-requisites and services used on the Power Platform, some Teams app template may not be available for O365 GCC/GCC-H. A complete list of O365 and Power Platform services available on Office 365 Gov and service plan is available here - + +[Office 365 US Government - Service Descriptions | Microsoft Docs](https://docs.microsoft.com/en-us/office365/servicedescriptions/office-365-platform-service-description/office-365-us-government/office-365-us-government) + +(6) For Teams app templates that require Dataverse for Teams in GCC/GCC-H, you will need an additional stand-alone license for Power Apps. This is a current limit. + +(7) Power Platform is not available on O365 DoD and is therefore out of scope. +  +# What is “hybrid deployment”? + +In this setup, the Office 365 users and Microsoft Teams service are hosted in a different Azure AD tenant as the Azure resources. Also, both O365 and Azure use a Government Cloud: + +- Office 365 with a GCC Azure AD tenant +- Azure Government (https://portal.azure.us) associated to its GCC-H Azure AD tenant + +To register a bot as an application in Microsoft Teams, the bot needs to be registered on the Azure Bot Service. This Azure Bot Service also needs to be deployed on an Azure subscription that is linked to the Azure AD tenant where your O365 users and Microsoft Teams service are. This is what we call the hybrid deployment. + +Here is a high-level representation for the hybrid deployment. + + [[/Images/GCCHigh2.png|GCCHigh2]] + +The hybrid deployment guide considers these multiple Azure subscriptions and Azure AD tenant. It also details the sequence and order of deployment of these resources. + +**Note** : Azure Bot Service is only here to register your application as a bot in your Microsoft Teams environment. It is also used to activate SSO when needed, using the same Azure AD tenant where your O365 users are. There is no conversation data or attachments that transit via the Azure Bot Service deployed on Azure Commercial: all communications are in direct transit (https) between your Microsoft Teams service in GCC and the application bot hosted on Azure Government. + +# Deployment in O365 GCC tenant and Azure Commercial subscription + +For O365 GCC deployment and Azure Commercial, the same [deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Deployment-Guide) (available on FAQ Plus GitHub repo) can be followed. Use O365 GCC tenant for App registration (Step 1 in deployment guide) and Azure commercial subscription for ARM deployment (Step 2 in deployment guide). Make sure tenant Id of O365 GCC tenant is added during ARM deployment. + +# Deployment in O365 GCC tenant and Azure Government subscription + +For hybrid deployment, follow these deployment guide steps. + +## Prerequisites + +To begin, you will need: + +- An Azure Governement subscription where you can create the following resources: + - App Service + - App Service plan + - Azure storage account + - Azure Search + - Azure Function + * Question answering cognitive service + - Application Insights +- An Azure Commercial subscription where you can create the following resources: + - Bot Channels Registration +- A team in Microsoft Teams GCC with your group of experts. (You can add and remove team members later!) +- A reasonable set of Question and Answer pairs to set up the knowledge base for the bot. +- A copy of the FAQ Plus app GitHub repo (https://github.com/OfficeDev/microsoft-teams-apps-faqplus) + +# Deployment Steps for hybrid mode + +## Step 1: Register Azure AD applications + +Register two Azure AD applications in directory where the Team's users are homed (Office 365 GCC + Azure Commercial): one for the bot, and another for the configuration app. + +1. Log in to the Azure Portal for your Azure Commerical subscription, and go to the "App registrations" blade [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps). +Verify that you are on the same Azure AD tenant than your O365 users and where Microsoft Teams is deployed. + +2. Click on "New registration", and create an Azure AD application. + 1. **Name**: The name of your Teams app - if you are following the template for a default deployment, we recommend "FAQ Plus". + 2. **Supported account types**: Select "Accounts in any organizational directory" + 3. Leave the "Redirect URL" field blank. + +![Azure registration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_creation.png) + +3. Click on the "Register" button. + +4. When the app is registered, you'll be taken to the app's "Overview" page. Copy the **Application (client) ID** and **Directory (tenant) ID**; we will need it later. Verify that the "Supported account types" is set to **Multiple organizations**. + +![Azure overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_overview.png) + +5. On the side rail in the Manage section, navigate to the "Certificates & secrets" section. In the Client secrets section, click on "+ New client secret". Add a description of the secret and select an expiry time. Click "Add". + +![Azure AD overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_secret.png) + +6. Once the client secret is created, copy its **Value**; we will need it later. + +7. Go back to “App registrations”, then repeat steps 2-3 to create another Azure AD application for the configuration app. + 1. **Name**: The name of your configuration app. We advise appending “Configuration” to the name of this app; for example, “FAQ Plus Configuration”. + 2. **Supported account types**: Select "Account in this organizational directory only" + 3. Leave the "Redirect URL" field blank for now. + +At this point you have 4 unique values: + +- Application (botclientId) ID for the bot +- Client secret for the bot +- Application (configAppClientId) ID for the configuration app +- Directory (tenant) ID, which is the same for both apps + +We recommend that you copy these values into a text file, using an application like Notepad. We will need these values later. + +![Configuration step 3](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/azure-config-app-step3.png) + +## Step 2: Deploy bot to your Azure Commercial subscription + +1. Click on the "Deploy to Azure" button below. + +[![Deploy To Azure Commercial](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FOfficeDev%2Fmicrosoft-teams-apps-faqplus%2Fmaster%2FDeployment%2FGCC%2Fbotazuredeploy.json) + +2. When prompted, log in to your **Azure Commercial subscription**. +3. Azure will create a "Custom deployment" based on the ARM template and ask you to fill in the template parameters. +4. Select a subscription and resource group. + - We recommend creating a new resource group. + - The resource group location MUST be in a data center that supports Application Insights +5. Enter a "Base Resource Name", which the template uses to generate names for the other resources. + - The app service names [Base Resource Name], [Base Resource Name]-config, and [Base Resource Name]-questionAnswering must be available. For example, if you select contosofaqplus as the base name, the names contosofaqplus, contosofaqplus-config, and contosofaqplus-questionAnswering must be available (not taken); otherwise, the deployment will fail with a conflict error. + - Remember the base resource name that you selected. We will need it later. +6. Fill in the various IDs in the template: + - Bot Client ID: The application (client) ID of the Microsoft Teams Bot app + - Make sure that the values are copied as-is, with no extra spaces. The template checks that GUIDs are exactly 36 characters. +7. If you wish to change the app name, description, and icon from the defaults, modify the corresponding template parameters. +8. Click on "Review + create" to start the deployment. It will validate the parameters provided in the template. Once the validation is passed, click on create to start the deployment. +9. Wait for the deployment to finish. You can check the progress of the deployment from the "Notifications" pane of the Azure Portal. It can take more than 10 minutes for the deployment to finish. +10. Once the deployment has finished, you would be directed to a page that has the following fields: + - botId - This is the Microsoft Application ID for the FAQ Plus bot. + - appDomain - This is the base domain for the FAQ Plus Bot. + +11. Search for Bot Service created by ARM template. Name of Bot Service will be base resource name which was provided earlier. + +12. Click on Channels in Settings section of Bot Services and click on "Edit" button for Microsoft Teams Channel. Click on Delete Channel. +13. Once deleted, click on "Configure Microsoft Teams Channel" icon inside "Add a featured Channel". Select "Microsoft Teams for Government" and click on Save. + +[[/Images/BotChannelGovernment.PNG|Add channel to bot]] + +## Step 3: Deploy remaining resources to your Azure Government subscription +1. Click on the "Deploy to Azure" button below. + +[![Deploy To Azure Gov](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FOfficeDev%2Fmicrosoft-teams-apps-faqplus%2Fmaster%2FDeployment%2FGCC%2Fotherresourcesazuredeploy.json) + +2. When prompted, log in to your **Azure Government subscription**. +3. Azure will create a "Custom deployment" based on the ARM template and ask you to fill in the template parameters. +4. Select a subscription and resource group. + - We recommend creating a new resource group. + - The resource group location MUST be in a data center that supports: + - Application Insights + - Azure Search + - Cognitive Service for Language (Question Answering) + - For an up-to-date list, click [here](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic-apps%2Ccognitive-services%2Csearch%2Cmonitor). , and select a region where the following services are available: + - Application Insights + - Azure Search + - Cognitive Service for Language (Question Answering) +5. Enter and reuse **the same Base Resource Name**, that you gave for bots deployment in your Azure commercial. + - The app service names [Base Resource Name], must be available. For example, if you select contosoworkplaceawards as the base name, the names contosoworkplaceawards must be available (not taken); otherwise, the deployment will fail with a Conflict error. +6. Fill in the various IDs in the template: + - botClientId - The application (client) ID registered in Step 1. + - botClientSecret - The client secret registered in Step 1. + - isGCCHigh - True if deployment tenant is GCC government tenant. + - tenantId - The tenant ID registered in Step 1. If your Microsoft Teams tenant is same as Azure subscription tenant, then we would recommend to keep the default values. + - botAppInsightsKey - Navigate to App Insights created during Azure commercial deployment. Under left menu, select Properties under Configure. Copy the instrumentation key. + - configAppClientId - The application (client) ID of the configuration app + - configAdminUPNList - a semicolon-delimited list of users who will be allowed to access the configuration app. + - For example, to allow Megan Bowen (meganb@contoso.com) and Adele Vance (adelev@contoso.com) to access the configuration app, set this parameter to `meganb@contoso.com;adelv@contoso.com`. + * You can change this list later by going to the configuration app service's "Configuration" blade. + - Make sure that the values are copied as-is, with no extra spaces.The template checks that GUIDs are exactly 36 characters. +7. If you wish to change the Git repo Url from the defaults, modify the corresponding template parameters. +8. Click on "Review + create" to start the deployment. It will validate the parameters provided in the template. Once the validation is passed, click on create to start the deployment. +9. Wait for the deployment to finish. You can check the progress of the deployment from the "Notifications" pane of the Azure Portal. It can take more than 20 minutes for the deployment to finish. + +## Step 4: Set up authentication for the configuration app + +1. Note the location of the configuration app that you deployed, which is `https://[BaseResourceName]-config.azurewebsites.us`. For example, if you chose "contosofaqplus" as the base name, the configuration app will be at `https://contosofaqplus-config.azurewebsites.us` + +2. Go back to the "App Registrations" page [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview). + +3. Click on the configuration app in the application list. Under "Manage", click on "Authentication" to bring up authentication settings. + +4. Click on Add a platform, select Web. + +![Adding Redirect URI1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage1.png) + +5. Add new entry to "Redirect URIs": + If your configuration app's URL is https://contosofaqplus-config.azurewebsites.us, then add the following entry as the Redirect URIs: + - https://contosofaqplus-config.azurewebsites.us + + Note: Please refer to Step 4.1 for more details about the URL. + +6. Under "Implicit grant", check "ID tokens" and "Access tokens". The reason to check "ID tokens" is because you are using only the accounts on your current Azure tenant and using that to authenticate yourself in the configuration app. Click configure. + +![Adding Redirect URI2](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage2.png) + +7. Add new entries to "Redirect URIs": + If your configuration app's URL is https://contosofaqplus-config.azurewebsites.us, then add the following entry as the Redirect URIs: + - https://contosofaqplus-config.azurewebsites.us/signin + - https://contosofaqplus-config.azurewebsites.us/configuration + +![Adding Redirect URI3](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage3.png) + +8. Click "Save" to commit your changes. + +## Step 5: Create the Question Answering Project + +Create a project on the [Question Answering portal](https://language.cognitive.azure.com/questionAnswering/projects), following the instructions in the Question Answering documentation [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/how-to/create-test-deploy). + +Select the existing Azure subscription and Choose language resource which created in step 1 "Deploy to your Azure subscription". + +Skip the step, "Create a new language resource", because the script that you deployed in Step 1 "Deploy to your Azure subscription" already created the language service. Proceed directly to the next step, by selecting the already create language resource. + +![Custom Question Answering](./Images/create-question-answering.png) + +Create a new Custom Question Answering project with the same name which was entered in the Step 1. +![Create Custom Question Answering Project](./Images/create-question-answering-project.png) + +![Create Custom Question Answering Project](./Images/create-question-answering-project-2.png) + +### Multi-Turn Enablement +With the new updates to the FAQ Plus app template, the knowledge base can now support multi-turn conversations. To understand the basics of multi-turn conversations, navigate to the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview#multi-turn-conversations) to understand about multi-turn conversations. + +To enable QnA pairs to appear in the multi-turn follow-up prompts please check the 'Show in contextual flow only' checkbox while creating the QnA pairs as multi-turn works only for contextual flow QnA pairs. +![multi_turn_contextual_flow](./Images/multi_turn_contextual_flow.png) + +## Step 6: Finish configuring the FAQ Plus app + +1. Go to the configuration app, which is at `https://[BaseResourceName]-config.azurewebsites.us`. For example, if you chose “contosofaqplus” as the base name, the configuration app will be at `https://contosofaqplus-config.azurewebsites.us`. + +2. You will be prompted to log in with your credentials. Make sure that you log in with an account that is in the list of users allowed to access the configuration app. + +![Config web app page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/config-web-app-login.png) + +3. Get the link to the team with your experts from the Teams client. To do so, open Microsoft Teams, and navigate to the team. Click on the "..." next to the team name, then select "Get link to team". + +![Get link to Team](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/get-link-to-Team.png) + +Click on "Copy" to copy the link to the clipboard. + +![Link to team](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/link-to-team.png) + +4. Paste the copied link into the "Team Id" field, then press "OK". + +![Add team link form](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/fill-in-team-link.png) + +5. Enter the Question Answering Project name into the "Project Name" field, then press "OK". + +6. Customize the "Welcome message" that's sent to your End-users when they install the app. This message supports basic markdown, such as bold, italics, bulleted lists, numbered lists, and hyperlinks. See [here](https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features#markdown) for complete details on what Markdown features are supported. + +### Note + +Remember to click on "OK" after changing a setting. To edit the setting later, click on "Edit" to make the text box editable. + +## Step 7: Create the Teams app packages + +Create two Teams app packages: one for end-users to install personally, and one to be installed to the experts' team. + +1. Open the `Manifest\manifest_enduser.json` file in a text editor. + +2. Change the placeholder fields in the manifest to values appropriate for your organization. + +- `developer.name` ([What's this?](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#developer)) +- `developer.websiteUrl` +- `developer.privacyUrl` +- `developer.termsOfUseUrl` + +3. Replace all the occurrences of `<>` placeholder to your Azure AD application's ID from above. This is the same GUID that you entered in the template under "Bot Client ID". + +4. In the "validDomains" section, replace all the occurrences of `<>` with your Bot App Service's domain. This will be `[BaseResourceName].azurewebsites.us`. For example, if you chose "contosofaqplus" as the base name, change the placeholder to `contosofaqplus.azurewebsites.us`. + +5. Save and Rename `manifest_enduser.json` file to a file named `manifest.json`. + +6. Create a ZIP package with the `manifest.json`,`color.png`, and `outline.png`. The two image files are the icons for your app in Teams. +- Name this package `faqplus-enduser.zip`, so you know that this is the app for end-users. +- Make sure that the 3 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer.png) + +7. Rename the `manifest.json` file to `manifest_enduser.json` for reusing the file. + +8. Open the `Manifest\manifest_sme.json` file in a text editor. + +9. Repeat the steps from 2 to 4 to replace all the placeholders in the file. + +10. Save and Rename `manifest_sme.json` file to a file named `manifest.json`. + +11. Create a ZIP package with the `manifest.json`,`color.png`, and `outline.png`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-experts.zip`, so you know that this is the app for sme/experts. +* Make sure that the 3 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer.png) + +12. Rename the `manifest.json` file to `manifest_sme.json` for reusing the file. + +## Step 8: Run the apps in Microsoft Teams + +1. If your tenant has sideloading apps enabled, you can install your app by following the instructions [here](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/apps/apps-upload#load-your-package-into-teams) + +2. You can also upload it to your tenant's app catalog so that it can be available for everyone in your tenant to install. See [here](https://docs.microsoft.com/en-us/microsoftteams/tenant-apps-catalog-teams) + +3. Install the experts' app (the `faqplus-experts.zip` package) to your team of subject-matter experts. This **MUST** be the same team that you selected in Step 6.3 above. + + **NOTE:** Do NOT use app permission policies to restrict the experts' app to the members of the subject matter experts team. Teams does not support applying different policies to the same bot via two different app packages. If you do this, you may find that the end-user app does not respond to some users. + +4. Install the end-user app (the `faqplus-enduser.zip` package) to your users. + +## Troubleshooting + +Please see our [Troubleshooting](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Troubleshooting) page. \ No newline at end of file diff --git a/Wiki/Deployment-Guide-manual.md b/Wiki/Deployment-Guide-manual.md new file mode 100644 index 00000000..dfface94 --- /dev/null +++ b/Wiki/Deployment-Guide-manual.md @@ -0,0 +1,261 @@ +## Prerequisites + +To begin, you will need: + +* An Azure subscription where you can create the following kind of resources: + * App service + * App service plan + * Bot channels registration + * Azure storage account + * Azure search + * Azure function + * Question answering cognitive service + * Application insights +* A team in Microsoft Teams with your group of experts. (You can add and remove team members later!) +* A copy of the FAQ Plus app GitHub repo (https://github.com/OfficeDev/microsoft-teams-apps-faqplus) +* A reasonable set of Question and Answer pairs to set up the knowledge base for the bot. + +## Step 1: Register Azure AD applications + +Register three Azure AD applications in your tenant's directory: one for the end user bot, one for the expert bot, and another for the configuration app. + +1. Log in to the Azure Portal for your subscription, and go to the "App registrations" blade [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps). + +2. Click on "New registration", and create an Azure AD application. + 1. **Name**: The name of your Expert's app - if you are following the template for a default deployment, we recommend "FAQ Plus Expert". + 2. **Supported account types**: Select "Accounts in any organizational directory". + 3. Leave the "Redirect URL" field blank. + +![Azure registration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_creation.png) + +3. Click on the "Register" button. + +4. When the app is registered, you'll be taken to the app's "Overview" page. Copy the **Application (client) ID** and **Directory (tenant) ID**; we will need it later. Verify that the "Supported account types" is set to **Multiple organizations**. + +![Azure overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_overview.png) + +5. On the side rail in the Manage section, navigate to the "Certificates & secrets" section. In the Client secrets section, click on "+ New client secret". Add a description of the secret and select an expiry time. Click "Add". + +![Azure AD overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_secret.png) + +6. Once the client secret is created, copy its **Value**; we will need it later. + +7. Go back to “App registrations”, then repeat steps 2-6 to create another Azure AD application for the end user app. + 1. **Name**: The name of your End User app - if you are following the template for a default deployment, we recommend "FAQ Plus User". + 2. **Supported account types**: Select "Accounts in any organizational directory". + 3. Leave the "Redirect URL" field blank for now. + +8. Go back to “App registrations”, then repeat steps 2-3 to create another Azure AD application for the configuration app. + 1. **Name**: The name of your configuration app. We advise appending “Configuration” to the name of this app; for example, “FAQ Plus Configuration”. + 2. **Supported account types**: Select "Account in this organizational directory only" + 3. Leave the "Redirect URL" field blank for now. + +At this point you have 6 unique values: + +* Application (client) ID for the expert bot. +* Client secret for the expert bot. +* Application (client) ID for the end user bot. +* Client secret for the end user bot. +* Application (client) ID for the configuration app. +* Directory (tenant) ID, which is the same for all the apps. + +We recommend that you copy these values into a text file, using an application like Notepad. We will need these values later. + +![Configuration step 3](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/azure-config-app-step3.png) + +## Step 2: Deploy to your Azure subscription + +1. Click on the "Deploy to Azure" button below. + +[![Deploy to Azure](./Images/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FOfficeDev%2Fmicrosoft-teams-apps-faqplus%2Fmaster%2FDeployment%2Fazuredeploy.json) + +2. When prompted, log in to your Azure subscription. + +3. Azure will create a "Custom deployment" based on the ARM template and ask you to fill in the template parameters. + +4. Select a subscription and resource group. +* We recommend creating a new resource group. +* The resource group location MUST be in a data center that supports: + * Application Insights + * Azure Search + * Cognitive Service for Language (Question Answering) +* For an up-to-date list, click [here](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic-apps,cognitive-services,search,monitor), and select a region where the following services are available: + * Application Insights + * Cognitive Service for Language (Question Answering) + * Azure Search + +5. Enter a "Base Resource Name", which the template uses to generate names for the other resources. +* The app service names `[Base Resource Name]`, `[Base Resource Name]-config`, and `[Base Resource Name]-questionAnswering` must be available. For example, if you select `contosofaqplus` as the base name, the names `contosofaqplus`, `contosofaqplus-config`, and `contosofaqplus-questionAnswering` must be available (not taken); otherwise, the deployment will fail with a conflict error. +* Remember the base resource name that you selected. We will need it later. + +6. Fill in the various IDs in the template: + + 1. **Expert Bot Client ID**: The application (client) ID of the Expert Bot app + + 2. **Expert Bot Client Secret**: The client secret of the Expert Bot app + + 3. **User Bot Client ID**: The application (client) ID of the User Bot app + + 4. **User Bot Client Secret**: The client secret of the User Bot app + + 5. **Config App Client Id**: The application (client) ID of the configuration app + + 6. **Tenant Id**: The tenant ID registered in Step 1. If your Microsoft Teams tenant is the same as Azure subscription tenant, then we would recommend keeping the default values. + +Make sure that the values are copied as-is, with no extra spaces. The template checks that GUIDs are exactly 36 characters. + +7. Fill in the "Config Admin UPN List", which is a semicolon-delimited list of users who will be allowed to access the configuration app. +* For example, to allow Megan Bowen (meganb@contoso.com) and Adele Vance (adelev@contoso.com) to access the configuration app, set this parameter to `meganb@contoso.com;adelv@contoso.com`. +* You can change this list later by going to the configuration app service's "Configuration" blade. + +8. If you wish to change the app name, description, and icon from the defaults, modify the corresponding template parameters. + +9. Agree to the Azure terms and conditions by clicking on the checkbox "I agree to the terms and conditions stated above" located at the bottom of the page. + +10. Click on "Purchase" to start the deployment. + +11. Wait for the deployment to finish. You can check the progress of the deployment from the "Notifications" pane of the Azure Portal. It can take more than 10 minutes for the deployment to finish. + +12. Once the deployment has finished, you would be directed to a page that has the following fields: +* expertBotId - This is the Expert bot ID for the FAQ Plus Expert bot. +* userBotId - This is the User bot ID for the FAQ Plus User bot. +* appDomain - This is the base domain for the FAQ Plus Bot. +* configurationAppUrl - This is the URL for the configuration web application. + +## Step 3: Set up authentication for the configuration app + +1. Note the location of the configuration app that you deployed, which is `https://[BaseResourceName]-config.azurewebsites.net`. For example, if you chose "contosofaqplus" as the base name, the configuration app will be at `https://contosofaqplus-config.azurewebsites.net` + +2. Go back to the "App Registrations" page [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview). + +3. Click on the configuration app in the application list. Under "Manage", click on "Authentication" to bring up authentication settings. + +4. Click on Add a platform, select Web. + +![Adding Redirect URI1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage1.png) + +5. Add new entry to "Redirect URIs": + If your configuration app's URL is https://contosofaqplus-config.azurewebsites.net, then add the following entry as the Redirect URIs: + - https://contosofaqplus-config.azurewebsites.net + + Note: Please refer to Step 3.1 for more details about the URL. + +6. Under "Implicit grant", check "ID tokens" and "Access tokens". The reason to check "ID tokens" is because you are using only the accounts on your current Azure tenant and using that to authenticate yourself in the configuration app. Click configure. + +![Adding Redirect URI2](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage2.png) + +7. Add new entries to "Redirect URIs": + If your configuration app's URL is https://contosofaqplus-config.azurewebsites.net, then add the following entry as the Redirect URIs: + - https://contosofaqplus-config.azurewebsites.net/signin + - https://contosofaqplus-config.azurewebsites.net/configuration + +![Adding Redirect URI3](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/AuthenticationImage3.png) + +8. Click "Save" to commit your changes. + +## Step 4: Create the Question Answering Project + +Create a project on the [Question Answering portal](https://language.cognitive.azure.com/questionAnswering/projects), following the instructions in the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/how-to/create-test-deploy). + +Select the existing Azure subscription and Choose language resource which created in step 2 "Deploy to your Azure subscription". + +Skip the step, "Create a new language resource", because the script that you deployed in Step 2 "Deploy to your Azure subscription" already created the language service. Proceed directly to the next step, by selecting the already create language resource. + +![Custom Question Answering](./Images/create-question-answering.png) + +Create a new Custom Question Answering project with the same name which was entered in the Step 2. +![Create Custom Question Answering Project](./Images/create-question-answering-project.png) + +![Create Custom Question Answering Project](./Images/create-question-answering-project-2.png) + +### Multi-Turn Enablement +With the new updates to the FAQ Plus app template, the knowledge base can now support multi-turn conversations. To understand the basics of multi-turn conversations, navigate to the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview#multi-turn-conversations) to understand about multi-turn conversations. + +To enable QnA pairs to appear in the multi-turn follow-up prompts please check the 'Show in contextual flow only' checkbox while creating the QnA pairs as multi-turn works only for contextual flow QnA pairs. +![multi_turn_contextual_flow](./Images/multi_turn_contextual_flow.png) + +## Step 5: Finish configuring the FAQ Plus app + +1. Go to the configuration app, which is at `https://[BaseResourceName]-config.azurewebsites.net`. For example, if you chose “contosofaqplus” as the base name, the configuration app will be at `https://contosofaqplus-config.azurewebsites.net`. + +2. You will be prompted to log in with your credentials. Make sure that you log in with an account that is in the list of users allowed to access the configuration app. + +![Config web app page](./Images/config-web-app-login.png) + +3. Get the link to the team with your experts from the Teams client. To do so, open Microsoft Teams, and navigate to the team. Click on the "..." next to the team name, then select "Get link to team". + +![Get link to Team](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/get-link-to-Team.png) + +Click on "Copy" to copy the link to the clipboard. + +![Link to team](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/link-to-team.png) + +4. Paste the copied link into the "Team Id" field, then press "OK". + +![Add team link form](./Images/fill-in-team-link.png) + +5. Enter the Question Answering Project name into the "Project Name" field, then press "OK". + +6. Customize the "Welcome message" that's sent to your End-users when they install the app. This message supports basic markdown, such as bold, italics, bulleted lists, numbered lists, and hyperlinks. See [here](https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features#markdown) for complete details on what Markdown features are supported. + +### Notes + +Remember to click on "OK" after changing a setting. To edit the setting later, click on "Edit" to make the text box editable. + +## Step 6: Create the Teams app packages + +Create two Teams app packages: one for end-users to install personally, and one to be installed to the experts' team. + +1. Open the `Manifest\EndUser\manifest_enduser.json` file in a text editor. + +2. Change the placeholder fields in the manifest to values appropriate for your organization. + +* `developer.name` ([What's this?](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#developer)) + +* `developer.websiteUrl` + +* `developer.privacyUrl` + +* `developer.termsOfUseUrl` + +3. Replace all the occurrences of `<>` placeholder to your Azure AD end user application's ID from above. This is the same GUID that you entered in the template under "User Bot Client ID". + +4. In the "validDomains" section, replace all the occurrences of `<>` with your Bot App Service's domain. This will be `[BaseResourceName].azurewebsites.net`. For example, if you chose "contosofaqplus" as the base name, change the placeholder to `contosofaqplus.azurewebsites.net`. + +5. Save and Rename `manifest_enduser.json` file to a file named `manifest.json`. + +6. Create a ZIP package with the all the files in `Manifest\EndUser` folder - `manifest.json`,`color.png` and `outline.png`, along with localization files - `ar.json`, `de.json`, `en.json`, `es.json`, `fr.json`, `he.json`, `ja.json`, `ko.json`, `pt-BR.json`, `ru.json`, `zh-CN.json`, `zh-TW.json`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-enduser.zip`, so you know that this is the app for end-users. +* Make sure that the 15 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer-user.png) + +7. Rename the `manifest.json` file to `manifest_enduser.json` for reusing the file. + +8. Open the `Manifest\SME\manifest_sme.json` file in a text editor. + +9. Repeat the steps from 2 to 4 to replace all the placeholders in the file. The placeholder `<>` should be replaced by your Azure AD expert application's ID from above. + +10. Save and Rename `manifest_sme.json` file to a file named `manifest.json`. + +11. Create a ZIP package with the all the files in `Manifest\SME` folder (except manifest_legacy) - `manifest.json`,`color.png` and `outline.png`, along with localization files - `ar.json`, `de.json`, `en.json`, `es.json`, `fr.json`, `he.json`, `ja.json`, `ko.json`, `pt-BR.json`, `ru.json`, `zh-CN.json`, `zh-TW.json`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-sme.zip`, so you know that this is the app for sme. +* Make sure that the 15 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer-sme.png) + +12. Rename the `manifest.json` file to `manifest_sme.json` for reusing the file. + +## Step 7: Run the apps in Microsoft Teams + +1. If your tenant has sideloading apps enabled, you can install your app by following the instructions [here](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/apps/apps-upload#load-your-package-into-teams) + +2. You can also upload it to your tenant's app catalog so that it can be available for everyone in your tenant to install. See [here](https://docs.microsoft.com/en-us/microsoftteams/tenant-apps-catalog-teams) + +3. Install the experts' app (the `faqplus-sme.zip` package) to your team of subject-matter experts. This **MUST** be the same team that you selected in Step 5.3 above. + +* We recommend using [app permission policies](https://docs.microsoft.com/en-us/microsoftteams/teams-app-permission-policies) to restrict access to this app to the members of the experts' team. + +4. Install the end-user app (the `faqplus-enduser.zip` package) to your users. + +## Troubleshooting + +Please see our [Troubleshooting](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Troubleshooting) page. \ No newline at end of file diff --git a/Wiki/Deployment-Guide.md b/Wiki/Deployment-Guide.md new file mode 100644 index 00000000..e4dee274 --- /dev/null +++ b/Wiki/Deployment-Guide.md @@ -0,0 +1,198 @@ +## Prerequisites + +To begin, you will need: + +* An Azure subscription where you can create the following kind of resources: + * App service + * App service plan + * Bot channels registration + * Azure storage account + * Azure search + * Azure function + * Question answering cognitive service + * Application insights +* A team in Microsoft Teams with your group of experts. (You can add and remove team members later!) +* A copy of the FAQ Plus app GitHub repo (https://github.com/OfficeDev/microsoft-teams-apps-faqplus) +* A reasonable set of Question and Answer pairs to set up the knowledge base for the bot. +* A user on the Azure subscription with a contributor role or higher. + +## Step 1: Deploy to your Azure subscription + +** For manual deployment (old approach) please use this [deployment guide](/Wiki/Deployment-Guide-manual.md). + +Please follow below steps to deploy app template: + +* Download the whole solution folder from [GitHub](https://github.com/OfficeDev/microsoft-teams-apps-faqplus) + + ![Screenshot of code download](/Wiki/Images/download-repo-github.png) + +* Open the PowerShell in **administrator** mode +* Navigate to [deploy.ps1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/blob/master/Deployment/deploy.ps1) in your local machine. + * cd `<`PathToLocalFolder`>`\Deployment +* Before running the script, some installations are needed for the user who is running the script for the first time. Please find the steps below: + - In the above-navigated path in PowerShell, run the command "**Set-ExecutionPolicy -ExecutionPolicy RemoteSigned**". This command will allow the user to run deploy.ps1 as execution policy is restricted by default. You can change it to restricted again after successful deployment. + - You will need to unblock the deployment script file before executing the script "**Unblock-File -Path .\deploy.ps1**" + - Install Azure CLI module by running the command below: + "**Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'**" + - Reboot the machine after installing the CLI module. + - Open a new PowerShell window and in administrator mode. Go to the path of deploy.ps1 script file again. + +* Fill-in the Deployment\Parameters.json file with required parameters values for the script. Replace `<<`value`>>` with the correct value for each parameter. +![Screenshot of parameters file](/Wiki/Images/parameters-file.png) + +The script requires the following parameters: + +* TenantId - Id of the tenant where this app would be used (If you are not sure how to get Tenant ID, please check Azure Active Directory in Azure Portal. Under Manage, click Properties. The tenant ID is shown in the Directory ID box). e.g 98f3ece2-3a5a-428b-aa4f-4c41b3f6eef0 +![Screenshot of Azure Active Directory](/Wiki/Images/azure-active-directory.png) +![Screenshot of Tenant Info](/Wiki/Images/fetch-tenant-id.png) + + +* SubscriptionId - Azure subscription to deploy the solution to (MUST be associated with the Azure AD of the Office 365 tenant that you wish to deploy this solution to.) e.g. 22f602c4-1b8f-46df-8b73-45d7bdfbf58e +![Screenshot of Subscription](/Wiki/Images/subscriptions_blade.png) +![Screenshot of Subscription](/Wiki/Images/subscriptions_id.png) + +* SubscriptionTenantId - Id of the tenant where all the resources would be deployed. It is the tenant Id of above subscription. + +* Location - Azure region in which to create the resources. The internal name should be used e.g. uksouth. The location MUST be in a data center that supports: + * Application Insights + * Azure Search + * Cognitive Service for Language (Question Answering) + * For an up-to-date list, click [Valid Azure Locations](https://azure.microsoft.com/en-us/global-infrastructure/services/?products=logic-apps,cognitive-services,search,monitor), and select a region where the above services are available. + +* ResourceGroupName - Name for a new resource group to deploy the solution to - the script will create this resource group. e.g. FAQPlusResourceGroup. + +* BaseAppName - Name for the Azure AD app that will be created e.g. FAQPlus. + +* BaseResourceName - Name which the template uses to generate names for the other resources. + * The app service names `[Base Resource Name]`, `[Base Resource Name]-config`, and `[Base Resource Name]-questionAnswering` must be available. For example, if you select `contosofaqplus` as the base name, the names `contosofaqplus`, `contosofaqplus-config`, and `contosofaqplus-questionAnswering` must be available (not taken in other resource groups/tenants); otherwise, the deployment will fail with a conflict error. + * Remember the base resource name that you selected. We will need it later. + +* ConfigAdminUPNList - A semicolon-delimited list of users who will be allowed to access the configuration app. e.g. adminuser@contoso.onmicrosoft.com;user2@contoso.onmicrosoft.com + +* CompanyName - Your company name which will added to the app metadata. + +* WebsiteUrl - The https:// URL to the company's website. This link should take users to your company or product-specific landing page. Url must start with **https** prefix. + +* PrivacyUrl - The https:// URL to the developer's privacy policy. Url must start with **https** prefix. + +* TermsOfUseUrl - The https:// URL to the developer's terms of use. Url must start with **https** prefix. + +The script has some optional parameters with default values. You can change the default values to fit your needs: + +* AppDisplayName - The app (and bot) display name. Default value: FAQ Plus. +* AppDescription - The app (and bot) description. Default value: A friendly FAQ bot that answers questions and connects you to experts. +* AppIconUrl - The link to the icon for the app. It must resolve to a PNG file. Default value https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-faqplus/master/Manifest/color.png +* Sku - The pricing tier for the hosting plan. Default value: Standard +* PlanSize - The size of the hosting plan (small, medium, or large). Default value: 2 +* QuestionAnsweringSku - The pricing tier for the Question Answering service. Default value: S +* SearchServiceSku - The pricing tier for the Azure Search service. Default value: B (15 indexes) +* GitRepoUrl - The URL to the GitHub repository to deploy. Default value: https://github.com/OfficeDev/microsoft-teams-apps-faqplus.git +* GitBranch - The branch of the GitHub repository to deploy. Default value: master +* IsMigration - True if the app template is being migrated from older version (< 4.0.0). False by default. +* DefaultCulture - Default Culture. "en" is default. + +* Execute the following script in Powershell window: + +`>.\deploy.ps1` + + +*The script will prompt for login thrice during execution, once to get access to the Azure subscription, the other to get access to AAD where app template would be used, and the last one to get access to Azure subscription again. Please login using an account that has **contributor** role or higher. If the tenantId and subscriptionTenantId are same, the script wouls prompt for login only once.* +![Screenshot of Authentication Window](/Wiki/Images/auth-window.png) + +![Screenshot of Authentication Window](/Wiki/Images/auth-window-browser.png) + +*The script will prompt for login with Azure subscription. Login using account that has Azure subscription.* + +*Then the script will validate the existence of Azure resources in the selected region and whether the resources names are available or not. If resources with same name already exist, the script will show a confirmation box to proceed with updating existing resources.* +![Screenshot of Resources Update Confirmation](/Wiki/Images/update-resources-confirmation.png) + +*If Azure AD applications already exist on tenant, The script will show confirmation dialog to update current applications' configurations.* +![Screenshot of Apps Update Confirmation](/Wiki/Images/ad-app-update-confirmation.png) + +*The script will prompt for login using account where the app template would be used. It creates three AAD apps - Expert, User, and Configuration. Once these apps are created, it logs out of this account. If the tenantId and subscriptionTenantId are same, it would create all without this login.* +![Screenshot of Apps Creation](/Wiki/Images/script-app-creation.png) + +*The script will again prompt for login with Azure subscription. Login using account that has Azure subscription. If the tenantId and subscriptionTenantId are same, it would skip this login* +![Screenshot of Azure Subscription](/Wiki/Images/script_login_aad.png) + +When the script has completed a "DEPLOYMENT SUCCEEDED" message will be displayed. + +* After running the script. AD apps, Expert/User/Config Apps, and all required resources will be created. + +* If PowerShell script breaks during deployment, you may run the deployment again if there is no conflict (a resource name already exist in other resource group or another tenant) or refer to [Troubleshooting](/Wiki/Troubleshooting.md) page. + +* If PowerShell script keeps failing, you may share deployment logs (generated in Deployment\\logs.zip) with the app template support team. +![Screenshot of logs file](/Wiki/Images/logs-share.png) + +## Step 2: Create the Question Answering Project + + +Create a project on the [Question Answering portal](https://language.cognitive.azure.com/questionAnswering/projects), following the instructions in the Question Answering documentation [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/how-to/create-test-deploy). + +Select the existing Azure subscription and Choose language resource which created in step 1 "Deploy to your Azure subscription". + +Skip the step, "Create a new language resource", because the script that you deployed in Step 1 "Deploy to your Azure subscription" already created the language service. Proceed directly to the next step, by selecting the already create language resource. + +![Custom Question Answering](./Images/create-question-answering.png) + +Create a new Custom Question Answering project with the same name which was entered in the Step 1. +![Create Custom Question Answering Project](./Images/create-question-answering-project.png) + +![Create Custom Question Answering Project](./Images/create-question-answering-project-2.png) + +### Multi-Turn Enablement +With the new updates to the FAQ Plus app template, the knowledge base can now support multi-turn conversations. To understand the basics of multi-turn conversations, navigate to the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview#multi-turn-conversations) to understand about multi-turn conversations. + +To enable QnA pairs to appear in the multi-turn follow-up prompts please check the 'Show in contextual flow only' checkbox while creating the QnA pairs as multi-turn works only for contextual flow QnA pairs. +![multi_turn_contextual_flow](./Images/multi_turn_contextual_flow.png) + +## Step 3: Finish configuring the FAQ Plus app + +1. Go to the configuration app, which is at `https://[BaseResourceName]-config.azurewebsites.net`. For example, if you chose “contosofaqplus” as the base name, the configuration app will be at `https://contosofaqplus-config.azurewebsites.net`. + +2. You will be prompted to log in with your credentials. Make sure that you log in with an account that is in the list of users allowed to access the configuration app. + +![Config web app page](/Wiki/Images/config-web-app-login.png) + +3. Get the link to the team with your experts from the Teams client. To do so, open Microsoft Teams, and navigate to the team. Click on the "..." next to the team name, then select "Get link to team". + +![Get link to Team](/Wiki/Images/get-link-to-Team.png) + +Click on "Copy" to copy the link to the clipboard. + +![Link to team](/Wiki/Images/link-to-team.png) + +4. Paste the copied link into the "Team Id" field, then press "OK". + +![Add team link form](/Wiki/Images/fill-in-team-link.png) + +5. Enter the Question Answering Project name into the "Project Name" field, then press "OK". + +6. Customize the "Welcome message" that is sent to your End-users when they install the app. This message supports basic markdown, such as bold, italics, bulleted lists, numbered lists, and hyperlinks. See [here](https://docs.microsoft.com/en-us/adaptive-cards/authoring-cards/text-features#markdown) for complete details on what Markdown features are supported. + +### Notes + +Remember to click on "OK" after changing a setting. To edit the setting later, click on "Edit" to make the text box editable. + +## Step 4: Run the apps in Microsoft Teams + +In step #1, The powershell script created two Teams app packages: one for end-users to install personally, and one to be installed to the experts' team. + +1. Open the `Manifest` folder in file explorer. + +2. There should be 2 ZIP packages with the names `faqplus-enduser.zip`, `faqplus-sme.zip` +![File Explorer](/Wiki/Images/file-explorer.png) + +3. If your tenant has sideloading apps enabled, you can install your app by following the instructions [here](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/apps/apps-upload#load-your-package-into-teams) + +4. You can also upload it to your tenant's app catalog so that it can be available for everyone in your tenant to install. See [here](https://docs.microsoft.com/en-us/microsoftteams/tenant-apps-catalog-teams) + +5. Install the experts' app (the `faqplus-experts.zip` package) to your team of subject-matter experts. This **MUST** be the same team that you selected in Step 3.3 above. + +* We recommend using [app permission policies](https://docs.microsoft.com/en-us/microsoftteams/teams-app-permission-policies) to restrict access to this app to the members of the experts' team. + +6. Install the end-user app (the `faqplus-enduser.zip` package) to your users. + +## Troubleshooting + +Please see our [Troubleshooting](/Wiki/Troubleshooting.md) page. \ No newline at end of file diff --git a/Wiki/Home.md b/Wiki/Home.md new file mode 100644 index 00000000..a77af5aa --- /dev/null +++ b/Wiki/Home.md @@ -0,0 +1,92 @@ +## FAQ Plus + +Chatbots on Microsoft Teams are an easy way to provide answers to frequently asked questions by users. However, most chatbots fail to engage with users in a meaningful way because there is no human in the loop when the chatbot fails to answer a question well. + +FAQ Plus bot is a friendly Q&A bot that brings a human in the loop when it is unable to help. A user can ask the bot a question and the bot responds with an answer if it's in the knowledge base. If not, the bot offers the user an option to "Ask an expert", which posts the question to a pre-configured team of experts to provide support. An expert can assign the question to themself, chat with the user to gain more context and add the question to the knowledge base from using a messaging extention so that the next user to ask that same question will get an answer from the chatbot! + +**FAQ Plus provides features to the expert team such as:** +* Adding/editing/deleting/previewing QnA +* Viewing update history of QnA +* View all the existing QnA +* View the original version of the edited QnA +* View details of manually added QnA + +**Here are some screenshots showing FAQ Plus in action:** + +* A user interacting with FAQ Plus through chat: + +![FAQ Plus in action (user view1)](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/UserInteraction1.png) + +![FAQ Plus in action (user view2)](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/UserInteraction2.png) + +![FAQ Plus in action (user view3)](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/UserInteraction3.png) + + +* Expert using FAQ Plus: + +![FAQ Plus in action (experts view1)](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/ExpertInteraction1.png) + +![FAQ Plus in action (experts view2)](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/ExpertInteraction2.png) + +* Expert invoking the task module to add QnA pair: + +![Invoking_taskmodule1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Invoking_taskmodule1.png) + + +* Expert configuring the bot to respond with a hero card as an answer to a question: + +![Add question screen 1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/add-question-richcard1.png) + +* Expert previewing the QnA pair before saving: + +![Preview_Rich_card](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Preview_Rich_card.png) + + +* Expert updating the QnA pair: + +![Updating_Question-ui1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Updating_Question-ui1.png) + +![Updating_Question-ui2](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Updating_Question-ui2.png) + +![Updating_Question-ui4](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Updating_Question-ui4.png) + + +* Some of the fields are markdown supported and are indicated with "(Markdown supported)" beside the field label: + +![Adding_Markdown-Support-1](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Adding_Markdown-Support1.png) + + +* This is how the card will look like when the bot responds with the answer to the Experts team: + +![Adding_Markdown-Support-3](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Adding_Markdown-Support3.png) + + +* This is how the card will look like when the bot responds with the answer to the End-user: + +![End-user_Rich_Card](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/End-user_Rich_Card.png) + +* Migrate action: This message action is to be called in case the expert wants to migrate the ticket posted by legacy bot. In the versions < 4.0.0, there was a single bot for end users as well as experts. In the latest version, there are two bots - one for end-user and other for experts. For previously installed versions of FAQ+, the old bot acts as legacy bot and the new one as expert. Already posted adaptive cards or tickets in SME team would be handled by legacy bot and any newly created tickets would be handled by the new expert bot. In the "Migrate" action, the expert bot would create a new adative card for the old ticket and update this new conversation id in table storage. +**Note**: The **Migrate** action is only used in case of migrating tickets from version < 4.0.0. + +![Migrate_Action](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Migrate_action.png) + +Migrate action invokes a task module. + +![Migrate_Action_Submit](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Migrate_action_submit.png) + +If the ticket is cannot be migrated, the task module displays the message appropriately. + +![Migrate_Action_Submit](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/Migrate_action_back.png) + +If you already have an older version of FAQ+ app installed, please follow the ![Migration Guide](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Migration-Guide-manual) for manual deployment. + + +Please refer the following documentation links for further details related to the app: + +- [Solution overview](Solution-Overview) + - [Data stores](Data-Stores) + - [Cost estimate](Cost-Estimates) + +- Deploying the app + - [Deployment guide](Deployment-Guide) + - [Troubleshooting](Troubleshooting) diff --git a/Wiki/Images/Adding_Markdown-Support1.png b/Wiki/Images/Adding_Markdown-Support1.png new file mode 100644 index 00000000..b4935e03 Binary files /dev/null and b/Wiki/Images/Adding_Markdown-Support1.png differ diff --git a/Wiki/Images/Adding_Markdown-Support3.png b/Wiki/Images/Adding_Markdown-Support3.png new file mode 100644 index 00000000..b629ca38 Binary files /dev/null and b/Wiki/Images/Adding_Markdown-Support3.png differ diff --git a/Wiki/Images/App_service_app-settings.png b/Wiki/Images/App_service_app-settings.png new file mode 100644 index 00000000..4a005766 Binary files /dev/null and b/Wiki/Images/App_service_app-settings.png differ diff --git a/Wiki/Images/AuthenticationImage1.png b/Wiki/Images/AuthenticationImage1.png new file mode 100644 index 00000000..611e3b51 Binary files /dev/null and b/Wiki/Images/AuthenticationImage1.png differ diff --git a/Wiki/Images/AuthenticationImage2.png b/Wiki/Images/AuthenticationImage2.png new file mode 100644 index 00000000..63fd360a Binary files /dev/null and b/Wiki/Images/AuthenticationImage2.png differ diff --git a/Wiki/Images/AuthenticationImage3.png b/Wiki/Images/AuthenticationImage3.png new file mode 100644 index 00000000..3045872b Binary files /dev/null and b/Wiki/Images/AuthenticationImage3.png differ diff --git a/Wiki/Images/BotChannelGovernment.PNG b/Wiki/Images/BotChannelGovernment.PNG new file mode 100644 index 00000000..46db245d Binary files /dev/null and b/Wiki/Images/BotChannelGovernment.PNG differ diff --git a/Wiki/Images/Configuration_app-settings.png b/Wiki/Images/Configuration_app-settings.png new file mode 100644 index 00000000..da34cb79 Binary files /dev/null and b/Wiki/Images/Configuration_app-settings.png differ diff --git a/Wiki/Images/Dfd_AddQuestion.png b/Wiki/Images/Dfd_AddQuestion.png new file mode 100644 index 00000000..bd228872 Binary files /dev/null and b/Wiki/Images/Dfd_AddQuestion.png differ diff --git a/Wiki/Images/Dfd_Publish.png b/Wiki/Images/Dfd_Publish.png new file mode 100644 index 00000000..3395f0af Binary files /dev/null and b/Wiki/Images/Dfd_Publish.png differ diff --git a/Wiki/Images/Dfd_UpdateDelete.png b/Wiki/Images/Dfd_UpdateDelete.png new file mode 100644 index 00000000..6cb2610c Binary files /dev/null and b/Wiki/Images/Dfd_UpdateDelete.png differ diff --git a/Wiki/Images/End-user_Rich_Card.png b/Wiki/Images/End-user_Rich_Card.png new file mode 100644 index 00000000..20533f8a Binary files /dev/null and b/Wiki/Images/End-user_Rich_Card.png differ diff --git a/Wiki/Images/ExpertInteraction1.png b/Wiki/Images/ExpertInteraction1.png new file mode 100644 index 00000000..5fa2b765 Binary files /dev/null and b/Wiki/Images/ExpertInteraction1.png differ diff --git a/Wiki/Images/ExpertInteraction2.png b/Wiki/Images/ExpertInteraction2.png new file mode 100644 index 00000000..d84e4d8f Binary files /dev/null and b/Wiki/Images/ExpertInteraction2.png differ diff --git a/Wiki/Images/FAQPlusEndUser.gif b/Wiki/Images/FAQPlusEndUser.gif new file mode 100644 index 00000000..2a93174a Binary files /dev/null and b/Wiki/Images/FAQPlusEndUser.gif differ diff --git a/Wiki/Images/FAQPlusExperts.gif b/Wiki/Images/FAQPlusExperts.gif new file mode 100644 index 00000000..4804e2be Binary files /dev/null and b/Wiki/Images/FAQPlusExperts.gif differ diff --git a/Wiki/Images/FAQPlus_QnA_fieldmapping.png b/Wiki/Images/FAQPlus_QnA_fieldmapping.png new file mode 100644 index 00000000..118ee040 Binary files /dev/null and b/Wiki/Images/FAQPlus_QnA_fieldmapping.png differ diff --git a/Wiki/Images/GCCHigh1.png b/Wiki/Images/GCCHigh1.png new file mode 100644 index 00000000..2b269f01 Binary files /dev/null and b/Wiki/Images/GCCHigh1.png differ diff --git a/Wiki/Images/GCCHigh2.png b/Wiki/Images/GCCHigh2.png new file mode 100644 index 00000000..ff48aabf Binary files /dev/null and b/Wiki/Images/GCCHigh2.png differ diff --git a/Wiki/Images/Invoking_taskmodule1.png b/Wiki/Images/Invoking_taskmodule1.png new file mode 100644 index 00000000..52debaa8 Binary files /dev/null and b/Wiki/Images/Invoking_taskmodule1.png differ diff --git a/Wiki/Images/Invoking_taskmodule2.png b/Wiki/Images/Invoking_taskmodule2.png new file mode 100644 index 00000000..02e98511 Binary files /dev/null and b/Wiki/Images/Invoking_taskmodule2.png differ diff --git a/Wiki/Images/Migrate_action.png b/Wiki/Images/Migrate_action.png new file mode 100644 index 00000000..565eb53c Binary files /dev/null and b/Wiki/Images/Migrate_action.png differ diff --git a/Wiki/Images/Migrate_action_back.png b/Wiki/Images/Migrate_action_back.png new file mode 100644 index 00000000..52201991 Binary files /dev/null and b/Wiki/Images/Migrate_action_back.png differ diff --git a/Wiki/Images/Migrate_action_submit.png b/Wiki/Images/Migrate_action_submit.png new file mode 100644 index 00000000..206e4367 Binary files /dev/null and b/Wiki/Images/Migrate_action_submit.png differ diff --git a/Wiki/Images/Preview_Rich_card.png b/Wiki/Images/Preview_Rich_card.png new file mode 100644 index 00000000..6b94a519 Binary files /dev/null and b/Wiki/Images/Preview_Rich_card.png differ diff --git a/Wiki/Images/Updating_Question-ui1.png b/Wiki/Images/Updating_Question-ui1.png new file mode 100644 index 00000000..fc88493e Binary files /dev/null and b/Wiki/Images/Updating_Question-ui1.png differ diff --git a/Wiki/Images/Updating_Question-ui2.png b/Wiki/Images/Updating_Question-ui2.png new file mode 100644 index 00000000..c800d066 Binary files /dev/null and b/Wiki/Images/Updating_Question-ui2.png differ diff --git a/Wiki/Images/Updating_Question-ui4.png b/Wiki/Images/Updating_Question-ui4.png new file mode 100644 index 00000000..7014737e Binary files /dev/null and b/Wiki/Images/Updating_Question-ui4.png differ diff --git a/Wiki/Images/UserInteraction1.png b/Wiki/Images/UserInteraction1.png new file mode 100644 index 00000000..c6505e8c Binary files /dev/null and b/Wiki/Images/UserInteraction1.png differ diff --git a/Wiki/Images/UserInteraction2.png b/Wiki/Images/UserInteraction2.png new file mode 100644 index 00000000..6d893aad Binary files /dev/null and b/Wiki/Images/UserInteraction2.png differ diff --git a/Wiki/Images/UserInteraction3.png b/Wiki/Images/UserInteraction3.png new file mode 100644 index 00000000..b6a530a8 Binary files /dev/null and b/Wiki/Images/UserInteraction3.png differ diff --git a/Wiki/Images/ad-app-consent-error.png b/Wiki/Images/ad-app-consent-error.png new file mode 100644 index 00000000..1f5e58be Binary files /dev/null and b/Wiki/Images/ad-app-consent-error.png differ diff --git a/Wiki/Images/ad-app-update-confirmation.png b/Wiki/Images/ad-app-update-confirmation.png new file mode 100644 index 00000000..b9a6edea Binary files /dev/null and b/Wiki/Images/ad-app-update-confirmation.png differ diff --git a/Wiki/Images/add-question-richcard1.png b/Wiki/Images/add-question-richcard1.png new file mode 100644 index 00000000..79a59d6b Binary files /dev/null and b/Wiki/Images/add-question-richcard1.png differ diff --git a/Wiki/Images/app-admin-consent.png b/Wiki/Images/app-admin-consent.png new file mode 100644 index 00000000..b9574e90 Binary files /dev/null and b/Wiki/Images/app-admin-consent.png differ diff --git a/Wiki/Images/architecture_overview.png b/Wiki/Images/architecture_overview.png new file mode 100644 index 00000000..cc295fee Binary files /dev/null and b/Wiki/Images/architecture_overview.png differ diff --git a/Wiki/Images/auth-window-browser.png b/Wiki/Images/auth-window-browser.png new file mode 100644 index 00000000..4ece1a6f Binary files /dev/null and b/Wiki/Images/auth-window-browser.png differ diff --git a/Wiki/Images/auth-window.png b/Wiki/Images/auth-window.png new file mode 100644 index 00000000..176901cf Binary files /dev/null and b/Wiki/Images/auth-window.png differ diff --git a/Wiki/Images/azure-active-directory.png b/Wiki/Images/azure-active-directory.png new file mode 100644 index 00000000..a4f1e1c9 Binary files /dev/null and b/Wiki/Images/azure-active-directory.png differ diff --git a/Wiki/Images/azure-config-app-step3.png b/Wiki/Images/azure-config-app-step3.png new file mode 100644 index 00000000..7064a2d9 Binary files /dev/null and b/Wiki/Images/azure-config-app-step3.png differ diff --git a/Wiki/Images/config-web-app-login.png b/Wiki/Images/config-web-app-login.png new file mode 100644 index 00000000..abb97906 Binary files /dev/null and b/Wiki/Images/config-web-app-login.png differ diff --git a/Wiki/Images/create-question-answering-project-2.png b/Wiki/Images/create-question-answering-project-2.png new file mode 100644 index 00000000..219f19d5 Binary files /dev/null and b/Wiki/Images/create-question-answering-project-2.png differ diff --git a/Wiki/Images/create-question-answering-project.png b/Wiki/Images/create-question-answering-project.png new file mode 100644 index 00000000..21b38f4b Binary files /dev/null and b/Wiki/Images/create-question-answering-project.png differ diff --git a/Wiki/Images/create-question-answering.png b/Wiki/Images/create-question-answering.png new file mode 100644 index 00000000..a7b4bc8c Binary files /dev/null and b/Wiki/Images/create-question-answering.png differ diff --git a/Wiki/Images/deploybutton.png b/Wiki/Images/deploybutton.png new file mode 100644 index 00000000..e81f2c1c Binary files /dev/null and b/Wiki/Images/deploybutton.png differ diff --git a/Wiki/Images/download-repo-github.png b/Wiki/Images/download-repo-github.png new file mode 100644 index 00000000..9a4e75bb Binary files /dev/null and b/Wiki/Images/download-repo-github.png differ diff --git a/Wiki/Images/fetch-tenant-id.png b/Wiki/Images/fetch-tenant-id.png new file mode 100644 index 00000000..1ed50c8a Binary files /dev/null and b/Wiki/Images/fetch-tenant-id.png differ diff --git a/Wiki/Images/file-explorer-legacy.png b/Wiki/Images/file-explorer-legacy.png new file mode 100644 index 00000000..56686bc1 Binary files /dev/null and b/Wiki/Images/file-explorer-legacy.png differ diff --git a/Wiki/Images/file-explorer-sme.png b/Wiki/Images/file-explorer-sme.png new file mode 100644 index 00000000..5b577cf6 Binary files /dev/null and b/Wiki/Images/file-explorer-sme.png differ diff --git a/Wiki/Images/file-explorer-user.png b/Wiki/Images/file-explorer-user.png new file mode 100644 index 00000000..d3e75a36 Binary files /dev/null and b/Wiki/Images/file-explorer-user.png differ diff --git a/Wiki/Images/file-explorer.png b/Wiki/Images/file-explorer.png new file mode 100644 index 00000000..13761041 Binary files /dev/null and b/Wiki/Images/file-explorer.png differ diff --git a/Wiki/Images/fill-in-team-link.png b/Wiki/Images/fill-in-team-link.png new file mode 100644 index 00000000..97a51887 Binary files /dev/null and b/Wiki/Images/fill-in-team-link.png differ diff --git a/Wiki/Images/get-link-to-Team.png b/Wiki/Images/get-link-to-Team.png new file mode 100644 index 00000000..9df2095c Binary files /dev/null and b/Wiki/Images/get-link-to-Team.png differ diff --git a/Wiki/Images/kb_publishing.png b/Wiki/Images/kb_publishing.png new file mode 100644 index 00000000..9efcd19c Binary files /dev/null and b/Wiki/Images/kb_publishing.png differ diff --git a/Wiki/Images/link-to-team.png b/Wiki/Images/link-to-team.png new file mode 100644 index 00000000..6f79222c Binary files /dev/null and b/Wiki/Images/link-to-team.png differ diff --git a/Wiki/Images/logs-share.png b/Wiki/Images/logs-share.png new file mode 100644 index 00000000..265a933e Binary files /dev/null and b/Wiki/Images/logs-share.png differ diff --git a/Wiki/Images/manifest_parsing_failed.png b/Wiki/Images/manifest_parsing_failed.png new file mode 100644 index 00000000..6d4c60a9 Binary files /dev/null and b/Wiki/Images/manifest_parsing_failed.png differ diff --git a/Wiki/Images/migration_add_channel.png b/Wiki/Images/migration_add_channel.png new file mode 100644 index 00000000..fdd0ae97 Binary files /dev/null and b/Wiki/Images/migration_add_channel.png differ diff --git a/Wiki/Images/migration_create_bot.png b/Wiki/Images/migration_create_bot.png new file mode 100644 index 00000000..6ac47d44 Binary files /dev/null and b/Wiki/Images/migration_create_bot.png differ diff --git a/Wiki/Images/migration_endpoint_user.png b/Wiki/Images/migration_endpoint_user.png new file mode 100644 index 00000000..c8575452 Binary files /dev/null and b/Wiki/Images/migration_endpoint_user.png differ diff --git a/Wiki/Images/migration_expert_endpoint.png b/Wiki/Images/migration_expert_endpoint.png new file mode 100644 index 00000000..ca652b17 Binary files /dev/null and b/Wiki/Images/migration_expert_endpoint.png differ diff --git a/Wiki/Images/migration_multitenant_app_creation.png b/Wiki/Images/migration_multitenant_app_creation.png new file mode 100644 index 00000000..43b42725 Binary files /dev/null and b/Wiki/Images/migration_multitenant_app_creation.png differ diff --git a/Wiki/Images/migration_multitenant_app_overview.png b/Wiki/Images/migration_multitenant_app_overview.png new file mode 100644 index 00000000..11310796 Binary files /dev/null and b/Wiki/Images/migration_multitenant_app_overview.png differ diff --git a/Wiki/Images/migration_update_configuration.png b/Wiki/Images/migration_update_configuration.png new file mode 100644 index 00000000..e5d43240 Binary files /dev/null and b/Wiki/Images/migration_update_configuration.png differ diff --git a/Wiki/Images/multi_turn_contextual_flow.png b/Wiki/Images/multi_turn_contextual_flow.png new file mode 100644 index 00000000..0cd13005 Binary files /dev/null and b/Wiki/Images/multi_turn_contextual_flow.png differ diff --git a/Wiki/Images/multitenant_app_creation.png b/Wiki/Images/multitenant_app_creation.png new file mode 100644 index 00000000..affd004f Binary files /dev/null and b/Wiki/Images/multitenant_app_creation.png differ diff --git a/Wiki/Images/multitenant_app_overview.png b/Wiki/Images/multitenant_app_overview.png new file mode 100644 index 00000000..1b177b72 Binary files /dev/null and b/Wiki/Images/multitenant_app_overview.png differ diff --git a/Wiki/Images/multitenant_app_secret.png b/Wiki/Images/multitenant_app_secret.png new file mode 100644 index 00000000..bf56dccf Binary files /dev/null and b/Wiki/Images/multitenant_app_secret.png differ diff --git a/Wiki/Images/parameters-file.png b/Wiki/Images/parameters-file.png new file mode 100644 index 00000000..6662d99c Binary files /dev/null and b/Wiki/Images/parameters-file.png differ diff --git a/Wiki/Images/script-app-creation.png b/Wiki/Images/script-app-creation.png new file mode 100644 index 00000000..4c7fb2f0 Binary files /dev/null and b/Wiki/Images/script-app-creation.png differ diff --git a/Wiki/Images/script_login_aad.png b/Wiki/Images/script_login_aad.png new file mode 100644 index 00000000..0a16f6fe Binary files /dev/null and b/Wiki/Images/script_login_aad.png differ diff --git a/Wiki/Images/subscriptions_blade.png b/Wiki/Images/subscriptions_blade.png new file mode 100644 index 00000000..bc54b701 Binary files /dev/null and b/Wiki/Images/subscriptions_blade.png differ diff --git a/Wiki/Images/subscriptions_id.png b/Wiki/Images/subscriptions_id.png new file mode 100644 index 00000000..64442cd7 Binary files /dev/null and b/Wiki/Images/subscriptions_id.png differ diff --git a/Wiki/Images/trace_example.png b/Wiki/Images/trace_example.png new file mode 100644 index 00000000..75d4f835 Binary files /dev/null and b/Wiki/Images/trace_example.png differ diff --git a/Wiki/Images/update-resources-confirmation.png b/Wiki/Images/update-resources-confirmation.png new file mode 100644 index 00000000..3fd8cf43 Binary files /dev/null and b/Wiki/Images/update-resources-confirmation.png differ diff --git a/Wiki/Images/user-not-in-upn-list.png b/Wiki/Images/user-not-in-upn-list.png new file mode 100644 index 00000000..5231cb06 Binary files /dev/null and b/Wiki/Images/user-not-in-upn-list.png differ diff --git a/Wiki/Migration-Guide-manual.md b/Wiki/Migration-Guide-manual.md new file mode 100644 index 00000000..256bcfdb --- /dev/null +++ b/Wiki/Migration-Guide-manual.md @@ -0,0 +1,123 @@ +**Note**: Migration approach to v5.0(lastest) is not yet finalized. This guide helps in migrating the older versions of FAQ+ (version < 4.0.0) to version = 4.0.0. + +FAQ v4.0 uses two bots - one for end user and the other for SME team. + +## Step 1. Create a new Azure AD app for Expert bot. +Register an Azure AD applications in your tenant's directory: the Expert bot app. + +1. Log in to the Azure Portal for your subscription and go to the "App registrations" blade [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps). + +2. Click on "New registration", and create an Azure AD application. + 1. **Name**: The name of your Expert's app - if your default bot name for older deployment is FAQ Plus, name the expert bot as "FAQ Plus Expert". + 2. **Supported account types**: Select "Accounts in any organizational directory". + 3. Leave the "Redirect URL" field blank. + +![Azure registration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_multitenant_app_creation.png) + +3. Click on the "Register" button. + +4. When the app is registered, you'll be taken to the app's "Overview" page. Copy the **Application (client) ID** and **Directory (tenant) ID**; we will need it later. Verify that the "Supported account types" is set to **Multiple organizations**. + +![Azure overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_multitenant_app_overview.png) + +5. On the side rail in the Manage section, navigate to the "Certificates & secrets" section. In the Client secrets section, click on "+ New client secret". Add a description of the secret and select an expiry time. Click "Add". + +![Azure AD overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/multitenant_app_secret.png) + +6. Once the client secret is created, copy its **Value**; we will need it later. + +7. At this point you have two values. +* Application (client) ID for the expert bot. +* Client secret for the expert bot. + +## Step 2. Create a new Azure Bot for Expert app. + +1. Log in to the Azure portal for your subscription, search for "Azure Bot", and create one [here](https://ms.portal.azure.com/#create/Microsoft.AzureBot). + +2. Fill the following fields - +* **Bot handle**: Name for Azure bot. +* **Subscription**: Your existing Azure subscription. +* **Resource group**: Existing resource group where other resources are deployed. +* **Pricing tier**: Select the appropriate pricing tier. +* **Microsoft App ID**: Choose "Use existing app registration". Enter the app id and password of above expert app. + +![Azure Bot overview page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_create_bot.png) + +3. Add Teams channel to the bot. +![Azure Bot channel page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_add_channel.png) + +4. Add messaging endpoint for the bot, e.g. `https://<>/api/messages/expert` +![Azure Bot configuration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_expert_endpoint.png) + +## Step 3. Update the exixting Azure Bot. +The existing Azure bot in the already deloyed resource group would act as the end user bot. + +1. Update the messaging endpoint of the user bot. Append `user` to the end, e.g. e.g. `https://<>/api/messages/user` +![Azure Bot configuration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_endpoint_user.png) + +## Step 4. Update the App Service Configuration for FAQ+ app. +Go to Azure App Service for FAQ+ app. Click on "Configuration" and update the following: + +1. Rename "MicrosoftAppId" to **"UserAppId"**. +2. Rename "MicrosoftAppPassword" to **"UserAppPassword"**. +3. Click "New application setting" and add **"ExpertAppId"** as expert app id from step 1. +4. Click "New application setting" and add **"ExpertAppPassword"** as expert app secret from step 1. + +![Azure App Service configuration page](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/migration_update_configuration.png) + +## Step 5. Update App service code. +Follow [Continuous Deployment](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Continuous-Deployment) for updating the app service code. + +## Step 6. Generate manifest. +Create three Teams app packages: one for end-users to install personally, one to be installed to the experts' team, and one the supports legacy code. + +1. Open the `Manifest\EndUser\manifest_enduser.json` file in a text editor. + +2. Change the placeholder fields in the manifest to values appropriate for your organization. + +* `developer.name` ([What's this?](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema#developer)) + +* `developer.websiteUrl` + +* `developer.privacyUrl` + +* `developer.termsOfUseUrl` + +3. Replace all the occurrences of `<>` placeholder to your Azure AD end user application's ID from above. This is the same GUID that you entered in the template under "User Bot Client ID". + +4. In the "validDomains" section, replace all the occurrences of `<>` with your Bot App Service's domain. This will be `[BaseResourceName].azurewebsites.net`. For example, if you chose "contosofaqplus" as the base name, change the placeholder to `contosofaqplus.azurewebsites.net`. + +5. Save and Rename `manifest_enduser.json` file to a file named `manifest.json`. + +6. Create a ZIP package with the all the files in `Manifest\EndUser` folder - `manifest.json`,`color.png` and `outline.png`, along with localization files - `ar.json`, `de.json`, `en.json`, `es.json`, `fr.json`, `he.json`, `ja.json`, `ko.json`, `pt-BR.json`, `ru.json`, `zh-CN.json`, `zh-TW.json`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-enduser.zip`, so you know that this is the app for end-users. +* Make sure that the 15 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer-user.png) + +7. Rename the `manifest.json` file to `manifest_enduser.json` for reusing the file. + +8. Open the `Manifest\SME\manifest_sme.json` file in a text editor. + +9. Repeat the steps from 2 to 4 to replace all the placeholders in the file. The placeholder `<>` should be replaced by your Azure AD expert application's ID from above. + +10. Save and Rename `manifest_sme.json` file to a file named `manifest.json`. + +11. Create a ZIP package with the all the files in `Manifest\SME` folder (except manifest_legacy) - `manifest.json`,`color.png` and `outline.png`, along with localization files - `ar.json`, `de.json`, `en.json`, `es.json`, `fr.json`, `he.json`, `ja.json`, `ko.json`, `pt-BR.json`, `ru.json`, `zh-CN.json`, `zh-TW.json`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-sme.zip`, so you know that this is the app for sme. +* Make sure that the 15 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer-sme.png) + +12. Rename the `manifest.json` file to `manifest_sme.json` for reusing the file. + +13. To support legacy code, open `Manifest\SME\manifest_legacy.json` in text editor. + +14. Repeat the steps from 2 to 4 to replace all the placeholders in the file. The placeholder `<>` should be replaced by your Azure AD end user application's ID from above. + +15. Create a ZIP package with the all the files in `Manifest\SME` folder (except manifest_sme) - `manifest.json`,`color.png` and `outline.png`, along with localization files - `ar.json`, `de.json`, `en.json`, `es.json`, `fr.json`, `he.json`, `ja.json`, `ko.json`, `pt-BR.json`, `ru.json`, `zh-CN.json`, `zh-TW.json`. The two image files are the icons for your app in Teams. +* Name this package `faqplus-legacy.zip`, so you know that this is the app for sme. +* Make sure that the 15 files are the _top level_ of the ZIP package, with no nested folders. +![File Explorer](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/file-explorer-legacy.png) + +12. Rename the `manifest.json` file to `manifest_legacy.json` for reusing the file. + +**Note**: Please re-install all the three packages to make the new and legacy code working. The legacy app and sme app packages are to be installed in Expert's team. The end user app package is to be installed for 1:1 chat with end user. The legacy app handles the requests for the existing ticket cards in Expert's team whereas the new sme app handles all the fresh requests in Expert's team. \ No newline at end of file diff --git a/Wiki/Release-Notes.md b/Wiki/Release-Notes.md new file mode 100644 index 00000000..a0cee462 --- /dev/null +++ b/Wiki/Release-Notes.md @@ -0,0 +1,32 @@ +This page contains the different release details for FAQ Plus app, + +## Version history +| Version | Release Date | +|----|----| +| 5.0 | Dec 29, 2022 | +| 4.0 | Jan 20, 2022 | +| 3.0 | Jul, 2020 | + +## Release notes + +### 5.0 (Dec 29, 2022) + +Below improvements released, + +- Remove the deprecated QnA Maker service and replace with Question Answering service. +- Fix for multi-turn bug +- Wiki updates + +### 4.0 (Jan 20, 2022) + +Below improvements released, + +- Separate user bot and SME bot experience to set up different permissions policies. +- Bug Fix and UI improvements. + +### 3.0 (Jul, 2020) + +Below improvements released, + +- New Feature: Multi-turn feature to the end user experience. +- Bug Fix and UI improvements. \ No newline at end of file diff --git a/Wiki/SequenceDiagram.md b/Wiki/SequenceDiagram.md new file mode 100644 index 00000000..21301b19 --- /dev/null +++ b/Wiki/SequenceDiagram.md @@ -0,0 +1,29 @@ +# Data flow diagram to detail out the data flow between various services in FAQ Plus bot + +## Add question using compose action + +- User clicks on '+' icon on messaging extension, task module is invoked. + +- On click of 'Save' button, bot checks for knowledge base associated with user's team from Azure storage. + +- With project name, question is added in Question Answering and corresponding result is shown in card. + +![dfd_add_ question](./Images/Dfd_AddQuestion.png) + +## Update/Delete question + +- User updates and deleted QnA pair using Update and Delete buttons on adaptive/hero card. + +- On clicking buttons, bot checks for project name associated with user's team from Azure storage. + +- With project name, question is updated/deleted in Question Answering and corresponding result is shown in card. + +![dfd_update_delete](./Images/Dfd_UpdateDelete.png) + +## Azure function for publishing Question Answering project knowledge base + +- Azure function is triggered every fifteen minutes to publish Question Answering project knowledge base. + +- It publishes knowledge base only if modification is done from last publish time. + +![dfd_Publish question answering project knowledge base](./Images/Dfd_Publish.png) \ No newline at end of file diff --git a/Wiki/Solution-Overview.md b/Wiki/Solution-Overview.md new file mode 100644 index 00000000..a2e952ef --- /dev/null +++ b/Wiki/Solution-Overview.md @@ -0,0 +1,45 @@ +![architecture-overview](./Images/architecture_overview.png) + +The **FAQ Plus** application has the following main components: + +* **Question Answering**: Resources that comprise the Question Answering service, which implements the "FAQ" part of the application. The installer creates a [Question Answering Project Knowledge Base](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/how-to/manage-knowledge-base) using the [tools](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/concepts/project-development-lifecycle) provided by Question Answering. + +* **FAQ Plus Expert Bot**: The bot serves experts team + * The experts team receives notifications from the bot when end-users ask questions to expert or create feedback items. The bot tracks questions in a simple "ticketing system", with a basic life cycle of Unassigned -> Assigned to expert -> Closed. The bot notifies both the end-user and the experts team as the request changes states. + * Using the bot, members of the experts team can add QnA to the existing knowledge base. + * The bot also implements a messaging extension that lets members of the expert team search for tickets or questions in the knowledge base. + +* **FAQ Plus User Bot**: The bot serves both end-users and experts team + * The knowledge base (KB) in Question Answering is presented to end-user in a 1:1 conversational bot interface. Through the bot, end-user can ask the question to bot, escalate to a designated experts team, send feedback about the app, or give feedback on specific answers. + +* **Blob Storage** : The knowledge base with QnA and associated metadata is stored in blob storage by Azure function. The same is shown by messaging extension for respective section and search categories using Azure search service. + +* **Azure Function**: QnA changes in Question Answering are published every fifteen minutes to knowledge base by time triggered Azure functions. + +* **Configuration Application**: An Azure App Service lets app admins configure the application to provide team and knowledge base details. These values are necessary to map the expert team and the associated knowledge base. Currently app supports only one Question Answering Project knowledge base per tenant(deployment). + +## Question Answering + +FAQ Plus uses Question Answering to respond to user questions; in fact, you can have a blank knowledge base to start using FAQ Plus. The prQuestion answering provides cloud-based Natural Language Processing (NLP) that allows you to create a natural conversational layer over your data. It is used to find the most appropriate answer for any input from your custom knowledge base (KB) of information. Please keep in mind that a good knowledge base requires curation and feedback: see [Development lifecycle of a knowledge base](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/concepts/project-development-lifecycle). + +For more details about Question Answering, please refer to the [Question Answering documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/language-service/question-answering/overview). + +## Bot and Messaging Extension + +The bot is built using the [Bot Framework SDK v4 for .NET](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) and [ASP.NET Core 3.1](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-3.1). The bot has a conversational interface in personal (1:1) scope for end-users and in team scope for the experts team. It also implements a messaging extension with [query commands](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/messaging-extensions/search-extensions), which the experts team can use to search for and share requests or knowledge base questions. + +## Azure Function +Azure function [ASP.NET Core 3.1](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-3.1) is used in building the bot. It publishes the QnA pair in the Question Answering and stores the QnA in the blob storage. It creates a search index to search questions in the knowledge base tab on messaging extension. + +## Blob Storage +Blob Storage stores the QnA pair in JSON format. QnA can be searched from the search tab in the messaging extension. + +## Configuration App +The configuration app is a standard [ASP.NET MVC 5](https://docs.microsoft.com/en-us/aspnet/mvc/mvc5) web app. The configuration app will be used infrequently, so the included ARM template puts it in the same App Service Plan as the bot and Question Answering. + +From this simple web interface, app administrators can: + +* designate the experts team +* set the knowledge base to query +* set the welcome message that's sent to all end-users +* set the content of the Help tab diff --git a/Wiki/Take-It-Further.md b/Wiki/Take-It-Further.md new file mode 100644 index 00000000..a749b0d9 --- /dev/null +++ b/Wiki/Take-It-Further.md @@ -0,0 +1,41 @@ +## Scenario 1: + +Customize the app to have a different name depending on the team it is installed in. + +For ex: IT aka Contoso IT Support. + + +**Suggested Solution:** Please follow below mentioned steps to configure the app to be used for different domains: + +**Code Changes:** + +- Change the text references in the associated resource(Strings.resx) file from FAQ Plus to the domain on which it should cater to. + +- Update the app name, description, tab name and other details in the associated manifest JSON files for end-user and experts team respectively. + +- Change the welcome message as desired in the configurator web app. + + +**Pros:** Very minimal changes required. + +## Scenario 2: + +Configure the bot to use an existing knowledge base with QnA pairs instead of a raw vanilla Question Answering knowledge base. + +**Suggested Solution:** + +1) Change the **Project Name** field value in the configurator app and your bot will start pointing to knowledge base with QnA pairs associated with updated Question Answering project knowledge base. + +![image4](./Images/config-web-app-login.png) + +2) Navigate to [Azure portal](https://portal.azure.com/) and go to configuration section of your bot app service, update the appsettings values for QuestionAnswerProjectName,QuestionAnswerHostUrl, QuestionAnswerApiEndpointKey & QuestionAnswerApiEndpointUrl of your existing knowledge base. + +![App_service_app-settings](./Images/App_service_app-settings.png) + +## Scenario 3: + +Expert wants to edit or delete the existing QnA pairs directly added from the Question Answering portal. + +**Suggested Solution:** + +If expert wants to edit or delete the QnA pairs, the expert needs to navigate to that particular knowledge base in the [Question Answering portal](https://language.cognitive.azure.com/) and can perform the required operations. \ No newline at end of file diff --git a/Wiki/Telemetry.md b/Wiki/Telemetry.md new file mode 100644 index 00000000..b92bbdf5 --- /dev/null +++ b/Wiki/Telemetry.md @@ -0,0 +1,110 @@ +# Telemetry + +The FAQ Plus app logs telemetry to [Azure Application Insights](https://azure.microsoft.com/en-us/services/monitor/). You can go to the respective Application Insights blade of the Azure App Services to view basic telemetry about your services, such as requests, failures, and dependency errors, custom events, traces etc. + +The FAQ Plus app integrates with Application Insights to gather bot activity analytics, as described [here](https://blog.botframework.com/2019/03/21/bot-analytics-behind-the-scenes/). + +The app logs AadObjectId of user for tracing logs. The deployer should ensure that the solution meets their privacy/data retention requirements, and can choose to remove it if they wish. + +The FAQ Plus app logs following events: + +`Activity`: +- Basic activity info: `ActivityId`, `ActivityType`, `Event Name` +- Basic user info: `FromID` + +`UserActivity`: +- Basic activity info: `ActivityId`, `ActivityType`, `Event Name` +- Basic user info: `UserAadObjectId` +- Context of how it was invoked: `ConversationType` + +`Logging`: +- Application uses application insights trace logging to track application execution logs. The logs in here can be helpful to determine user actions. Following are the common application insight queries that would be of interest. + +*Application Insights queries:* + +- Get list of traces messages and count when bot is added to 1:1 chat in last 30 days + +``` +traces +| where message contains "Bot added to 1:1 chat" +| where timestamp >= ago(30d) +| summarize count() by message +``` +- Number of times bot is added to team successfully in last 30 days + +``` +traces +| where message contains "Bot added to team" +| where timestamp >= ago(30d) +| summarize count() by message +``` +- Number of times users sends feedback card in last 30 days + +``` +traces +| where message contains "Sending user feedback card" +| where timestamp >= ago(30d) +| summarize count() by message +``` +- Number of times users sends ask an expert card in last 30 days + +``` +traces +| where message contains "Sending user ask an expert card" +| where timestamp >= ago(30d) +| summarize count() by message +``` +- Number of times Bot sends tour card in last 30 days + +``` +traces +| where message contains "Sending team tour card" +| where timestamp >= ago(30d) +``` +- Number of times bot posts question to expert team in last 30 days + +``` +traces +| where message contains "Received question for expert" +| where timestamp >= ago(30d) +``` +- Number of times the user submitted the feedback in last 30 days + +``` +traces +| where message contains "Received app feedback" +| where timestamp >= ago(30d) +``` +For e.g.: trace showing the total number of times feedback card is sent. + +![trace_example](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/trace_example.png) + +The **Configurator** app with Application Insights to gather event activity analytics, as described [here]((https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview)). + +The Configurator App logs following events: + +`Exceptions`: + +- Global exceptions logging. + +*Application Insights Log Levels:* +- **Trace = 0** : Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment. +- **Debug = 1** : Logs that are used for interactive investigation during development. These logs should primarily contain information useful for debugging and have no long-term value. +- **Information = 2** : Logs that track the general flow of the application. These logs should have long-term value. +- **Warning = 3** :Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop +- **Error = 4** : Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure. +- **Critical = 5** :Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention. +- **None = 6** : Not used for writing log messages. Specifies that a logging category should not write any messages. + +If the Admin user wants to change Log Level, he/she has to go to Application Settings in the Configuration of the App Service and change the Log Level value for "ApplicationInsightsLogLevel" . +For e.g. +"ApplicationInsightsLogLevel": "Information" + +Below are the possible values of Log Level: +1. Trace +2. Debug +3. Information +4. Warning +5. Error +6. Critical +7. None \ No newline at end of file diff --git a/Wiki/Troubleshooting.md b/Wiki/Troubleshooting.md new file mode 100644 index 00000000..0894bfd2 --- /dev/null +++ b/Wiki/Troubleshooting.md @@ -0,0 +1,147 @@ +# General template issues + +### **Generic possible issues** + +Certain issues can arise that are common to many of the app templates. Please check [here](https://github.com/OfficeDev/microsoft-teams-stickers-app/wiki/Troubleshooting) for reference to these. + +### **Problems related to PowerShell script** + +**1. File is not digitally signed** + +While running PowerShell script, sometimes user gets an error showing 'File is not digitally signed'. + +**Fix**: If this type of error occurs then run this: "Set-ExecutionPolicy -ExecutionPolicy unrestricted" + +**2. Azure subscription access failed** + +Connect-AzAccount : The provided account **.onmicrosoft.com does not have access to subscription ID "XXXX-". Please try logging in with different credentials or a different subscription ID. + +**Fix**: User must be added as a contributor on the Azure subscription." + + +**3. Failed to acquire a token** + +Exception calling "AcquireAccessToken" with "1" argument(s): "multiple_matching_tokens_detected: The cache contains multiple tokens satisfying the requirements + +**Fix**: This means user is logged-in with multiple accounts in the current powershell session. Close the shell window and open a new one." + + +**4. Azure AD app permission consent error** + +#### Description + +![Screenshot of consent error](/Wiki/Images/ad-app-consent-error.png) + +The apps created by this app template requires an admin consent for "User.Read" graph permission so it can operate correctly. + +``` +Errors: Forbidden({"ClassName":"Microsoft.Portal.Framework.Exceptions.ClientException","Message":"Graph call failed with httpCode=Forbidden, errorCode=Authorization_RequestDenied, errorMessage=This operation can only be performed by an administrator. Sign out and sign in as an administrator or contact one of your organization's administrators., reason=Forbidden + +``` + +#### Fix + +Please ask your tenant administrator to consent the "User.Read" permission for both apps (bot app, config app). + +![Apps Admin Consent](/Wiki/Images/app-admin-consent.png) + + +**5. Error when attempting to reuse a Microsoft Azure AD application ID for the bot registration** + +#### Description + +The bot is not valid. + +``` +Errors: MsaAppId is already in use. +``` + +Creating the resource of type Microsoft.BotService/botServices failed with status "BadRequest" + +This happens when the Microsoft Azure application ID entered during the setup of the deployment has already been used and registered for a bot. + +#### Fix + +Either register a new Microsoft Azure AD application or delete the bot registration that is currently using the attempted Microsoft Azure application ID. + + +**6. Error while deploying the ARM Template** + +#### Description + +This happens when the resources are already created or due to some conflicts. +``` + +Errors: The resource operation completed with terminal provisioning state 'Failed' + +``` +#### Fix + +In case of such a scenario, the user needs to navigate to the deployment center section of failed/conflict resources through the Azure portal and check the error logs to get the actual errors and fix them accordingly. + +Redeploy it after fixing the issue/conflict. + +### **Problems deploying to Azure** + +### **1. The bot is unable to create more KBs and store additional questions** + +#### Description + +The bot will reply to the user post with the error message if it finds that it cannot store any additional QnA pair to the Knowledge-base + +``` +Errors: I cannot save this qna pair due to storage space limitations. Please contact your system administrator to provide additional storage space. +``` + +#### Fix + +In case of such a scenario, the system administrator or the app installer will need to update the pricing tier accordingly for QnA service in Azure Portal. + +### **Problems related to App installation and manifest** +### **1. Manifest parsing has failed** + +#### Description + +This happens when the admin tries to install the app to Microsoft Teams using the zip file. + +![Manifest parsing has failed](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/wiki/Images/manifest_parsing_failed.png) + +#### Fix + +In case of such a scenario, the admin user needs to click on the "Copy error details to clipboard" and check the error details. + +If the error specifies as invalid value, then fix the invalid value and create the new zip folder to install the app. + +If the error specifies as format issue, then fix the manifest format issue and create the new zip folder to install the app. + +If the error specifies as related to folder structure then make sure the that the 3 files `manifest.json`,`color.png`, and `outline.png` are the top level of the ZIP package, with no nested folders + +### **2. Config web app - User login/access issue** + +#### Description + +This happens when the user alias is not a part of the UPN admin list of the config web application. + +![Screenshot of access issue](/Wiki/Images/user-not-in-upn-list.png) + +#### Fix + +Please add the user alias to the ConfigAdminUPNList parameter in parameters.json file. Append the user alias/email to the list using semi-colon `;` separator. e.g. adminuser@contoso.onmicrosoft.com;user2@contoso.onmicrosoft.com + + +### **Problems related to Messaging Extension** +### **1. Experts team members unable to see data in "Knowledgebase" tab** + +#### Description + +This happens when the admin user does not update the knowledge base id in the configuration app or if there are no QnA pairs in the knowledge base. + +#### Fix + +In case of such a scenario, the admin user needs to make sure that the knowledge base id is updated in the configuration app and QnA pairs are existing in the knowledge base. + +If no QnA pairs exist in the knowledge base, then add new ones either directly from the Question Answering portal or add it from the messaging extension in the experts' team. + +**Didn't find your problem here?** + +Please, report the issue [here](https://github.com/OfficeDev/microsoft-teams-apps-faqplus/issues/new) \ No newline at end of file