diff --git a/Source/CompanyCommunicator.Common/Extensions/SendQueueMessageContentExtension.cs b/Source/CompanyCommunicator.Common/Extensions/SendQueueMessageContentExtension.cs index 15f621bce..3b49b4521 100644 --- a/Source/CompanyCommunicator.Common/Extensions/SendQueueMessageContentExtension.cs +++ b/Source/CompanyCommunicator.Common/Extensions/SendQueueMessageContentExtension.cs @@ -7,6 +7,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions { using System; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MessageQueues.SendQueue; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGraph; /// /// Extension class for . @@ -44,5 +45,28 @@ public static string GetConversationId(this SendQueueMessageContent message) _ => throw new ArgumentException("Invalid recipient type"), }; } + + /// + /// Check if recipient guest user. + /// + /// Send Queue message. + /// Boolean indicating if it is a guest user. + public static bool IsRecipientGuestUser(this SendQueueMessageContent message) + { + var recipient = message.RecipientData; + if (recipient.RecipientType == RecipientDataType.User) + { + if (string.IsNullOrEmpty(recipient.UserData.UserType)) + { + throw new ArgumentNullException(nameof(recipient.UserData.UserType)); + } + else if (recipient.UserData.UserType.Equals(UserType.Guest, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } } } diff --git a/Source/CompanyCommunicator.Common/Extensions/UserExtensions.cs b/Source/CompanyCommunicator.Common/Extensions/UserExtensions.cs index cb54b93cf..967489378 100644 --- a/Source/CompanyCommunicator.Common/Extensions/UserExtensions.cs +++ b/Source/CompanyCommunicator.Common/Extensions/UserExtensions.cs @@ -7,6 +7,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Extensions { using System; using System.Collections.Generic; + using Microsoft.Graph; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.UserData; using Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGraph; @@ -45,7 +46,7 @@ public static IEnumerable> AsGroups(this IList userI } /// - /// Get the user type for a user. + /// Get the userType for a user. /// /// the user principal name. /// the user type such as Member or Guest. @@ -58,5 +59,25 @@ public static string GetUserType(this string userPrincipalName) return userPrincipalName.ToLower().Contains("#ext#") ? UserType.Guest : UserType.Member; } + + /// + /// Get the userType for a user. + /// + /// the microsoft graph user. + /// the user type such as Member or Guest. + public static string GetUserType(this User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (!string.IsNullOrEmpty(user.UserType)) + { + return user.UserType; + } + + return user.UserPrincipalName.GetUserType(); + } } } diff --git a/Source/CompanyCommunicator.Common/Policies/PollyPolicy.cs b/Source/CompanyCommunicator.Common/Policies/PollyPolicy.cs new file mode 100644 index 000000000..70fe072a0 --- /dev/null +++ b/Source/CompanyCommunicator.Common/Policies/PollyPolicy.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + +namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Policies +{ + using System; + using System.Net; + using Microsoft.Graph; + using Polly; + using Polly.Contrib.WaitAndRetry; + using Polly.Retry; + + /// + /// Polly policies. + /// + public class PollyPolicy + { + /// + /// Get the graph retry policy. + /// + /// the number of max attempts. + /// A retry policy that can be applied to async delegates. + public static AsyncRetryPolicy GetGraphRetryPolicy(int maxAttempts) + { + var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: maxAttempts); + + // Only Handling 502 Bad Gateway Exception + // Other exception such as 429, 503, 504 is handled by default by Graph SDK. + return Policy + .Handle(e => + e.StatusCode == HttpStatusCode.BadGateway) + .WaitAndRetryAsync(delay); + } + } +} diff --git a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs index 1b4ff48b9..9ce36a3eb 100644 --- a/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs +++ b/Source/CompanyCommunicator.Common/Repositories/SentNotificationData/SentNotificationDataEntity.cs @@ -40,6 +40,12 @@ public class SentNotificationDataEntity : TableEntity /// public static readonly int FinalFaultedStatusCode = -2; + /// + /// This value indicates that operation is not supported by Azure Function and will + /// not be processed further. + /// + public static readonly int NotSupportedStatusCode = -3; + /// /// String indicating the recipient type for the given notification was a user. /// @@ -169,6 +175,11 @@ public class SentNotificationDataEntity : TableEntity /// public string TenantId { get; set; } + /// + /// Gets or sets the user type for the recipient. + /// + public string UserType { get; set; } + /// /// Gets or sets the user id for the recipient. /// diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs b/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs index c32ebaa71..c7c035340 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs +++ b/Source/CompanyCommunicator.Common/Resources/Strings.Designer.cs @@ -384,6 +384,15 @@ public static string Guest { } } + /// + /// Looks up a localized string similar to Guest User not supported. + /// + public static string GuestUserNotSupported { + get { + return ResourceManager.GetString("GuestUserNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to Member. /// diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.ar-SA.resx b/Source/CompanyCommunicator.Common/Resources/Strings.ar-SA.resx index 93b62d1af..8db19a1fb 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.ar-SA.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.ar-SA.resx @@ -248,12 +248,15 @@ مقيّد - User Type + نوع المستخدم - Guest + الضيف - Member + العضو + + + المستخدم الضيف غير مدعم \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.de-DE.resx b/Source/CompanyCommunicator.Common/Resources/Strings.de-DE.resx index 4b0f61799..903c78d0e 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.de-DE.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.de-DE.resx @@ -160,7 +160,7 @@ {0} (kopieren) - Da hat etwas nicht geklappt. Versuchen Sie noch Mal, die Ergebnisse zu exportieren. + Etwas ist schief gegangen. Versuchen Sie noch Mal, die Ergebnisse zu exportieren. Fehlgeschlagen @@ -221,7 +221,7 @@ Ihre Datei kann jetzt herunter geladen werden. Eine Kopie ist auch auf OneDrive verfügbar. - Da hat etwas nicht geklappt. Versuchen Sie noch Mal, die Ergebnisse zu exportieren. + Etwas ist schief gegangen. Versuchen Sie noch Mal, die Ergebnisse zu exportieren. Die Benachrichtigung hat {0} Gruppen als Ihre Empfänger. Sie sollte {1} Gruppen nicht über schreiten. @@ -256,4 +256,7 @@ Mitglied + + Gastbenutzer nicht unterstützt + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.es-ES.resx b/Source/CompanyCommunicator.Common/Resources/Strings.es-ES.resx index eb9c0ac4d..a10e9f240 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.es-ES.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.es-ES.resx @@ -248,12 +248,15 @@ Limitado - User Type + Tipo de usuario - Guest + Invitado - Member + Miembro + + + No se admite el usuario invitado \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.fr-FR.resx b/Source/CompanyCommunicator.Common/Resources/Strings.fr-FR.resx index 532a7037b..f3e3446af 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.fr-FR.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.fr-FR.resx @@ -256,4 +256,7 @@ Membre + + Utilisateur invité non pris en charge + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.he-IL.resx b/Source/CompanyCommunicator.Common/Resources/Strings.he-IL.resx index e2cf20e0d..14d6186b4 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.he-IL.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.he-IL.resx @@ -256,4 +256,7 @@ חבר + + משתמש אורח אינו נתמך + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.ja-JP.resx b/Source/CompanyCommunicator.Common/Resources/Strings.ja-JP.resx index 6014cbccd..d767a315c 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.ja-JP.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.ja-JP.resx @@ -256,4 +256,7 @@ メンバー + + ゲスト ユーザーはサポートされていません + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.ko-KR.resx b/Source/CompanyCommunicator.Common/Resources/Strings.ko-KR.resx index 18e66d935..d17abbc8e 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.ko-KR.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.ko-KR.resx @@ -256,4 +256,7 @@ 구성원 + + 게스트 사용자가 지원되지 않음 + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.pt-BR.resx b/Source/CompanyCommunicator.Common/Resources/Strings.pt-BR.resx index d27cfc847..8a3795015 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.pt-BR.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.pt-BR.resx @@ -248,12 +248,15 @@ Limitado - User Type + Tipo de Usuário - Guest + Convidado - Member + Membro + + + Não há suporte para o Usuário Convidado \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploc.resx b/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploc.resx index 128ace755..ed9fb26e6 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploc.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploc.resx @@ -118,142 +118,145 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - _Cбйтд©т чфџя ЇЋ дdмїл fфґ pёгмїssїюи тб vїзщ тнїs dдтд.ЍѝцзджфЍ_ + _Cфйтд©т чфцґ ЇЋ дdмїй fбг pэямїssїбп тю vїзш тнїs dдтд.ЍѝцзджфЍ_ - _Дpp Йфт ЇиsтдllзdЍѝц_ + _Дpp Лфт ЇпsтдllёdЍѝц_ - _Dэlїvёяу SтдтµsЍѝ_ + _Dёlїvзґу SтдтµsЍѝ_ - _Эжpбгтэd БчЍѝ_ + _Эжpюятзd БўЍѝ_ - _Эжpфгт ЋїмэSтдмpЍѝ_ + _Эжpюгт ЋїмзSтдмpЍѝ_ - _Mэssдgз ЋїтlёЍѝ_ + _Mёssдgэ ЋїтlёЍѝ_ - _Sєлт ЋїмёSтдмpЍѝ_ + _Sёпт ЋїмёSтдмpЍѝ_ - _Sтдтµs ЃёдsюпЍѝ_ + _Sтдтџs ЯєдsблЍѝ_ - _Ћёдм ЇDЍ_ + _Ћздм ЇDЍ_ - _Ћёдм ИдмэЍ_ + _Ћэдм ИдмёЍ_ _ЦPП_ - _Џsєя ЇDЍ_ + _Џsзґ ЇDЍ_ - _ИдмёЍ_ + _ПдмзЍ_ - _{0} (©фpу)Ѝѝ_ + _{0} (©юpў)Ѝѝ_ - _Sбмэтћїпg щэлт шяюпg. Ћґч эжpбґтїйg тнё гёsµlтs дgдїй.ЍѝцзджфЍ_ + _Sфмєтнїиg щзит щяблg. Ћґў єжpбятїпg тнз яєsџlтs дgдїп.ЍѝцзджфЍ_ - _FдїlєdЍ_ + _FдїlэdЍ_ - _Цsёя дpplї©дтїюй лбт fфцпd. Mдкё sџґз тћз Цsэг дpp їs µplбдdэd тб убџґ фґgдлїzдтїбл's дpp ©дтдlфg.ЍѝцзджфЍѝцзджфЍ_ + _Џsєя дpplї©дтїби пфт fфџйd. Mдкз sцгз тћз Цsєг дpp їs цplюдdзd тф чбџґ фґgдпїzдтїби's дpp ©дтдlюg.ЍѝцзджфЍѝцзджфЍ_ - _Fдїlєd тю fїлd тћэ Џsэґ дpplї©дтїфй: {0} їй тнє югgдиїzдтїюл's дpp ©дтдlбg.ЍѝцзджфЍѝцз_ + _Fдїlєd тю fїйd тћз Џsёґ дpplї©дтїюп: {0} їп тнє бгgдиїzдтїфй's дpp ©дтдlбg.ЍѝцзджфЍѝцз_ {0} - app Id. (GUID). - _Fдїlєd тф ©гёдтє ©блvзгsдтїюл. Єґябг мєssдgё: {0}Ѝѝцзджф_ + _Fдїlєd тф ©ґєдтэ ©бпvёґsдтїбл. Єгяфя мєssдgё: {0}Ѝѝцзджф_ - _Fдїlєd тф ©гёдтз ©фиvєгsдтїби щїтћ тёдмs цsзя: {0}. Ёж©єpтїюи: {1}ЍѝцзджфЍѝц_ + _Fдїlєd тф ©яздтё ©юпvєяsдтїбл щїтћ тёдмs џsєг: {0}. Єж©зpтїюп: {1}ЍѝцзджфЍѝц_ - _Fдїlёd тф ©гэдтє ©бйvзгsдтїюй. Гёqµзsт тћгфттlэd. Єягюґ мєssдgё: {0}"ЍѝцзджфЍѝц_ + _Fдїlєd тю ©ґэдтё ©юлvєґsдтїбй. Язqџзsт тнгюттlзd. Ёггфг мзssдgє: {0}"ЍѝцзджфЍѝц_ - _Fдїlэd тф fїйd тнз тэдм {0} їй DЪ.Ѝѝцзд_ + _Fдїlэd тб fїпd тћє тєдм {0} їй DБ.Ѝѝцзд_ - _Fдїlэd тю sул© дll цsєяs. Sтдтџs Cфdз: {0} Ёж©зpтїфп: {1}ЍѝцзджфЍѝ_ + _Fдїlєd тю sўп© дll цsэґs. Sтдтцs Cбdё: {0} Ёж©зpтїбп: {1}ЍѝцзджфЍѝ_ - _Fдїlзd тф gёт ©бпvєяsдтїюл їd fбг цsэґ: {0}. Sтдтµs Cюdє: {1} Эж©єpтїюл: {2}ЍѝцзджфЍѝцз_ + _Fдїlёd тб gёт ©бпvєгsдтїюп їd fбя џsэя: {0}. Sтдтµs Cфdє: {1} Ёж©єpтїфи: {2}ЍѝцзджфЍѝцз_ - _Fдїlёd тф gёт мзмьзґs fфґ gгюџp {0}: {1}Ѝѝцздж_ + _Fдїlєd тф gэт мёмьзяs fюя gябџp {0}: {1}Ѝѝцздж_ - _Fдїlёd тб gєт мэмъзґs fюг тэдм {0}: {1}Ѝѝцздж_ + _Fдїlёd тю gёт мємьэґs fюґ тєдм {0}: {1}Ѝѝцздж_ - _Fдїlєd тб їиsтдll дpplї©дтїюй fюя џsєґ: {0}. Эж©эpтїфл: {1}ЍѝцзджфЍѝ_ + _Fдїlзd тф їиsтдll дpplї©дтїбй fбг џsзг: {0}. Зж©ёpтїбл: {1}ЍѝцзджфЍѝ_ - _Fдїlэd тб pґзpдгэ тнё мёssдgє fфя sэлdїлg:{0}Ѝѝцзджф_ + _Fдїlёd тю pґёpдґё тћє мєssдgё fфґ sєлdїлg:{0}Ѝѝцзджф_ - _Ћћїs fїlз ©фйтдїпs тнё ґёsџlтs ўфц ёжpбґтёd.Ѝѝцзджф_ + _Ћћїs fїlэ ©бптдїпs тнё ґєsцlтs чфц ёжpфгтэd.Ѝѝцзджф_ - _Ћћё lїиќ fбг тћїs dфшйlюдd ндs зжpїяєd. Єжpюят тћё яёsцlтs дgдїп.ЍѝцзджфЍѝц_ + _Ћћё lїиќ fфя тћїs dфшлlбдd ћдs єжpїґєd. Зжpбят тћэ ґєsџlтs дgдїй.ЍѝцзджфЍѝц_ - _ЄжpбятDдтдЍѝ_ + _ЗжpюгтDдтдЍѝ_ - _Mєssдgє_DэlїvзгуЍѝ_ + _Mёssдgз_DёlїvэячЍѝ_ - _MзтдdдтдЍ_ + _MётдdдтдЍ_ - _Чфµг fїlэ їs яздdч тю dфшиlбдd. Д ©фpч їs дlsб дvдїlдъlэ їп ЮиэDяїvз.ЍѝцзджфЍѝц_ + _Чфµґ fїlє їs яэдdу тб dющпlбдd. Д ©фpў їs дlsб дvдїlдьlз їй ЮйзDгїvё.ЍѝцзджфЍѝц_ - _Sюмєтћїлg щёлт щгбиg. Ћяч зжpюґтїиg тнз яэsцlтs дgдїи.ЍѝцзджфЍ_ + _Sюмєтнїлg шёлт шґбпg. Ћяч єжpфятїиg тнє язsцlтs дgдїй.ЍѝцзджфЍ_ - _Ћћэ йбтїfї©дтїюи ндs {0} gяюµps дs їтs гё©їpїєйтs. Їт sнбџldл'т єж©эєd {1} gґбµps.ЍѝцзджфЍѝцзд_ + _Ћћё ифтїfї©дтїюл ћдs {0} gяюцps дs їтs яё©їpїзитs. Їт sнфџldл'т ёж©єэd {1} gябµps.ЍѝцзджфЍѝцзд_ - _Ћнё пбтїfї©дтїюй ндs {0} яфsтєґs дs їтs гз©їpїёптs. Їт sћфцldи'т єж©ззd {1} яюsтзгs.ЍѝцзджфЍѝцздж_ + _Ћћз йбтїfї©дтїфи ћдs {0} ґюsтзяs дs їтs яє©їpїзптs. Їт sнфџldл'т зж©зєd {1} ґфsтёгs.ЍѝцзджфЍѝцздж_ - _Ћнє лфтїfї©дтїюи ћдs {0} тєдмs дs їтs яє©їpїёйтs. Їт sнюџldи'т эж©єэd {1} тєдмs.ЍѝцзджфЍѝцзд_ + _Ћнё йбтїfї©дтїюи ндs {0} тэдмs дs їтs ґє©їpїэйтs. Їт sнфµldй'т зж©єэd {1} тэдмs.ЍѝцзджфЍѝцзд_ _ЮЌ_ - _Pзґмїssїбл dє©lїлёd. Шё шїll пбт pгб©ёэd шїтн тћё ёжpюгт.ЍѝцзджфЍѝ_ + _Pёґмїssїбп dз©lїлзd. Щэ щїll лбт pяф©єэd шїтћ тнё зжpфґт.ЍѝцзджфЍѝ_ - _Ґз©їpїёит Ифт FфµйdЍѝц_ + _Гз©їpїєит Иют FбµпdЍѝц_ - _Sµ©©ёёdзdЍ_ + _Sџ©©эєdэdЍ_ - _ЋћябттlзdЍ_ + _ЋнґбттlёdЍ_ - _Цsэґ ЋўpэЍ_ + _Цsёя ЋўpєЍ_ _GµёsтЍ_ - _MємьёяЍ_ + _MзмвэгЍ_ + + + _Gµэsт Џsэя йфт sцppфґтэdЍѝцз_ \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploca.resx b/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploca.resx index d392634b0..c7d092c9f 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploca.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.qps-ploca.resx @@ -118,142 +118,145 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - _Cфлтд©т ўбµг ЇЋ дdмїл fфг pёгмїssїфп тю vїєщ тћїs dдтд.ЍѝцзджфЍ_ + _Cблтд©т уфцґ ЇЋ дdмїп fюя pёґмїssїфп тб vїзш тнїs dдтд.ЍѝцзджфЍ_ - _Дpp Лфт ЇлsтдllёdЍѝц_ + _Дpp Пфт ЇиsтдllзdЍѝц_ - _Dєlїvэяў SтдтцsЍѝ_ + _Dзlїvзгч SтдтџsЍѝ_ - _Єжpфятёd bўЍѝ_ + _Эжpбгтєd bўЍѝ_ - _Зжpфгт ЋїмзSтдмpЍѝ_ + _Эжpфят ЋїмёSтдмpЍѝ_ - _Mєssдgё ЋїтlёЍѝ_ + _Mёssдgё ЋїтlєЍѝ_ - _Sєлт ЋїмёSтдмpЍѝ_ + _Sёлт ЋїмёSтдмpЍѝ_ - _Sтдтµs ЯєдsблЍѝ_ + _Sтдтџs ЯёдsфйЍѝ_ - _Ћэдм ЇDЍ_ + _Ћздм ЇDЍ_ - _Ћздм ПдмзЍ_ + _Ћэдм ПдмєЍ_ _ЦPЛ_ - _Џsзг ЇDЍ_ + _Цsзґ ЇDЍ_ - _ЛдмєЍ_ + _ЙдмэЍ_ - _{0} (©юpу)Ѝѝ_ + _{0} (©фpу)Ѝѝ_ - _Sюмётнїпg шєйт щґюиg. Ћгч ёжpбятїлg тћз язsџlтs дgдїи.ЍѝцзджфЍ_ + _Sфмєтћїйg шэпт щяюлg. Ћгч єжpюятїлg тћє ґєsџlтs дgдїл.ЍѝцзджфЍ_ - _FдїlёdЍ_ + _FдїlэdЍ_ - _Цsєґ дpplї©дтїюй ифт fюцйd. Mдќэ sџґє тнз Џsёг дpp їs µplфдdєd тб ўюця фгgдйїzдтїфп's дpp ©дтдlфg.ЍѝцзджфЍѝцзджфЍ_ + _Џsэґ дpplї©дтїюй йют fфµпd. Mдќє sµяє тнє Џsєя дpp їs µplфдdёd тф ўюцг бяgдлїzдтїби's дpp ©дтдlфg.ЍѝцзджфЍѝцзджфЍ_ - _Fдїlєd тю fїпd тнэ Џsзг дpplї©дтїюл: {0} їп тнє юґgдйїzдтїбл's дpp ©дтдlфg.ЍѝцзджфЍѝцз_ + _Fдїlэd тб fїиd тћз Цsзг дpplї©дтїфй: {0} їл тнє бяgдпїzдтїфй's дpp ©дтдlбg.ЍѝцзджфЍѝцз_ {0} - app Id. (GUID). - _Fдїlэd тю ©яёдтэ ©фиvёгsдтїюй. Зґяфґ мэssдgё: {0}Ѝѝцзджф_ + _Fдїlзd тю ©гэдтё ©бпvёґsдтїюи. Зяґфг мёssдgэ: {0}Ѝѝцзджф_ - _Fдїlэd тю ©ґздтё ©фиvзґsдтїюл шїтћ тэдмs џsзя: {0}. Эж©зpтїюл: {1}ЍѝцзджфЍѝц_ + _Fдїlёd тф ©гздтё ©бпvзгsдтїюл шїтћ тёдмs µsэґ: {0}. Эж©зpтїюп: {1}ЍѝцзджфЍѝц_ - _Fдїlєd тф ©яздтё ©фиvєгsдтїюп. Ѓзqџєsт тнгфттlзd. Эґгюг мёssдgэ: {0}"ЍѝцзджфЍѝц_ + _Fдїlёd тб ©ґёдтє ©фпvзґsдтїфп. Язqџэsт тнґфттlзd. Ёґгфґ мэssдgэ: {0}"ЍѝцзджфЍѝц_ - _Fдїlэd тю fїиd тнэ тздм {0} їп DБ.Ѝѝцзд_ + _Fдїlёd тб fїпd тћэ тёдм {0} їй Db.Ѝѝцзд_ - _Fдїlєd тю sуп© дll µsзяs. Sтдтцs Cбdэ: {0} Ёж©эpтїфл: {1}ЍѝцзджфЍѝ_ + _Fдїlэd тб sчл© дll цsєґs. Sтдтџs Cюdэ: {0} Зж©єpтїфй: {1}ЍѝцзджфЍѝ_ - _Fдїlзd тб gёт ©бпvёґsдтїюй їd fфґ µsэг: {0}. Sтдтµs Cфdє: {1} Єж©ёpтїюй: {2}ЍѝцзджфЍѝцз_ + _Fдїlєd тю gэт ©бпvзяsдтїфп їd fбя цsэя: {0}. Sтдтџs Cюdз: {1} Эж©зpтїюи: {2}ЍѝцзджфЍѝцз_ - _Fдїlєd тб gзт мємьёяs fюг gґбџp {0}: {1}Ѝѝцздж_ + _Fдїlєd тф gёт мємьёяs fбг gяфµp {0}: {1}Ѝѝцздж_ - _Fдїlєd тю gєт мёмъэґs fюя тєдм {0}: {1}Ѝѝцздж_ + _Fдїlзd тб gєт мэмъєгs fюя тэдм {0}: {1}Ѝѝцздж_ - _Fдїlэd тю їиsтдll дpplї©дтїбп fбґ цsзґ: {0}. Эж©зpтїфп: {1}ЍѝцзджфЍѝ_ + _Fдїlёd тю їпsтдll дpplї©дтїфи fфґ µsзґ: {0}. Ёж©зpтїбп: {1}ЍѝцзджфЍѝ_ - _Fдїlєd тю pяёpдяэ тћэ мёssдgё fюг sэиdїйg:{0}Ѝѝцзджф_ + _Fдїlєd тю pяёpдяє тћз мзssдgё fюґ sєпdїпg:{0}Ѝѝцзджф_ - _Ћнїs fїlз ©юлтдїйs тнє яєsџlтs ўюџ ёжpбґтєd.Ѝѝцзджф_ + _Ћнїs fїlз ©юлтдїйs тћэ гєsµlтs уюц зжpбґтєd.Ѝѝцзджф_ - _Ћћз lїиќ fфг тћїs dфшлlфдd ћдs єжpїгєd. Єжpюят тнё яэsµlтs дgдїи.ЍѝцзджфЍѝц_ + _Ћћё lїик fюг тћїs dфшлlюдd ћдs ёжpїґёd. Єжpюґт тнз яэsµlтs дgдїи.ЍѝцзджфЍѝц_ - _ЗжpфгтDдтдЍѝ_ + _ЁжpюгтDдтдЍѝ_ - _Mєssдgз_DєlїvэґўЍѝ_ + _Mєssдgё_DёlїvэяуЍѝ_ - _MэтдdдтдЍ_ + _MётдdдтдЍ_ - _Уюџг fїlз їs гєдdў тю dфшлlбдd. Д ©фpу їs дlsф дvдїlдьlє їй ФлєDґїvз.ЍѝцзджфЍѝц_ + _Чюµя fїlё їs гздdу тф dюшпlюдd. Д ©юpў їs дlsб дvдїlдвlэ їи ЮпзDяїvё.ЍѝцзджфЍѝц_ - _Sфмзтћїпg шэйт шгюлg. Ћґў эжpбгтїиg тћз ґёsµlтs дgдїп.ЍѝцзджфЍ_ + _Sюмётћїиg щёит щгбиg. Ћгў єжpюґтїлg тћз ґєsµlтs дgдїй.ЍѝцзджфЍ_ - _Ћћз пютїfї©дтїюи ћдs {0} gгбцps дs їтs ґэ©їpїёптs. Їт sћфµldй'т зж©ёєd {1} gґюµps.ЍѝцзджфЍѝцзд_ + _Ћнє пютїfї©дтїфл ндs {0} gґбµps дs їтs яє©їpїэитs. Їт sћбџldп'т зж©єєd {1} gяюџps.ЍѝцзджфЍѝцзд_ - _Ћћз пбтїfї©дтїюй ндs {0} гфsтєяs дs їтs гє©їpїєитs. Їт sнюµldл'т зж©єёd {1} ґбsтэяs.ЍѝцзджфЍѝцздж_ + _Ћнё пютїfї©дтїфй ћдs {0} яюsтёгs дs їтs гё©їpїэптs. Їт sћбцldи'т єж©ёєd {1} ґфsтєяs.ЍѝцзджфЍѝцздж_ - _Ћћё пбтїfї©дтїюп ћдs {0} тэдмs дs їтs ґз©їpїэитs. Їт sнюµldл'т єж©эзd {1} тєдмs.ЍѝцзджфЍѝцзд_ + _Ћћз пфтїfї©дтїюп ћдs {0} тэдмs дs їтs яэ©їpїёлтs. Їт sнфџldп'т ёж©ээd {1} тздмs.ЍѝцзджфЍѝцзд_ _ФЌ_ - _Pёґмїssїюи dз©lїйёd. Щэ щїll лют pґб©єзd щїтн тћз єжpюгт.ЍѝцзджфЍѝ_ + _Pзґмїssїфи dз©lїлёd. Щє щїll лбт pгю©ёєd шїтн тћє єжpбґт.ЍѝцзджфЍѝ_ - _Ґз©їpїєпт Йют FбцпdЍѝц_ + _Яё©їpїзйт Пфт FюцпdЍѝц_ - _Sµ©©ёзdёdЍ_ + _Sц©©ээdзdЍ_ - _ЋнгфттlэdЍ_ + _ЋћяюттlёdЍ_ - _Цsёя ЋуpёЍ_ + _Цsзґ ЋуpёЍ_ - _GцёsтЍ_ + _GµєsтЍ_ - _MємьёгЍ_ + _MємвёяЍ_ + + + _Gµэsт Цsёя ифт sџppюятзdЍѝцз_ \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.qps-plocm.resx b/Source/CompanyCommunicator.Common/Resources/Strings.qps-plocm.resx index 03486eb26..65d196353 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.qps-plocm.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.qps-plocm.resx @@ -118,142 +118,145 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - _Cфлтд©т чюџґ ЇЋ дdмїп fбг pёгмїssїюи тб vїзш тћїs dдтд.ЍѝцзджфЍ_ + _Cфлтд©т уфџг ЇЋ дdмїп fфя pэґмїssїюи тф vїєш тћїs dдтд.ЍѝцзджфЍ_ - _Дpp Иют ЇлsтдllєdЍѝц_ + _Дpp Йфт ЇпsтдllзdЍѝц_ - _Dэlїvзяу SтдтµsЍѝ_ + _Dэlїvзяу SтдтџsЍѝ_ - _Ёжpфґтзd БуЍѝ_ + _Ёжpюгтєd bуЍѝ_ - _Ёжpюгт ЋїмэSтдмpЍѝ_ + _Єжpбят ЋїмёSтдмpЍѝ_ - _Mзssдgэ ЋїтlзЍѝ_ + _Mэssдgэ ЋїтlэЍѝ_ - _Sэпт ЋїмзSтдмpЍѝ_ + _Sэит ЋїмєSтдмpЍѝ_ - _Sтдтцs ҐєдsфлЍѝ_ + _Sтдтµs ЃэдsюйЍѝ_ - _Ћёдм ЇDЍ_ + _Ћздм ЇDЍ_ _Ћэдм ЙдмзЍ_ - _ЦPИ_ + _ЏPЛ_ - _Цsёґ ЇDЍ_ + _Џsзґ ЇDЍ_ - _ПдмёЍ_ + _ЛдмэЍ_ - _{0} (©фpу)Ѝѝ_ + _{0} (©фpч)Ѝѝ_ - _Sюмэтћїпg шзпт щґфлg. Ћгч эжpюгтїиg тнє яєsџlтs дgдїп.ЍѝцзджфЍ_ + _Sбмєтнїпg шэпт щгфйg. Ћґу зжpюятїлg тнё язsџlтs дgдїл.ЍѝцзджфЍ_ - _FдїlєdЍ_ + _FдїlёdЍ_ - _Џsєя дpplї©дтїбп ибт fюциd. Mдкє sџяз тнё Џsёґ дpp їs џplюдdєd тб ўюцг фгgдиїzдтїфп's дpp ©дтдlюg.ЍѝцзджфЍѝцзджфЍ_ + _Цsэґ дpplї©дтїюй иют fюџиd. Mдќє sџяз тћє Цsэг дpp їs цplюдdёd тю чбця югgдпїzдтїфл's дpp ©дтдlбg.ЍѝцзджфЍѝцзджфЍ_ - _Fдїlзd тф fїлd тнэ Џsєґ дpplї©дтїфй: {0} їл тћз бґgдйїzдтїфп's дpp ©дтдlфg.ЍѝцзджфЍѝцз_ + _Fдїlєd тф fїлd тћэ Цsёя дpplї©дтїюи: {0} їп тнє фґgдлїzдтїюп's дpp ©дтдlбg.ЍѝцзджфЍѝцз_ {0} - app Id. (GUID). - _Fдїlэd тб ©гёдтє ©фйvёяsдтїби. Зяґюг мёssдgє: {0}Ѝѝцзджф_ + _Fдїlзd тб ©ґэдтё ©юйvзгsдтїюп. Зггбя мзssдgё: {0}Ѝѝцзджф_ - _Fдїlёd тб ©гєдтэ ©юлvєгsдтїюи шїтн тздмs џsєя: {0}. Єж©зpтїюй: {1}ЍѝцзджфЍѝц_ + _Fдїlэd тб ©яздтз ©юиvєґsдтїби шїтн тєдмs µsёґ: {0}. Ёж©эpтїюл: {1}ЍѝцзджфЍѝц_ - _Fдїlєd тб ©гэдтз ©фйvёяsдтїфл. Яэqџзsт тћгфттlёd. Єяяюг мзssдgз: {0}"ЍѝцзджфЍѝц_ + _Fдїlєd тю ©гёдтз ©блvзґsдтїфл. Ґєqµзsт тнґбттlєd. Єяґюя мзssдgз: {0}"ЍѝцзджфЍѝц_ - _Fдїlзd тю fїлd тћё тздм {0} їп Db.Ѝѝцзд_ + _Fдїlэd тф fїлd тћэ тёдм {0} їл DЪ.Ѝѝцзд_ - _Fдїlєd тю sчл© дll цsёґs. Sтдтцs Cбdэ: {0} Зж©ёpтїфп: {1}ЍѝцзджфЍѝ_ + _Fдїlзd тю sўл© дll µsзяs. Sтдтµs Cбdз: {0} Єж©єpтїфи: {1}ЍѝцзджфЍѝ_ - _Fдїlзd тф gєт ©бйvзяsдтїбл їd fюґ цsэг: {0}. Sтдтџs Cбdє: {1} Єж©зpтїюй: {2}ЍѝцзджфЍѝцз_ + _Fдїlзd тю gэт ©фйvзгsдтїби їd fбґ џsзя: {0}. Sтдтцs Cбdє: {1} Эж©ёpтїюй: {2}ЍѝцзджфЍѝцз_ - _Fдїlэd тб gєт мзмъєгs fюя gгбцp {0}: {1}Ѝѝцздж_ + _Fдїlзd тб gэт мзмъэяs fюг gябµp {0}: {1}Ѝѝцздж_ - _Fдїlєd тф gёт мёмьєяs fфг тёдм {0}: {1}Ѝѝцздж_ + _Fдїlэd тю gёт мёмьёгs fюя тёдм {0}: {1}Ѝѝцздж_ - _Fдїlёd тф їпsтдll дpplї©дтїфл fбя џsєг: {0}. Зж©эpтїбл: {1}ЍѝцзджфЍѝ_ + _Fдїlёd тю їлsтдll дpplї©дтїбл fюг µsэг: {0}. Зж©зpтїфи: {1}ЍѝцзджфЍѝ_ - _Fдїlзd тф pяєpдґз тнэ мзssдgё fюг sэлdїпg:{0}Ѝѝцзджф_ + _Fдїlёd тф pгёpдгэ тћэ мёssдgз fбя sєлdїлg:{0}Ѝѝцзджф_ - _Ћћїs fїlз ©юлтдїпs тнз ґєsцlтs чюц єжpфґтєd.Ѝѝцзджф_ + _Ћћїs fїlэ ©фйтдїиs тнз гёsцlтs ўюџ зжpбятзd.Ѝѝцзджф_ - _Ћнэ lїпк fфґ тнїs dющйlбдd ћдs эжpїґэd. Єжpбґт тћё язsµlтs дgдїи.ЍѝцзджфЍѝц_ + _Ћнё lїйк fбя тнїs dфшпlбдd ндs ёжpїгёd. Зжpфґт тћё яэsџlтs дgдїл.ЍѝцзджфЍѝц_ - _ЭжpфґтDдтдЍѝ_ + _ЄжpфятDдтдЍѝ_ - _Mёssдgз_DєlїvёґчЍѝ_ + _Mзssдgз_DёlїvєгўЍѝ_ - _MэтдdдтдЍ_ + _MётдdдтдЍ_ - _Ўбџґ fїlё їs гєдdў тб dбшиlбдd. Д ©бpч їs дlsф дvдїlдъlё їй ЮиёDґїvз.ЍѝцзджфЍѝц_ + _Чфµя fїlё їs яздdу тб dющлlфдd. Д ©фpў їs дlsю дvдїlдъlз їп ФпэDяїvз.ЍѝцзджфЍѝц_ - _Sфмєтнїлg шёйт шгбйg. Ћґч эжpфгтїлg тћє ґэsµlтs дgдїп.ЍѝцзджфЍ_ + _Sюмзтћїпg щзит шгюлg. Ћяў ёжpбґтїиg тћє гєsµlтs дgдїл.ЍѝцзджфЍ_ - _Ћћэ пфтїfї©дтїфи ћдs {0} gґюџps дs їтs ґэ©їpїєлтs. Їт sнбцldп'т зж©зэd {1} gяфцps.ЍѝцзджфЍѝцзд_ + _Ћнэ йбтїfї©дтїюи ћдs {0} gгфџps дs їтs гэ©їpїєитs. Їт sнфџldп'т зж©зэd {1} gябџps.ЍѝцзджфЍѝцзд_ - _Ћћэ ибтїfї©дтїфп ћдs {0} ґфsтэґs дs їтs ґё©їpїєитs. Їт sнбцldй'т єж©ёёd {1} гфsтєгs.ЍѝцзджфЍѝцздж_ + _Ћнз лбтїfї©дтїбп ћдs {0} гфsтэгs дs їтs ґз©їpїєлтs. Їт sћбџldл'т зж©ёэd {1} яюsтёґs.ЍѝцзджфЍѝцздж_ - _Ћћэ йютїfї©дтїфй ндs {0} тєдмs дs їтs ґэ©їpїэитs. Їт sнбцldи'т ёж©єёd {1} тєдмs.ЍѝцзджфЍѝцзд_ + _Ћћє йбтїfї©дтїбл ндs {0} тёдмs дs їтs яз©їpїёитs. Їт sћфµldи'т эж©ёэd {1} тэдмs.ЍѝцзджфЍѝцзд_ _ЮЌ_ - _Pэгмїssїби dё©lїйєd. Шє шїll йют pяю©зёd щїтћ тнз эжpюят.ЍѝцзджфЍѝ_ + _Pєгмїssїфй dэ©lїпэd. Щз щїll йбт pгю©эєd шїтћ тнэ зжpюят.ЍѝцзджфЍѝ_ - _Ґз©їpїёйт Лют FфџлdЍѝц_ + _Гё©їpїзит Ибт FюџпdЍѝц_ - _Sџ©©зєdєdЍ_ + _Sц©©ёєdёdЍ_ - _ЋнґбттlэdЍ_ + _ЋћгфттlёdЍ_ - _Џsзг ЋчpєЍ_ + _Цsёг ЋуpзЍ_ - _GцзsтЍ_ + _GџзsтЍ_ - _MёмвёяЍ_ + _MэмьзґЍ_ + + + _Gџёsт Џsзя пют sџppбятзdЍѝцз_ \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.resx b/Source/CompanyCommunicator.Common/Resources/Strings.resx index 2510b054f..dd5d3adda 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.resx @@ -256,4 +256,7 @@ Member + + Guest User not supported + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.ru-RU.resx b/Source/CompanyCommunicator.Common/Resources/Strings.ru-RU.resx index 6ab94d189..9c9bfd2e7 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.ru-RU.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.ru-RU.resx @@ -256,4 +256,7 @@ Участник + + Guest User not supported + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.zh-CN.resx b/Source/CompanyCommunicator.Common/Resources/Strings.zh-CN.resx index 1337511cd..e02d98ad4 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.zh-CN.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.zh-CN.resx @@ -256,4 +256,7 @@ 成员 + + Guest User not supported + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Resources/Strings.zh-TW.resx b/Source/CompanyCommunicator.Common/Resources/Strings.zh-TW.resx index aeea46cb6..4119fe310 100644 --- a/Source/CompanyCommunicator.Common/Resources/Strings.zh-TW.resx +++ b/Source/CompanyCommunicator.Common/Resources/Strings.zh-TW.resx @@ -256,4 +256,7 @@ 成員 + + Guest User not supported + \ No newline at end of file diff --git a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/AppManagerService.cs b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/AppManagerService.cs index 50b034f8e..41b5b58a4 100644 --- a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/AppManagerService.cs +++ b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/AppManagerService.cs @@ -9,9 +9,8 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGrap using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Microsoft.Graph; - using Microsoft.Teams.Apps.CompanyCommunicator.Common.Repositories.UserData; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Policies; /// /// Manage Teams Apps for a user or a team. @@ -51,17 +50,14 @@ public async Task InstallAppForUserAsync(string appId, string userId) }, }; - // Skip Guest users. - var user = await this.graphServiceClient.Users[userId].Request().GetAsync(); - if (string.Equals(user?.UserType, UserType.Member, StringComparison.OrdinalIgnoreCase)) - { + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + await retryPolicy.ExecuteAsync(async () => await this.graphServiceClient.Users[userId] - .Teamwork - .InstalledApps - .Request() - .WithMaxRetry(GraphConstants.MaxRetry) - .AddAsync(userScopeTeamsAppInstallation); - } + .Teamwork + .InstalledApps + .Request() + .WithMaxRetry(GraphConstants.MaxRetry) + .AddAsync(userScopeTeamsAppInstallation)); } /// @@ -85,11 +81,13 @@ public async Task InstallAppForTeamAsync(string appId, string teamId) }, }; - await this.graphServiceClient.Teams[teamId] - .InstalledApps - .Request() - .WithMaxRetry(GraphConstants.MaxRetry) - .AddAsync(userScopeTeamsAppInstallation); + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + await retryPolicy.ExecuteAsync(async () => + await this.graphServiceClient.Teams[teamId] + .InstalledApps + .Request() + .WithMaxRetry(GraphConstants.MaxRetry) + .AddAsync(userScopeTeamsAppInstallation)); } /// @@ -105,14 +103,16 @@ public async Task IsAppInstalledForUserAsync(string appId, string userId) throw new ArgumentNullException(nameof(userId)); } - var pagedApps = await this.graphServiceClient.Users[userId] - .Teamwork - .InstalledApps - .Request() - .Expand("teamsApp") - .Filter($"teamsApp/id eq '{appId}'") - .WithMaxRetry(GraphConstants.MaxRetry) - .GetAsync(); + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + var pagedApps = await retryPolicy.ExecuteAsync(async () => + await this.graphServiceClient.Users[userId] + .Teamwork + .InstalledApps + .Request() + .Expand("teamsApp") + .Filter($"teamsApp/id eq '{appId}'") + .WithMaxRetry(GraphConstants.MaxRetry) + .GetAsync()); return pagedApps.CurrentPage.Any(); } @@ -130,13 +130,15 @@ public async Task IsAppInstalledForTeamAsync(string appId, string teamId) throw new ArgumentNullException(nameof(teamId)); } - var pagedApps = await this.graphServiceClient.Teams[teamId] - .InstalledApps - .Request() - .Expand("teamsApp") - .Filter($"teamsApp/id eq '{appId}'") - .WithMaxRetry(GraphConstants.MaxRetry) - .GetAsync(); + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + var pagedApps = await retryPolicy.ExecuteAsync(async () => + await this.graphServiceClient.Teams[teamId] + .InstalledApps + .Request() + .Expand("teamsApp") + .Filter($"teamsApp/id eq '{appId}'") + .WithMaxRetry(GraphConstants.MaxRetry) + .GetAsync()); return pagedApps.CurrentPage.Any(); } @@ -154,14 +156,16 @@ public async Task GetAppInstallationIdForUserAsync(string appId, string throw new ArgumentNullException(nameof(userId)); } - var collection = await this.graphServiceClient.Users[userId] - .Teamwork - .InstalledApps - .Request() - .Expand("teamsApp") - .Filter($"teamsApp/id eq '{appId}'") - .WithMaxRetry(GraphConstants.MaxRetry) - .GetAsync(); + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + var collection = await retryPolicy.ExecuteAsync(async () => + await this.graphServiceClient.Users[userId] + .Teamwork + .InstalledApps + .Request() + .Expand("teamsApp") + .Filter($"teamsApp/id eq '{appId}'") + .WithMaxRetry(GraphConstants.MaxRetry) + .GetAsync()); return collection?.FirstOrDefault().Id; } @@ -179,13 +183,15 @@ public async Task GetAppInstallationIdForTeamAsync(string appId, string throw new ArgumentNullException(nameof(teamId)); } - var collection = await this.graphServiceClient.Teams[teamId] - .InstalledApps - .Request() - .Expand("teamsApp") - .Filter($"teamsApp/id eq '{appId}'") - .WithMaxRetry(GraphConstants.MaxRetry) - .GetAsync(); + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + var collection = await retryPolicy.ExecuteAsync(async () => + await this.graphServiceClient.Teams[teamId] + .InstalledApps + .Request() + .Expand("teamsApp") + .Filter($"teamsApp/id eq '{appId}'") + .WithMaxRetry(GraphConstants.MaxRetry) + .GetAsync()); return collection?.FirstOrDefault().Id; } diff --git a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/ChatsService.cs b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/ChatsService.cs index c441bedf1..45a5d95a4 100644 --- a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/ChatsService.cs +++ b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/TeamWork/ChatsService.cs @@ -8,6 +8,7 @@ namespace Microsoft.Teams.Apps.CompanyCommunicator.Common.Services.MicrosoftGrap using System; using System.Threading.Tasks; using Microsoft.Graph; + using Microsoft.Teams.Apps.CompanyCommunicator.Common.Policies; /// /// Chats Service. @@ -44,13 +45,14 @@ public async Task GetChatThreadIdAsync(string userId, string appId) } var installationId = await this.appManagerService.GetAppInstallationIdForUserAsync(appId, userId); - var chat = await this.graphServiceClient.Users[userId] + var retryPolicy = PollyPolicy.GetGraphRetryPolicy(GraphConstants.MaxRetry); + var chat = await retryPolicy.ExecuteAsync(async () => await this.graphServiceClient.Users[userId] .Teamwork .InstalledApps[installationId] .Chat .Request() .WithMaxRetry(GraphConstants.MaxRetry) - .GetAsync(); + .GetAsync()); return chat?.Id; } diff --git a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/Users/UsersService.cs b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/Users/UsersService.cs index 0a9fc28fd..95cd325b1 100644 --- a/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/Users/UsersService.cs +++ b/Source/CompanyCommunicator.Common/Services/MicrosoftGraph/Users/UsersService.cs @@ -133,6 +133,7 @@ public async Task GetUserAsync(string userId) user.Id, user.DisplayName, user.UserPrincipalName, + user.UserType, }) .WithMaxRetry(GraphConstants.MaxRetry) .GetAsync(); diff --git a/Source/CompanyCommunicator.Common/Services/User/UserTypeService.cs b/Source/CompanyCommunicator.Common/Services/User/UserTypeService.cs index 932786bf8..f03e501ad 100644 --- a/Source/CompanyCommunicator.Common/Services/User/UserTypeService.cs +++ b/Source/CompanyCommunicator.Common/Services/User/UserTypeService.cs @@ -99,7 +99,10 @@ await this.userDataRepository.InsertOrMergeAsync( PartitionKey = UserDataTableNames.UserDataPartition, RowKey = user.Id, AadId = user.Id, - UserType = user.UserType, + + // At times userType value from Graph response is null, to avoid null value + // using fallback logic to derive the userType from UserPrincipalName. + UserType = user.GetUserType(), }); } } diff --git a/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs b/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs index 137e52669..18985aba1 100644 --- a/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs +++ b/Source/CompanyCommunicator.Prep.Func/Export/Streams/DataStreamFacade.cs @@ -146,16 +146,26 @@ private async Task> CreateUserDataAsync( { var user = users. FirstOrDefault(user => user != null && user.Id.Equals(sentNotification.RowKey)); + string userType = sentNotification.UserType; + + // For version less than CC v4.1.2 fetch from graph or user data table. + if (string.IsNullOrEmpty(userType)) + { + var userDataEntity = await this.userDataRepository.GetAsync(UserDataTableNames.UserDataPartition, sentNotification.RowKey); + userType = userDataEntity.UserType; + if (user != null && string.IsNullOrEmpty(userType)) + { + // This is to set the UserType of the user. + await this.userTypeService.UpdateUserTypeForExistingUserAsync(userDataEntity, user.GetUserType()); + } + } - // This is to set the UserType of the user. - var userDataEntity = await this.userDataRepository.GetAsync(UserDataTableNames.UserDataPartition, sentNotification.RowKey); - await this.userTypeService.UpdateUserTypeForExistingUserAsync(userDataEntity, user?.UserType); var userData = new UserData { Id = sentNotification.RowKey, Name = user?.DisplayName ?? this.localizer.GetString("AdminConsentError"), Upn = user?.UserPrincipalName ?? this.localizer.GetString("AdminConsentError"), - UserType = this.localizer.GetString(user?.UserType ?? "AdminConsentError"), + UserType = this.localizer.GetString(userType ?? (user?.GetUserType() ?? "AdminConsentError")), DeliveryStatus = this.localizer.GetString(sentNotification.DeliveryStatus), StatusReason = this.GetStatusReason(sentNotification.ErrorMessage, sentNotification.StatusCode.ToString()), }; diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SendBatchMessagesActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SendBatchMessagesActivity.cs index d7502c3c0..1b958dc7d 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SendBatchMessagesActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SendBatchMessagesActivity.cs @@ -85,6 +85,7 @@ private RecipientData ConvertToRecipientData(SentNotificationDataEntity recipien ConversationId = recipient.ConversationId, ServiceUrl = recipient.ServiceUrl, TenantId = recipient.TenantId, + UserType = recipient.UserType, }, }; } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncAllUsersActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncAllUsersActivity.cs index 6a845d37a..72c265ca9 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncAllUsersActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncAllUsersActivity.cs @@ -81,13 +81,10 @@ public async Task RunAsync([ActivityTrigger] NotificationDataEntity notification // Get users. var users = await this.userDataRepository.GetAllAsync(); - // This is to set user type. + // This is to set UserType. await this.userTypeService.UpdateUserTypeForExistingUserListAsync(users); users = await this.userDataRepository.GetAllAsync(); - // Filter for only Members. - users = users?.Where(user => user.UserType.Equals(UserType.Member, StringComparison.OrdinalIgnoreCase)); - if (!users.IsNullOrEmpty()) { // Store in sent notification table. @@ -169,7 +166,7 @@ private async Task ProcessUserAsync(User user) } // skip Guest users. - if (string.Equals(user.UserType, UserType.Guest, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(user.GetUserType(), UserType.Guest, StringComparison.OrdinalIgnoreCase)) { return; } @@ -196,7 +193,10 @@ await this.userDataRepository.InsertOrMergeAsync( PartitionKey = UserDataTableNames.UserDataPartition, RowKey = user.Id, AadId = user.Id, - UserType = user.UserType, + + // At times userType value from Graph response is null, to avoid null value + // using fallback logic to derive the userType from UserPrincipalName. + UserType = user.GetUserType(), }); } } diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncGroupMembersActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncGroupMembersActivity.cs index 2040fc8e6..713d8d476 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncGroupMembersActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncGroupMembersActivity.cs @@ -129,19 +129,23 @@ await Task.WhenAll(users.ForEachAsync(maxParallelism, async user => // This is to set the type of user(exisiting only, new ones will be skipped) to identify later if it is member or guest. var userType = user.UserPrincipalName.GetUserType(); + if (userEntity == null && userType.Equals(UserType.Guest, StringComparison.OrdinalIgnoreCase)) + { + // Skip processing new Guest users. + return; + } + await this.userTypeService.UpdateUserTypeForExistingUserAsync(userEntity, userType); - if (userType.Equals(UserType.Member, StringComparison.OrdinalIgnoreCase)) + if (userEntity == null) { - if (userEntity == null) + userEntity = new UserDataEntity() { - userEntity = new UserDataEntity() - { - AadId = user.Id, - }; - } - - recipients.Add(userEntity.CreateInitialSentNotificationDataEntity(partitionKey: notificationId)); + AadId = user.Id, + UserType = userType, + }; } + + recipients.Add(userEntity.CreateInitialSentNotificationDataEntity(partitionKey: notificationId)); })); return recipients; diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncTeamMembersActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncTeamMembersActivity.cs index 3ea017b9d..f6fda7cdd 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncTeamMembersActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/SyncTeamMembersActivity.cs @@ -145,14 +145,16 @@ private async Task> GetRecipientsAsync(s await Task.WhenAll(users.ForEachAsync(maxParallelism, async user => { var userEntity = await this.userDataRepository.GetAsync(UserDataTableNames.UserDataPartition, user.AadId); + if (userEntity == null && user.UserType.Equals(UserType.Guest, StringComparison.OrdinalIgnoreCase)) + { + // Skip processing new Guest users. + return; + } // This is to set the type of user(exisiting only, new ones will be skipped) to identify later if it is member or guest. await this.userTypeService.UpdateUserTypeForExistingUserAsync(userEntity, user.UserType); - if (user.UserType.Equals(UserType.Member, StringComparison.OrdinalIgnoreCase)) - { - user.ConversationId ??= userEntity?.ConversationId; - recipients.Add(user.CreateInitialSentNotificationDataEntity(partitionKey: notificationId)); - } + user.ConversationId ??= userEntity?.ConversationId; + recipients.Add(user.CreateInitialSentNotificationDataEntity(partitionKey: notificationId)); })); return recipients; diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs index ccbe5f36c..202b60925 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Activities/TeamsConversationActivity.cs @@ -108,6 +108,16 @@ public async Task CreateConversationAsync( return; } + // Skip Guest users. + if (string.IsNullOrEmpty(recipient.UserType)) + { + throw new ArgumentNullException(nameof(recipient.UserType)); + } + else if (recipient.UserType.Equals(UserType.Guest, StringComparison.OrdinalIgnoreCase)) + { + return; + } + // create conversation. string conversationId; if (!string.IsNullOrEmpty(recipient.UserId)) diff --git a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Extensions/UserDataEntityExtensions.cs b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Extensions/UserDataEntityExtensions.cs index 76e68d768..3685ab4c7 100644 --- a/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Extensions/UserDataEntityExtensions.cs +++ b/Source/CompanyCommunicator.Prep.Func/PreparingToSend/Extensions/UserDataEntityExtensions.cs @@ -36,6 +36,7 @@ public static SentNotificationDataEntity CreateInitialSentNotificationDataEntity TenantId = userDataEntity.TenantId, UserId = userDataEntity.UserId, ServiceUrl = userDataEntity.ServiceUrl, + UserType = userDataEntity.UserType, }; } } diff --git a/Source/CompanyCommunicator.Send.Func/SendFunction.cs b/Source/CompanyCommunicator.Send.Func/SendFunction.cs index ec5de8302..b663439d0 100644 --- a/Source/CompanyCommunicator.Send.Func/SendFunction.cs +++ b/Source/CompanyCommunicator.Send.Func/SendFunction.cs @@ -18,6 +18,7 @@ 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; @@ -105,6 +106,19 @@ public async Task Run( try { + // Check if recipient is a guest user. + if (messageContent.IsRecipientGuestUser()) + { + await this.notificationService.UpdateSentNotification( + notificationId: messageContent.NotificationId, + recipientId: messageContent.RecipientData.RecipientId, + totalNumberOfSendThrottles: 0, + statusCode: SentNotificationDataEntity.NotSupportedStatusCode, + allSendStatusCodes: $"{SentNotificationDataEntity.NotSupportedStatusCode},", + errorMessage: this.localizer.GetString("GuestUserNotSupported")); + return; + } + // Check if notification is pending. var isPending = await this.notificationService.IsPendingNotification(messageContent); if (!isPending) 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 d148f658e..a5cac354c 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/Export/Streams/DataStreamFacadeTest.cs @@ -129,6 +129,9 @@ public async Task Get_BatchByUserIdsSevice_ShouldInvokeAtleastOnce() this.sentNotificationDataRepository .Setup(x => x.GetStreamsAsync(this.notificationId, null)) .Returns(this.sentNotificationDataList.ToAsyncEnumerable()); + this.userDataRepository + .Setup(x => x.GetAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UserDataEntity()); this.usersService .Setup(x => x.GetBatchByUserIds(It.IsAny>>())) @@ -186,6 +189,9 @@ public async Task GetUsersData_CorrectMapping_ReturnsUserDataObject() .Setup(x => x.GetStreamsAsync(this.notificationId, null)) .Returns(this.sentNotificationDataList.ToAsyncEnumerable()); var sendNotificationData = this.sentNotificationDataList.Select(x => x.Where(y => y.RowKey == "RowKey").FirstOrDefault()).FirstOrDefault(); + this.userDataRepository + .Setup(x => x.GetAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UserDataEntity()); this.usersService .Setup(x => x.GetBatchByUserIds(It.IsAny>>())) .ReturnsAsync(userDataList); @@ -233,6 +239,9 @@ public async Task Get_ForbiddenGraphPermission_ReturnsAdminConsentError() this.sentNotificationDataRepository .Setup(x => x.GetStreamsAsync(this.notificationId, null)) .Returns(this.sentNotificationDataWithErrorList.ToAsyncEnumerable()); + this.userDataRepository + .Setup(x => x.GetAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UserDataEntity()); var sendNotificationData = this.sentNotificationDataWithErrorList.Select(x => x.Where(y => y.RowKey == "RowKey").FirstOrDefault()).FirstOrDefault(); this.usersService .Setup(x => x.GetBatchByUserIds(It.IsAny>>())) @@ -265,7 +274,9 @@ public async Task Get_UserStatusReason_withErrorStatus() .Setup(x => x.GetStreamsAsync(this.notificationId, null)) .Returns(this.sentNotificationDataWithErrorList.ToAsyncEnumerable()); var sendNotificationData = this.sentNotificationDataWithErrorList.Select(x => x.Where(y => y.RowKey == "RowKey").FirstOrDefault()).FirstOrDefault(); - + this.userDataRepository + .Setup(x => x.GetAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new UserDataEntity()); this.usersService .Setup(x => x.GetBatchByUserIds(It.IsAny>>())) .ReturnsAsync(userDataList); diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncAllUsersActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncAllUsersActivityTest.cs index 82e192815..43f28b15d 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncAllUsersActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncAllUsersActivityTest.cs @@ -113,7 +113,7 @@ public async Task SyncAllUsers_OnlyMemberTypeUsers_ShouldBeSavedInSentNotificati // Assert await task.Should().NotThrowAsync(); - this.userDataRepository.Verify(x => x.InsertOrMergeAsync(It.Is(x => x.RowKey == tuple.Item1.FirstOrDefault().Id))); + this.userDataRepository.Verify(x => x.InsertOrMergeAsync(It.Is(x => x.RowKey == tuple.Item1.FirstOrDefault().Id)), Times.AtLeastOnce); this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 2)), Times.Once); } @@ -166,23 +166,22 @@ public async Task SyncAllUsers_GuestUsersFromGraph_ShouldNotBeSavedInTable() // Assert await task.Should().NotThrowAsync(); this.userDataRepository.Verify(x => x.InsertOrMergeAsync(It.IsAny()), Times.Never); - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 1)), Times.Once); } /// - /// Test case to verify both guest users from Graph and existing guest users gets filtered and not stored in sentNotificatinData table. + /// Test case to verify existing guest users gets stored in sentNotificatinData table. /// /// A task that represents the work queued to execute. [Fact] - public async Task SyncAllUsers_AllGuestUsers_ShouldNeverBeSavedInTable() + public async Task SyncAllUsers_AllGuestUsersFromDB_ShouldBeSavedInTable() { // Arrange var activityContext = this.GetSyncAllUsersActivity(); string deltaLink = "deltaLink"; IEnumerable userDataResponse = new List() { - new UserDataEntity() { Name = string.Empty, UserType = UserType.Guest }, - new UserDataEntity() { Name = string.Empty, UserType = UserType.Guest }, + new UserDataEntity() { Name = "user1", UserType = UserType.Member }, + new UserDataEntity() { Name = "user2", UserType = UserType.Guest }, }; NotificationDataEntity notification = new NotificationDataEntity() { @@ -213,11 +212,11 @@ public async Task SyncAllUsers_AllGuestUsers_ShouldNeverBeSavedInTable() this.sentNotificationDataRepository.Setup(x => x.BatchInsertOrMergeAsync(It.IsAny>())); // Act - await activityContext.RunAsync(notification, this.logger.Object); + Func task = async () => await activityContext.RunAsync(notification, this.logger.Object); // Assert - this.userDataRepository.Verify(x => x.InsertOrMergeAsync(It.IsAny()), Times.Never); - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.IsAny>()), Times.Never); + await task.Should().NotThrowAsync(); + this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 2)), Times.Once); } /// @@ -379,7 +378,7 @@ public async Task SyncAllUsers_NoTeamsLicense_NeverGetSavedInTable() new UserDataEntity() { Name = "user1", UserType = UserType.Guest }, }; var notification = new NotificationDataEntity() { Id = "notificationId1" }; - var tuple = (new List() { new User() { Id = "101" } }, string.Empty); + var tuple = (new List() { new User() { Id = "101", UserType = UserType.Member } }, string.Empty); this.userDataRepository .Setup(x => x.GetDeltaLinkAsync()) diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncGroupMembersActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncGroupMembersActivityTest.cs index e95637bca..c6ea46572 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncGroupMembersActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncGroupMembersActivityTest.cs @@ -223,11 +223,11 @@ public async Task SyncGroupMembers_OnlyMemberExistingUserType_StoreInSentNotific } /// - /// Test case to verify that existing Guest Users never gets saved in Sent Notification Table. + /// Test case to verify that existing Guest Users gets saved in Sent Notification Table. /// /// A task that represents the work queued to execute. [Fact] - public async Task SyncGroupMembers_OnlyGuestExistingUsersType_NeverStoreInSentNotificationTable() + public async Task SyncGroupMembers_OnlyGuestExistingUsersType_ShouldStoreInSentNotificationTable() { // Arrange var groupId = "Group1"; @@ -261,16 +261,15 @@ public async Task SyncGroupMembers_OnlyGuestExistingUsersType_NeverStoreInSentNo // Assert await task.Should().NotThrowAsync(); - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.IsAny>()), Times.Never); + this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.IsAny>()), Times.Once); } /// - /// Test case to verify that only Member user type is filtered from list of existing Member user and Guest user, - /// and is saved in Sent Notification Table. + /// Test case to verify that both existing user is saved in Sent Notification Table. /// /// A task that represents the work queued to execute. [Fact] - public async Task SyncGroupMembers_BothUserTypeForExistingUser_StoreOnlyMemberUserType() + public async Task SyncGroupMembers_BothUserTypeForExistingUser_ShouldStoreInSentNotificationTable() { // Arrange var groupId = "Group1"; @@ -305,7 +304,7 @@ public async Task SyncGroupMembers_BothUserTypeForExistingUser_StoreOnlyMemberUs // Assert await task.Should().NotThrowAsync(); - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 1)), Times.Once); + this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 2)), Times.Once); } /// diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncTeamMembersActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncTeamMembersActivityTest.cs index 1770782f6..78279738f 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncTeamMembersActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/SyncTeamMembersActivityTest.cs @@ -105,11 +105,11 @@ public async Task SyncTeamMembers_OnlyExistingMemberUser_StoreInSentNotification } /// - /// Test case to verify that existing Guest Users never gets saved in Sent Notification Table. + /// Test case to verify that existing Guest Users gets saved in Sent Notification Table. /// /// A task that represents the work queued to execute. [Fact] - public async Task SyncTeamMembers_OnlyExistingGuestUser_NeverStoreInSentNotificationTable() + public async Task SyncTeamMembers_OnlyExistingGuestUser_StoreInSentNotificationTable() { // Arrange var activityContext = this.GetSyncTeamMembersActivity(); @@ -137,16 +137,16 @@ public async Task SyncTeamMembers_OnlyExistingGuestUser_NeverStoreInSentNotifica await activityContext.RunAsync((this.notificationId, this.teamId), this.logger.Object); // Assert - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.IsAny>()), Times.Never); + this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.IsAny>()), Times.Once); } /// - /// Test case to verify that only Member user type is filtered from list of existing Member user and Guest user, - /// and is saved in Sent Notification Table. + /// Test case to verify that both user type i.e. existing Member user and Guest user + /// is saved in Sent Notification Table. /// /// A task that represents the work queued to execute. [Fact] - public async Task SyncTeamMembers_BothUserTypeForExistingUser_StoreOnlyMemberUserType() + public async Task SyncTeamMembers_BothUserTypeForExistingUser_ShouldStoreBothUserType() { // Arrange var activityContext = this.GetSyncTeamMembersActivity(); @@ -175,7 +175,7 @@ public async Task SyncTeamMembers_BothUserTypeForExistingUser_StoreOnlyMemberUse await activityContext.RunAsync((this.notificationId, this.teamId), this.logger.Object); // Assert - this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 1)), Times.Once); + this.sentNotificationDataRepository.Verify(x => x.BatchInsertOrMergeAsync(It.Is>(l => l.Count() == 2)), Times.Once); } /// diff --git a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/TeamsConversationActivityTest.cs b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/TeamsConversationActivityTest.cs index a845d2cb5..6f1173aef 100644 --- a/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/TeamsConversationActivityTest.cs +++ b/Source/Test/CompanyCommunicator.Prep.Func.Test/PreparingToSend/Activities/TeamsConversationActivityTest.cs @@ -107,6 +107,7 @@ public async Task CreateConversationAsync() UserId = "userId", TenantId = "tenantId", ServiceUrl = "serviceUrl", + UserType = "Member", }; CreateConversationResponse response = new CreateConversationResponse() { @@ -154,6 +155,7 @@ public async Task CreateConversationAsync_UserIdNullOrEmpty() { UserId = string.Empty, RecipientId = "recipientId", + UserType = "Member", }; // Act @@ -163,6 +165,55 @@ public async Task CreateConversationAsync_UserIdNullOrEmpty() await task.Should().NotThrowAsync(); } + /// + /// Test case to verify that do not process anything in case of guest user. + /// + /// A task that represents the work queued to execute. + [Fact] + public async Task TeamConversation_GuestUser_ShouldNotDoAnything() + { + // Arrange + var activityContext = this.GetTeamsConversationActivity(true/*proactivelyInstallUserApp*/); + var notificationId = "notificationId"; + SentNotificationDataEntity recipient = new SentNotificationDataEntity() + { + UserId = string.Empty, + RecipientId = "recipientId", + UserType = "Guest", + }; + + // Act + Func task = async () => await activityContext.CreateConversationAsync((notificationId, recipient), this.logger.Object); + + // Assert + await task.Should().NotThrowAsync(); + this.appSettingsService.Verify(x => x.GetUserAppIdAsync(), Times.Never); + } + + /// + /// Test case to verify that exception is thrown in case of null user type. + /// + /// A task that represents the work queued to execute. + [Fact] + public async Task TeamConversation_NullUserType_ShouldThrowException() + { + // Arrange + var activityContext = this.GetTeamsConversationActivity(false/*proactivelyInstallUserApp*/); + var notificationId = "notificationId"; + SentNotificationDataEntity recipient = new SentNotificationDataEntity() + { + UserId = string.Empty, + RecipientId = "recipientId", + UserType = null, + }; + + // Act + Func task = async () => await activityContext.CreateConversationAsync((notificationId, recipient), this.logger.Object); + + // Assert + await task.Should().ThrowAsync(); + } + /// /// Create Conversation check when Proactive app installation flag enabled. ConversationId is empty. /// @@ -180,6 +231,7 @@ public async Task ProactiveAppInstallationEnabledTest() { UserId = string.Empty, RecipientId = "recipientId", + UserType = "Member", }; this.appSettingsService diff --git a/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs b/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs index 34f98d3ba..9220c5b08 100644 --- a/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs +++ b/Source/Test/CompanyCommunicator.Send.Func.Test/SendFunctionTest.cs @@ -72,8 +72,11 @@ public async Task PendingSendNotificationTest() // Arrange // Notification is already sent or failed var sendFunctionInstance = this.GetSendFunction(); - string data = "{\"NotificationId\":\"notificationId\"}"; + 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())) + .Returns(Task.CompletedTask); // Act Func task = async () => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext()); @@ -92,7 +95,7 @@ public async Task SendNotificationWhenNoConversationIdTest() { // Arrange var sendFunctionInstance = this.GetSendFunction(); - string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"\"}}}"; + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"\",\"UserType\":\"Member\"}}}"; SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); this.notificationService .Setup(x => x.IsPendingNotification(It.IsAny())) @@ -106,7 +109,46 @@ 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())); + this.notificationService.Verify(x => x.UpdateSentNotification(It.Is(x => x.Equals("notificationId")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + + /// + /// Test for send Notification in case of a guest user. + /// + /// representing the asynchronous operation. + [Fact] + public async Task SendFunc_GuestUser_ShouldNotSendMessage() + { + // Arrange + var sendFunctionInstance = this.GetSendFunction(); + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Guest\"}}}"; + SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); + + // Act + Func task = async () => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext()); + + // 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); + } + + /// + /// Test for send Notification when userType is set as null. + /// + /// representing the asynchronous operation. + [Fact] + public async Task SendFunc_NullUserType_ShouldThrowArgumentNullException() + { + // Arrange + var sendFunctionInstance = this.GetSendFunction(); + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"\"}}}"; + SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); + + // Act + Func task = async () => await sendFunctionInstance.Run(data, this.deliveryCount, this.dateTime, string.Empty, this.logger.Object, new ExecutionContext()); + + // Assert + await task.Should().ThrowAsync(); } /// @@ -119,7 +161,7 @@ public async Task Re_QueueSendNotificationWithDelayTest() // Arrange // SendNotificationThrottled var sendFunctionInstance = this.GetSendFunction(); - string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\"}}}"; + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}"; SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); this.notificationService .Setup(x => x.IsPendingNotification(It.IsAny())) @@ -153,7 +195,7 @@ public async Task SendNotificationSuccess_Test() { ResultType = SendMessageResult.Succeeded, }; - string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\"}}}"; + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}"; SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); this.notificationService .Setup(x => x.IsPendingNotification(It.IsAny())) @@ -193,7 +235,7 @@ public async Task SendNotificationResponseThrottledTest() { ResultType = SendMessageResult.Throttled, }; - string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\"}}}"; + string data = "{\"NotificationId\":\"notificationId\",\"RecipientData\": {\"RecipientId\" : \"TestResp\", \"UserData\": { \"UserId\" : \"userId\",\"ConversationId\":\"conversationId\",\"UserType\":\"Member\"}}}"; SendQueueMessageContent messageContent = JsonConvert.DeserializeObject(data); this.notificationService .Setup(x => x.IsPendingNotification(It.IsAny()))