-
Notifications
You must be signed in to change notification settings - Fork 333
Fs 897 partial success for list users #3117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
7ce12e0
fd50d79
5a16c53
761f2a5
f12495d
d00881d
a734dab
2e17c53
8b92905
e171a3c
0b99b16
2a1f51f
9e274cc
9abda4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Adding a new version of /list-users that allows for partial success. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,7 @@ import qualified Wire.API.Connection as Public | |
| import Wire.API.Error | ||
| import qualified Wire.API.Error.Brig as E | ||
| import Wire.API.Federation.API | ||
| import Wire.API.Federation.Error | ||
| import qualified Wire.API.Properties as Public | ||
| import qualified Wire.API.Routes.Internal.Brig as BrigInternalAPI | ||
| import qualified Wire.API.Routes.Internal.Cannon as CannonInternalAPI | ||
|
|
@@ -241,6 +242,7 @@ servantSitemap = | |
| :<|> Named @"get-user-by-handle-qualified" (callsFed (exposeAnnotations Handle.getHandleInfo)) | ||
| :<|> Named @"list-users-by-unqualified-ids-or-handles" (callsFed (exposeAnnotations listUsersByUnqualifiedIdsOrHandles)) | ||
| :<|> Named @"list-users-by-ids-or-handles" (callsFed (exposeAnnotations listUsersByIdsOrHandles)) | ||
| :<|> Named @"list-users-by-ids-or-handles@V3" (callsFed (exposeAnnotations listUsersByIdsOrHandlesV3)) | ||
| :<|> Named @"send-verification-code" sendVerificationCode | ||
| :<|> Named @"get-rich-info" getRichInfo | ||
|
|
||
|
|
@@ -738,6 +740,40 @@ listUsersByIdsOrHandles self q = do | |
| byIds :: Local UserId -> [Qualified UserId] -> (Handler r) [Public.UserProfile] | ||
| byIds lself uids = API.lookupProfiles lself uids !>> fedError | ||
|
|
||
| -- Similar to listUsersByIdsOrHandles, except that it allows partial successes | ||
| -- using a new return type | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is that the new field is optional. If that is so, then its addition makes the response backwards-compatible. I understand that the new endpoint will not throw for unavailable remote backends, but cannot you bake this into the existing handler? The old endpoint versions wouldn't fail anymore, and they might end up providing an additional field in response, but clients should ignore that field by contract because the field wasn't there when Swaggers for the client API versions were finalised.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It isn't backwards compatible because it introduces a new layer of structure to the response. Previously it was a top level list with nowhere to add a second list, now it is an object containing at least one list, and optionally a second.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, right, it's not backwards compatible in terms of JSON. Nevertheless, I believe the handler can be generalised and reused for both endpoint versions. Or at least factoring out the common bits would be good to have.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated these functions to pull out the common bits |
||
| listUsersByIdsOrHandlesV3 :: | ||
|
lepsa marked this conversation as resolved.
Outdated
|
||
| forall r. | ||
| ( Member GalleyProvider r, | ||
| Member (Concurrency 'Unsafe) r | ||
| ) => | ||
| UserId -> | ||
| Public.ListUsersQuery -> | ||
| Handler r ListUsersById | ||
| listUsersByIdsOrHandlesV3 self q = do | ||
| lself <- qualifyLocal self | ||
| (errors, foundUsers) <- case q of | ||
| Public.ListUsersByIds us -> | ||
| byIds lself us | ||
| Public.ListUsersByHandles hs -> do | ||
| let (localHandles, _) = partitionQualified lself (fromRange hs) | ||
| us <- getIds localHandles | ||
| (l, r) <- byIds lself us | ||
| r' <- Handle.filterHandleResults lself r | ||
| pure (l, r') | ||
| pure $ ListUsersById foundUsers $ if null errors then Nothing else pure $ fst <$> errors | ||
| where | ||
| getIds :: [Handle] -> Handler r [Qualified UserId] | ||
| getIds localHandles = do | ||
| localUsers <- catMaybes <$> traverse (lift . wrapClient . API.lookupHandle) localHandles | ||
| domain <- viewFederationDomain | ||
| pure $ map (`Qualified` domain) localUsers | ||
| byIds :: | ||
| Local UserId -> | ||
| [Qualified UserId] -> | ||
| Handler r ([(Qualified UserId, FederationError)], [Public.UserProfile]) | ||
| byIds lself uids = lift (API.lookupProfilesV3 lself uids) !>> fedError | ||
|
|
||
| newtype GetActivationCodeResp | ||
| = GetActivationCodeResp (Public.ActivationKey, Public.ActivationCode) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,7 @@ import Data.Domain | |
| import Data.Handle | ||
| import Data.Id hiding (client) | ||
| import Data.Json.Util (fromUTCTimeMillis) | ||
| import Data.LegalHold | ||
| import Data.List1 (singleton) | ||
| import qualified Data.List1 as List1 | ||
| import Data.Misc (PlainTextPassword (..)) | ||
|
|
@@ -131,6 +132,7 @@ tests _ at opts p b c ch g aws userJournalWatcher = | |
| test p "head /users/:domain/:uid - 200" $ testUserExists b, | ||
| test p "head /users/:domain/:uid - 404" $ testUserDoesNotExist b, | ||
| test p "post /list-users - 200" $ testMultipleUsers b, | ||
| test p "post /list-users@v3 - 200" $ testMultipleUsersV3 opts b, | ||
| test p "put /self - 200" $ testUserUpdate b c userJournalWatcher, | ||
| test p "put /access/self/email - 2xx" $ testEmailUpdate b userJournalWatcher, | ||
| test p "put /self/phone - 202" $ testPhoneUpdate b, | ||
|
|
@@ -773,7 +775,8 @@ testMultipleUsers brig = do | |
| (Just $ userDisplayName u3, Nothing) | ||
| ] | ||
| post | ||
| ( brig | ||
| ( apiVersion "v2" | ||
|
lepsa marked this conversation as resolved.
Outdated
|
||
| . brig | ||
| . zUser (userId u1) | ||
| . contentJson | ||
| . path "list-users" | ||
|
|
@@ -790,6 +793,73 @@ testMultipleUsers brig = do | |
| field :: FromJSON a => Key -> Value -> Maybe a | ||
| field f u = u ^? key f >>= maybeFromJSON | ||
|
|
||
| testMultipleUsersV3 :: Opt.Opts -> Brig -> Http () | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this one be called |
||
| testMultipleUsersV3 opts brig = do | ||
| u1 <- randomUser brig | ||
| u2 <- randomUser brig | ||
| u3 <- createAnonUser "a" brig | ||
| -- A remote user that can't be listed | ||
| u4 <- Qualified <$> randomId <*> pure (Domain "far-away.example.com") | ||
| -- A remote user that can be listed | ||
| let evenFurtherAway = Domain "even-further-away.example.com" | ||
| u5 <- Qualified <$> randomId <*> pure evenFurtherAway | ||
| let u5Profile = | ||
| UserProfile | ||
| { profileQualifiedId = u5, | ||
| profileName = Name "u5", | ||
| profilePict = Pict [], | ||
| profileAssets = [], | ||
| profileAccentId = ColourId 0, | ||
| profileDeleted = False, | ||
| profileService = Nothing, | ||
| profileHandle = Nothing, | ||
| profileExpire = Nothing, | ||
| profileTeam = Nothing, | ||
| profileEmail = Nothing, | ||
| profileLegalholdStatus = UserLegalHoldDisabled | ||
| } | ||
| users = [u1, u2, u3] | ||
| q = ListUsersByIds $ u5 : u4 : map userQualifiedId users | ||
| expected = | ||
| Set.fromList | ||
| [ (Just $ userDisplayName u1, Nothing :: Maybe Email), | ||
| (Just $ userDisplayName u2, Nothing), | ||
| (Just $ userDisplayName u3, Nothing), | ||
| (Just $ profileName u5Profile, profileEmail u5Profile) | ||
| ] | ||
| expectedFailed = Set.fromList [u4] | ||
|
|
||
| let fedMockResponse req = do | ||
| -- Check that our allowed remote user is being asked for | ||
| if frTargetDomain req == evenFurtherAway | ||
| then -- Return the data for u5 | ||
| pure $ encode [u5Profile] | ||
| else -- Otherwise mock an unavailable federation server | ||
| throw $ MockErrorResponse Http.status500 "Down for maintenance" | ||
| -- Galley isn't needed, but this is what mock federators are available. | ||
| galleyHandler _ = error "not mocked" | ||
| (response, _rpcCalls, _galleyCalls) <- liftIO $ | ||
| withMockedFederatorAndGalley opts (Domain "example.com") fedMockResponse galleyHandler $ do | ||
| post | ||
| ( brig | ||
| . zUser (userId u1) | ||
| . contentJson | ||
| . path "list-users" | ||
| . body (RequestBodyLBS (Aeson.encode q)) | ||
| ) | ||
|
|
||
| pure response !!! do | ||
| const 200 === statusCode | ||
| const (Just expected) === result | ||
| const (pure $ pure expectedFailed) === resultFailed | ||
| where | ||
| result r = | ||
| Set.fromList | ||
| . map (\u -> (pure $ profileName u, profileEmail u)) | ||
| . listUsersByIdFound | ||
| <$> responseJsonMaybe r | ||
| resultFailed r = fmap Set.fromList . listUsersByIdFailed <$> responseJsonMaybe r | ||
|
|
||
| testCreateUserAnonExpiry :: Brig -> Http () | ||
| testCreateUserAnonExpiry b = do | ||
| u1 <- randomUser b | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.