# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. package Bugzilla::Extension::PhabBugz::User; use 5.10.1; use Moo; use Bugzilla::User; use Bugzilla::Types qw(:types); use Bugzilla::Extension::PhabBugz::Util qw(request); use List::Util qw(first); use Types::Standard -all; use Type::Utils; use Type::Params qw(compile); ######################### # Initialization # ######################### has 'id' => ( is => 'ro', isa => Int ); has 'type' => ( is => 'ro', isa => Str ); has 'phid' => ( is => 'ro', isa => Str ); has 'name' => ( is => 'ro', isa => Str ); has 'realname' => ( is => 'ro', isa => Str ); has 'creation_ts' => ( is => 'ro', isa => Int ); has 'modification_ts' => ( is => 'ro', isa => Int ); has 'roles' => ( is => 'ro', isa => ArrayRef [Str] ); has 'view_policy' => ( is => 'ro', isa => Str ); has 'edit_policy' => ( is => 'ro', isa => Str ); has 'bugzilla_id' => ( is => 'ro', isa => Maybe [Int] ); has 'bugzilla_user' => ( is => 'lazy', isa => Maybe [User] ); my $Invocant = class_type { class => __PACKAGE__ }; sub BUILDARGS { my ( $class, $params ) = @_; $params->{name} = $params->{fields}->{username}; $params->{realname} = $params->{fields}->{realName}; $params->{creation_ts} = $params->{fields}->{dateCreated}; $params->{modification_ts} = $params->{fields}->{dateModified}; $params->{roles} = $params->{fields}->{roles}; $params->{view_policy} = $params->{fields}->{policy}->{view}; $params->{edit_policy} = $params->{fields}->{policy}->{edit}; delete $params->{fields}; my $external_accounts = $params->{attachments}{'external-accounts'}{'external-accounts'}; if ($external_accounts) { my $bug_user = first { $_->{type} eq 'bmo' } @$external_accounts; $params->{bugzilla_id} = $bug_user->{id}; } delete $params->{attachments}; return $params; } # { # "data": [ # { # "id": 2, # "type": "USER", # "phid": "PHID-USER-h4aqihzqsnytz7nsegsr", # "fields": { # "username": "phab-bot", # "realName": "Phabricator Automation", # "roles": [ # "admin", # "verified", # "approved", # "activated" # ], # "dateCreated": 1512573120, # "dateModified": 1512574523, # "policy": { # "view": "public", # "edit": "no-one" # } # }, # "attachments": { # "external-accounts": { # "external-accounts": [ # { # "id": "9", # "type": "bmo" # } # ] # } # } # } # ], # "maps": {}, # "query": { # "queryKey": null # }, # "cursor": { # "limit": 100, # "after": null, # "before": null, # "order": null # } # } sub new_from_query { my ( $class, $params ) = @_; my $matches = $class->match($params); return $matches->[0]; } sub match { state $check = compile( $Invocant | ClassName, Dict[ ids => ArrayRef[Int] ] | Dict[ phids => ArrayRef[Str] ] ); my ( $class, $params ) = $check->(@_); # BMO id search takes precedence if bugzilla_ids is used. my $bugzilla_ids = delete $params->{ids}; if ($bugzilla_ids) { my $bugzilla_data = $class->get_phab_bugzilla_ids( { ids => $bugzilla_ids } ); $params->{phids} = [ map { $_->{phid} } @$bugzilla_data ]; } return [] if !@{ $params->{phids} }; # Look for BMO external user id in external-accounts attachment my $data = { constraints => { phids => $params->{phids} }, attachments => { 'external-accounts' => 1 } }; # We can only fetch 100 users at a time so we need to do this in lumps my $phab_users = []; my $result; do { $result = request( 'user.search', $data )->{result}; if ( exists $result->{data} && @{ $result->{data} } ) { foreach my $user ( @{ $result->{data} } ) { push @$phab_users, $class->new($user); } } $data->{after} = $result->{cursor}->{after}; } while ($result->{cursor}->{after}); return $phab_users; } ################# # Accessors # ################# sub _build_bugzilla_user { my ($self) = @_; return undef unless $self->bugzilla_id; return Bugzilla::User->new( { id => $self->bugzilla_id, cache => 1 } ); } sub get_phab_bugzilla_ids { state $check = compile($Invocant | ClassName, Dict[ids => ArrayRef[Int]]); my ( $class, $params ) = $check->(@_); my $memcache = Bugzilla->memcached; # Try to find the values in memcache first my @results; my %bugzilla_ids = map { $_ => 1 } @{ $params->{ids} }; foreach my $bugzilla_id ( keys %bugzilla_ids ) { my $phid = $memcache->get( { key => "phab_user_bugzilla_id_" . $bugzilla_id } ); if ($phid) { push @results, { id => $bugzilla_id, phid => $phid }; delete $bugzilla_ids{$bugzilla_id}; } } if (%bugzilla_ids) { $params->{ids} = [ keys %bugzilla_ids ]; my $result = request( 'bugzilla.account.search', $params ); # Store new values in memcache for later retrieval foreach my $user ( @{ $result->{result} } ) { next if !$user->{phid}; $memcache->set( { key => "phab_user_bugzilla_id_" . $user->{id}, value => $user->{phid} } ); push @results, $user; } } return \@results; } 1;