diff --git a/.github/fabricbot.json b/.github/fabricbot.json new file mode 100644 index 000000000..358029277 --- /dev/null +++ b/.github/fabricbot.json @@ -0,0 +1,529 @@ +[ + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssuesOnlyResponder", + "version": "1.0", + "config": { + "taskName": "Add needs triage label to new issues", + "conditions": { + "operator": "and", + "operands": [ + { + "name": "isAction", + "parameters": { + "action": "opened" + } + }, + { + "operator": "not", + "operands": [ + { + "name": "isPartOfProject", + "parameters": {} + } + ] + }, + { + "operator": "not", + "operands": [ + { + "name": "isAssignedToSomeone", + "parameters": {} + } + ] + } + ] + }, + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "Needs: Triage :mag:" + } + } + ], + "eventType": "issue", + "eventNames": [ + "issues", + "project_card" + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssueCommentResponder", + "version": "1.0", + "config": { + "taskName": "Replace needs author feedback label with needs attention label when the author comments on an issue", + "conditions": { + "operator": "and", + "operands": [ + { + "name": "isAction", + "parameters": { + "action": "created" + } + }, + { + "name": "isActivitySender", + "parameters": { + "user": { + "type": "author" + } + } + }, + { + "name": "hasLabel", + "parameters": { + "label": "Needs: Author Feedback" + } + }, + { + "name": "isOpen", + "parameters": {} + } + ] + }, + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "Needs: Attention :wave:" + } + }, + { + "name": "removeLabel", + "parameters": { + "label": "Needs: Author Feedback" + } + } + ], + "eventType": "issue", + "eventNames": [ + "issue_comment" + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssuesOnlyResponder", + "version": "1.0", + "config": { + "taskName": "Remove no recent activity label from issues", + "conditions": { + "operator": "and", + "operands": [ + { + "operator": "not", + "operands": [ + { + "name": "isAction", + "parameters": { + "action": "closed" + } + } + ] + }, + { + "name": "hasLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + } + ] + }, + "actions": [ + { + "name": "removeLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + } + ], + "eventType": "issue", + "eventNames": [ + "issues", + "project_card" + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "IssueResponder", + "subCapability": "IssueCommentResponder", + "version": "1.0", + "config": { + "taskName": "Remove no recent activity label when an issue is commented on", + "conditions": { + "operator": "and", + "operands": [ + { + "name": "hasLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + } + ] + }, + "actions": [ + { + "name": "removeLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + } + ], + "eventType": "issue", + "eventNames": [ + "issue_comment" + ] + } + }, + { + "taskType": "scheduled", + "capabilityId": "ScheduledSearch", + "subCapability": "ScheduledSearch", + "version": "1.1", + "config": { + "taskName": "Close stale issues", + "frequency": [ + { + "weekDay": 0, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 1, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 2, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 3, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 4, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 5, + "hours": [ + 1, + 7, + 13, + 19 + ] + }, + { + "weekDay": 6, + "hours": [ + 1, + 7, + 13, + 19 + ] + } + ], + "searchTerms": [ + { + "name": "isIssue", + "parameters": {} + }, + { + "name": "isOpen", + "parameters": {} + }, + { + "name": "hasLabel", + "parameters": { + "label": "Needs: Author Feedback" + } + }, + { + "name": "hasLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + }, + { + "name": "noActivitySince", + "parameters": { + "days": 3 + } + } + ], + "actions": [ + { + "name": "closeIssue", + "parameters": {} + } + ] + } + }, + { + "taskType": "scheduled", + "capabilityId": "ScheduledSearch", + "subCapability": "ScheduledSearch", + "version": "1.1", + "config": { + "taskName": "Add no recent activity label to issues", + "frequency": [ + { + "weekDay": 0, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 1, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 2, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 3, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 4, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 5, + "hours": [ + 2, + 8, + 14, + 20 + ] + }, + { + "weekDay": 6, + "hours": [ + 2, + 8, + 14, + 20 + ] + } + ], + "searchTerms": [ + { + "name": "isIssue", + "parameters": {} + }, + { + "name": "isOpen", + "parameters": {} + }, + { + "name": "hasLabel", + "parameters": { + "label": "Needs: Author Feedback" + } + }, + { + "name": "noActivitySince", + "parameters": { + "days": 4 + } + }, + { + "name": "noLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + } + ], + "actions": [ + { + "name": "addLabel", + "parameters": { + "label": "Status: No Recent Activity" + } + }, + { + "name": "addReply", + "parameters": { + "comment": "This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**." + } + } + ] + } + }, + { + "taskType": "scheduled", + "capabilityId": "ScheduledSearch", + "subCapability": "ScheduledSearch", + "version": "1.1", + "config": { + "taskName": "Close duplicate issues", + "frequency": [ + { + "weekDay": 0, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 1, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 2, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 3, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 4, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 5, + "hours": [ + 3, + 9, + 15, + 21 + ] + }, + { + "weekDay": 6, + "hours": [ + 3, + 9, + 15, + 21 + ] + } + ], + "searchTerms": [ + { + "name": "isIssue", + "parameters": {} + }, + { + "name": "isOpen", + "parameters": {} + }, + { + "name": "hasLabel", + "parameters": { + "label": "Resolution: Duplicate" + } + }, + { + "name": "noActivitySince", + "parameters": { + "days": 1 + } + } + ], + "actions": [ + { + "name": "addReply", + "parameters": { + "comment": "This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes." + } + }, + { + "name": "closeIssue", + "parameters": {} + } + ] + } + }, + { + "taskType": "trigger", + "capabilityId": "InPrLabel", + "subCapability": "InPrLabel", + "version": "1.0", + "config": { + "taskName": "Add 'In-PR' label on issue when an open pull request is targeting it", + "inPrLabelText": "Status: In PR", + "fixedLabelText": "Status: Fixed", + "fixedLabelEnabled": true + } + } +] \ No newline at end of file diff --git a/CompanyCommunicator_Context.tm7 b/CompanyCommunicator_Context.tm7 index 4eba3cfa3..b2d305540 100644 --- a/CompanyCommunicator_Context.tm7 +++ b/CompanyCommunicator_Context.tm7 @@ -1,4 +1,4 @@ -DRAWINGSURFACE418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2DiagramNameDiagram 1DRAWINGSURFACE92e00741-0e95-4a75-b38d-2153cf978db5GE.EI92e00741-0e95-4a75-b38d-2153cf978db5Human UserNameHuman UserOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeHuman0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.User100461242100837fee15-1f67-4add-b2e8-cb602f6567ddGE.EI837fee15-1f67-4add-b2e8-cb602f6567ddExternal Web ApplicationNameTeams AppOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeCode0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.WebApp10017818010017e8d0b4-e1a3-4d53-a55a-8ebdcff76c2eGE.EI17e8d0b4-e1a3-4d53-a55a-8ebdcff76c2eExternal Web ServiceNameSMBA (Bot Framework)Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeCode0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.WebSvc100353178100f39b41ff-538a-4f04-b911-ba7a2fe73fa2GE.DSf39b41ff-538a-4f04-b911-ba7a2fe73fa2Non Relational DatabaseNameAzure Table StorageOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesStore TypestoreTypeNon Relational Database0Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0SE.DS.TMCore.NoSQL1008291207100d8fe453f-286e-4fec-b7c1-33c9a1e56d4cGE.DSd8fe453f-286e-4fec-b7c1-33c9a1e56d4cCloud StorageNameApplication InsightsOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0Store TypestoreTypeSQL Relational DatabaseNon Relational DatabaseFile SystemRegistryConfigurationCacheHTML5 StorageCookieDevice0SE.DS.TMCore.CloudStorage10082919010005fec340-05db-4922-946d-112ff7373c03GE.P05fec340-05db-4922-946d-112ff7373c03Web ApplicationNameCompany CommunicatorOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebApp100555123310078cb7043-4910-4f9e-8544-e7b2153dff3eGE.P78cb7043-4910-4f9e-8544-e7b2153dff3eManaged ApplicationNameService BusOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeManaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.NetApp10042012461006480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76GE.P6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76Web ServiceNameAzure FunctionOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebSvc10024013911002f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18GE.P2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18Web ServiceNameMicrosoft GraphOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebSvc100593146210063a3b273-2c3b-40ef-bc41-ae67e941c8bdGE.DS63a3b273-2c3b-40ef-bc41-ae67e941c8bdNon Relational DatabaseNameAzure Key VaultOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesStore TypestoreTypeNon Relational Database0Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0SE.DS.TMCore.NoSQL100702010100
Diagram 1
b11fc5fb-f7af-441d-9e05-52b3755d9509GE.DFb11fc5fb-f7af-441d-9e05-52b3755d9509HTTPSName1. User interacts with app in TeamsDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS168210EastWest92e00741-0e95-4a75-b38d-2153cf978db5141292837fee15-1f67-4add-b2e8-cb602f6567dd183130f0d0ff30-4d82-4acc-8794-05aff33a45f1GE.DFf0d0ff30-4d82-4acc-8794-05aff33a45f1HTTPSName2.Teams sends activities +DRAWINGSURFACE418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2DiagramNameDiagram 1DRAWINGSURFACE92e00741-0e95-4a75-b38d-2153cf978db5GE.EI92e00741-0e95-4a75-b38d-2153cf978db5Human UserNameHuman UserOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeHuman0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.User100461242100837fee15-1f67-4add-b2e8-cb602f6567ddGE.EI837fee15-1f67-4add-b2e8-cb602f6567ddExternal Web ApplicationNameTeams AppOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeCode0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.WebApp10017818010017e8d0b4-e1a3-4d53-a55a-8ebdcff76c2eGE.EI17e8d0b4-e1a3-4d53-a55a-8ebdcff76c2eExternal Web ServiceNameSMBA (Bot Framework)Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesTypetypeCode0Configurable AttributesAs Generic External InteractorAuthenticates ItselfauthenticatesItselfNot ApplicableNoYes0MicrosoftMSNoYes0SE.EI.TMCore.WebSvc100353178100f39b41ff-538a-4f04-b911-ba7a2fe73fa2GE.DSf39b41ff-538a-4f04-b911-ba7a2fe73fa2Non Relational DatabaseNameAzure Table StorageOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesStore TypestoreTypeNon Relational Database0Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0SE.DS.TMCore.NoSQL1008291207100d8fe453f-286e-4fec-b7c1-33c9a1e56d4cGE.DSd8fe453f-286e-4fec-b7c1-33c9a1e56d4cCloud StorageNameApplication InsightsOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0Store TypestoreTypeSQL Relational DatabaseNon Relational DatabaseFile SystemRegistryConfigurationCacheHTML5 StorageCookieDevice0SE.DS.TMCore.CloudStorage10082919010005fec340-05db-4922-946d-112ff7373c03GE.P05fec340-05db-4922-946d-112ff7373c03Web ApplicationNameCompany CommunicatorOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebApp100555123310078cb7043-4910-4f9e-8544-e7b2153dff3eGE.P78cb7043-4910-4f9e-8544-e7b2153dff3eManaged ApplicationNameService BusOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeManaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.NetApp10042012461006480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76GE.P6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76Web ServiceNameAzure FunctionOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebSvc10024013911002f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18GE.P2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18Web ServiceNameMicrosoft GraphOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesCode TypecodeTypeUnmanaged0Configurable AttributesAs Generic ProcessRunning AsrunningAsNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store App0Isolation LevelIsolationNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)Sandbox0Accepts Input FromacceptsInputFromNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOther0Implements or Uses an Authentication MechanismimplementsAuthenticationSchemeNoYes0Implements or Uses an Authorization MechanismimplementsCustomAuthorizationMechanismNoYes0Implements or Uses a Communication ProtocolimplementsCommunicationProtocolNoYes0Sanitizes InputhasInputSanitizersNot SelectedYesNo0Sanitizes OutputhasOutputSanitizersNot SelectedYesNo0SE.P.TMCore.WebSvc100593146210063a3b273-2c3b-40ef-bc41-ae67e941c8bdGE.DS63a3b273-2c3b-40ef-bc41-ae67e941c8bdNon Relational DatabaseNameAzure Key VaultOut Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesStore TypestoreTypeNon Relational Database0Configurable AttributesAs Generic Data StoreStores CredentialsstoresCredentialsNoYes0Stores Log DatastoresLogDataNoYes0EncryptedEncryptedNoYes0SignedSignedNoYes0Write AccessAccessTypeNoYes0Removable StorageRemoveableStorageNoYes0BackupBackupNoYes0SharedsharedNoYes0SE.DS.TMCore.NoSQL100702110100
Diagram 1
b11fc5fb-f7af-441d-9e05-52b3755d9509GE.DFb11fc5fb-f7af-441d-9e05-52b3755d9509HTTPSName1. User interacts with app in TeamsDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS168210EastWest92e00741-0e95-4a75-b38d-2153cf978db5141292837fee15-1f67-4add-b2e8-cb602f6567dd183130f0d0ff30-4d82-4acc-8794-05aff33a45f1GE.DFf0d0ff30-4d82-4acc-8794-05aff33a45f1HTTPSName2.Teams sends activities [CC, EUII, EUPI, OII]Dataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103trueReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Covered by threat model of bots in Teams Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS31925EastWest837fee15-1f67-4add-b2e8-cb602f6567dd27313017e8d0b4-e1a3-4d53-a55a-8ebdcff76c2e3581283848cb23-93ed-4266-91f4-4d638eab2a31GE.DF3848cb23-93ed-4266-91f4-4d638eab2a31HTTPSName3.Bot activities HTTPS; SMBA -> bot token [CC, EUII, EUPDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS51115EastNorthWest00000000-0000-0000-0000-00000000000044812805fec340-05db-4922-946d-112ff7373c03573251d692dfd7-b8cf-4757-9753-6ac27d34e69dGE.DFd692dfd7-b8cf-4757-9753-6ac27d34e69dHTTPSName7. Queue the message @@ -10,9 +10,9 @@ HTTPS; API key HTTPS; connection string [CC, EUII, EUPIDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS741298EastWest05fec340-05db-4922-946d-112ff7373c03650283f39b41ff-538a-4f04-b911-ba7a2fe73fa2834257c47fbb19-55fe-45a0-883a-1dcb4f3e12b4GE.DFc47fbb19-55fe-45a0-883a-1dcb4f3e12b4HTTPSName10. Read/update data HTTPS; connection string -[CC, EUII, EUPIDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS844396NorthEastSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76321409f39b41ff-538a-4f04-b911-ba7a2fe73fa2879302f3f99a81-3930-4708-b3b6-a71786518de5GE.DFf3f99a81-3930-4708-b3b6-a71786518de5HTTPSName11. Post Message to User HTTPSDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS252260WestSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e7624544117e8d0b4-e1a3-4d53-a55a-8ebdcff76c2e403173433a9d8a-84b0-4846-a972-5394dffe3529GE.DF433a9d8a-84b0-4846-a972-5394dffe3529HTTPSName9.Read data HTTPS; application tokenDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS443470SouthSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e762904862f23a6eb-aa7a-425c-9d2f-07a6d7ef6a1864355728ded6cf-6aad-4a36-a207-6cae85e1746eGE.DF28ded6cf-6aad-4a36-a207-6cae85e1746eHTTPSName4. Get secrets +[CC, EUII, EUPIDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS844396NorthEastSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76321409f39b41ff-538a-4f04-b911-ba7a2fe73fa2879302f3f99a81-3930-4708-b3b6-a71786518de5GE.DFf3f99a81-3930-4708-b3b6-a71786518de5HTTPSName11. Post Message to User HTTPSDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS250260WestSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e7624544117e8d0b4-e1a3-4d53-a55a-8ebdcff76c2e403173433a9d8a-84b0-4846-a972-5394dffe3529GE.DF433a9d8a-84b0-4846-a972-5394dffe3529HTTPSName9.Read data HTTPS; application tokenDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS443470SouthSouth6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e762904862f23a6eb-aa7a-425c-9d2f-07a6d7ef6a1864355728ded6cf-6aad-4a36-a207-6cae85e1746eGE.DF28ded6cf-6aad-4a36-a207-6cae85e1746eHTTPSName4. Get secrets HTTPS; Managed identity -[SM]Dataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS645104NoneSouth00000000-0000-0000-0000-00000000000065026163a3b273-2c3b-40ef-bc41-ae67e941c8bd7521051
DRAWINGSURFACEf7b3d20a-64c9-438b-8f60-a1703c181bbaDiagramNameDiagram 2DRAWINGSURFACE
Diagram 2
1
E56480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e763b63b270-d39a-4d5f-9df4-33665a91d32b78cb7043-4910-4f9e-8544-e7b2153dff3e418b6a60-9f3a-42a7-9a39-8ef51ee8c0d23b63b270-d39a-4d5f-9df4-33665a91d32b206480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:3b63b270-d39a-4d5f-9df4-33665a91d32b:78cb7043-4910-4f9e-8544-e7b2153dff3e0001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionService Bus may be able to impersonate the context of Azure Function in order to gain additional privilege.InteractionString12.Queue the message HTTPS; Managed Identity [SM]PriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated78cb7043-4910-4f9e-8544-e7b2153dff3eE5falsefalseE578cb7043-4910-4f9e-8544-e7b2153dff3e273c0d07-97af-4fa8-8a6d-756d72076e196480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2273c0d07-97af-4fa8-8a6d-756d72076e191978cb7043-4910-4f9e-8544-e7b2153dff3e:273c0d07-97af-4fa8-8a6d-756d72076e19:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionAzure Function may be able to impersonate the context of Service Bus in order to gain additional privilege.InteractionString8. App Trigger over HTTPSPriorityHigh78cb7043-4910-4f9e-8544-e7b2153dff3eAutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76E5falsefalseE56480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76433a9d8a-84b0-4846-a972-5394dffe35292f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2433a9d8a-84b0-4846-a972-5394dffe3529226480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:433a9d8a-84b0-4846-a972-5394dffe3529:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionMicrosoft Graph may be able to impersonate the context of Azure Function in order to gain additional privilege.InteractionString9.Read data HTTPS; application tokenPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18E5falsefalseT26480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76433a9d8a-84b0-4846-a972-5394dffe35292f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2433a9d8a-84b0-4846-a972-5394dffe3529216480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:433a9d8a-84b0-4846-a972-5394dffe3529:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleAzure Function Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Azure Function is given access to memory, such as shared memory or pointers, or is given the ability to control what Microsoft Graph executes (for example, passing back a function pointer.), then Azure Function can tamper with Microsoft Graph. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString9.Read data HTTPS; application tokenPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18T2falsefalseT278cb7043-4910-4f9e-8544-e7b2153dff3e273c0d07-97af-4fa8-8a6d-756d72076e196480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2273c0d07-97af-4fa8-8a6d-756d72076e191878cb7043-4910-4f9e-8544-e7b2153dff3e:273c0d07-97af-4fa8-8a6d-756d72076e19:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleService Bus Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Service Bus is given access to memory, such as shared memory or pointers, or is given the ability to control what Azure Function executes (for example, passing back a function pointer.), then Service Bus can tamper with Azure Function. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString8. App Trigger over HTTPSPriorityHigh78cb7043-4910-4f9e-8544-e7b2153dff3eAutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76T2falsefalseE505fec340-05db-4922-946d-112ff7373c03d692dfd7-b8cf-4757-9753-6ac27d34e69d78cb7043-4910-4f9e-8544-e7b2153dff3e418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d692dfd7-b8cf-4757-9753-6ac27d34e69d1705fec340-05db-4922-946d-112ff7373c03:d692dfd7-b8cf-4757-9753-6ac27d34e69d:78cb7043-4910-4f9e-8544-e7b2153dff3e0001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionService Bus may be able to impersonate the context of Company Communicator in order to gain additional privilege.InteractionString7. Queue the message HTTPS; Managed Identity[SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated78cb7043-4910-4f9e-8544-e7b2153dff3eE5falsefalseD205fec340-05db-4922-946d-112ff7373c035c421ce7-b8d7-42c2-89e0-8eebc61fc6bbd8fe453f-286e-4fec-b7c1-33c9a1e56d4c418b6a60-9f3a-42a7-9a39-8ef51ee8c0d25c421ce7-b8d7-42c2-89e0-8eebc61fc6bb1105fec340-05db-4922-946d-112ff7373c03:5c421ce7-b8d7-42c2-89e0-8eebc61fc6bb:d8fe453f-286e-4fec-b7c1-33c9a1e56d4c0001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Company Communicator or Application InsightsUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Company Communicator or Application Insights take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString13. Log telemetry HTTPS; API key [EUPI, SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedd8fe453f-286e-4fec-b7c1-33c9a1e56d4cD2falsefalseS7.105fec340-05db-4922-946d-112ff7373c035c421ce7-b8d7-42c2-89e0-8eebc61fc6bbd8fe453f-286e-4fec-b7c1-33c9a1e56d4c418b6a60-9f3a-42a7-9a39-8ef51ee8c0d25c421ce7-b8d7-42c2-89e0-8eebc61fc6bb1005fec340-05db-4922-946d-112ff7373c03:5c421ce7-b8d7-42c2-89e0-8eebc61fc6bb:d8fe453f-286e-4fec-b7c1-33c9a1e56d4c0001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Application InsightsUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionApplication Insights may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Application Insights. Consider using a standard authentication mechanism to identify the destination data store.InteractionString13. Log telemetry HTTPS; API key [EUPI, SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedd8fe453f-286e-4fec-b7c1-33c9a1e56d4cS7.1falsefalseT205fec340-05db-4922-946d-112ff7373c03059e20ab-f3df-4db4-94d7-e3c0fd81d9052f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2059e20ab-f3df-4db4-94d7-e3c0fd81d905805fec340-05db-4922-946d-112ff7373c03:059e20ab-f3df-4db4-94d7-e3c0fd81d905:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleCompany Communicator Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Company Communicator is given access to memory, such as shared memory or pointers, or is given the ability to control what Microsoft Graph executes (for example, passing back a function pointer.), then Company Communicator can tamper with Microsoft Graph. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString6.Read data HTTPS; delegated tokenPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18T2falsefalseE505fec340-05db-4922-946d-112ff7373c03059e20ab-f3df-4db4-94d7-e3c0fd81d9052f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2059e20ab-f3df-4db4-94d7-e3c0fd81d905905fec340-05db-4922-946d-112ff7373c03:059e20ab-f3df-4db4-94d7-e3c0fd81d905:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionMicrosoft Graph may be able to impersonate the context of Company Communicator in order to gain additional privilege.InteractionString6.Read data HTTPS; delegated tokenPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18E5falsefalseS7.105fec340-05db-4922-946d-112ff7373c03d3145983-ea29-4e12-8ba6-24b6792eb8f6f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d3145983-ea29-4e12-8ba6-24b6792eb8f61205fec340-05db-4922-946d-112ff7373c03:d3145983-ea29-4e12-8ba6-24b6792eb8f6:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Azure Table StorageUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionAzure Table Storage may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Azure Table Storage. Consider using a standard authentication mechanism to identify the destination data store.InteractionString5. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2S7.1falsefalseD205fec340-05db-4922-946d-112ff7373c03d3145983-ea29-4e12-8ba6-24b6792eb8f6f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d3145983-ea29-4e12-8ba6-24b6792eb8f61305fec340-05db-4922-946d-112ff7373c03:d3145983-ea29-4e12-8ba6-24b6792eb8f6:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Company Communicator or Azure Table StorageUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Company Communicator or Azure Table Storage take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString5. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2D2falsefalseS7.16480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76c47fbb19-55fe-45a0-883a-1dcb4f3e12b4f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2c47fbb19-55fe-45a0-883a-1dcb4f3e12b4146480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:c47fbb19-55fe-45a0-883a-1dcb4f3e12b4:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Azure Table StorageUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionAzure Table Storage may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Azure Table Storage. Consider using a standard authentication mechanism to identify the destination data store.InteractionString10. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2S7.1falsefalseD26480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76c47fbb19-55fe-45a0-883a-1dcb4f3e12b4f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2c47fbb19-55fe-45a0-883a-1dcb4f3e12b4156480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:c47fbb19-55fe-45a0-883a-1dcb4f3e12b4:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Azure Function or Azure Table StorageUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Azure Function or Azure Table Storage take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString10. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2D2falsefalsetrue4.3falsefalseNot SelectedManagedUnmanagedCode TypeVirtualDynamiccodeTypeListfalseNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store AppRunning AsVirtualDynamicrunningAsListfalseNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)SandboxIsolation LevelVirtualDynamicIsolationListfalseNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOtherAccepts Input FromVirtualDynamicacceptsInputFromListfalseNoYesImplements or Uses an Authentication MechanismVirtualDynamicimplementsAuthenticationSchemeListfalseNoYesImplements or Uses an Authorization MechanismVirtualDynamicimplementsCustomAuthorizationMechanismListfalseNoYesImplements or Uses a Communication ProtocolVirtualDynamicimplementsCommunicationProtocolListfalseNot SelectedYesNoSanitizes InputVirtualDynamichasInputSanitizersListfalseNot SelectedYesNoSanitizes OutputVirtualDynamichasOutputSanitizersListA representation of a generic process.falseGE.PCentered on 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Generic ProcessROOTEllipsefalseAnyAnyfalsefalseNot ApplicableNoYesAuthenticates ItselfVirtualDynamicauthenticatesItselfListfalseNot SelectedCodeHumanTypeVirtualDynamictypeListfalseNoYesMicrosoftVirtualDynamicMSList +[SM]
Dataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0
SE.DF.TMCore.HTTPS645104NoneSouth00000000-0000-0000-0000-00000000000065026163a3b273-2c3b-40ef-bc41-ae67e941c8bd752105
2329b368-9cbe-43a5-85a8-b8ce63d005b4GE.DF2329b368-9cbe-43a5-85a8-b8ce63d005b4HTTPSName13. Cancel Message HTTPs; authorization keyDataflow Order15ccd509-98eb-49ad-b9c2-b4a2926d17800Out Of Scope71f3d9aa-b8ef-4e54-8126-607a1d903103falseReason For Out Of Scope752473b6-52d4-4776-9a24-202153f7d579Predefined Static AttributesDestination AuthenticatedauthenticatesDestinationYes0Provides ConfidentialityprovidesConfidentialityYes0Provides IntegrityprovidesIntegrityYes0Configurable AttributesAs Generic Data FlowPhysical NetworkchannelNot SelectedWireWi-FiBluetooth2G-4G0Source AuthenticatedauthenticatesSourceNot SelectedNoYes0Transmits XMLXMLencNoYes0Contains CookiesCookiesYesNo0SOAP PayloadSOAPNoYes0REST PayloadRESTNoYes0RSS PayloadRSSNoYes0JSON PayloadJSONNoYes0Forgery Protection54851a3b-65da-4902-b4e0-94ef015be735Not SelectedValidateAntiForgeryTokenAttributeViewStateUserKeyNonceOther dynamic canaryStatic header not available to the browserOtherNoneNot applicable because the request does not change data0SE.DF.TMCore.HTTPS128368SouthWestSouthWest05fec340-05db-4922-946d-112ff7373c035733146480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76258472
1
DRAWINGSURFACEf7b3d20a-64c9-438b-8f60-a1703c181bbaDiagramNameDiagram 2DRAWINGSURFACE
Diagram 2
1
E56480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e763b63b270-d39a-4d5f-9df4-33665a91d32b78cb7043-4910-4f9e-8544-e7b2153dff3e418b6a60-9f3a-42a7-9a39-8ef51ee8c0d23b63b270-d39a-4d5f-9df4-33665a91d32b206480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:3b63b270-d39a-4d5f-9df4-33665a91d32b:78cb7043-4910-4f9e-8544-e7b2153dff3e0001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionService Bus may be able to impersonate the context of Azure Function in order to gain additional privilege.InteractionString12.Queue the message HTTPS; Managed Identity [SM]PriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated78cb7043-4910-4f9e-8544-e7b2153dff3eE5falsefalseE578cb7043-4910-4f9e-8544-e7b2153dff3e273c0d07-97af-4fa8-8a6d-756d72076e196480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2273c0d07-97af-4fa8-8a6d-756d72076e191978cb7043-4910-4f9e-8544-e7b2153dff3e:273c0d07-97af-4fa8-8a6d-756d72076e19:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionAzure Function may be able to impersonate the context of Service Bus in order to gain additional privilege.InteractionString8. App Trigger over HTTPSPriorityHigh78cb7043-4910-4f9e-8544-e7b2153dff3eAutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76E5falsefalseE56480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76433a9d8a-84b0-4846-a972-5394dffe35292f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2433a9d8a-84b0-4846-a972-5394dffe3529226480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:433a9d8a-84b0-4846-a972-5394dffe3529:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionMicrosoft Graph may be able to impersonate the context of Azure Function in order to gain additional privilege.InteractionString9.Read data HTTPS; application tokenPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18E5falsefalseT26480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76433a9d8a-84b0-4846-a972-5394dffe35292f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2433a9d8a-84b0-4846-a972-5394dffe3529216480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:433a9d8a-84b0-4846-a972-5394dffe3529:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleAzure Function Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Azure Function is given access to memory, such as shared memory or pointers, or is given the ability to control what Microsoft Graph executes (for example, passing back a function pointer.), then Azure Function can tamper with Microsoft Graph. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString9.Read data HTTPS; application tokenPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18T2falsefalseT278cb7043-4910-4f9e-8544-e7b2153dff3e273c0d07-97af-4fa8-8a6d-756d72076e196480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2273c0d07-97af-4fa8-8a6d-756d72076e191878cb7043-4910-4f9e-8544-e7b2153dff3e:273c0d07-97af-4fa8-8a6d-756d72076e19:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleService Bus Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Service Bus is given access to memory, such as shared memory or pointers, or is given the ability to control what Azure Function executes (for example, passing back a function pointer.), then Service Bus can tamper with Azure Function. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString8. App Trigger over HTTPSPriorityHigh78cb7043-4910-4f9e-8544-e7b2153dff3eAutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76T2falsefalseE505fec340-05db-4922-946d-112ff7373c03d692dfd7-b8cf-4757-9753-6ac27d34e69d78cb7043-4910-4f9e-8544-e7b2153dff3e418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d692dfd7-b8cf-4757-9753-6ac27d34e69d1705fec340-05db-4922-946d-112ff7373c03:d692dfd7-b8cf-4757-9753-6ac27d34e69d:78cb7043-4910-4f9e-8544-e7b2153dff3e0001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionService Bus may be able to impersonate the context of Company Communicator in order to gain additional privilege.InteractionString7. Queue the message HTTPS; Managed Identity[SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated78cb7043-4910-4f9e-8544-e7b2153dff3eE5falsefalseD205fec340-05db-4922-946d-112ff7373c035c421ce7-b8d7-42c2-89e0-8eebc61fc6bbd8fe453f-286e-4fec-b7c1-33c9a1e56d4c418b6a60-9f3a-42a7-9a39-8ef51ee8c0d25c421ce7-b8d7-42c2-89e0-8eebc61fc6bb1105fec340-05db-4922-946d-112ff7373c03:5c421ce7-b8d7-42c2-89e0-8eebc61fc6bb:d8fe453f-286e-4fec-b7c1-33c9a1e56d4c0001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Company Communicator or Application InsightsUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Company Communicator or Application Insights take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString13. Log telemetry HTTPS; API key [EUPI, SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedd8fe453f-286e-4fec-b7c1-33c9a1e56d4cD2falsefalseS7.105fec340-05db-4922-946d-112ff7373c035c421ce7-b8d7-42c2-89e0-8eebc61fc6bbd8fe453f-286e-4fec-b7c1-33c9a1e56d4c418b6a60-9f3a-42a7-9a39-8ef51ee8c0d25c421ce7-b8d7-42c2-89e0-8eebc61fc6bb1005fec340-05db-4922-946d-112ff7373c03:5c421ce7-b8d7-42c2-89e0-8eebc61fc6bb:d8fe453f-286e-4fec-b7c1-33c9a1e56d4c0001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Application InsightsUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionApplication Insights may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Application Insights. Consider using a standard authentication mechanism to identify the destination data store.InteractionString13. Log telemetry HTTPS; API key [EUPI, SM]PriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedd8fe453f-286e-4fec-b7c1-33c9a1e56d4cS7.1falsefalseT205fec340-05db-4922-946d-112ff7373c03059e20ab-f3df-4db4-94d7-e3c0fd81d9052f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2059e20ab-f3df-4db4-94d7-e3c0fd81d905805fec340-05db-4922-946d-112ff7373c03:059e20ab-f3df-4db4-94d7-e3c0fd81d905:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleCompany Communicator Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Company Communicator is given access to memory, such as shared memory or pointers, or is given the ability to control what Microsoft Graph executes (for example, passing back a function pointer.), then Company Communicator can tamper with Microsoft Graph. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString6.Read data HTTPS; delegated tokenPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18T2falsefalseE505fec340-05db-4922-946d-112ff7373c03059e20ab-f3df-4db4-94d7-e3c0fd81d9052f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2059e20ab-f3df-4db4-94d7-e3c0fd81d905905fec340-05db-4922-946d-112ff7373c03:059e20ab-f3df-4db4-94d7-e3c0fd81d905:2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a180001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionMicrosoft Graph may be able to impersonate the context of Company Communicator in order to gain additional privilege.InteractionString6.Read data HTTPS; delegated tokenPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated2f23a6eb-aa7a-425c-9d2f-07a6d7ef6a18E5falsefalseS7.105fec340-05db-4922-946d-112ff7373c03d3145983-ea29-4e12-8ba6-24b6792eb8f6f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d3145983-ea29-4e12-8ba6-24b6792eb8f61205fec340-05db-4922-946d-112ff7373c03:d3145983-ea29-4e12-8ba6-24b6792eb8f6:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Azure Table StorageUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionAzure Table Storage may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Azure Table Storage. Consider using a standard authentication mechanism to identify the destination data store.InteractionString5. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2S7.1falsefalseD205fec340-05db-4922-946d-112ff7373c03d3145983-ea29-4e12-8ba6-24b6792eb8f6f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2d3145983-ea29-4e12-8ba6-24b6792eb8f61305fec340-05db-4922-946d-112ff7373c03:d3145983-ea29-4e12-8ba6-24b6792eb8f6:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Company Communicator or Azure Table StorageUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Company Communicator or Azure Table Storage take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString5. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2D2falsefalseS7.16480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76c47fbb19-55fe-45a0-883a-1dcb4f3e12b4f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2c47fbb19-55fe-45a0-883a-1dcb4f3e12b4146480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:c47fbb19-55fe-45a0-883a-1dcb4f3e12b4:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitleSpoofing of Destination Data Store Azure Table StorageUserThreatCategorySpoofingUserThreatShortDescriptionSpoofing is when a process or entity is something other than its claimed identity. Examples include substituting a process, a file, website or a network address.UserThreatDescriptionAzure Table Storage may be spoofed by an attacker and this may lead to data being written to the attacker's target instead of Azure Table Storage. Consider using a standard authentication mechanism to identify the destination data store.InteractionString10. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2S7.1falsefalseD26480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76c47fbb19-55fe-45a0-883a-1dcb4f3e12b4f39b41ff-538a-4f04-b911-ba7a2fe73fa2418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2c47fbb19-55fe-45a0-883a-1dcb4f3e12b4156480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76:c47fbb19-55fe-45a0-883a-1dcb4f3e12b4:f39b41ff-538a-4f04-b911-ba7a2fe73fa20001-01-01T00:00:00HighTitlePotential Excessive Resource Consumption for Azure Function or Azure Table StorageUserThreatCategoryDenial Of ServiceUserThreatShortDescriptionDenial of Service happens when the process or a datastore is not able to service incoming requests or perform up to spec.UserThreatDescriptionDoes Azure Function or Azure Table Storage take explicit steps to control resource consumption? Resource consumption attacks can be hard to deal with, and there are times that it makes sense to let the OS do the job. Be careful that your resource requests don't deadlock, and that they do timeout.InteractionString10. Read/update data HTTPS; connection string [CC, EUII, EUPIPriorityHigh6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76AutoGeneratedf39b41ff-538a-4f04-b911-ba7a2fe73fa2D2falsefalseBS77c06474-f7c2-46e1-bf17-25e5f7bc69091d2f580d-6fee-444a-9cf9-a2b57b4927c9b7ee7f25-911a-4020-9d46-aa68ca0bbbb1fb79fa9c-1446-4ede-acfa-889bd1edeb1bFAREAST\prsaxen418b6a60-9f3a-42a7-9a39-8ef51ee8c0d2b7ee7f25-911a-4020-9d46-aa68ca0bbbb1231d2f580d-6fee-444a-9cf9-a2b57b4927c9:b7ee7f25-911a-4020-9d46-aa68ca0bbbb1:fb79fa9c-1446-4ede-acfa-889bd1edeb1b2022-01-24T17:00:10.9490801+05:30HighTitleNonstandard threat to describe user specific conditionsUserThreatCategoryUser-definedUserThreatShortDescriptionUser-Defined Threat Category to describe custom threat typesUserThreatDescriptionInteractionStringNo FlowPriorityHigh1d2f580d-6fee-444a-9cf9-a2b57b4927c9AutoGeneratedfb79fa9c-1446-4ede-acfa-889bd1edeb1bBS77c06474-f7c2-46e1-bf17-25e5f7bc6909falsetrueT205fec340-05db-4922-946d-112ff7373c032329b368-9cbe-43a5-85a8-b8ce63d005b46480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d22329b368-9cbe-43a5-85a8-b8ce63d005b42405fec340-05db-4922-946d-112ff7373c03:2329b368-9cbe-43a5-85a8-b8ce63d005b4:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleCompany Communicator Process Memory TamperedUserThreatCategoryTamperingUserThreatShortDescriptionTampering is the act of altering the bits. Tampering with a process involves changing bits in the running process. Similarly, Tampering with a data flow involves changing bits on the wire or between two running processes.UserThreatDescriptionIf Company Communicator is given access to memory, such as shared memory or pointers, or is given the ability to control what Azure Function executes (for example, passing back a function pointer.), then Company Communicator can tamper with Azure Function. Consider if the function could work with less access to memory, such as passing data rather than pointers. Copy in data provided, and then validate it.InteractionString13. Cancel Message HTTPs; authorization keyPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76T2falsefalseE505fec340-05db-4922-946d-112ff7373c032329b368-9cbe-43a5-85a8-b8ce63d005b46480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76418b6a60-9f3a-42a7-9a39-8ef51ee8c0d22329b368-9cbe-43a5-85a8-b8ce63d005b42505fec340-05db-4922-946d-112ff7373c03:2329b368-9cbe-43a5-85a8-b8ce63d005b4:6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e760001-01-01T00:00:00HighTitleElevation Using ImpersonationUserThreatCategoryElevation Of PrivilegeUserThreatShortDescriptionA user subject gains increased capability or privilege by taking advantage of an implementation bug.UserThreatDescriptionAzure Function may be able to impersonate the context of Company Communicator in order to gain additional privilege.InteractionString13. Cancel Message HTTPs; authorization keyPriorityHigh05fec340-05db-4922-946d-112ff7373c03AutoGenerated6480d8e0-a4f9-4cc4-90ab-d5ab2a4d4e76E5falsefalsetrue4.3falsefalseNot SelectedManagedUnmanagedCode TypeVirtualDynamiccodeTypeListfalseNot SelectedKernelSystemNetwork ServiceLocal ServiceAdministratorStandard User With ElevationStandard User Without ElevationWindows Store AppRunning AsVirtualDynamicrunningAsListfalseNot SelectedAppContainerLow Integrity LevelMicrosoft Office Isolated Conversion Environment (MOICE)SandboxIsolation LevelVirtualDynamicIsolationListfalseNot SelectedAny Remote User or EntityKernel, System, or Local AdminLocal or Network ServiceLocal Standard User With ElevationLocal Standard User Without ElevationWindows Store Apps or App Container ProcessesNothingOtherAccepts Input FromVirtualDynamicacceptsInputFromListfalseNoYesImplements or Uses an Authentication MechanismVirtualDynamicimplementsAuthenticationSchemeListfalseNoYesImplements or Uses an Authorization MechanismVirtualDynamicimplementsCustomAuthorizationMechanismListfalseNoYesImplements or Uses a Communication ProtocolVirtualDynamicimplementsCommunicationProtocolListfalseNot SelectedYesNoSanitizes InputVirtualDynamichasInputSanitizersListfalseNot SelectedYesNoSanitizes OutputVirtualDynamichasOutputSanitizersListA representation of a generic process.falseGE.PCentered on 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Generic ProcessROOTEllipsefalseAnyAnyfalsefalseNot ApplicableNoYesAuthenticates ItselfVirtualDynamicauthenticatesItselfListfalseNot SelectedCodeHumanTypeVirtualDynamictypeListfalseNoYesMicrosoftVirtualDynamicMSList A representation of an external interactor. falseGE.EILower right of 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Generic External InteractorROOTRectanglefalseAnyAnyfalsefalseNoYesStores CredentialsVirtualDynamicstoresCredentialsListfalseNoYesStores Log DataVirtualDynamicstoresLogDataListfalseNoYesEncryptedVirtualDynamicEncryptedListfalseNoYesSignedVirtualDynamicSignedListfalseNoYesWrite AccessVirtualDynamicAccessTypeListfalseNoYesRemovable StorageVirtualDynamicRemoveableStorageListfalseNoYesBackupVirtualDynamicBackupListfalseNoYesSharedVirtualDynamicsharedListfalseSQL Relational DatabaseNon Relational DatabaseFile SystemRegistryConfigurationCacheHTML5 StorageCookieDeviceStore TypeVirtualDynamicstoreTypeList A representation of a data store. diff --git a/Deployment/azuredeploy.json b/Deployment/azuredeploy.json index e0e3c9e09..5581e4631 100644 --- a/Deployment/azuredeploy.json +++ b/Deployment/azuredeploy.json @@ -311,7 +311,8 @@ "kind": "Storage", "properties": { "supportsHttpsTrafficOnly": true, - "allowBlobPublicAccess": false + "allowBlobPublicAccess": false, + "minimumTlsVersion": "TLS1_2" }, "sku": { "name": "Standard_LRS" @@ -449,14 +450,14 @@ } }, { - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "name": "[variables('authorBotName')]", "type": "Microsoft.BotService/botServices", "location": "global", "sku": { "name": "F0" }, - "kind": "sdk", + "kind": "azurebot", "properties": { "displayName": "[concat(parameters('appDisplayName'),'-author')]", "description": "[parameters('appDescription')]", @@ -469,7 +470,7 @@ { "name": "[concat(variables('authorBotName'), '/MsTeamsChannel')]", "type": "Microsoft.BotService/botServices/channels", - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "location": "global", "sku": { "name": "F0" @@ -488,14 +489,14 @@ ] }, { - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "name": "[variables('botName')]", "type": "Microsoft.BotService/botServices", "location": "global", "sku": { "name": "F0" }, - "kind": "sdk", + "kind": "azurebot", "properties": { "displayName": "[parameters('appDisplayName')]", "description": "[parameters('appDescription')]", @@ -508,7 +509,7 @@ { "name": "[concat(variables('botName'), '/MsTeamsChannel')]", "type": "Microsoft.BotService/botServices/channels", - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "location": "global", "sku": { "name": "F0" @@ -893,6 +894,7 @@ }, "properties": { "tenantId": "[variables('subscriptionTenantId')]", + "enableSoftDelete": true, "enabledForDeployment": false, "enabledForDiskEncryption": false, "enabledForTemplateDeployment": true, @@ -1313,4 +1315,4 @@ "value": "[if(variables('useFrontDoor'), variables('frontDoorDomain'), concat('Please create a custom domain name for ', variables('botAppDomain'), ' and use that in the manifest'))]" } } -} \ No newline at end of file +} diff --git a/Deployment/azuredeploywithcert.json b/Deployment/azuredeploywithcert.json index 33755dd3e..8ec6b2a0b 100644 --- a/Deployment/azuredeploywithcert.json +++ b/Deployment/azuredeploywithcert.json @@ -311,7 +311,8 @@ "kind": "Storage", "properties": { "supportsHttpsTrafficOnly": true, - "allowBlobPublicAccess": false + "allowBlobPublicAccess": false, + "minimumTlsVersion": "TLS1_2" }, "sku": { "name": "Standard_LRS" @@ -449,14 +450,14 @@ } }, { - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "name": "[variables('authorBotName')]", "type": "Microsoft.BotService/botServices", "location": "global", "sku": { "name": "F0" }, - "kind": "sdk", + "kind": "azurebot", "properties": { "displayName": "[concat(parameters('appDisplayName'),'-author')]", "description": "[parameters('appDescription')]", @@ -469,7 +470,7 @@ { "name": "[concat(variables('authorBotName'), '/MsTeamsChannel')]", "type": "Microsoft.BotService/botServices/channels", - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "location": "global", "sku": { "name": "F0" @@ -488,14 +489,14 @@ ] }, { - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "name": "[variables('botName')]", "type": "Microsoft.BotService/botServices", "location": "global", "sku": { "name": "F0" }, - "kind": "sdk", + "kind": "azurebot", "properties": { "displayName": "[parameters('appDisplayName')]", "description": "[parameters('appDescription')]", @@ -508,7 +509,7 @@ { "name": "[concat(variables('botName'), '/MsTeamsChannel')]", "type": "Microsoft.BotService/botServices/channels", - "apiVersion": "2018-07-12", + "apiVersion": "2021-03-01", "location": "global", "sku": { "name": "F0" @@ -904,6 +905,7 @@ ], "properties": { "tenantId": "[variables('subscriptionTenantId')]", + "enableSoftDelete": true, "accessPolicies": [ { "tenantId": "[reference(concat('Microsoft.Web/sites/', variables('botAppName')), '2018-02-01', 'Full').identity.tenantId]", @@ -1273,4 +1275,4 @@ "value": "[if(variables('useFrontDoor'), variables('frontDoorDomain'), concat('Please create a custom domain name for ', variables('botAppDomain'), ' and use that in the manifest'))]" } } -} \ No newline at end of file +} diff --git a/Deployment/deploy.ps1 b/Deployment/deploy.ps1 index 09c1dd668..89bd5aa72 100644 --- a/Deployment/deploy.ps1 +++ b/Deployment/deploy.ps1 @@ -1035,7 +1035,9 @@ function logout { } WriteI -message "Azure AD sign-in..." - $ADaccount = Connect-AzureAD -Tenant $parameters.subscriptionTenantId.Value -ErrorAction Stop + $context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext + $aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken + $ADaccount = Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -ErrorAction Stop $userAlias = (($user | ConvertFrom-Json) | where {$_.id -eq $parameters.subscriptionId.Value}).user.name diff --git a/Manifest/manifest_authors.json b/Manifest/manifest_authors.json index 9af81d8a7..e99184ce6 100644 --- a/Manifest/manifest_authors.json +++ b/Manifest/manifest_authors.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "manifestVersion": "1.5", - "version": "5.0.0", + "version": "5.1.0", "id": "1c07cd26-a088-4db8-8928-ace382fa219f", "packageName": "com.microsoft.teams.companycommunicator.authors", "developer": { diff --git a/Manifest/manifest_users.json b/Manifest/manifest_users.json index f75126404..330138406 100644 --- a/Manifest/manifest_users.json +++ b/Manifest/manifest_users.json @@ -1,7 +1,7 @@ { "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "manifestVersion": "1.5", - "version": "5.0.0", + "version": "5.1.0", "id": "148a66bb-e83d-425a-927d-09f4299a9274", "packageName": "com.microsoft.teams.companycommunicator", "developer": { diff --git a/Source/CompanyCommunicator.Common/Extensions/NotificationDataExtensions.cs b/Source/CompanyCommunicator.Common/Extensions/NotificationDataExtensions.cs index 141ab0eb0..a32323e3d 100644 --- a/Source/CompanyCommunicator.Common/Extensions/NotificationDataExtensions.cs +++ b/Source/CompanyCommunicator.Common/Extensions/NotificationDataExtensions.cs @@ -23,6 +23,7 @@ public static bool IsCompleted(this NotificationDataEntity entity) { return NotificationStatus.Failed.ToString().Equals(entity.Status) || NotificationStatus.Sent.ToString().Equals(entity.Status) || + NotificationStatus.Canceled.ToString().Equals(entity.Status) || entity.IsCompleted; } diff --git a/Source/CompanyCommunicator.Common/Extensions/StringExtensions.cs b/Source/CompanyCommunicator.Common/Extensions/StringExtensions.cs new file mode 100644 index 000000000..bae7d0df3 --- /dev/null +++ b/Source/CompanyCommunicator.Common/Extensions/StringExtensions.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions +{ + using System; + + /// + /// Extension class for strings. + /// + public static class StringExtensions + { + /// + /// Append new line to the original string. + /// + /// the original string. + /// the string to be appended. + /// the appended string. + public static string AppendNewLine(this string originalString, string newString) + { + return string.IsNullOrEmpty(newString) + ? originalString + : string.IsNullOrWhiteSpace(originalString) + ? newString + : $"{originalString}{Environment.NewLine}{newString}"; + } + } +} diff --git a/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs b/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs index 4dd7b787e..9ccaabb6a 100644 --- a/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/BaseRepository.cs @@ -20,6 +20,13 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories public abstract class BaseRepository : IRepository where T : TableEntity, new() { + /// + /// Maximum length of error and warning messages to save in the entity. + /// This limit ensures that we don't hit the Azure table storage limits for the max size of the data + /// in a column, and the total size of an entity. + /// + public const int MaxMessageLengthToSave = 1024; + private readonly string defaultPartitionKey; /// diff --git a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataEntity.cs b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataEntity.cs index ddfa9dfc0..8acad23f2 100644 --- a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataEntity.cs +++ b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataEntity.cs @@ -161,6 +161,11 @@ public IEnumerable Groups /// public int Throttled { get; set; } + /// + /// Gets or sets the number of not found recipients. + /// + public int? RecipientNotFound { get; set; } + /// /// Gets or sets the number or recipients who have an unknown status - this means a status /// that has not changed from the initial initialization status after the notification has @@ -168,6 +173,11 @@ public IEnumerable Groups /// public int Unknown { get; set; } + /// + /// Gets or sets the number or recipients who have canceled status. + /// + public int? Canceled { get; set; } + /// /// Gets or sets a value indicating whether the sending process is completed. /// [DEPRECATED - kept for backward compatibility]. @@ -208,5 +218,10 @@ public IEnumerable Groups /// Gets or sets notification status. /// public string Status { get; set; } + + /// + /// Gets or sets the payload of the durable function instance. + /// + public string FunctionInstancePayload { get; set; } } } diff --git a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs index 5cad11b52..44f698551 100644 --- a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationDataRepository.cs @@ -10,19 +10,13 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.Notificat using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions; /// /// Repository of the notification data in the table storage. /// public class NotificationDataRepository : BaseRepository, INotificationDataRepository { - /// - /// Maximum length of error and warning messages to save in the entity. - /// This limit ensures that we don't hit the Azure table storage limits for the max size of the data - /// in a column, and the total size of an entity. - /// - public const int MaxMessageLengthToSave = 1024; - /// /// Initializes a new instance of the class. /// @@ -179,7 +173,7 @@ public async Task SaveExceptionInNotificationDataEntityAsync( notificationDataEntityId); if (notificationDataEntity != null) { - var newMessage = this.AppendNewLine(notificationDataEntity.ErrorMessage, errorMessage); + var newMessage = notificationDataEntity.ErrorMessage.AppendNewLine(errorMessage); // Restrict the total length of stored message to avoid hitting table storage limits if (newMessage.Length <= MaxMessageLengthToSave) @@ -208,7 +202,7 @@ public async Task SaveWarningInNotificationDataEntityAsync( notificationDataEntityId); if (notificationDataEntity != null) { - var newMessage = this.AppendNewLine(notificationDataEntity.WarningMessage, warningMessage); + var newMessage = notificationDataEntity.WarningMessage.AppendNewLine(warningMessage); // Restrict the total length of stored message to avoid hitting table storage limits if (newMessage.Length <= MaxMessageLengthToSave) @@ -225,12 +219,5 @@ public async Task SaveWarningInNotificationDataEntityAsync( throw; } } - - private string AppendNewLine(string originalString, string newString) - { - return string.IsNullOrWhiteSpace(originalString) - ? newString - : $"{originalString}{Environment.NewLine}{newString}"; - } } } diff --git a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationStatus.cs b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationStatus.cs index 8e004c342..f9251e9de 100644 --- a/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationStatus.cs +++ b/Source/CompanyCommunicator.Common/Repositories/NotificationData/NotificationStatus.cs @@ -45,5 +45,15 @@ public enum NotificationStatus /// Failed to send the message. This is an end state. /// Failed, + + /// + /// In process of canceling the message. + /// + Canceling, + + /// + /// Canceled the message. This is an end state. + /// + Canceled, } } diff --git a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/ISentNotificationDataRepository.cs b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/ISentNotificationDataRepository.cs index fce50e53e..ba1cf9e86 100644 --- a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/ISentNotificationDataRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/ISentNotificationDataRepository.cs @@ -20,5 +20,14 @@ public interface ISentNotificationDataRepository : IRepository /// A representing the result of the asynchronous operation. public Task EnsureSentNotificationDataTableExistsAsync(); + + /// + /// Save exception error message in a notification data entity. + /// + /// notification Id. + /// recipient Id. + /// Error message. + /// A representing the result of the asynchronous operation. + public Task SaveExceptionInSentNotificationDataEntityAsync(string notificationId, string recipientId, string errorMessage); } } diff --git a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs index 9ce36a3eb..e49aa189e 100644 --- a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs +++ b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs @@ -195,5 +195,10 @@ public class SentNotificationDataEntity : TableEntity /// only the final one will be stored here. /// public string ErrorMessage { get; set; } + + /// + /// Gets or sets the exception details for the user. + /// + public string Exception { get; set; } } } diff --git a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataRepository.cs b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataRepository.cs index 4f29f8de3..03d15d649 100644 --- a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataRepository.cs +++ b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataRepository.cs @@ -5,9 +5,11 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.SentNotificationData { + using System; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions; /// /// Repository of the notification data in the table storage. @@ -40,5 +42,28 @@ public async Task EnsureSentNotificationDataTableExistsAsync() await this.Table.CreateAsync(); } } + + /// + public async Task SaveExceptionInSentNotificationDataEntityAsync( + string notificationId, + string recipientId, + string errorMessage) + { + var sentNotificationDataEntity = await this.GetAsync(notificationId, recipientId); + + if (sentNotificationDataEntity == null) + { + return; + } + + var newMessage = sentNotificationDataEntity.ErrorMessage.AppendNewLine(errorMessage); + + // Restrict the total length of stored message to avoid hitting table storage limits + if (newMessage.Length <= MaxMessageLengthToSave) + { + sentNotificationDataEntity.ErrorMessage = newMessage; + await this.InsertOrMergeAsync(sentNotificationDataEntity); + } + } } } diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs b/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs index c7c035340..83ea09419 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs +++ b/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs @@ -78,6 +78,15 @@ public static string AppNotInstalled { } } + /// + /// Looks up a localized string similar to Canceled. + /// + public static string Canceled { + get { + return ResourceManager.GetString("Canceled", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delivery Status. /// @@ -87,6 +96,15 @@ public static string ColumnName_DeliveryStatus { } } + /// + /// Looks up a localized string similar to Error. + /// + public static string ColumnName_Error { + get { + return ResourceManager.GetString("ColumnName_Error", resourceCulture); + } + } + /// /// Looks up a localized string similar to Exported By. /// diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.resx b/Source/CompanyCommunicator.Common/Resources/Strings.resx index dd5d3adda..fa0de75e7 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.resx @@ -259,4 +259,10 @@ Guest User not supported + + Canceled + + + Error + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/DataQueue.cs b/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/DataQueue.cs index 20b7f939d..01b9a36a9 100644 --- a/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/DataQueue.cs +++ b/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/DataQueue.cs @@ -5,6 +5,8 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MessageQueues.DataQueue { + using System; + using System.Threading.Tasks; using global::Azure.Messaging.ServiceBus; /// @@ -27,5 +29,19 @@ public DataQueue(ServiceBusClient serviceBusClient) queueName: DataQueue.QueueName) { } + + /// + public async Task SendMessageAsync(string notificationId, TimeSpan messageDelay) + { + var dataQueueMessageContent = new DataQueueMessageContent + { + NotificationId = notificationId, + ForceMessageComplete = false, + }; + + await this.SendDelayedAsync( + dataQueueMessageContent, + messageDelay.TotalSeconds); + } } } diff --git a/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/IDataQueue.cs b/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/IDataQueue.cs index 0c0616086..c3506abef 100644 --- a/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/IDataQueue.cs +++ b/Source/CompanyCommunicator.Common/Services/MessageQueues/DataQueue/IDataQueue.cs @@ -5,10 +5,20 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MessageQueues.DataQueue { + using System; + using System.Threading.Tasks; + /// /// interface for DataQueue. /// public interface IDataQueue : IBaseQueue { + /// + /// Sends message to data queue to trigger Data function. + /// + /// the notification id. + /// time to delay the message. + /// A representing the result of the asynchronous operation. + Task SendMessageAsync(string notificationId, TimeSpan messageDelay); } } diff --git a/Source/CompanyCommunicator.Common/Services/Teams/Messages/MessageService.cs b/Source/CompanyCommunicator.Common/Services/Teams/Messages/MessageService.cs index 13a11e182..14bda0836 100644 --- a/Source/CompanyCommunicator.Common/Services/Teams/Messages/MessageService.cs +++ b/Source/CompanyCommunicator.Common/Services/Teams/Messages/MessageService.cs @@ -98,15 +98,15 @@ await this.botAdapter.ContinueConversationAsync( response.StatusCode = (int)HttpStatusCode.Created; response.AllSendStatusCodes += $"{(int)HttpStatusCode.Created},"; } - catch (ErrorResponseException e) + catch (ErrorResponseException exception) { - var errorMessage = $"{e.GetType()}: {e.Message}"; - log.LogError(e, $"Failed to send message. Exception message: {errorMessage}"); + var errorMessage = $"{exception.GetType()}: {exception.Message}"; + log.LogError(exception, $"Failed to send message. Exception message: {errorMessage}"); - response.StatusCode = (int)e.Response.StatusCode; - response.AllSendStatusCodes += $"{(int)e.Response.StatusCode},"; - response.ErrorMessage = e.Response.Content; - switch (e.Response.StatusCode) + response.StatusCode = (int)exception.Response.StatusCode; + response.AllSendStatusCodes += $"{(int)exception.Response.StatusCode},"; + response.ErrorMessage = exception.ToString(); + switch (exception.Response.StatusCode) { case HttpStatusCode.TooManyRequests: response.ResultType = SendMessageResult.Throttled; diff --git a/Source/CompanyCommunicator.Data.Func/CompanyCommunicatorDataFunction.cs b/Source/CompanyCommunicator.Data.Func/CompanyCommunicatorDataFunction.cs index 3d579f1f6..26233afba 100644 --- a/Source/CompanyCommunicator.Data.Func/CompanyCommunicatorDataFunction.cs +++ b/Source/CompanyCommunicator.Data.Func/CompanyCommunicatorDataFunction.cs @@ -89,14 +89,21 @@ public async Task Run( // If notification is already marked complete, then there is nothing left to do for the data queue trigger. if (!notificationDataEntity.IsCompleted()) { + string orchestrationStatus = string.Empty; + if (notificationDataEntity.Status.Equals(NotificationStatus.Canceling.ToString())) + { + orchestrationStatus = await this.updateNotificationDataService.GetOrchestrationStatusAsync(notificationDataEntity.FunctionInstancePayload); + } + // Get all of the result counts (Successes, Failures, etc.) from the Sent Notification Data. var aggregatedSentNotificationDataResults = await this.aggregateSentNotificationDataService - .AggregateSentNotificationDataResultsAsync(messageContent.NotificationId, log); + .AggregateSentNotificationDataResultsAsync(messageContent.NotificationId, log); // Use these counts to update the Notification Data accordingly. var notificationDataEntityUpdate = await this.updateNotificationDataService .UpdateNotificationDataAsync( notificationId: messageContent.NotificationId, + orchestrationStatus: orchestrationStatus, shouldForceCompleteNotification: messageContent.ForceMessageComplete, totalExpectedNotificationCount: notificationDataEntity.TotalMessageCount, aggregatedSentNotificationDataResults: aggregatedSentNotificationDataResults, diff --git a/Source/CompanyCommunicator.Data.Func/Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.csproj b/Source/CompanyCommunicator.Data.Func/Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.csproj index 4b2f07952..3d061b24a 100644 --- a/Source/CompanyCommunicator.Data.Func/Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.csproj +++ b/Source/CompanyCommunicator.Data.Func/Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.csproj @@ -13,6 +13,7 @@ + diff --git a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatus.cs b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatus.cs new file mode 100644 index 000000000..0ef888cab --- /dev/null +++ b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatus.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.Services.NotificationDataServices +{ + /// + /// Orchestration instance status. + /// + public enum OrchestrationStatus + { + /// + /// Orchestration instance is Running. + /// + Running, + + /// + /// Orchestration instance is Terminated. + /// + Terminated, + + /// + /// Orchestration instance is Completed. + /// + Completed, + } +} diff --git a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatusResponse.cs b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatusResponse.cs new file mode 100644 index 000000000..2a38ce25f --- /dev/null +++ b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/OrchestrationStatusResponse.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.Services.NotificationDataServices +{ + /// + /// Orchestration Status response. + /// + public class OrchestrationStatusResponse + { + /// + /// Gets or sets the orchestration name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the Instance id of the orchestration. + /// + public string InstanceId { get; set; } + + /// + /// Gets or sets the runtime status of the orchestration. + /// + public string RuntimeStatus { get; set; } + } +} diff --git a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataEntity.cs b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataEntity.cs index fc238746b..a4e6ab074 100644 --- a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataEntity.cs +++ b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataEntity.cs @@ -15,8 +15,6 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.Services.Notificati /// overwrite unset non-nullable values from the full NotificationDataEntity and remove /// unexpected data from the existing database row e.g. not setting TotalMessageCount for this /// entity will not result in the value being set to 0 in the database by mistake. - /// - /// TODO(guptaa): Remove this file. /// public class UpdateNotificationDataEntity : TableEntity { @@ -55,6 +53,11 @@ public class UpdateNotificationDataEntity : TableEntity /// public string Status { get; set; } + /// + /// Gets or sets the number or recipients who have canceled status. + /// + public int? Canceled { get; set; } + /// /// Checks if the notification is completed. /// diff --git a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataService.cs b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataService.cs index b1fa64b68..0a6b9ce34 100644 --- a/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataService.cs +++ b/Source/CompanyCommunicator.Data.Func/Services/NotificationDataServices/UpdateNotificationDataService.cs @@ -6,10 +6,13 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.Services.NotificationDataServices { using System; + using System.Net.Http; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Extensions.Logging; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.NotificationData; + using Newtonsoft.Json; /// /// Service to update notification data. @@ -17,21 +20,26 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Data.Func.Services.Notificati public class UpdateNotificationDataService { private readonly INotificationDataRepository notificationDataRepository; + private readonly IHttpClientFactory httpClientFactory; /// /// Initializes a new instance of the class. /// /// The notification data repository. + /// The HTTP client factory. public UpdateNotificationDataService( - INotificationDataRepository notificationDataRepository) + INotificationDataRepository notificationDataRepository, + IHttpClientFactory httpClientFactory) { - this.notificationDataRepository = notificationDataRepository; + this.notificationDataRepository = notificationDataRepository ?? throw new ArgumentNullException(nameof(notificationDataRepository)); + this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } /// /// Updates the notification totals with the given information and results. /// /// The notification ID. + /// The orchestration status of the notification. /// Flag to indicate if the notification should /// be forced to be marked as completed. /// The total expected count of notifications to be sent. @@ -41,6 +49,7 @@ public UpdateNotificationDataService( /// A representing the result of the asynchronous operation. public async Task UpdateNotificationDataAsync( string notificationId, + string orchestrationStatus, bool shouldForceCompleteNotification, int totalExpectedNotificationCount, AggregatedSentNotificationDataResults aggregatedSentNotificationDataResults, @@ -66,20 +75,28 @@ public async Task UpdateNotificationDataAsync( Throttled = throttledCount, }; - // If it should be marked as complete, set the other values accordingly. - if (currentTotalNotificationCount >= totalExpectedNotificationCount - || shouldForceCompleteNotification) + if (orchestrationStatus.Equals(nameof(OrchestrationStatus.Terminated), StringComparison.InvariantCultureIgnoreCase) + || orchestrationStatus.Equals(nameof(OrchestrationStatus.Completed), StringComparison.InvariantCultureIgnoreCase)) { - // Update the status to Sent. - notificationDataEntityUpdate.Status = NotificationStatus.Sent.ToString(); + if (currentTotalNotificationCount >= totalExpectedNotificationCount) + { + this.SetSentStatus(ref notificationDataEntityUpdate, lastSentDate); + } + else + { + var countDifference = totalExpectedNotificationCount - currentTotalNotificationCount; + this.SetCanceledStatus(ref notificationDataEntityUpdate, countDifference); + } + } + else + // If it should be marked as complete, set the other values accordingly. + if (currentTotalNotificationCount >= totalExpectedNotificationCount + || shouldForceCompleteNotification) + { if (currentTotalNotificationCount >= totalExpectedNotificationCount) { - // If the message is being completed because all messages have been accounted for, - // then make sure the unknown count is 0 and update the sent date with the date - // of the last sent message. - notificationDataEntityUpdate.Unknown = 0; - notificationDataEntityUpdate.SentDate = lastSentDate ?? DateTime.UtcNow; + this.SetSentStatus(ref notificationDataEntityUpdate, lastSentDate); } else if (shouldForceCompleteNotification) { @@ -88,12 +105,7 @@ public async Task UpdateNotificationDataAsync( // notification will eventually be marked as complete, then update the unknown count of messages // not accounted for and update the sent date to the current time. var countDifference = totalExpectedNotificationCount - currentTotalNotificationCount; - - // This count must stay 0 or above. - var unknownCount = countDifference >= 0 ? countDifference : 0; - - notificationDataEntityUpdate.Unknown = unknownCount; - notificationDataEntityUpdate.SentDate = DateTime.UtcNow; + this.SetSentStatusWithUnknownCount(ref notificationDataEntityUpdate, countDifference); } } @@ -109,5 +121,52 @@ public async Task UpdateNotificationDataAsync( throw; } } + + /// + /// Get the orchestration status of the notification. + /// + /// the payload of the orchestration containing Status Uri, Terminate Uri etc. + /// the status of the orchestration. + public async Task GetOrchestrationStatusAsync(string functionPayload) + { + var instancePayload = JsonConvert.DeserializeObject(functionPayload); + var client = this.httpClientFactory.CreateClient(); + var response = await client.GetAsync(instancePayload.StatusQueryGetUri); + var content = await response.Content.ReadAsStringAsync(); + var functionResp = JsonConvert.DeserializeObject(content); + return functionResp.RuntimeStatus; + } + + private void SetSentStatus(ref UpdateNotificationDataEntity notificationDataEntityUpdate, DateTime? lastSentDate) + { + // Update the status to Sent. + notificationDataEntityUpdate.Status = NotificationStatus.Sent.ToString(); + + // If the message is being completed because all messages have been accounted for, + // then make sure the unknown count is 0 and update the sent date with the date + // of the last sent message. + notificationDataEntityUpdate.Unknown = 0; + notificationDataEntityUpdate.SentDate = lastSentDate ?? DateTime.UtcNow; + } + + private void SetCanceledStatus(ref UpdateNotificationDataEntity notificationDataEntityUpdate, int canceledCount) + { + notificationDataEntityUpdate.Status = NotificationStatus.Canceled.ToString(); + + // This count must stay 0 or above. + canceledCount = canceledCount >= 0 ? canceledCount : 0; + notificationDataEntityUpdate.Canceled = canceledCount; + notificationDataEntityUpdate.SentDate = DateTime.UtcNow; + } + + private void SetSentStatusWithUnknownCount(ref UpdateNotificationDataEntity notificationDataEntityUpdate, int unknownCount) + { + notificationDataEntityUpdate.Status = NotificationStatus.Sent.ToString(); + + // This count must stay 0 or above. + unknownCount = unknownCount >= 0 ? unknownCount : 0; + notificationDataEntityUpdate.Unknown = unknownCount; + notificationDataEntityUpdate.SentDate = DateTime.UtcNow; + } } } diff --git a/Source/CompanyCommunicator.Data.Func/Startup.cs b/Source/CompanyCommunicator.Data.Func/Startup.cs index e51061950..45b428fb0 100644 --- a/Source/CompanyCommunicator.Data.Func/Startup.cs +++ b/Source/CompanyCommunicator.Data.Func/Startup.cs @@ -79,6 +79,7 @@ public override void Configure(IFunctionsHostBuilder builder) }); builder.Services.AddLocalization(); + builder.Services.AddHttpClient(); var useManagedIdentity = bool.Parse(Environment.GetEnvironmentVariable("UseManagedIdentity")); builder.Services.AddBlobClient(useManagedIdentity); diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Activities/UploadActivity.cs b/Source/CompanyCommunicator.Prep.Func/Export/Activities/UploadActivity.cs index 68b2e6d49..3fbe65c75 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Activities/UploadActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Activities/UploadActivity.cs @@ -107,7 +107,7 @@ public async Task UploadActivityAsync( { var teamDataMap = new TeamDataMap(this.localizer); csv.Configuration.RegisterClassMap(teamDataMap); - var teamDataStream = this.userDataStream.GetTeamDataStreamAsync(uploadData.sentNotificationDataEntity.Id); + var teamDataStream = this.userDataStream.GetTeamDataStreamAsync(uploadData.sentNotificationDataEntity.Id, uploadData.sentNotificationDataEntity.Status); await foreach (var data in teamDataStream) { await csv.WriteRecordsAsync(data); @@ -117,7 +117,7 @@ public async Task UploadActivityAsync( { var userDataMap = new UserDataMap(this.localizer); csv.Configuration.RegisterClassMap(userDataMap); - var userDataStream = this.userDataStream.GetUserDataStreamAsync(uploadData.sentNotificationDataEntity.Id); + var userDataStream = this.userDataStream.GetUserDataStreamAsync(uploadData.sentNotificationDataEntity.Id, uploadData.sentNotificationDataEntity.Status); await foreach (var data in userDataStream) { await csv.WriteRecordsAsync(data); diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Mappers/TeamDataMap.cs b/Source/CompanyCommunicator.Prep.Func/Export/Mappers/TeamDataMap.cs index c91bf35a8..93d21f57e 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Mappers/TeamDataMap.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Mappers/TeamDataMap.cs @@ -29,6 +29,7 @@ public TeamDataMap(IStringLocalizer localizer) this.Map(x => x.Name).Name(this.localizer.GetString("ColumnName_TeamName")); this.Map(x => x.DeliveryStatus).Name(this.localizer.GetString("ColumnName_DeliveryStatus")); this.Map(x => x.StatusReason).Name(this.localizer.GetString("ColumnName_StatusReason")); + this.Map(x => x.Error).Name(this.localizer.GetString("ColumnName_Error")); } } } diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Mappers/UserDataMap.cs b/Source/CompanyCommunicator.Prep.Func/Export/Mappers/UserDataMap.cs index c74cec200..1ea9d3de3 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Mappers/UserDataMap.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Mappers/UserDataMap.cs @@ -31,6 +31,7 @@ public UserDataMap(IStringLocalizer localizer) this.Map(x => x.UserType).Name(this.localizer.GetString("ColumnName_UserType")); this.Map(x => x.DeliveryStatus).Name(this.localizer.GetString("ColumnName_DeliveryStatus")); this.Map(x => x.StatusReason).Name(this.localizer.GetString("ColumnName_StatusReason")); + this.Map(x => x.Error).Name(this.localizer.GetString("ColumnName_Error")); } } } diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Model/TeamData.cs b/Source/CompanyCommunicator.Prep.Func/Export/Model/TeamData.cs index 5f953be10..4440099a3 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Model/TeamData.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Model/TeamData.cs @@ -29,5 +29,10 @@ public class TeamData /// Gets or sets the status reason value. /// public string StatusReason { get; set; } + + /// + /// Gets or sets the error message. + /// + public string Error { get; set; } } } diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Model/UserData.cs b/Source/CompanyCommunicator.Prep.Func/Export/Model/UserData.cs index ac378b2dc..362f4f18f 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Model/UserData.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Model/UserData.cs @@ -39,5 +39,10 @@ public class UserData /// Gets or sets the status reason value. /// public string StatusReason { get; set; } + + /// + /// Gets or sets the error message. + /// + public string Error { get; set; } } } \ No newline at end of file diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs b/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs index c37224cd3..52c739492 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs @@ -13,6 +13,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Prep.Func.Export.Streams using Microsoft.Extensions.Localization; using Microsoft.Graph; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.NotificationData; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.SentNotificationData; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.TeamData; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.UserData; @@ -59,14 +60,11 @@ public DataStreamFacade( this.localizer = localizer ?? throw new ArgumentNullException(nameof(localizer)); } - /// - /// get the users data streams. - /// - /// the notification id. - /// the streams of user data. - public async IAsyncEnumerable> GetUserDataStreamAsync(string notificationId) + /// + public async IAsyncEnumerable> GetUserDataStreamAsync(string notificationId, string notificationStatus) { _ = notificationId ?? throw new ArgumentNullException(nameof(notificationId)); + _ = notificationStatus ?? throw new ArgumentNullException(nameof(notificationStatus)); var sentNotificationDataEntitiesStream = this.sentNotificationDataRepository.GetStreamsAsync(notificationId); var isForbidden = false; @@ -76,7 +74,16 @@ public async IAsyncEnumerable> GetUserDataStreamAsync(stri var users = new List(); // filter the recipient not found users. - var recipients = sentNotifications.Where(sentNotifcation => !sentNotifcation.DeliveryStatus.Equals(SentNotificationDataEntity.RecipientNotFound, StringComparison.CurrentCultureIgnoreCase)); + var recipients = new List(); + foreach (var sentNotification in sentNotifications) + { + if (sentNotification.DeliveryStatus != null && sentNotification.DeliveryStatus.Equals(SentNotificationDataEntity.RecipientNotFound, StringComparison.CurrentCultureIgnoreCase)) + { + continue; + } + + recipients.Add(sentNotification); + } try { @@ -106,26 +113,20 @@ public async IAsyncEnumerable> GetUserDataStreamAsync(stri if (isForbidden) { - yield return this.CreatePartialUserData(recipients); + yield return this.CreatePartialUserData(recipients, notificationStatus); } else { - yield return await this.CreateUserDataAsync(recipients, users); + yield return await this.CreateUserDataAsync(recipients, users, notificationStatus); } } } - /// - /// get the team data streams. - /// - /// the notification id. - /// the streams of team data. - public async IAsyncEnumerable> GetTeamDataStreamAsync(string notificationId) + /// + public async IAsyncEnumerable> GetTeamDataStreamAsync(string notificationId, string notificationStatus) { - if (notificationId == null) - { - throw new ArgumentNullException(nameof(notificationId)); - } + _ = notificationId ?? throw new ArgumentNullException(nameof(notificationId)); + _ = notificationStatus ?? throw new ArgumentNullException(nameof(notificationStatus)); var sentNotificationDataEntitiesStream = this.sentNotificationDataRepository.GetStreamsAsync(notificationId); await foreach (var sentNotificationDataEntities in sentNotificationDataEntitiesStream) @@ -139,7 +140,8 @@ public async IAsyncEnumerable> GetTeamDataStreamAsync(stri Id = sentNotificationDataEntity.RowKey, Name = team?.Name, DeliveryStatus = sentNotificationDataEntity.DeliveryStatus is null ? sentNotificationDataEntity.DeliveryStatus : this.localizer.GetString(sentNotificationDataEntity.DeliveryStatus), - StatusReason = this.GetStatusReason(sentNotificationDataEntity.ErrorMessage, sentNotificationDataEntity.StatusCode), + StatusReason = this.GetStatusReason(sentNotificationDataEntity.ErrorMessage, sentNotificationDataEntity.StatusCode, notificationStatus), + Error = sentNotificationDataEntity.Exception, }; teamDataList.Add(teamData); } @@ -153,10 +155,12 @@ public async IAsyncEnumerable> GetTeamDataStreamAsync(stri /// /// the list of sent notification data entities. /// the user list. + /// the notification status. /// list of created user data. private async Task> CreateUserDataAsync( IEnumerable sentNotificationDataEntities, - IEnumerable users) + IEnumerable users, + string notificationStatus) { var userdatalist = new List(); foreach (var sentNotification in sentNotificationDataEntities) @@ -185,7 +189,8 @@ private async Task> CreateUserDataAsync( Upn = user?.UserPrincipalName, UserType = userType is null ? userType : this.localizer.GetString(userType), DeliveryStatus = sentNotification.DeliveryStatus is null ? sentNotification.DeliveryStatus : this.localizer.GetString(sentNotification.DeliveryStatus), - StatusReason = this.GetStatusReason(sentNotification.ErrorMessage, sentNotification.StatusCode), + StatusReason = this.GetStatusReason(sentNotification.ErrorMessage, sentNotification.StatusCode, notificationStatus), + Error = sentNotification.Exception, }); } @@ -196,8 +201,9 @@ private async Task> CreateUserDataAsync( /// Create partial user data. /// /// the list of sent notification data entities. + /// the notification status. /// user data list. - private IEnumerable CreatePartialUserData(IEnumerable sentNotificationDataEntities) + private IEnumerable CreatePartialUserData(IEnumerable sentNotificationDataEntities, string notificationStatus) { return sentNotificationDataEntities .Select(sentNotification => @@ -208,7 +214,8 @@ private IEnumerable CreatePartialUserData(IEnumerable CreatePartialUserData(IEnumerable /// the error message. /// the status code. + /// the notification status. /// status code appended error message. - private string GetStatusReason(string errorMessage, int statusCode) + private string GetStatusReason(string errorMessage, int statusCode, string notificationStatus) { string result; if (string.IsNullOrEmpty(errorMessage)) { - result = this.localizer.GetString("OK"); + // If the statusCode is initialized (i.e. the notification was picked to process for the recipient) and the notification status is Canceled, + // then show the status reason as Canceled. + if (statusCode == SentNotificationDataEntity.InitializationStatusCode && notificationStatus.Equals(NotificationStatus.Canceled.ToString())) + { + result = this.localizer.GetString("Canceled"); + } + else + { + result = this.localizer.GetString("OK"); + } } else if (errorMessage.Contains("error")) { diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Streams/IDataStreamFacade.cs b/Source/CompanyCommunicator.Prep.Func/Export/Streams/IDataStreamFacade.cs index aa5fec3c4..fda587589 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Streams/IDataStreamFacade.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Streams/IDataStreamFacade.cs @@ -14,17 +14,19 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Prep.Func.Export.Streams public interface IDataStreamFacade { /// - /// get the users data streams. + /// Get the user data list, which can be iterated asynchronously. /// /// the notification id. + /// the notification status. /// the streams of user data. - IAsyncEnumerable> GetUserDataStreamAsync(string notificationId); + IAsyncEnumerable> GetUserDataStreamAsync(string notificationId, string notificationStatus); /// - /// get the team data streams. + /// Get the team data list, which can be iterated asynchronously. /// /// the notification id. + /// the notification status. /// the streams of team data. - IAsyncEnumerable> GetTeamDataStreamAsync(string notificationId); + IAsyncEnumerable> GetTeamDataStreamAsync(string notificationId, string notificationStatus); } } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/DataAggregationTriggerActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/DataAggregationTriggerActivity.cs index 352e7a8e7..0b3c1117b 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/DataAggregationTriggerActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/DataAggregationTriggerActivity.cs @@ -25,7 +25,7 @@ public class DataAggregationTriggerActivity { private readonly INotificationDataRepository notificationDataRepository; private readonly IDataQueue dataQueue; - private readonly double messageDelayInSeconds; + private readonly int messageDelayInSeconds; /// /// Initializes a new instance of the class. @@ -70,7 +70,8 @@ public async Task RunAsync( await this.UpdateNotification(input.notificationId, input.recipientCount, log); // Send message to data queue. - await this.SendMessageToDataQueue(input.notificationId); + var messageDelay = new TimeSpan(0, 0, this.messageDelayInSeconds); + await this.dataQueue.SendMessageAsync(input.notificationId, messageDelay); } /// @@ -96,23 +97,5 @@ private async Task UpdateNotification(string notificationId, int recipientCount, await this.notificationDataRepository.CreateOrUpdateAsync(notificationDataEntity); } - - /// - /// Sends message to data queue to trigger Data function. - /// - /// Notification id. - /// A representing the asynchronous operation. - private async Task SendMessageToDataQueue(string notificationId) - { - var dataQueueMessageContent = new DataQueueMessageContent - { - NotificationId = notificationId, - ForceMessageComplete = false, - }; - - await this.dataQueue.SendDelayedAsync( - dataQueueMessageContent, - this.messageDelayInSeconds); - } } } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs index fd5a8a543..fb2ad6566 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs @@ -195,6 +195,8 @@ private async Task CreateConversationWithTeamsUser( var errorMessage = this.localizer.GetString("FailedToCreateConversationForUserFormat", recipient?.UserId, exception.Message); log.LogError(exception, errorMessage); await this.notificationDataRepository.SaveWarningInNotificationDataEntityAsync(notificationId, errorMessage); + var exceptionDetails = this.localizer.GetString("FailedToCreateConversationForUserFormat", recipient?.UserId, exception.ToString()); + await this.sentNotificationDataRepository.SaveExceptionInSentNotificationDataEntityAsync(notificationId, recipient?.RecipientId, exceptionDetails); return null; } } @@ -240,6 +242,8 @@ private async Task InstallAppAndGetConversationId( var errorMessage = this.localizer.GetString("FailedToInstallApplicationForUserFormat", recipient?.RecipientId, exception.Message); log.LogError(exception, errorMessage); await this.notificationDataRepository.SaveWarningInNotificationDataEntityAsync(notificationId, errorMessage); + var exceptionDetails = this.localizer.GetString("FailedToInstallApplicationForUserFormat", recipient?.RecipientId, exception.ToString()); + await this.sentNotificationDataRepository.SaveExceptionInSentNotificationDataEntityAsync(notificationId, recipient?.RecipientId, exceptionDetails); return string.Empty; } } @@ -254,6 +258,8 @@ private async Task InstallAppAndGetConversationId( var errorMessage = this.localizer.GetString("FailedToGetConversationForUserFormat", recipient?.UserId, exception.StatusCode, exception.Message); log.LogError(exception, errorMessage); await this.notificationDataRepository.SaveWarningInNotificationDataEntityAsync(notificationId, errorMessage); + var exceptionDetails = this.localizer.GetString("FailedToGetConversationForUserFormat", recipient?.UserId, exception.StatusCode, exception.ToString()); + await this.sentNotificationDataRepository.SaveExceptionInSentNotificationDataEntityAsync(notificationId, recipient?.RecipientId, exceptionDetails); return string.Empty; } } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/DataQueueMessageOptions.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/DataQueueMessageOptions.cs index adf4e4568..7c4e6fef1 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/DataQueueMessageOptions.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/DataQueueMessageOptions.cs @@ -13,6 +13,6 @@ public class DataQueueMessageOptions /// /// Gets or sets the value for the delay to be applied to the data queue message. /// - public double MessageDelayInSeconds { get; set; } + public int MessageDelayInSeconds { get; set; } } } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/PrepareToSendFunction.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/PrepareToSendFunction.cs index 4553d9fc2..bf22d2fde 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/PrepareToSendFunction.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/PrepareToSendFunction.cs @@ -68,6 +68,10 @@ public async Task Run( sentNotificationDataEntity); log.LogInformation($"Started orchestration with ID = '{instanceId}'."); + + var httpManagementPayload = starter.CreateHttpManagementPayload(instanceId); + sentNotificationDataEntity.FunctionInstancePayload = JsonConvert.SerializeObject(httpManagementPayload); + await this.notificationDataRepository.InsertOrMergeAsync(sentNotificationDataEntity); } } } diff --git a/Source/CompanyCommunicator.Prep.Func/Startup.cs b/Source/CompanyCommunicator.Prep.Func/Startup.cs index 85bbdd71b..aa3905f8a 100644 --- a/Source/CompanyCommunicator.Prep.Func/Startup.cs +++ b/Source/CompanyCommunicator.Prep.Func/Startup.cs @@ -87,7 +87,7 @@ public override void Configure(IFunctionsHostBuilder builder) .Configure((dataQueueMessageOptions, configuration) => { dataQueueMessageOptions.MessageDelayInSeconds = - configuration.GetValue("DataQueueMessageDelayInSeconds", 5); + configuration.GetValue("DataQueueMessageDelayInSeconds", 5); }); builder.Services.AddOptions() diff --git a/Source/CompanyCommunicator.Send.Func/SendFunction.cs b/Source/CompanyCommunicator.Send.Func/SendFunction.cs index b663439d0..b85e67a8c 100644 --- a/Source/CompanyCommunicator.Send.Func/SendFunction.cs +++ b/Source/CompanyCommunicator.Send.Func/SendFunction.cs @@ -18,7 +18,6 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Send.Func using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.SentNotificationData; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Resources; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MessageQueues.SendQueue; - using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGraph; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.Teams; using Microsoft.Teams.Apps.CompanyCommunicator.Send.Func.Services; using Newtonsoft.Json; @@ -106,6 +105,14 @@ public async Task Run( try { + // Check if notification is canceled. + var isCanceled = await this.notificationService.IsNotificationCanceled(messageContent); + if (isCanceled) + { + // No-op in case notification is canceled. + return; + } + // Check if recipient is a guest user. if (messageContent.IsRecipientGuestUser()) { @@ -166,10 +173,10 @@ await this.notificationService.UpdateSentNotification( // Bad message shouldn't be requeued. log.LogError(exception, $"InvalidOperationException thrown. Error message: {exception.Message}"); } - catch (Exception e) + catch (Exception exception) { - var errorMessage = $"{e.GetType()}: {e.Message}"; - log.LogError(e, $"Failed to send message. ErrorMessage: {errorMessage}"); + var exceptionMessage = $"{exception.GetType()}: {exception.Message}"; + log.LogError(exception, $"Failed to send message. ErrorMessage: {exceptionMessage}"); // Update status code depending on delivery count. var statusCode = SentNotificationDataEntity.FaultedAndRetryingStatusCode; @@ -186,7 +193,8 @@ await this.notificationService.UpdateSentNotification( totalNumberOfSendThrottles: 0, statusCode: statusCode, allSendStatusCodes: $"{statusCode},", - errorMessage: errorMessage); + errorMessage: this.localizer.GetString("Failed"), + exception: exception.ToString()); throw; } @@ -203,6 +211,7 @@ private async Task ProcessResponseAsync( SendMessageResponse sendMessageResponse, ILogger log) { + var statusReason = string.Empty; if (sendMessageResponse.ResultType == SendMessageResult.Succeeded) { log.LogInformation($"Successfully sent the message." + @@ -214,6 +223,8 @@ private async Task ProcessResponseAsync( $"\nRecipient Id: {messageContent.RecipientData.RecipientId}" + $"\nResult: {sendMessageResponse.ResultType}." + $"\nErrorMessage: {sendMessageResponse.ErrorMessage}."); + + statusReason = this.localizer.GetString("Failed"); } await this.notificationService.UpdateSentNotification( @@ -222,7 +233,8 @@ await this.notificationService.UpdateSentNotification( totalNumberOfSendThrottles: sendMessageResponse.TotalNumberOfSendThrottles, statusCode: sendMessageResponse.StatusCode, allSendStatusCodes: sendMessageResponse.AllSendStatusCodes, - errorMessage: sendMessageResponse.ErrorMessage); + errorMessage: statusReason, + exception: sendMessageResponse.ErrorMessage); // Throttled if (sendMessageResponse.ResultType == SendMessageResult.Throttled) diff --git a/Source/CompanyCommunicator.Send.Func/Services/INotificationService.cs b/Source/CompanyCommunicator.Send.Func/Services/INotificationService.cs index 005c21bd5..46cf43074 100644 --- a/Source/CompanyCommunicator.Send.Func/Services/INotificationService.cs +++ b/Source/CompanyCommunicator.Send.Func/Services/INotificationService.cs @@ -26,6 +26,13 @@ public interface INotificationService /// true if the notification is pending, false otherwise. public Task IsPendingNotification(SendQueueMessageContent message); + /// + /// Check if the notification is canceled. + /// + /// Send Queue message. + /// true if the notification is canceled, false otherwise. + public Task IsNotificationCanceled(SendQueueMessageContent message); + /// /// Set SendNotification Throttled. /// @@ -44,7 +51,8 @@ public interface INotificationService /// Status code. /// A comma separated list representing all of the status code responses received when trying /// to send the notification to the recipient. - /// The error message to store in the database. + /// The error reason to store in the database. + /// The exception message to store in the database. /// A representing the asynchronous operation. public Task UpdateSentNotification( string notificationId, @@ -52,6 +60,7 @@ public Task UpdateSentNotification( int totalNumberOfSendThrottles, int statusCode, string allSendStatusCodes, - string errorMessage); + string errorMessage, + string exception = null); } } diff --git a/Source/CompanyCommunicator.Send.Func/Services/NotificationService.cs b/Source/CompanyCommunicator.Send.Func/Services/NotificationService.cs index 746e951e7..51b7105b2 100644 --- a/Source/CompanyCommunicator.Send.Func/Services/NotificationService.cs +++ b/Source/CompanyCommunicator.Send.Func/Services/NotificationService.cs @@ -20,18 +20,22 @@ public class NotificationService : INotificationService { private readonly IGlobalSendingNotificationDataRepository globalSendingNotificationDataRepository; private readonly ISentNotificationDataRepository sentNotificationDataRepository; + private readonly INotificationDataRepository notificationDataRepository; /// /// Initializes a new instance of the class. /// /// The global sending notification data repository. /// The sent notification data repository. + /// The notification data repository. public NotificationService( IGlobalSendingNotificationDataRepository globalSendingNotificationDataRepository, - ISentNotificationDataRepository sentNotificationDataRepository) + ISentNotificationDataRepository sentNotificationDataRepository, + INotificationDataRepository notificationDataRepository) { this.globalSendingNotificationDataRepository = globalSendingNotificationDataRepository ?? throw new ArgumentNullException(nameof(globalSendingNotificationDataRepository)); this.sentNotificationDataRepository = sentNotificationDataRepository ?? throw new ArgumentNullException(nameof(sentNotificationDataRepository)); + this.notificationDataRepository = notificationDataRepository ?? throw new ArgumentNullException(nameof(notificationDataRepository)); } /// @@ -73,6 +77,24 @@ public async Task IsPendingNotification(SendQueueMessageContent message) return false; } + /// + public async Task IsNotificationCanceled(SendQueueMessageContent message) + { + // Check notification status. + var notification = await this.notificationDataRepository.GetAsync( + partitionKey: NotificationDataTableNames.SentNotificationsPartition, + rowKey: message.NotificationId); + + // Check if the Status is Canceled. + if (notification.Status.Equals(nameof(NotificationStatus.Canceled)) || + notification.Status.Equals(nameof(NotificationStatus.Canceling))) + { + return true; + } + + return false; + } + /// public async Task SetSendNotificationThrottled(double sendRetryDelayNumberOfSeconds) { @@ -93,7 +115,8 @@ public async Task UpdateSentNotification( int totalNumberOfSendThrottles, int statusCode, string allSendStatusCodes, - string errorMessage) + string errorMessage, + string exception = null) { // Current time as sent date time. var sentDateTime = DateTime.UtcNow; @@ -108,6 +131,11 @@ public async Task UpdateSentNotification( notification.IsStatusCodeFromCreateConversation = false; notification.StatusCode = (int)statusCode; notification.ErrorMessage = errorMessage; + if (!string.IsNullOrEmpty(exception)) + { + notification.Exception = exception; + } + notification.NumberOfFunctionAttemptsToSend = notification.NumberOfFunctionAttemptsToSend + 1; notification.AllSendStatusCodes = $"{notification.AllSendStatusCodes ?? string.Empty}{allSendStatusCodes}"; diff --git a/Source/CompanyCommunicator.Send.Func/Startup.cs b/Source/CompanyCommunicator.Send.Func/Startup.cs index f44e8f979..ad84e8b68 100644 --- a/Source/CompanyCommunicator.Send.Func/Startup.cs +++ b/Source/CompanyCommunicator.Send.Func/Startup.cs @@ -92,6 +92,7 @@ public override void Configure(IFunctionsHostBuilder builder) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // Add service bus message queues. builder.Services.AddSingleton(); diff --git a/Source/CompanyCommunicator/ClientApp/package-lock.json b/Source/CompanyCommunicator/ClientApp/package-lock.json index c4d853990..7f6b3a297 100644 --- a/Source/CompanyCommunicator/ClientApp/package-lock.json +++ b/Source/CompanyCommunicator/ClientApp/package-lock.json @@ -1,6 +1,6 @@ { "name": "company-communicator", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2692,6 +2692,11 @@ "loader-utils": "^2.0.0" } }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -3440,6 +3445,14 @@ "regex-parser": "^2.2.11" } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -3642,14 +3655,6 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -3692,11 +3697,6 @@ } } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -3754,16 +3754,6 @@ "postcss-value-parser": "^4.1.0" } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" - }, "axe-core": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.2.tgz", @@ -3778,9 +3768,9 @@ }, "dependencies": { "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" } } }, @@ -4323,14 +4313,6 @@ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=" }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, "bfj": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.0.2.tgz", @@ -4735,11 +4717,6 @@ "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==" }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5259,6 +5236,14 @@ "sha.js": "^2.4.8" } }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -5654,14 +5639,6 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==" }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -6133,15 +6110,6 @@ } } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6361,61 +6329,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, "eslint": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", @@ -7228,11 +7141,6 @@ } } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -7311,11 +7219,6 @@ } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, "faker": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", @@ -7624,9 +7527,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==" }, "font-awesome": { "version": "4.7.0", @@ -7638,11 +7541,6 @@ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, "fork-ts-checker-webpack-plugin": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz", @@ -7754,16 +7652,6 @@ } } }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -7938,14 +7826,6 @@ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -8028,20 +7908,6 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "harmony-reflect": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", @@ -8408,6 +8274,16 @@ "requires-port": "^1.0.0" } }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, "http-proxy-middleware": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", @@ -8516,21 +8392,20 @@ } } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", @@ -8588,18 +8463,11 @@ } }, "i18next-http-backend": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.0.21.tgz", - "integrity": "sha512-UDeHoV2B+31Gr++0KFAVjM5l+SEwePpF6sfDyaDq5ennM9QNJ78PBEMPStwkreEm4h5C8sT7M1JdNQrLcU1Wdg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.3.2.tgz", + "integrity": "sha512-SfcoUmsSWnc2LYsDsCq5TCg18cxJXvXymX9N37V+qqMKQY8Gf0rWkjOnRd20sMK633Dq4NF9tvqPbOiFJ49Kbw==", "requires": { - "node-fetch": "2.6.1" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - } + "cross-fetch": "3.1.5" } }, "iconv-lite": { @@ -8998,11 +8866,6 @@ "isobject": "^3.0.1" } }, - "is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" - }, "is-regex": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", @@ -9078,11 +8941,6 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, "istanbul-lib-coverage": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", @@ -10682,42 +10540,156 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, "jsdom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", - "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", "requires": { - "abab": "^2.0.3", - "acorn": "^7.1.1", + "abab": "^2.0.5", + "acorn": "^8.2.4", "acorn-globals": "^6.0.0", "cssom": "^0.4.4", - "cssstyle": "^2.2.0", + "cssstyle": "^2.3.0", "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", + "decimal.js": "^10.2.1", "domexception": "^2.0.1", - "escodegen": "^1.14.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", "nwsapi": "^2.2.0", - "parse5": "5.1.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", + "tough-cookie": "^4.0.0", "w3c-hr-time": "^1.0.2", "w3c-xmlserializer": "^2.0.0", "webidl-conversions": "^6.1.0", "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.2.3", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "requires": { + "punycode": "^2.1.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + } } }, "jsesc": { @@ -10735,11 +10707,6 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -10750,11 +10717,6 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, "json3": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", @@ -10777,17 +10739,6 @@ "universalify": "^2.0.0" } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, "jsx-ast-utils": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", @@ -11425,6 +11376,11 @@ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, + "nanoid": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -11492,6 +11448,35 @@ } } }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", @@ -11665,11 +11650,6 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12034,11 +12014,6 @@ "lines-and-columns": "^1.1.6" } }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13193,11 +13168,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" - }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" } } } @@ -13453,11 +13423,6 @@ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, "query-string": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", @@ -14233,78 +14198,6 @@ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15358,22 +15251,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -15431,11 +15308,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -16094,16 +15966,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tr46": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", @@ -16225,19 +16087,6 @@ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -16605,16 +16454,6 @@ "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -17126,8 +16965,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "resolved": "", "requires": { "figgy-pudding": "^3.5.1" } diff --git a/Source/CompanyCommunicator/ClientApp/package.json b/Source/CompanyCommunicator/ClientApp/package.json index 8cafb0c0a..64aeb5253 100644 --- a/Source/CompanyCommunicator/ClientApp/package.json +++ b/Source/CompanyCommunicator/ClientApp/package.json @@ -1,6 +1,6 @@ { "name": "company-communicator", - "version": "5.0.0", + "version": "5.1.0", "private": true, "dependencies": { "@fluentui/react-northstar": "^0.52.0", @@ -12,7 +12,7 @@ "font-awesome": "^4.7.0", "i18next": "^19.5.1", "i18next-browser-languagedetector": "^5.0.0", - "i18next-http-backend": "^1.0.15", + "i18next-http-backend": "^1.3.2", "moment": "2.24.0", "msteams-ui-components-react": "^0.8.1", "msteams-ui-icons-react": "^0.4.1", @@ -25,8 +25,8 @@ "react-router-dom": "^5.2.0", "react-scripts": "^4.0.3", "redux": "^4.0.1", - "sass": "^1.32.7", "redux-thunk": "^2.3.0", + "sass": "^1.32.7", "typescript": "3.5.1", "typestyle": "^2.0.2" }, diff --git a/Source/CompanyCommunicator/ClientApp/public/locales/en-US/translation.json b/Source/CompanyCommunicator/ClientApp/public/locales/en-US/translation.json index 89ed7f818..26a9beece 100644 --- a/Source/CompanyCommunicator/ClientApp/public/locales/en-US/translation.json +++ b/Source/CompanyCommunicator/ClientApp/public/locales/en-US/translation.json @@ -21,6 +21,7 @@ "SyncingRecipients": "Syncing recipients...", "InstallingApp": "Installing app...", "SendingMessages": "Sending... {{SentCount}} of {{TotalCount}}", + "Canceling": "Canceling...", "ImageURL": "Image URL", "Summary": "Summary", "Author": "Author", @@ -63,11 +64,13 @@ "Success": "Success : {{SuccessCount}}", "Failure": "Failure : {{FailureCount}}", "Unknown": "Unknown : {{UnknownCount}}", + "Canceled": "Canceled : {{CanceledCount}}", "Throttled": "Throttled : ", "TooltipSuccess": "Success", "TooltipFailure": "Failure", "TooltipUnknown": "Unknown", "TooltipThrottled": "Throttled", + "TooltipCanceled": "Canceled", "Errors": "Errors", "Warnings": "Warnings", "SignInPromptMessage": "Please sign in to continue.", diff --git a/Source/CompanyCommunicator/ClientApp/src/apis/messageListApi.ts b/Source/CompanyCommunicator/ClientApp/src/apis/messageListApi.ts index af1f55a2c..284ace1a9 100644 --- a/Source/CompanyCommunicator/ClientApp/src/apis/messageListApi.ts +++ b/Source/CompanyCommunicator/ClientApp/src/apis/messageListApi.ts @@ -77,6 +77,11 @@ export const getTeams = async (): Promise => { return await axios.get(url); } +export const cancelSentNotification = async (id: number): Promise => { + let url = baseAxiosUrl + "/sentnotifications/cancel/" + id; + return await axios.post(url); +} + export const getConsentSummaries = async (id: number): Promise => { let url = baseAxiosUrl + "/draftnotifications/consentSummaries/" + id; return await axios.get(url); diff --git a/Source/CompanyCommunicator/ClientApp/src/components/Messages/messages.tsx b/Source/CompanyCommunicator/ClientApp/src/components/Messages/messages.tsx index 8b6112013..f3b0bf1ec 100644 --- a/Source/CompanyCommunicator/ClientApp/src/components/Messages/messages.tsx +++ b/Source/CompanyCommunicator/ClientApp/src/components/Messages/messages.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { connect } from 'react-redux'; import { withTranslation, WithTranslation } from "react-i18next"; import { TooltipHost } from 'office-ui-fabric-react'; -import { Loader, List, Flex, Text, AcceptIcon, CloseIcon, ExclamationCircleIcon } from '@fluentui/react-northstar'; +import { Loader, List, Flex, Text, AcceptIcon, CloseIcon, ExclamationCircleIcon, ExclamationTriangleIcon } from '@fluentui/react-northstar'; import * as microsoftTeams from "@microsoft/teams-js"; import { selectMessage, getMessagesList, getDraftMessagesList } from '../../actions'; @@ -180,6 +180,10 @@ class Messages extends React.Component { text = this.localize("SendingMessages", { "SentCount": formatNumber(sentCount), "TotalCount": formatNumber(message.totalMessageCount) }); break; + case "Canceling": + text = this.localize("Canceling"); + break; + case "Canceled": case "Sent": case "Failed": text = ""; @@ -211,10 +215,17 @@ class Messages extends React.Component { {formatNumber(message.failed)} + { + message.canceled && + + + {formatNumber(message.canceled)} + + } { message.unknown && - + {formatNumber(message.unknown)} } diff --git a/Source/CompanyCommunicator/ClientApp/src/components/OverFlow/sentMessageOverflow.tsx b/Source/CompanyCommunicator/ClientApp/src/components/OverFlow/sentMessageOverflow.tsx index 1e5737a9b..e868ea84a 100644 --- a/Source/CompanyCommunicator/ClientApp/src/components/OverFlow/sentMessageOverflow.tsx +++ b/Source/CompanyCommunicator/ClientApp/src/components/OverFlow/sentMessageOverflow.tsx @@ -7,7 +7,7 @@ import { withTranslation, WithTranslation } from "react-i18next"; import { Menu, MoreIcon } from '@fluentui/react-northstar'; import { getBaseUrl } from '../../configVariables'; import * as microsoftTeams from "@microsoft/teams-js"; -import { duplicateDraftNotification } from '../../apis/messageListApi'; +import { duplicateDraftNotification, cancelSentNotification } from '../../apis/messageListApi'; import { selectMessage, getMessagesList, getDraftMessagesList } from '../../actions'; import { TFunction } from "i18next"; @@ -49,6 +49,12 @@ class Overflow extends React.Component { } public render(): JSX.Element { + let shouldNotShowCancel; + if (this.props.message != undefined && this.props.message.status != undefined) { + const status = this.props.message.status.toUpperCase(); + shouldNotShowCancel = status === "SENT" || status === "UNKNOWN" || status === "FAILED" || status === "CANCELED" || status === "CANCELING"; + } + const items = [ { key: 'more', @@ -83,6 +89,20 @@ class Overflow extends React.Component { }); } }, + { + key: 'cancel', + content: this.localize("Cancel"), + hidden: shouldNotShowCancel, + onClick: (event: any) => { + event.stopPropagation(); + this.setState({ + menuOpen: false, + }); + this.cancelSentMessage(this.props.message.id).then(() => { + this.props.getMessagesList(); + }); + } + }, ], }, onMenuOpenChange: (e: any, { menuOpen }: any) => { @@ -116,6 +136,14 @@ class Overflow extends React.Component { return error; } } + + private cancelSentMessage = async (id: number) => { + try { + await cancelSentNotification(id); + } catch (error) { + return error; + } + } } const mapStateToProps = (state: any) => { diff --git a/Source/CompanyCommunicator/ClientApp/src/components/StatusTaskModule/statusTaskModule.tsx b/Source/CompanyCommunicator/ClientApp/src/components/StatusTaskModule/statusTaskModule.tsx index 537cae672..acdcd9f0c 100644 --- a/Source/CompanyCommunicator/ClientApp/src/components/StatusTaskModule/statusTaskModule.tsx +++ b/Source/CompanyCommunicator/ClientApp/src/components/StatusTaskModule/statusTaskModule.tsx @@ -32,6 +32,7 @@ export interface IMessage { succeeded?: string; failed?: string; unknown?: string; + canceled?: string; sentDate?: string; imageLink?: string; summary?: string; @@ -126,6 +127,7 @@ class StatusTaskModule extends React.Component{this.localize("Success", { "SuccessCount": this.state.message.succeeded })}
-
+ {this.state.message.canceled && + <> +
+ + + } {this.state.message.unknown && <> +
} diff --git a/Source/CompanyCommunicator/Controllers/SentNotificationsController.cs b/Source/CompanyCommunicator/Controllers/SentNotificationsController.cs index 745203889..704cb75ad 100644 --- a/Source/CompanyCommunicator/Controllers/SentNotificationsController.cs +++ b/Source/CompanyCommunicator/Controllers/SentNotificationsController.cs @@ -8,10 +8,13 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Controllers using System; using System.Collections.Generic; using System.Linq; + using System.Net.Http; using System.Security.Claims; + using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.WebJobs.Extensions.DurableTask; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Graph; @@ -27,6 +30,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Controllers using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGraph; using Microsoft.Teams.Apps.CompanyCommunicator.Controllers.Options; using Microsoft.Teams.Apps.CompanyCommunicator.Models; + using Newtonsoft.Json; /// /// Controller for the sent notification data. @@ -46,6 +50,7 @@ public class SentNotificationsController : ControllerBase private readonly IAppCatalogService appCatalogService; private readonly IAppSettingsService appSettingsService; private readonly UserAppOptions userAppOptions; + private readonly IHttpClientFactory clientFactory; private readonly ILogger logger; /// @@ -62,6 +67,7 @@ public class SentNotificationsController : ControllerBase /// App catalog service. /// App settings service. /// User app options. + /// the http client factory. /// The logger factory. public SentNotificationsController( INotificationDataRepository notificationDataRepository, @@ -75,6 +81,7 @@ public SentNotificationsController( IAppCatalogService appCatalogService, IAppSettingsService appSettingsService, IOptions userAppOptions, + IHttpClientFactory clientFactory, ILoggerFactory loggerFactory) { if (dataQueueMessageOptions is null) @@ -93,6 +100,7 @@ public SentNotificationsController( this.appCatalogService = appCatalogService ?? throw new ArgumentNullException(nameof(appCatalogService)); this.appSettingsService = appSettingsService ?? throw new ArgumentNullException(nameof(appSettingsService)); this.userAppOptions = userAppOptions?.Value ?? throw new ArgumentNullException(nameof(userAppOptions)); + this.clientFactory = clientFactory ?? throw new ArgumentNullException(nameof(clientFactory)); this.logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -168,6 +176,7 @@ public async Task> GetSentNotificationsAsyn Succeeded = notificationEntity.Succeeded, Failed = notificationEntity.Failed, Unknown = this.GetUnknownCount(notificationEntity), + Canceled = notificationEntity.Canceled > 0 ? notificationEntity.Canceled : (int?)null, TotalMessageCount = notificationEntity.TotalMessageCount, SendingStartedDate = notificationEntity.SendingStartedDate, Status = notificationEntity.GetStatus(), @@ -222,6 +231,7 @@ public async Task GetSentNotificationByIdAsync(string id) Succeeded = notificationEntity.Succeeded, Failed = notificationEntity.Failed, Unknown = this.GetUnknownCount(notificationEntity), + Canceled = notificationEntity.Canceled > 0 ? notificationEntity.Canceled : (int?)null, TeamNames = await this.teamDataRepository.GetTeamNamesByIdsAsync(notificationEntity.Teams), RosterNames = await this.teamDataRepository.GetTeamNamesByIdsAsync(notificationEntity.Rosters), GroupNames = groupNames, @@ -236,6 +246,53 @@ public async Task GetSentNotificationByIdAsync(string id) return this.Ok(result); } + /// + /// Cancel the sent notification by id. + /// + /// notification id. + /// The result of an action method. + [HttpPost("cancel/{id}")] + public async Task CancelSentNotificationByIdAsync(string id) + { + _ = id ?? throw new ArgumentNullException(nameof(id)); + + var notificationDataEntity = await this.notificationDataRepository.GetAsync( + NotificationDataTableNames.SentNotificationsPartition, + id); + if (notificationDataEntity == null) + { + return this.NotFound(); + } + + var instancePayload = JsonConvert.DeserializeObject(notificationDataEntity.FunctionInstancePayload); + var client = this.clientFactory.CreateClient(); + var httpContent = new StringContent(string.Empty, Encoding.UTF8, "application/json"); + + // Update the reason of termination. + var terminateUri = instancePayload.TerminatePostUri.Replace("{text}", "Canceled"); + var response = await client.PostAsync(terminateUri, httpContent); + if (response.StatusCode == System.Net.HttpStatusCode.Accepted || + response.StatusCode == System.Net.HttpStatusCode.Gone) + { + if (!notificationDataEntity.IsCompleted()) + { + notificationDataEntity.Status = NotificationStatus.Canceling.ToString(); + await this.notificationDataRepository.InsertOrMergeAsync(notificationDataEntity); + + // send message to data queue + var messageDelay = new TimeSpan(0, 0, 5); + await this.dataQueue.SendMessageAsync(id, messageDelay); + return this.Accepted(); + } + } + else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return this.NotFound(); + } + + return this.Ok(); + } + private int? GetUnknownCount(NotificationDataEntity notificationEntity) { var unknown = notificationEntity.Unknown; diff --git a/Source/CompanyCommunicator/Microsoft.Teams.Apps.CompanyCommunicator.csproj b/Source/CompanyCommunicator/Microsoft.Teams.Apps.CompanyCommunicator.csproj index 9ea0234dc..7f7315010 100644 --- a/Source/CompanyCommunicator/Microsoft.Teams.Apps.CompanyCommunicator.csproj +++ b/Source/CompanyCommunicator/Microsoft.Teams.Apps.CompanyCommunicator.csproj @@ -17,6 +17,7 @@ + diff --git a/Source/CompanyCommunicator/Models/SentNotification.cs b/Source/CompanyCommunicator/Models/SentNotification.cs index c90b26fea..8e6690b67 100644 --- a/Source/CompanyCommunicator/Models/SentNotification.cs +++ b/Source/CompanyCommunicator/Models/SentNotification.cs @@ -38,6 +38,11 @@ public class SentNotification : BaseNotification /// public int? Unknown { get; set; } + /// + /// Gets or sets the number of recipients whose delivery status is canceled. + /// + public int? Canceled { get; set; } + /// /// Gets or sets Teams audience name collection. /// diff --git a/Source/CompanyCommunicator/Models/SentNotificationSummary.cs b/Source/CompanyCommunicator/Models/SentNotificationSummary.cs index 4c5fd2e50..ba65c49c6 100644 --- a/Source/CompanyCommunicator/Models/SentNotificationSummary.cs +++ b/Source/CompanyCommunicator/Models/SentNotificationSummary.cs @@ -48,6 +48,11 @@ public class SentNotificationSummary /// public int? Unknown { get; set; } + /// + /// Gets or sets the number of recipients whose delivery status is canceled. + /// + public int? Canceled { get; set; } + /// /// Gets or sets the total number of messages to be sent. /// diff --git a/Source/Test/CompanyCommunicator.Common.Test/Extensions/StringExtensionsTest.cs b/Source/Test/CompanyCommunicator.Common.Test/Extensions/StringExtensionsTest.cs new file mode 100644 index 000000000..d91cf001b --- /dev/null +++ b/Source/Test/CompanyCommunicator.Common.Test/Extensions/StringExtensionsTest.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.App.CompanyCommunicator.Common.Test.Extensions +{ + using System; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions; + using Xunit; + + /// + /// String extension tests. + /// + public class StringExtensionsTest + { + /// + /// Test case to check if new string is appended to original string. + /// + [Fact] + public void Check_AppendNewLine_ShouldBeSuccess() + { + string original = "foo"; + string newString = "bar"; + var actualString = $"{original}{Environment.NewLine}{newString}"; + var expectedString = original.AppendNewLine(newString); + + Assert.Equal(expectedString, actualString); + } + + /// + /// Test case to check if original string is returned if empty string is tried to be appended. + /// + [Fact] + public void AddEmptyString_AppendNewLine_ShouldBeSuccess() + { + string original = "foo"; + string newString = string.Empty; + var expectedString = original.AppendNewLine(newString); + + Assert.Equal(expectedString, original); + } + + /// + /// Test case to check if new string is returned in case original string is empty. + /// + [Fact] + public void AddOnEmptyString_AppendNewLine_ShouldBeSuccess() + { + string original = string.Empty; + string newString = "bar"; + var expectedString = original.AppendNewLine(newString); + + Assert.Equal(expectedString, newString); + } + } +} diff --git a/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs b/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs index 5ae1a6eaa..e6fabff7f 100644 --- a/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs +++ b/Source/Test/CompanyCommunicator.Common.Test/Repositories/NotificationData/NotificationDataRepositoryTests.cs @@ -49,13 +49,13 @@ public static IEnumerable SaveMessageTestCasesData }, new SaveMessageTestData { - InitialMessage = new string('x', NotificationDataRepository.MaxMessageLengthToSave - 1), + InitialMessage = new string('x', BaseRepository.MaxMessageLengthToSave - 1), ShouldUpdateMessage = false, FailMessage = "Should not update message that will exceed max length.", }, new SaveMessageTestData { - InitialMessage = new string('x', NotificationDataRepository.MaxMessageLengthToSave), + InitialMessage = new string('x', BaseRepository.MaxMessageLengthToSave), ShouldUpdateMessage = false, FailMessage = "Should not update message that is already at max length.", }, diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Activities/UploadActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Activities/UploadActivityTest.cs index 3726fa733..5039d0f79 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Activities/UploadActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Activities/UploadActivityTest.cs @@ -130,13 +130,13 @@ public async Task NotificationWithTeams_GetTeamDataStreamAsync_ShouldInvodeOnce( string fileNameMessage = "FileName_Message_Delivery"; var fileNameMessageString = new LocalizedString(fileNameMessage, fileNameMessage); this.localizer.Setup(_ => _[fileNameMessageString]).Returns(metaDataFileName); - this.userDataStream.Setup(x => x.GetTeamDataStreamAsync(It.IsAny())).Returns(teamDatalist.ToAsyncEnumerable); + this.userDataStream.Setup(x => x.GetTeamDataStreamAsync(It.IsAny(), It.IsAny())).Returns(teamDatalist.ToAsyncEnumerable); // Act await activityInstance.UploadActivityAsync((notificationData, metaData, this.fileName)); // Assert - this.userDataStream.Verify(x => x.GetTeamDataStreamAsync(It.IsAny()), Times.Once); + this.userDataStream.Verify(x => x.GetTeamDataStreamAsync(It.IsAny(), It.IsAny()), Times.Once); } /// @@ -164,13 +164,13 @@ public async Task NotificationWithNoTeams_GetUserDataStreamAsync_ShouldInvodeOnc string fileNameMessage = "FileName_Message_Delivery"; var fileNameMessageString = new LocalizedString(fileNameMessage, fileNameMessage); this.localizer.Setup(_ => _[fileNameMessageString]).Returns(metaDataFileName); - this.userDataStream.Setup(x => x.GetUserDataStreamAsync(It.IsAny())).Returns(userDatalist.ToAsyncEnumerable); + this.userDataStream.Setup(x => x.GetUserDataStreamAsync(It.IsAny(), It.IsAny())).Returns(userDatalist.ToAsyncEnumerable); // Act await activityInstance.UploadActivityAsync((notificationData, metaData, this.fileName)); // Assert - this.userDataStream.Verify(x => x.GetUserDataStreamAsync(It.IsAny()), Times.Once); + this.userDataStream.Verify(x => x.GetUserDataStreamAsync(It.IsAny(), It.IsAny()), Times.Once); } private static Mock GetBlobContainerClientMock() diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs index a5e4f6987..aab592a04 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs @@ -37,13 +37,14 @@ public class DataStreamFacadeTest private readonly Mock usersService = new Mock(); private readonly Mock> localizer = new Mock>(); private readonly string notificationId = "notificationId"; + private readonly string notificationStatus = "status"; private readonly IEnumerable> sentNotificationDataList = new List>() { new List() { new SentNotificationDataEntity() { - ConversationId = "conversationId", DeliveryStatus = "Succeeded", RowKey = "RowKey", ErrorMessage = string.Empty, + ConversationId = "conversationId", DeliveryStatus = "Succeeded", RowKey = "RowKey", ErrorMessage = string.Empty, Exception = "error", }, }, }; @@ -110,7 +111,7 @@ public void CreateInstance_NullParamters_ThrowsArgumentNullException() } /// - /// Test case to check if method handles null paramaters. + /// Test case to check if method handles null parameters. /// /// A task that represents the work queued to execute. [Fact] @@ -120,14 +121,16 @@ public async Task GetUserData_NullParameter_ThrowsAgrumentNullException() var activityInstance = this.GetDataStreamFacadeInstance(); // Act - Func task = async () => await activityInstance.GetTeamDataStreamAsync(null /*notificationId*/).ForEachAsync(x => x.ToList()); + Func task = async () => await activityInstance.GetTeamDataStreamAsync(null /*notificationId*/, "foo" /*notificationStatus*/).ForEachAsync(x => x.ToList()); + Func task1 = async () => await activityInstance.GetTeamDataStreamAsync("123" /*notificationId*/, null /*notificationStatus*/).ForEachAsync(x => x.ToList()); // Assert await task.Should().ThrowAsync("notificationId is null"); + await task1.Should().ThrowAsync("notificationStatus is null"); } /// - /// Test case to check if GetBatchByUserIds method is called atleast once based on GetStreamsService Response. + /// Test case to check if GetBatchByUserIds method is called at-least once based on GetStreamsService Response. /// /// A task that represents the work queued to execute. [Fact] @@ -149,7 +152,7 @@ public async Task Get_BatchByUserIdsSevice_ShouldInvokeAtleastOnce() .ReturnsAsync(userData); // Act - var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId); + var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus); await userDataStream.ForEachAsync(x => x.ToList()); @@ -177,7 +180,7 @@ public async Task Get_BatchByUserIdsSevice_ShouldNeverBeInvokedForEmptysentNotif .ReturnsAsync(userData); // Act - var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId); + var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus); await userDataStream.ForEachAsync(x => x.ToList()); @@ -201,7 +204,7 @@ public async Task GetUserData_RecipientNotFound_ShouldNeverInvokeBatchByUserIds( .Returns(this.sentNotificationDataWithRecipientNotFound.ToAsyncEnumerable()); // Act - var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId); + var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus); await userDataStream.ForEachAsync(x => x.ToList()); @@ -247,7 +250,7 @@ public async Task GetUsersData_CorrectMapping_ReturnsUserDataObject() this.localizer.Setup(_ => _[userType]).Returns(userTypeString); // Act - var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var userData = userDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); var user = userDataList.FirstOrDefault(user => user != null && user.Id.Equals(sendNotificationData.RowKey)); @@ -258,6 +261,7 @@ public async Task GetUsersData_CorrectMapping_ReturnsUserDataObject() Assert.Equal(userData.UserType, userTypeString.Value); Assert.Equal(userData.DeliveryStatus, deliveryStatus.Value); Assert.Equal(userData.StatusReason, $"{sendNotificationData.StatusCode} : {result.Value}"); + Assert.Equal(userData.Error, sendNotificationData.Exception); } /// @@ -292,7 +296,7 @@ public async Task Get_ForbiddenGraphPermission_ReturnsAdminConsentError() this.localizer.Setup(_ => _[adminConsentError]).Returns(localizedString); // Act - var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var userData = userDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -323,7 +327,7 @@ public async Task Get_UserDeletedFromTenant_ReturnsEmptyRecord() .ReturnsAsync(userDataList); // Act - var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var userData = userDataStream.FirstOrDefault().FirstOrDefault(); // Assert @@ -357,7 +361,7 @@ public async Task Get_UserResponseNullFromGraph_ReturnsEmptyRecord() this.localizer.Setup(_ => _[userType]).Returns(localizedString); // Act - var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var userData = userDataStream.FirstOrDefault().FirstOrDefault(); // Assert @@ -392,7 +396,7 @@ public async Task Get_UserStatusReason_withErrorStatus() var result = rootMessage.Error.Message; // Act - var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var userData = userDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -419,7 +423,7 @@ public async Task Get_TeamDataSevice_ShouldInvokeAtleastOnce() this.teamDataRepository.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(teamData); // Act - var teamDataStream = activityInstance.GetTeamDataStreamAsync(this.notificationId); + var teamDataStream = activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus); await teamDataStream.ForEachAsync(x => x.ToList()); // Assert @@ -443,7 +447,7 @@ public async Task Get_TeamDataSevice_ShouldNeverBeInvokedForEmptysentNotificatio this.teamDataRepository.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(teamData); // Act - var teamDataStream = activityInstance.GetTeamDataStreamAsync(this.notificationId); + var teamDataStream = activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus); await teamDataStream.ForEachAsync(x => x.ToList()); // Assert @@ -475,7 +479,7 @@ public async Task GetTeamData_CorrectMapping_ReturnsTeamDataObject() this.teamDataRepository.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(teamDataEntity); // Act - var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId).ToListAsync(); + var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var teamData = teamDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -483,6 +487,7 @@ public async Task GetTeamData_CorrectMapping_ReturnsTeamDataObject() Assert.Equal(teamData.Name, teamDataEntity.Name); Assert.Equal(teamData.DeliveryStatus, deliveryStatus.Value); Assert.Equal(teamData.StatusReason, $"{sendNotificationData.StatusCode} : {result.Value}"); + Assert.Equal(teamData.Error, sendNotificationData.Exception); } /// @@ -507,7 +512,7 @@ public async Task Get_TeamDeliveryStatus_SucceededFromNotificationData() this.teamDataRepository.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(teamDataEntity); // Act - var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId).ToListAsync(); + var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var teamData = teamDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -536,7 +541,7 @@ public async Task Get_TeamStatusReason_ReturnsErrorWithStatusReasonFromNotificat var result = rootMessage.Error.Message; // Act - var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId).ToListAsync(); + var teamDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var teamData = teamDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -561,7 +566,7 @@ public async Task Get_NullFromDownStream_ReturnsNullForTeamName() this.teamDataRepository.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(default(TeamDataEntity))); // Act - var userDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId).ToListAsync(); + var userDataStream = await activityInstance.GetTeamDataStreamAsync(this.notificationId, this.notificationStatus).ToListAsync(); var teamData = userDataStream.Select(x => x.Where(y => y.Id == "RowKey").FirstOrDefault()).FirstOrDefault(); // Assert @@ -587,7 +592,7 @@ public async Task Get_CallBatchByUserIdsSevice_ThrowsServiceException() .ThrowsAsync(serviceException); // Act - var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId); + var userDataStream = activityInstance.GetUserDataStreamAsync(this.notificationId, this.notificationStatus); Func task = async () => await userDataStream.ForEachAsync(x => x.ToList()); // Assert diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/DataAggregationTriggerActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/DataAggregationTriggerActivityTest.cs index c5880620e..aa6a33369 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/DataAggregationTriggerActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/DataAggregationTriggerActivityTest.cs @@ -27,7 +27,7 @@ public class DataAggregationTriggerActivityTest private readonly int messageDelayInSeconds = 20; /// - /// Consturctor tests. + /// Constructor tests. /// [Fact] public void DataAggregationTriggerActivityConstructorTest() @@ -46,7 +46,7 @@ public void DataAggregationTriggerActivityConstructorTest() } /// - /// Test to check update notificatin and send message to data queue. + /// Test to check update notification and send message to data queue. /// /// A representing the asynchronous operation. [Fact] @@ -68,8 +68,9 @@ public async Task DataAggregationTriggerActivitySuccessTest() .Setup(x => x.CreateOrUpdateAsync(It.IsAny())) .Returns(Task.CompletedTask); this.dataQueue - .Setup(x => x.SendDelayedAsync(It.IsAny(), It.IsAny())) + .Setup(x => x.SendMessageAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); + var messageDelayInTimeSpan = new TimeSpan(0, 0, this.messageDelayInSeconds); // Act Func task = async () => await dataAggregationTriggerActivity.RunAsync((notificationId, recipientCount), logger.Object); @@ -78,7 +79,7 @@ public async Task DataAggregationTriggerActivitySuccessTest() await task.Should().NotThrowAsync(); this.notificationDataRepository.Verify(x => x.GetAsync(It.IsAny(), It.Is(x => x.Equals(notificationId))), Times.Once()); this.notificationDataRepository.Verify(x => x.CreateOrUpdateAsync(It.Is(x => x.TotalMessageCount == recipientCount))); - this.dataQueue.Verify(x => x.SendDelayedAsync(It.Is(x => x.NotificationId == notificationId), It.Is(x => x.Equals(this.messageDelayInSeconds)))); + this.dataQueue.Verify(x => x.SendMessageAsync(It.Is(x => x.Equals(notificationId)), It.Is(x => x.Equals(messageDelayInTimeSpan)))); } /// @@ -102,8 +103,9 @@ public async Task DataAggregationTriggerActivityNotificationDataNotFound() .Setup(x => x.CreateOrUpdateAsync(It.IsAny())) .Returns(Task.CompletedTask); this.dataQueue - .Setup(x => x.SendDelayedAsync(It.IsAny(), It.IsAny())) + .Setup(x => x.SendMessageAsync(It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); + var messageDelayInTimeSpan = new TimeSpan(0, 0, this.messageDelayInSeconds); // Act Func task = async () => await dataAggregationTriggerActivity.RunAsync((notificationId, recipientCount), logger.Object); @@ -112,7 +114,7 @@ public async Task DataAggregationTriggerActivityNotificationDataNotFound() await task.Should().NotThrowAsync(); this.notificationDataRepository.Verify(x => x.GetAsync(It.IsAny(), It.Is(x => x.Equals(notificationId))), Times.Once()); this.notificationDataRepository.Verify(x => x.CreateOrUpdateAsync(It.Is(x => x.TotalMessageCount == recipientCount)), Times.Never()); - this.dataQueue.Verify(x => x.SendDelayedAsync(It.Is(x => x.NotificationId == notificationId), It.Is(x => x.Equals(this.messageDelayInSeconds)))); + this.dataQueue.Verify(x => x.SendMessageAsync(It.Is(x => x.Equals(notificationId)), It.Is(x => x.Equals(messageDelayInTimeSpan)))); } /// diff --git a/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs b/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs index 0501f0b84..e0784c6f3 100644 --- a/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs +++ b/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs @@ -75,7 +75,7 @@ public async Task PendingSendNotificationTest() string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}"; this.notificationService.Setup(x => x.IsPendingNotification(It.IsAny())).ReturnsAsync(false); // Notification is pending this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act @@ -101,7 +101,7 @@ public async Task SendNotificationWhenNoConversationIdTest() .Setup(x => x.IsPendingNotification(It.IsAny())) .ReturnsAsync(true); this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act @@ -109,7 +109,7 @@ public async Task SendNotificationWhenNoConversationIdTest() // Assert await task.Should().NotThrowAsync(); - this.notificationService.Verify(x => x.UpdateSentNotification(It.Is(x => x.Equals("notificationId")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.notificationService.Verify(x => x.UpdateSentNotification(It.Is(x => x.Equals("notificationId")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } /// @@ -129,7 +129,7 @@ public async Task SendFunc_GuestUser_ShouldNotSendMessage() // Assert await task.Should().NotThrowAsync(); - this.notificationService.Verify(x => x.UpdateSentNotification(It.Is(x => x.Equals("notificationId")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + this.notificationService.Verify(x => x.UpdateSentNotification(It.Is(x => x.Equals("notificationId")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } /// @@ -167,7 +167,7 @@ public async Task Re_QueueSendNotificationWithDelayTest() .Setup(x => x.IsPendingNotification(It.IsAny())) .ReturnsAsync(true); this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // mocking throttled. @@ -201,7 +201,7 @@ public async Task SendNotificationSuccess_Test() .Setup(x => x.IsPendingNotification(It.IsAny())) .ReturnsAsync(true); this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); this.notificationService.Setup(x => x.IsSendNotificationThrottled()).ReturnsAsync(false); @@ -212,7 +212,7 @@ public async Task SendNotificationSuccess_Test() var notificatioData = new SendingNotificationDataEntity() { Content = "{\"text\":\"Welcome\",\"displayText\":\"Hello\"}" }; this.notificationRepo.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(notificatioData); this.messageService.Setup(x => x.SendMessageAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), this.logger.Object)).ReturnsAsync(sendMessageResponse); - this.notificationService.Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + this.notificationService.Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); // Act Func task = async () => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext()); @@ -241,7 +241,7 @@ public async Task SendNotificationResponseThrottledTest() .Setup(x => x.IsPendingNotification(It.IsAny())) .ReturnsAsync(true); this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); this.notificationService @@ -254,7 +254,7 @@ public async Task SendNotificationResponseThrottledTest() var notificatioData = new SendingNotificationDataEntity() { Content = "{\"text\":\"Welcome\",\"displayText\":\"Hello\"}" }; this.notificationRepo.Setup(x => x.GetAsync(It.IsAny(), It.IsAny())).ReturnsAsync(notificatioData); this.messageService.Setup(x => x.SendMessageAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), this.logger.Object)).ReturnsAsync(sendMessageResponse); - this.notificationService.Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + this.notificationService.Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); // Act Func task = async () => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext()); @@ -283,7 +283,7 @@ public async Task SendNotificationException_Test() .Setup(x => x.IsPendingNotification(It.IsAny())) .ReturnsAsync(true); this.notificationService - .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.UpdateSentNotification(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.CompletedTask); // Act diff --git a/Source/Test/CompanyCommunicator.Send.Func.Test/Services/NotificationServiceTest.cs b/Source/Test/CompanyCommunicator.Send.Func.Test/Services/NotificationServiceTest.cs index 044545b82..f363b45a3 100644 --- a/Source/Test/CompanyCommunicator.Send.Func.Test/Services/NotificationServiceTest.cs +++ b/Source/Test/CompanyCommunicator.Send.Func.Test/Services/NotificationServiceTest.cs @@ -22,6 +22,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Send.Func.Test public class NotificationServiceTest { private readonly Mock sentNotificationDataRepository = new Mock(); + private readonly Mock notificationDataReposioty = new Mock(); private readonly Mock globalSendingNotificationDataRepository = new Mock(); private readonly SendQueueMessageContent sendQueueMessageContent = new SendQueueMessageContent() { @@ -44,13 +45,15 @@ public class NotificationServiceTest public void NotificationServiceConstructorTest() { // Arrange - Action action1 = () => new NotificationService(null /*globalSendingNotificationDataRepository*/, this.sentNotificationDataRepository.Object); - Action action2 = () => new NotificationService(this.globalSendingNotificationDataRepository.Object, null /*sentNotificationDataRepository*/); - Action action3 = () => new NotificationService(this.globalSendingNotificationDataRepository.Object, this.sentNotificationDataRepository.Object); + Action action1 = () => new NotificationService(null /*globalSendingNotificationDataRepository*/, this.sentNotificationDataRepository.Object, this.notificationDataReposioty.Object); + Action action2 = () => new NotificationService(this.globalSendingNotificationDataRepository.Object, null /*sentNotificationDataRepository*/, this.notificationDataReposioty.Object); + Action action3 = () => new NotificationService(this.globalSendingNotificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.notificationDataReposioty.Object); + Action action4 = () => new NotificationService(this.globalSendingNotificationDataRepository.Object, this.sentNotificationDataRepository.Object, null /*notificationDataReposioty*/); // Act and Assert. action1.Should().Throw("globalSendingNotificationDataRepository is null."); action2.Should().Throw("sentNotificationDataRepository is null."); + action4.Should().Throw("notificationDataRepository is null."); action3.Should().NotThrow(); } @@ -364,7 +367,7 @@ public async Task UpdateSentNotification_Status_Failed_Test() /// private NotificationService GetNotificationService() { - return new NotificationService(this.globalSendingNotificationDataRepository.Object, this.sentNotificationDataRepository.Object); + return new NotificationService(this.globalSendingNotificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.notificationDataReposioty.Object); } } } diff --git a/Source/Test/CompanyCommunicator.Test/Controllers/SentNotificationsControllerTest.cs b/Source/Test/CompanyCommunicator.Test/Controllers/SentNotificationsControllerTest.cs index b41a8d5b8..30e70c5bd 100644 --- a/Source/Test/CompanyCommunicator.Test/Controllers/SentNotificationsControllerTest.cs +++ b/Source/Test/CompanyCommunicator.Test/Controllers/SentNotificationsControllerTest.cs @@ -9,6 +9,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Test.Controllers using System.Collections.Generic; using System.Linq; using System.Net; + using System.Net.Http; using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -48,6 +49,7 @@ public class SentNotificationsControllerTest private readonly Mock appCatalogService = new Mock(); private readonly Mock appSettingsService = new Mock(); private readonly Mock> userAppOptions = new Mock>(); + private readonly Mock httpClientFactory = new Mock(); private readonly Mock loggerFactory = new Mock(); /// @@ -72,18 +74,19 @@ public void CreateInstance_NullParameter_ThrowsArgumentNullException() // Arrange this.dataQueueMessageOptions.Setup(x => x.Value).Returns(new DataQueueMessageOptions() { ForceCompleteMessageDelayInSeconds = 100 }); this.userAppOptions.Setup(x => x.Value).Returns(new UserAppOptions() { ProactivelyInstallUserApp = false }); - Action action1 = () => new SentNotificationsController(null /*notificationDataRepository*/, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action2 = () => new SentNotificationsController(this.notificationDataRepository.Object, null /*sentNotificationDataRepository*/, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action3 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, null/*teamDataRepository*/, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action4 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, null/*prepareToSendQueue*/, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action5 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, null/*dataQueue*/, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action6 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, null/*dataQueueMessageOptions*/, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action7 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, null/*groupsService*/, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action8 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, null/*exportDataRepository*/, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action9 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, null/*appCatalogServicet*/, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); - Action action10 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, null/*appSettingsService*/, this.userAppOptions.Object, this.loggerFactory.Object); - Action action11 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, null/*userAppOptions*/, this.loggerFactory.Object); - Action action12 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, null/*loggerFactory*/); + Action action1 = () => new SentNotificationsController(null /*notificationDataRepository*/, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action2 = () => new SentNotificationsController(this.notificationDataRepository.Object, null /*sentNotificationDataRepository*/, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action3 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, null/*teamDataRepository*/, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action4 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, null/*prepareToSendQueue*/, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action5 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, null/*dataQueue*/, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action6 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, null/*dataQueueMessageOptions*/, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action7 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, null/*groupsService*/, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action8 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, null/*exportDataRepository*/, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action9 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, null/*appCatalogServicet*/, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action10 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, null/*appSettingsService*/, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action11 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, null/*userAppOptions*/, this.httpClientFactory.Object, this.loggerFactory.Object); + Action action12 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, null/*loggerFactory*/); + Action action13 = () => new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, null /*httpClientFactory*/, this.loggerFactory.Object); // Act and Assert. action1.Should().Throw("notificationDataRepository is null."); @@ -98,6 +101,7 @@ public void CreateInstance_NullParameter_ThrowsArgumentNullException() action10.Should().Throw("appSettingsService is null."); action11.Should().Throw("userAppOptions is null."); action12.Should().Throw("authenticationOptions is null."); + action13.Should().Throw("clientFactory is null."); } /// @@ -302,6 +306,7 @@ public async Task GetSummary_CorrectMapping_ReturnsNotificationSummaryListObject Assert.Equal(notification.SendingStartedDate, sentNotificationSummary.SendingStartedDate); Assert.Equal(notification.Status, sentNotificationSummary.Status); Assert.Equal(notification.Unknown, sentNotificationSummary.Unknown); + Assert.Equal(notification.Canceled, sentNotificationSummary.Canceled); } /// @@ -341,7 +346,7 @@ public async Task GetNotication_ForInvalidId_ReturnsNotFoundResult() } /// - /// Test case to pass valid parameter gives sataus code 200. + /// Test case to pass valid parameter gives status code 200. /// /// A task that represents the work queued to execute. [Fact] @@ -412,7 +417,7 @@ private SentNotificationsController GetControllerInstance(bool proactivelyInstal this.userAppOptions.Setup(x => x.Value).Returns(new UserAppOptions() { ProactivelyInstallUserApp = proactivelyInstallUserApp, UserAppExternalId = "externalId" }); Mock> log = new Mock>(); this.loggerFactory.Setup(x => x.CreateLogger("SentNotificationsController")).Returns(log.Object); - var controller = new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.loggerFactory.Object); + var controller = new SentNotificationsController(this.notificationDataRepository.Object, this.sentNotificationDataRepository.Object, this.teamDataRepository.Object, this.prepareToSendQueue.Object, this.dataQueue.Object, this.dataQueueMessageOptions.Object, this.groupsService.Object, this.exportDataRepository.Object, this.appCatalogService.Object, this.appSettingsService.Object, this.userAppOptions.Object, this.httpClientFactory.Object, this.loggerFactory.Object); var user = new ClaimsPrincipal(new ClaimsIdentity( new Claim[] { @@ -429,7 +434,7 @@ private NotificationDataEntity GetNotification() return new NotificationDataEntity() { Id = "id", - Title = "titile", + Title = "title", ImageLink = "imageLink", Summary = "summary", Author = "author", @@ -449,6 +454,7 @@ private NotificationDataEntity GetNotification() SendingStartedDate = DateTime.Now, Status = "success", Unknown = 1, + Canceled = 2, TeamsInString = "['item1','item2']", RostersInString = "['item1','item2']", GroupsInString = "['group1','group2']", diff --git a/Wiki/Cost-estimate.md b/Wiki/Cost-estimate.md index 0e169363e..5c65fd3ac 100644 --- a/Wiki/Cost-estimate.md +++ b/Wiki/Cost-estimate.md @@ -17,7 +17,7 @@ We ignore: ## SKU recommendations The recommended SKUs for a production environment are: -* App Service: Standard (S1) +* App Service: Standard (S2) * Service Bus: Basic ## Estimated load @@ -56,7 +56,7 @@ Min. execution time = 100 ms. **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 19 October 2020, for the West US 2 region. +Prices were taken from the [Azure Pricing Overview](https://azure.microsoft.com/en-us/pricing/) on 28 April 2022, for the West US 2 region. Use the [Azure Pricing Calculator](https://azure.com/e/c3bb51eeb3284a399ac2e9034883fcfa) to model different service tiers and usage patterns. @@ -64,12 +64,12 @@ Resource | Tier | Load --- | --- | --- | --- Storage account (Table) | Standard_LRS | < 1GB data, 45000 operations | $0.045 + $0.01 = $0.05 Bot Channels Registration | F0 | N/A | Free -App Service Plan | S1 | 744 hours | $74.40 +App Service Plan | S2 | 744 hours | $148.80 App Service (Bot + Tab) | - | | (charged to App Service Plan) Azure Function | Dedicated | 10000 executions | (free up to 1 million executions) Service Bus | Basic | 10000 operations | $0.05 Application Insights | - | < 5GB data | (free up to 5 GB) -**Total** | | | **$74.50** +**Total** | | | **$148.90** ## Estimated load - 1M messages @@ -108,7 +108,7 @@ Min. execution time = 100 ms. **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 19 October 2020, for the West US 2 region. +Prices were taken from the [Azure Pricing Overview](https://azure.microsoft.com/en-us/pricing/) on 28 April 2022, for the West US 2 region. Use the [Azure Pricing Calculator](https://azure.com/e/c3bb51eeb3284a399ac2e9034883fcfa) to model different service tiers and usage patterns. @@ -116,12 +116,12 @@ Resource | Tier | Load --- | --- | --- | --- Storage account (Table) | Standard_LRS | < 3GB data, 9M operations | $0.14 + $0.32 = $0.46 Bot Channels Registration | F0 | N/A | Free -App Service Plan | S1 | 744 hours | $74.40 +App Service Plan | S2 | 744 hours | $148.80 App Service (Bot + Tab) | - | | (charged to App Service Plan) Azure Function | Dedicated | 1M executions | (free up to 1 million executions) Service Bus | Basic | 2M operations | $0.10 Application Insights | - | < 5GB data | (free up to 5 GB) -**Total** | | | **$74.96** +**Total** | | | **$149.36** ## Estimated load - 2M messages @@ -159,7 +159,7 @@ Min. execution time = 100 ms. **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 19 October 2020, for the West US 2 region. +Prices were taken from the [Azure Pricing Overview](https://azure.microsoft.com/en-us/pricing/) on 28 April 2022, for the West US 2 region. Use the [Azure Pricing Calculator](https://azure.com/e/c3bb51eeb3284a399ac2e9034883fcfa) to model different service tiers and usage patterns. @@ -167,9 +167,9 @@ Resource | Tier | Load --- | --- | --- | --- Storage account (Table) | Standard_LRS | < 6GB data, 18M operations | $0.27 + $0.65 = $0.92 Bot Channels Registration | F0 | N/A | Free -App Service Plan | S1 | 744 hours | $74.40 +App Service Plan | S2 | 744 hours | $148.80 App Service (Bot + Tab) | - | | (charged to App Service Plan) Azure Function | Dedicated | 2M executions | $5.80 Service Bus | Basic | 2M operations | $0.20 Application Insights | - | < 5GB data | (free up to 5 GB) -**Total** | | | **$81.32** +**Total** | | | **$155.72** diff --git a/Wiki/FAQ.md b/Wiki/FAQ.md new file mode 100644 index 000000000..4d144fb89 --- /dev/null +++ b/Wiki/FAQ.md @@ -0,0 +1,25 @@ +# Known Limitations +## 1. Author/publishing experience is not supported on Mobile + +The tab where authors/creators of messages create a message is not supported on mobile. The recommended approach is to create the messages on the desktop only. + +# FAQs + +## 1. Are messages sent to guest users? +As of version 4.1.1, guest users are excluded from receiving messages. Note that they will still be able to view messages posted to a channel. + +> **IMPORTANT:** If you are using a version of Company Communicator **v4.1.1**, please update to the latest version, and see the guidance in [Excluding guest users from messages](https://github.com/OfficeDev/microsoft-teams-apps-company-communicator/wiki/Excluding-guest-users-from-messages). + +## 2. Does Company Communicator respond with a message to users who ask a question or reply to a message? +No, by default the bot only sends messages and does not respond with a message. The bot can be customized to reply with a custom message or connected to a knowledge base to respond with answers from the knowledge base. + +## 3. Is it mandatory to choose multi-tenant account types while app registration? +Yes. Bot Channels Registration only supports multi-tenant account types. Please choose multi-tenant type options only even if the app users belong to single-tenant only. Please refer [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration?view=azure-bot-service-4.0#manual-app-registration) for more information + +| Type | Description | +|--|--| +| Accounts in any organizational directory (Any Azure AD - Multitenant) | This option provides less exposure by restricting access and in case OAuth is not supported. | +| Accounts in any organizational directory (Any Azure AD - Multitenant) and personal Microsoft accounts (for example, Xbox, Outlook.com) | This option is well-suited to support OAuth and bot authentication. | + +## 4. How to clone the GitHub repository? +Please follow this [link](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) for detailed instructions on cloning GitHub repository to create a local copy on your computer and sync between the two locations. diff --git a/Wiki/Release-notes.md b/Wiki/Release-notes.md index 8ab0a09fc..8571c4ac0 100644 --- a/Wiki/Release-notes.md +++ b/Wiki/Release-notes.md @@ -6,7 +6,8 @@ Cumulative improvements in Company Communicator App. |Release |Published to
Microsoft Store | |---|---| -| 5.0 | Nov, 2021 +| 5.1 | April 28, 2022 +| 5.0 | Nov 10, 2021 | 4.1.5 | Sep 29, 2021 | 4.1.4 | Sep 14, 2021 | 4.1.3 | Jul 2, 2021 @@ -23,7 +24,13 @@ Cumulative improvements in Company Communicator App. ### Company Communicator feature release notes -#### 5.0 (Nov, 2021) +#### 5.1 (April 28, 2022) +##### Changes introduced +- Ability to cancel a notification. +- Export installation errors. +- Arm fixes. + +#### 5.0 (Nov 10, 2021) ##### Changes introduced - Added Key Vault and Managed Identity. - Support certificate authentication. diff --git a/Wiki/Troubleshooting.md b/Wiki/Troubleshooting.md index fe613aa1a..f79c97856 100644 --- a/Wiki/Troubleshooting.md +++ b/Wiki/Troubleshooting.md @@ -48,27 +48,105 @@ If you had to do this, you may not have received the **authorBotId**, **userBotI We are currently looking into how to make this process more resilient to intermittent failures. -## 2. Forgetting the botId or appDomain -If you forgot to copy your **authorBotId**, **userBotId** and **appDomain** values from the end of the deployment. You can find them in the "Configuration" section of your Web App. +## 2. App Deployment failure - RoleAssignmentUpdateNotPermitted +Error Logs: +``` +{ +"code": "DeploymentFailed", +"message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.", +"details": [ +{ +"code": "RoleAssignmentUpdateNotPermitted", +"message": "Tenant ID, application ID, principal ID, and scope are not allowed to be updated." +}, +``` +#### Fix +This issue occurs when attempting multiple deployments with the same GUID. As per suggestion it is recommended to have new GUID’s generated for each deployment. -* **authorBotId:** This is the author Microsoft Application ID for the Company Communicator app. It can be found in the "AuthorAppId" field of your configuration e.g. 5630f8a2-c2a0-4cda-bdfa-c2fa87654321. For the following steps, it will be referred to as %authorBotId%. -* **userBotId:** This is the user Microsoft Application ID for the Company Communicator app. It can be found in the "UserAppId" field of your configuration e.g. 5630f8a2-c2a0-4cda-bdfa-c2fa87654321. For the following steps, it will be referred to as %userBotId%. -* **appDomain:** This is the base domain for the Company Communicator app. It is the value in the "AzureAd:ApplicationIdURI" field of your configuration without the "api://" e.g. appName.azurefd.net. For the following steps, it will be referred to as %appDomain%. +1. Go to the Azure portal and delete the existing resources. +2. Update the below fields with newly generated valid GUIDs during the re-deployment. Any online tool can be used to generate a valid GUID. + +![Screenshot of troubleshooting roleassignment error code deployment](images/troubleshooting_roleassignmenterror.png) + +## 3. Send in chat to members of the following M365 groups, Distribution groups or Security groups option is Disabled. + +#### Fix + +Verify if the below permissions has been added to the graph app registration and the admin consent is granted. + +1. Delegated permissions + * GroupMember.Read.All + * AppCatalog.Read.All + * User.Read + +2. Application permissions + * GroupMember.Read.All + * User.Read.All + * TeamsAppInstallation.ReadWriteForUser.All + + +![Screenshot of troubleshooting permission error](images/troubleshooting_permissionerror.png) + + +## 4. Multiple instance of Company Communicator in the same tenant action items. + +#### Steps + +To deploy the second instance of company communicator in single-tenant +1. Register Azure AD Applications as mentioned in Step 1 of [Deployment Guide](https://github.com/OfficeDev/microsoft-teams-apps-company-communicator/wiki/Deployment-guide). +2. While doing ARM Template deployment, update “User App External Id” with following different value example: `148a66bb-e83d-425a-927d-09f4299a9275` +3. Update the below fields with newly generated valid GUIDs during the re-deployment. Any online tool can be used to generate a valid GUID. -## 3. Error when attempting to reuse a Microsoft Azure AD application ID for the bot registration +![Screenshot of troubleshooting roleassignment error code deployment](images/troubleshooting_roleassignmenterror.png) + +4. Proceed with remaining steps 3, 4 of [Deployment Guide](https://github.com/OfficeDev/microsoft-teams-apps-company-communicator/wiki/Deployment-guide). +5. In Step 5, Please update user app manifest id from `148a66bb-e83d-425a-927d-09f4299a9274` to `148a66bb-e83d-425a-927d-09f4299a9275` and please update author app manifest id from `1c07cd26-a088-4db8-8928-ace382fa219f` to `1c07cd26-a088-4db8-8928-ace382fa219d` (Different value) +and proceed with the remaining steps as mentioned in deployment guide. + +If you miss updating “User App External Id” in step 2, you can update the value in the following sections: + +* Go to second instance azure resource group. Open the App service and click on Configuration (in left navigation). Update “UserAppExternalId” value to `148a66bb-e83d-425a-927d-09f4299a9275`. Click on “ok” and then on “save” and restart app service. + +* Go to second instance azure resource group. Open function app which is ending with “-prep-function” and click on Configuration (in left navigation).Update the“UserAppExternalId” value to `148a66bb-e83d-425a-927d-09f4299a9275`. Click on “ok” and the on “save” and restart function app. + +## 5. App service Sync throws npm error + +Error Logs: ``` -Bot is not valid. Errors: The Microsoft App ID is already registered to another bot application.. See https://aka.ms/bot-requirements for detailed requirements. +react-scripts' is not recognized as an internal or external command, +operable program or batch file. +npm ERR! code ELIFECYCLE +Failed exitCode=1, command=npm run build +npm ERR! errno 1 +An error has occurred during web site deployment. +npm ERR! company-communicator@4.1.5 build: react-scripts build +npm ERR! Exit status 1 +npm ERR! +npm ERR! Failed at the company-communicator@4.1.5 build script. +npm ERR! This is probably not a problem with npm. There is likely additional logging output above ``` +#### Fix +This issue occurs when attempting multiple times node modules are installed by changing the branch endpoint. -* Creating the resource of type Microsoft.BotService/botServices failed with status "BadRequest" +1. Go to the Deployment center. Click on Disconnect. -This happens when the Microsoft Azure application ID entered during the setup of the deployment has already been used and registered for a bot, for instance, if a previous deployment step failed **after** the bot was created. +![Screenshot of troubleshooting app service deployment](images/troubleshooting_appservicesyncerror_1.png) -#### Fix -Either register a new Microsoft Azure AD application or delete the bot registration that is currently using the attempted Microsoft Azure application ID. +2. Wait for 5 to 10 minutes until the entry under the **Logs** tab will be deleted automatically. + +![Screenshot of troubleshooting app service deployment](images/troubleshooting_appservicesyncerror_2.png) + +3. Once the disconnection is completed. Go to Settings and select External Git and add below URL and branch name. + + * Repository : https://github.com/OfficeDev/microsoft-teams-apps-company-communicator.git + * Branch : master (If you are using older version of CC, please select the branch name accordingly.) + + ![Screenshot of troubleshooting app service deployment](images/troubleshooting_appservicesyncerror_3.png) -## 4. Proactive app installation is not working + + +## 6. Proactive app installation is not working If proactive app installation for a user is not working as expected, make sure you have performed the following: 1. Grant Admin consent to the application for all the graph permissions mentioned [here](https://github.com/OfficeDev/microsoft-teams-company-communicator-app/wiki/Deployment-guide#4-add-permissions-to-your-app). @@ -76,17 +154,74 @@ If proactive app installation for a user is not working as expected, make sure y 3. "UserAppExternalId" configuration matches with the User app Id (in the Teams App manifest) for the web app. 4. [Upload](https://docs.microsoft.com/en-us/microsoftteams/tenant-apps-catalog-teams) the User app to your tenant's app catalog so that it is available for everyone in your tenant to install. -## 5. ARM template deployment timeout/error message +## 7. ARM template deployment timeout/error message If you encounter the following error message while deploying with the PowerShell script. This is expected and the script will recover from this failure automatically. ![Screenshot of ARM template deployment timeout](images/ARM-Deployment-Timeout.png) -## 6. Unable to sign-in after upgrading the Company Communicator from a version prior to v4. -Go to the tenant where app is installed and open **Enterprise Applicationss** page [here](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/EnterpriseApps) and search for the app you created in Company Communicator v3. -1. Under **Manage**, click on **Properties** and then **Delete**. -![Remove app registration](images/remove_registration.png) -## 7. When upgrading Company Commmunicator from a version prior to v4, please ensure that the **users** app is installed in the teams to send messages to the team or its members. -Install the **User** app (the `company-communicator-users.zip` package) to the teams that will be the target audience. +## 8. Cannot find any Teams or Teams Users in the dropdown while sending the message. + +#### Fix +Teams only show up when the user app is installed to specific Teams in the tenant. + +1. Click on the User App and Add to Specific Teams. + +![Screenshot of troubleshooting teams not available](images/troubleshooting_teamsnamesemptyerror.png) + + + +## 9. User is getting DeltaLink older than 30 days is not supported error. + +#### Fix +This issue occurs when attempting multiple times node modules are insatlled by changing the branch endpoint. + +1. Go to Resource Group which was created and then to to storage account. + +2. Click on storage preview. Expand **Tables** section + +3. Open **UserData** table. Remove the last record where RowKey is entered as **AllUSersDeltaLink**. + + + ![Screenshot of troubleshooting deltalink error](images/troubleshooting_deltalinkerror.png) + +4. Restart all the services. + + +## 10. Owners of the Teams aren't getting broadcasts messages. + +When sending chat to 'members of following teams' or 'members of M365 groups' people who are listed as owners of those groups and not members are not getting broadcasts. + +#### Fix +Teams only show up when the user app is installed to specific Teams in the tenant. + +1. If you are sending the message to teams channel, both members and owner will be receiving the message. + +2. For distribution lists(DL) and MS365, only the members will receive the message but not the owner. + +The workaround is to add the owners email to the members list so that they will be able to receive the messages. + +![Screenshot of troubleshooting teams not available](images/troubleshooting_teamsnamesemptyerror.png) + + +## 11. Error when attempting to reuse a Microsoft Azure AD application ID for the bot registration +``` +Bot is not valid. Errors: The Microsoft App ID is already registered to another bot application.. See https://aka.ms/bot-requirements for detailed requirements. +``` + +* 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, for instance, if a previous deployment step failed **after** the bot was created. + +#### Fix +Either register a new Microsoft Azure AD application or delete the bot registration that is currently using the attempted Microsoft Azure application ID. + +## 12. Forgetting the botId or appDomain +If you forgot to copy your **authorBotId**, **userBotId** and **appDomain** values from the end of the deployment. You can find them in the "Configuration" section of your Web App. + +* **authorBotId:** This is the author Microsoft Application ID for the Company Communicator app. It can be found in the "AuthorAppId" field of your configuration e.g. 5630f8a2-c2a0-4cda-bdfa-c2fa87654321. For the following steps, it will be referred to as %authorBotId%. +* **userBotId:** This is the user Microsoft Application ID for the Company Communicator app. It can be found in the "UserAppId" field of your configuration e.g. 5630f8a2-c2a0-4cda-bdfa-c2fa87654321. For the following steps, it will be referred to as %userBotId%. +* **appDomain:** This is the base domain for the Company Communicator app. It is the value in the "AzureAd:ApplicationIdURI" field of your configuration without the "api://" e.g. appName.azurefd.net. For the following steps, it will be referred to as %appDomain%. + # Didn't find your problem here? Please report the issue [here](https://github.com/OfficeDev/microsoft-teams-company-communicator-app/issues/new) diff --git a/Wiki/images/multitenant_app_creation.png b/Wiki/images/multitenant_app_creation.png index a364779d9..379c59476 100644 Binary files a/Wiki/images/multitenant_app_creation.png and b/Wiki/images/multitenant_app_creation.png differ diff --git a/Wiki/images/multitenant_app_overview_1.png b/Wiki/images/multitenant_app_overview_1.png index 3c01863bb..29e0cbec3 100644 Binary files a/Wiki/images/multitenant_app_overview_1.png and b/Wiki/images/multitenant_app_overview_1.png differ diff --git a/Wiki/images/multitenant_app_overview_2.png b/Wiki/images/multitenant_app_overview_2.png index c01a2a3c5..ad0ed908c 100644 Binary files a/Wiki/images/multitenant_app_overview_2.png and b/Wiki/images/multitenant_app_overview_2.png differ diff --git a/Wiki/images/multitenant_app_permissions_1.png b/Wiki/images/multitenant_app_permissions_1.png index 06ef9062b..e4f900307 100644 Binary files a/Wiki/images/multitenant_app_permissions_1.png and b/Wiki/images/multitenant_app_permissions_1.png differ diff --git a/Wiki/images/multitenant_app_permissions_2.png b/Wiki/images/multitenant_app_permissions_2.png index 3d11a6132..409a717f8 100644 Binary files a/Wiki/images/multitenant_app_permissions_2.png and b/Wiki/images/multitenant_app_permissions_2.png differ diff --git a/Wiki/images/multitenant_app_secret.png b/Wiki/images/multitenant_app_secret.png index 6158f1ef9..346fac5d2 100644 Binary files a/Wiki/images/multitenant_app_secret.png and b/Wiki/images/multitenant_app_secret.png differ diff --git a/Wiki/images/troubleshooting_appservicesyncerror_1.png b/Wiki/images/troubleshooting_appservicesyncerror_1.png new file mode 100644 index 000000000..bf496c26f Binary files /dev/null and b/Wiki/images/troubleshooting_appservicesyncerror_1.png differ diff --git a/Wiki/images/troubleshooting_appservicesyncerror_2.png b/Wiki/images/troubleshooting_appservicesyncerror_2.png new file mode 100644 index 000000000..d54f5ae8a Binary files /dev/null and b/Wiki/images/troubleshooting_appservicesyncerror_2.png differ diff --git a/Wiki/images/troubleshooting_appservicesyncerror_3.png b/Wiki/images/troubleshooting_appservicesyncerror_3.png new file mode 100644 index 000000000..1aac68163 Binary files /dev/null and b/Wiki/images/troubleshooting_appservicesyncerror_3.png differ diff --git a/Wiki/images/troubleshooting_deltalinkerror.png b/Wiki/images/troubleshooting_deltalinkerror.png new file mode 100644 index 000000000..e30c42eaa Binary files /dev/null and b/Wiki/images/troubleshooting_deltalinkerror.png differ diff --git a/Wiki/images/troubleshooting_permissionerror.png b/Wiki/images/troubleshooting_permissionerror.png new file mode 100644 index 000000000..f4bac4d36 Binary files /dev/null and b/Wiki/images/troubleshooting_permissionerror.png differ diff --git a/Wiki/images/troubleshooting_roleassignmenterror.png b/Wiki/images/troubleshooting_roleassignmenterror.png new file mode 100644 index 000000000..0665f4de3 Binary files /dev/null and b/Wiki/images/troubleshooting_roleassignmenterror.png differ diff --git a/Wiki/images/troubleshooting_teamsnamesemptyerror.png b/Wiki/images/troubleshooting_teamsnamesemptyerror.png new file mode 100644 index 000000000..bed04ebc5 Binary files /dev/null and b/Wiki/images/troubleshooting_teamsnamesemptyerror.png differ