# This Source Code Form is hasject 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::Revision; use 5.10.1; use Moo; use Scalar::Util qw(blessed); use Types::Standard -all; use Type::Utils; use Bugzilla::Bug; use Bugzilla::Types qw(JSONBool); use Bugzilla::Error; use Bugzilla::Util qw(trim); use Bugzilla::Extension::PhabBugz::Project; use Bugzilla::Extension::PhabBugz::User; use Bugzilla::Extension::PhabBugz::Types qw(:types); use Bugzilla::Extension::PhabBugz::Util qw(request); ######################### # Initialization # ######################### has id => ( is => 'ro', isa => Int ); has phid => ( is => 'ro', isa => Str ); has title => ( is => 'ro', isa => Str ); has summary => ( is => 'ro', isa => Str ); has status => ( is => 'ro', isa => Str ); has creation_ts => ( is => 'ro', isa => Str ); has modification_ts => ( is => 'ro', isa => Str ); has author_phid => ( is => 'ro', isa => Str ); has bug_id => ( is => 'ro', isa => Str ); has view_policy => ( is => 'ro', isa => Str ); has edit_policy => ( is => 'ro', isa => Str ); has subscriber_count => ( is => 'ro', isa => Int ); has bug => ( is => 'lazy', isa => Object ); has author => ( is => 'lazy', isa => Object ); has reviews => ( is => 'lazy', isa => ArrayRef [ Dict [ user => PhabUser, status => Str ] ] ); has subscribers => ( is => 'lazy', isa => ArrayRef [PhabUser] ); has projects => ( is => 'lazy', isa => ArrayRef [Project] ); has reviewers_raw => ( is => 'ro', isa => ArrayRef [ Dict [ reviewerPHID => Str, status => Str, isBlocking => Bool | JSONBool, actorPHID => Maybe [Str], ], ] ); has subscribers_raw => ( is => 'ro', isa => Dict [ subscriberPHIDs => ArrayRef [Str], subscriberCount => Int, viewerIsSubscribed => Bool | JSONBool, ] ); has projects_raw => ( is => 'ro', isa => Dict [ projectPHIDs => ArrayRef [Str] ] ); sub new_from_query { my ( $class, $params ) = @_; my $data = { queryKey => 'all', attachments => { projects => 1, reviewers => 1, subscribers => 1 }, constraints => $params }; my $result = request( 'differential.revision.search', $data ); if ( exists $result->{result}{data} && @{ $result->{result}{data} } ) { $result = $result->{result}{data}[0]; # Some values in Phabricator for bug ids may have been saved # white whitespace so we remove any here just in case. $result->{fields}->{'bugzilla.bug-id'} = $result->{fields}->{'bugzilla.bug-id'} ? trim( $result->{fields}->{'bugzilla.bug-id'} ) : ""; return $class->new($result); } return undef; } sub BUILDARGS { my ( $class, $params ) = @_; $params->{title} = $params->{fields}->{title}; $params->{summary} = $params->{fields}->{summary}; $params->{status} = $params->{fields}->{status}->{value}; $params->{creation_ts} = $params->{fields}->{dateCreated}; $params->{modification_ts} = $params->{fields}->{dateModified}; $params->{author_phid} = $params->{fields}->{authorPHID}; $params->{bug_id} = $params->{fields}->{'bugzilla.bug-id'}; $params->{view_policy} = $params->{fields}->{policy}->{view}; $params->{edit_policy} = $params->{fields}->{policy}->{edit}; $params->{reviewers_raw} = $params->{attachments}->{reviewers}->{reviewers} // []; $params->{subscribers_raw} = $params->{attachments}->{subscribers}; $params->{projects_raw} = $params->{attachments}->{projects}; $params->{subscriber_count} = $params->{attachments}->{subscribers}->{subscriberCount}; delete $params->{fields}; delete $params->{attachments}; return $params; } # { # "data": [ # { # "id": 25, # "type": "DREV", # "phid": "PHID-DREV-uozm3ggfp7e7uoqegmc3", # "fields": { # "title": "Added .arcconfig", # "authorPHID": "PHID-USER-4wigy3sh5fc5t74vapwm", # "dateCreated": 1507666113, # "dateModified": 1508514027, # "policy": { # "view": "public", # "edit": "admin" # }, # "bugzilla.bug-id": "1154784" # }, # "attachments": { # "reviewers": { # "reviewers": [ # { # "reviewerPHID": "PHID-USER-2gjdpu7thmpjxxnp7tjq", # "status": "added", # "isBlocking": false, # "actorPHID": null # }, # { # "reviewerPHID": "PHID-USER-o5dnet6dp4dkxkg5b3ox", # "status": "rejected", # "isBlocking": false, # "actorPHID": "PHID-USER-o5dnet6dp4dkxkg5b3ox" # } # ] # }, # "subscribers": { # "subscriberPHIDs": [], # "subscriberCount": 0, # "viewerIsSubscribed": true # }, # "projects": { # "projectPHIDs": [] # } # } # } # ], # "maps": {}, # "query": { # "queryKey": null # }, # "cursor": { # "limit": 100, # "after": null, # "before": null, # "order": null # } # } ######################### # Modification # ######################### sub update { my ($self) = @_; my $data = { objectIdentifier => $self->phid, transactions => [] }; if ( $self->{added_comments} ) { foreach my $comment ( @{ $self->{added_comments} } ) { push @{ $data->{transactions} }, { type => 'comment', value => $comment }; } } if ( $self->{set_subscribers} ) { push @{ $data->{transactions} }, { type => 'subscribers.set', value => $self->{set_subscribers} }; } if ( $self->{add_subscribers} ) { push @{ $data->{transactions} }, { type => 'subscribers.add', value => $self->{add_subscribers} }; } if ( $self->{remove_subscribers} ) { push @{ $data->{transactions} }, { type => 'subscribers.remove', value => $self->{remove_subscribers} }; } if ( $self->{set_reviewers} ) { push @{ $data->{transactions} }, { type => 'reviewers.set', value => $self->{set_reviewers} }; } if ( $self->{add_reviewers} ) { push @{ $data->{transactions} }, { type => 'reviewers.add', value => $self->{add_reviewers} }; } if ( $self->{remove_reviewers} ) { push @{ $data->{transactions} }, { type => 'reviewers.remove', value => $self->{remove_reviewers} }; } if ( $self->{set_policy} ) { foreach my $name ( "view", "edit" ) { next unless $self->{set_policy}->{$name}; push @{ $data->{transactions} }, { type => $name, value => $self->{set_policy}->{$name} }; } } if ($self->{add_projects}) { push(@{ $data->{transactions} }, { type => 'projects.add', value => $self->{add_projects} }); } if ($self->{remove_projects}) { push(@{ $data->{transactions} }, { type => 'projects.remove', value => $self->{remove_projects} }); } my $result = request( 'differential.revision.edit', $data ); return $result; } ######################### # Builders # ######################### sub _build_bug { my ($self) = @_; return $self->{bug} ||= Bugzilla::Bug->new( { id => $self->bug_id, cache => 1 } ); } sub _build_author { my ($self) = @_; return $self->{author} if $self->{author}; my $phab_user = Bugzilla::Extension::PhabBugz::User->new_from_query( { phids => [ $self->author_phid ] } ); if ($phab_user) { return $self->{author} = $phab_user; } } sub _build_reviews { my ($self) = @_; my %by_phid = map { $_->{reviewerPHID} => $_ } @{ $self->reviewers_raw }; my $users = Bugzilla::Extension::PhabBugz::User->match( { phids => [keys %by_phid] } ); return [ map { { user => $_, status => $by_phid{ $_->phid }{status}, } } @$users ]; } sub _build_subscribers { my ($self) = @_; return $self->{subscribers} if $self->{subscribers}; return [] unless $self->subscribers_raw->{subscriberPHIDs}; my @phids; foreach my $phid ( @{ $self->subscribers_raw->{subscriberPHIDs} } ) { push @phids, $phid; } my $users = Bugzilla::Extension::PhabBugz::User->match( { phids => \@phids } ); return $self->{subscribers} = $users; } sub _build_projects { my ($self) = @_; return $self->{projects} if $self->{projects}; return [] unless $self->projects_raw->{projectPHIDs}; my @projects; foreach my $phid ( @{ $self->projects_raw->{projectPHIDs} } ) { push @projects, Bugzilla::Extension::PhabBugz::Project->new_from_query( { phids => [ $phid ] } ); } return $self->{projects} = \@projects; } ######################### # Mutators # ######################### sub add_comment { my ( $self, $comment ) = @_; $comment = trim($comment); $self->{added_comments} ||= []; push @{ $self->{added_comments} }, $comment; } sub add_reviewer { my ( $self, $reviewer ) = @_; $self->{add_reviewers} ||= []; my $reviewer_phid = blessed $reviewer ? $reviewer->phid : $reviewer; push @{ $self->{add_reviewers} }, $reviewer_phid; } sub remove_reviewer { my ( $self, $reviewer ) = @_; $self->{remove_reviewers} ||= []; my $reviewer_phid = blessed $reviewer ? $reviewer->phid : $reviewer; push @{ $self->{remove_reviewers} }, $reviewer_phid; } sub set_reviewers { my ( $self, $reviewers ) = @_; $self->{set_reviewers} = [ map { $_->phid } @$reviewers ]; } sub add_subscriber { my ( $self, $subscriber ) = @_; $self->{add_subscribers} ||= []; my $subscriber_phid = blessed $subscriber ? $subscriber->phid : $subscriber; push @{ $self->{add_subscribers} }, $subscriber_phid; } sub remove_subscriber { my ( $self, $subscriber ) = @_; $self->{remove_subscribers} ||= []; my $subscriber_phid = blessed $subscriber ? $subscriber->phid : $subscriber; push @{ $self->{remove_subscribers} }, $subscriber_phid; } sub set_subscribers { my ( $self, $subscribers ) = @_; $self->{set_subscribers} = $subscribers; } sub set_policy { my ( $self, $name, $policy ) = @_; $self->{set_policy} ||= {}; $self->{set_policy}->{$name} = $policy; } sub add_project { my ( $self, $project ) = @_; $self->{add_projects} ||= []; my $project_phid = blessed $project ? $project->phid : $project; return undef unless $project_phid; push @{ $self->{add_projects} }, $project_phid; } sub remove_project { my ( $self, $project ) = @_; $self->{remove_projects} ||= []; my $project_phid = blessed $project ? $project->phid : $project; return undef unless $project_phid; push @{ $self->{remove_projects} }, $project_phid; } sub make_private { my ( $self, $project_names ) = @_; my $secure_revision_project = Bugzilla::Extension::PhabBugz::Project->new_from_query( { name => 'secure-revision' } ); my @set_projects; foreach my $name (@$project_names) { my $set_project = Bugzilla::Extension::PhabBugz::Project->new_from_query( { name => $name } ); push @set_projects, $set_project; } my $new_policy = Bugzilla::Extension::PhabBugz::Policy->create(\@set_projects); $self->set_policy('view', $new_policy->phid); $self->set_policy('edit', $new_policy->phid); foreach my $project ($secure_revision_project, @set_projects) { $self->add_project($project->phid); } return $self; } sub make_public { my ( $self ) = @_; my $editbugs = Bugzilla::Extension::PhabBugz::Project->new_from_query( { name => 'bmo-editbugs-team' } ); $self->set_policy( 'view', 'public' ); $self->set_policy( 'edit', ( $editbugs ? $editbugs->phid : 'users' ) ); my @current_group_projects = grep { $_->name =~ /^(bmo-.*|secure-revision)$/ } @{ $self->projects }; foreach my $project (@current_group_projects) { $self->remove_project($project->phid); } return $self; } 1;