]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1489706 - Add section to show_bug displaying the status of associated revisions
authordklawren <dklawren@users.noreply.github.com>
Wed, 14 Nov 2018 22:58:39 +0000 (17:58 -0500)
committerGitHub <noreply@github.com>
Wed, 14 Nov 2018 22:58:39 +0000 (17:58 -0500)
16 files changed:
extensions/PhabBugz/Extension.pm
extensions/PhabBugz/lib/Revision.pm
extensions/PhabBugz/lib/WebService.pm
extensions/PhabBugz/t/basic.t
extensions/PhabBugz/template/en/default/hook/bug/edit-after_bug_data.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/hook/bug/show-header-end.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/hook/bug_modal/edit-module.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/hook/bug_modal/header-end.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/phabricator/header.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/template/en/default/phabricator/table.html.tmpl [new file with mode: 0644]
extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff [new file with mode: 0644]
extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff2 [new file with mode: 0644]
extensions/PhabBugz/web/fonts/fontawesome-webfont.woff [new file with mode: 0644]
extensions/PhabBugz/web/fonts/fontawesome-webfont.woff2 [new file with mode: 0644]
extensions/PhabBugz/web/js/phabricator.js [new file with mode: 0644]
extensions/PhabBugz/web/style/phabricator.css [new file with mode: 0644]

index c857c60abccf855ac491348456054a3b4ad54dd8..af12985ce174a680d93ca4d5527b1eb5584e097f 100644 (file)
@@ -14,10 +14,30 @@ use warnings;
 use parent qw(Bugzilla::Extension);
 
 use Bugzilla::Constants;
-use Bugzilla::Extension::PhabBugz::Feed;
+use Bugzilla::Extension::PhabBugz::Constants;
 
 our $VERSION = '0.01';
 
+sub template_before_process {
+    my ( $self, $args ) = @_;
+    my $file = $args->{'file'};
+    my $vars = $args->{'vars'};
+
+    return unless Bugzilla->params->{phabricator_enabled};
+    return unless Bugzilla->params->{phabricator_base_uri};
+    return unless $file =~ /bug_modal\/(header|edit).html.tmpl$/;
+
+    if ( my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'} ) {
+        my $has_revisions = 0;
+        foreach my $attachment ( @{ $bug->attachments } ) {
+            next if $attachment->contenttype ne PHAB_CONTENT_TYPE;
+            $has_revisions = 1;
+            last;
+        }
+        $vars->{phabricator_revisions} = $has_revisions;
+    }
+}
+
 sub config_add_panels {
     my ($self, $args) = @_;
     my $modules = $args->{panel_modules};
index 41a158ac84b4176146dd5676591e988013eb12c0..6683b9348d87862a7b2e57603fb3156d46e8cfdf 100644 (file)
@@ -36,6 +36,7 @@ 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 diff_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 );
@@ -65,11 +66,21 @@ has subscribers_raw => (
     ]
 );
 has projects_raw => (
-    is => 'ro',
+    is  => 'ro',
     isa => Dict [
         projectPHIDs => ArrayRef [Str]
     ]
 );
+has reviewers_extra_raw => (
+    is  => 'ro',
+    isa => ArrayRef [
+        Dict [
+            reviewerPHID => Str,
+            voidedPHID   => Maybe [Str],
+            diffPHID     => Maybe [Str]
+        ]
+    ]
+);
 
 sub new_from_query {
     my ( $class, $params ) = @_;
@@ -109,12 +120,14 @@ sub BUILDARGS {
     $params->{creation_ts}      = $params->{fields}->{dateCreated};
     $params->{modification_ts}  = $params->{fields}->{dateModified};
     $params->{author_phid}      = $params->{fields}->{authorPHID};
+    $params->{diff_phid}        = $params->{fields}->{diffPHID};
     $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->{reviewers_extra_raw} = $params->{attachments}->{'reviewers-extra'}->{'reviewers-extra'} // [];
     $params->{subscriber_count} =
       $params->{attachments}->{subscribers}->{subscriberCount};
 
@@ -321,14 +334,28 @@ sub _build_reviews {
         }
     );
 
-    return [
-        map {
-            {
-                user => $_,
-                status => $by_phid{ $_->phid }{status},
+    my @reviewers;
+    foreach my $user (@{ $users }) {
+        my $reviewer_data = {
+            user   => $user,
+            status => $by_phid{ $user->phid }{status}
+        };
+        # Set to accepted-prior if the diffs reviewer are different and the reviewer status is accepted
+        foreach my $reviewer_extra (@{$self->reviewers_extra_raw}) {
+            if ($reviewer_extra->{reviewerPHID} eq $user->phid) {
+                if ($reviewer_extra->{diffPHID}) {
+                    if ( $reviewer_data->{status} eq 'accepted'
+                      && $reviewer_extra->{diffPHID} ne $self->diff_phid)
+                    {
+                        $reviewer_data->{status} = 'accepted-prior';
+                    }
+                }
             }
-        } @$users
-    ];
+        }
+        push @reviewers, $reviewer_data;
+    }
+
+    return \@reviewers;
 }
 
 sub _build_subscribers {
index 19a758a70e7d3fbfc620b280eecc98db54fe80f1..36a3f00978b53d303be15485b2b2566ad842c4be 100644 (file)
@@ -13,24 +13,31 @@ use warnings;
 
 use base qw(Bugzilla::WebService);
 
+use Bugzilla::Bug;
 use Bugzilla::Constants;
 use Bugzilla::Error;
+use Bugzilla::Logging;
 use Bugzilla::User;
 use Bugzilla::Util qw(detaint_natural trick_taint);
 use Bugzilla::WebService::Constants;
+use Types::Standard qw(-types slurpy);
+use Type::Params qw(compile);
 
 use Bugzilla::Extension::PhabBugz::Constants;
+use Bugzilla::Extension::PhabBugz::Revision;
+use Bugzilla::Extension::PhabBugz::Util qw(request);
 
-use List::Util qw(first);
-use List::MoreUtils qw(any);
 use MIME::Base64 qw(decode_base64);
+use Try::Tiny;
 
 use constant READ_ONLY => qw(
+    bug_revisions
     check_user_enter_bug_permission
     check_user_permission_for_bug
 );
 
 use constant PUBLIC_METHODS => qw(
+    bug_revisions
     check_user_enter_bug_permission
     check_user_permission_for_bug
     set_build_target
@@ -122,7 +129,7 @@ sub set_build_target {
     trick_taint($build_target);
 
     Bugzilla->dbh->do(
-        "INSERT INTO phabbugz (name, value) VALUES (?, ?)",
+        'INSERT INTO phabbugz (name, value) VALUES (?, ?)',
         undef,
         'build_target_' . $revision_id,
         $build_target
@@ -131,6 +138,112 @@ sub set_build_target {
     return { result => 1 };
 }
 
+sub bug_revisions {
+    state $check = compile(Object, Dict[bug_id => Int]);
+    my ( $self, $params ) = $check->(@_);
+
+    $self->_check_phabricator();
+
+    my $user = Bugzilla->login(LOGIN_REQUIRED);
+
+    # Validate that a bug id and user id are provided
+    ThrowUserError('phabricator_invalid_request_params')
+        unless $params->{bug_id};
+
+    # Validate that the user can see the bug itself
+    my $bug = Bugzilla::Bug->check( { id => $params->{bug_id}, cache => 1 } );
+
+    my @revision_ids;
+    foreach my $attachment ( @{ $bug->attachments } ) {
+        next if $attachment->contenttype ne PHAB_CONTENT_TYPE;
+        my ($revision_id) = ( $attachment->filename =~ PHAB_ATTACHMENT_PATTERN );
+        next if !$revision_id;
+        push @revision_ids, int $revision_id;
+    }
+
+    my $response = request(
+        'differential.revision.search',
+        {
+            attachments => {
+                'projects'        => 1,
+                'reviewers'       => 1,
+                'subscribers'     => 1,
+                'reviewers-extra' => 1,
+            },
+            constraints => {
+                ids => \@revision_ids,
+            },
+            order => 'newest',
+        }
+    );
+
+    state $SearchResult = Dict[
+        result => Dict[
+            # HashRef below could be better,
+            # but ::Revision takes a lot of options.
+            data => ArrayRef[ HashRef ],
+            slurpy Any,
+        ],
+        slurpy Any,
+    ];
+
+    my $error = $SearchResult->validate($response);
+    ThrowCodeError( 'phabricator_api_error', { reason => $error } )
+        if defined $error;
+
+    my $revision_status_map = {
+        'abandoned'       => 'Abandoned',
+        'accepted'        => 'Accepted',
+        'changes-planned' => 'Changes Planned',
+        'needs-review'    => 'Needs Review',
+        'needs-revision'  => 'Needs Revision',
+    };
+
+    my $review_status_map = {
+        'accepted'       => 'Accepted',
+        'accepted-prior' => 'Accepted Prior Diff',
+        'added'          => 'Review Requested',
+        'blocking'       => 'Blocking Review',
+        'rejected'       => 'Requested Changes',
+        'resigned'       => 'Resigned'
+    };
+
+    my @revisions;
+    foreach my $revision ( @{ $response->{result}{data} } ) {
+        my $revision_obj = Bugzilla::Extension::PhabBugz::Revision->new($revision);
+        my $revision_data = {
+            id     => 'D' . $revision_obj->id,
+            author => $revision_obj->author->name,
+            status => $revision_obj->status,
+            long_status => $revision_status_map->{$revision_obj->status} || $revision_obj->status
+        };
+
+        my @reviews;
+        foreach my $review ( @{ $revision_obj->reviews } ) {
+            push @reviews, {
+                user        => $review->{user}->name,
+                status      => $review->{status},
+                long_status => $review_status_map->{$review->{status}} || $review->{status}
+            };
+        }
+        $revision_data->{reviews} = \@reviews;
+
+        if ( $revision_obj->view_policy ne 'public' ) {
+            $revision_data->{title} = '(secured)';
+        }
+        else {
+            $revision_data->{title} = $revision_obj->title;
+        }
+
+        push @revisions, $revision_data;
+    }
+
+    # sort by revision id
+    @revisions = sort { $a->{id} cmp $b->{id} } @revisions;
+
+    return { revisions => \@revisions };
+}
+
 sub rest_resources {
     return [
         # Set build target in Phabricator
@@ -162,6 +275,14 @@ sub rest_resources {
                 },
             },
         },
+        qr{^/phabbugz/bug_revisions/(\d+)$}, {
+            GET => {
+                method => 'bug_revisions',
+                params => sub {
+                    return { bug_id => $_[0] };
+                },
+            },
+        },
     ];
 }
 
index af92dc64f68e407bec681d0cb33e3b7e970aec2d..2f05496cd20ccc411c7f2b55761e6f7d21399344 100644 (file)
@@ -186,6 +186,7 @@ do {
                     "authorPHID": "PHID-USER-4wigy3sh5fc5t74vapwm",
                     "dateCreated": 1507666113,
                     "dateModified": 1508514027,
+                    "diffPHID": "PHID-DIFF-x5fnvkz5rpco2pogzcrf",
                     "policy": {
                         "view": "public",
                         "edit": "admin"
diff --git a/extensions/PhabBugz/template/en/default/hook/bug/edit-after_bug_data.html.tmpl b/extensions/PhabBugz/template/en/default/hook/bug/edit-after_bug_data.html.tmpl
new file mode 100644 (file)
index 0000000..7dc7c6e
--- /dev/null
@@ -0,0 +1,25 @@
+[%# 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.
+  #%]
+
+[% RETURN UNLESS phabricator_revisions %]
+
+<table class="phabricator-table">
+  <thead>
+    <tr>
+      <th>Phabricator Revisions</th>
+    </tr>
+  </thead>
+
+  <tbody>
+    <tr>
+      <td>
+        [% INCLUDE phabricator/table.html.tmpl %]
+      </td>
+    </tr>
+  </tbody>
+</table>
diff --git a/extensions/PhabBugz/template/en/default/hook/bug/show-header-end.html.tmpl b/extensions/PhabBugz/template/en/default/hook/bug/show-header-end.html.tmpl
new file mode 100644 (file)
index 0000000..9d6001a
--- /dev/null
@@ -0,0 +1,13 @@
+[%# 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.
+  #%]
+
+[%
+  IF phabricator_revisions;
+    PROCESS phabricator/header.html.tmpl;
+  END;
+%]
diff --git a/extensions/PhabBugz/template/en/default/hook/bug_modal/edit-module.html.tmpl b/extensions/PhabBugz/template/en/default/hook/bug_modal/edit-module.html.tmpl
new file mode 100644 (file)
index 0000000..054cc72
--- /dev/null
@@ -0,0 +1,16 @@
+[%# 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.
+  #%]
+
+[% RETURN UNLESS phabricator_revisions %]
+
+[% WRAPPER bug_modal/module.html.tmpl
+    title = "Phabricator Revisions"
+    collapsed = 0
+%]
+  [% INCLUDE phabricator/table.html.tmpl %]
+[% END %]
diff --git a/extensions/PhabBugz/template/en/default/hook/bug_modal/header-end.html.tmpl b/extensions/PhabBugz/template/en/default/hook/bug_modal/header-end.html.tmpl
new file mode 100644 (file)
index 0000000..9d6001a
--- /dev/null
@@ -0,0 +1,13 @@
+[%# 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.
+  #%]
+
+[%
+  IF phabricator_revisions;
+    PROCESS phabricator/header.html.tmpl;
+  END;
+%]
diff --git a/extensions/PhabBugz/template/en/default/phabricator/header.html.tmpl b/extensions/PhabBugz/template/en/default/phabricator/header.html.tmpl
new file mode 100644 (file)
index 0000000..f4e96af
--- /dev/null
@@ -0,0 +1,10 @@
+[%# 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.
+  #%]
+
+[% style_urls.push('extensions/PhabBugz/web/style/phabricator.css') %]
+[% javascript_urls.push('extensions/PhabBugz/web/js/phabricator.js') %]
diff --git a/extensions/PhabBugz/template/en/default/phabricator/table.html.tmpl b/extensions/PhabBugz/template/en/default/phabricator/table.html.tmpl
new file mode 100644 (file)
index 0000000..3331c19
--- /dev/null
@@ -0,0 +1,29 @@
+[%# 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.
+  #%]
+
+<table class="phabricator-revisions"
+       data-phabricator-base-uri="[% Param('phabricator_base_uri') FILTER html %]">
+  <thead>
+    <tr>
+      <th>ID</th>
+      <th>Title</th>
+      <th>Author</th>
+      <th>Status</th>
+      <th>Reviewers</th>
+    </tr>
+  </thead>
+  <tbody class="phabricator-revision">
+    <tr class="phabricator-loading-row">
+      <td colspan="4">Loading...</td>
+    </tr>
+    <tr class="phabricator-loading-error-row bz_default_hidden">
+      <td colspan="4">Error loading Phabricator revisions:
+        <span class="phabricator-load-error-string"></span></td>
+    </tr>
+  </tbody>
+</table>
diff --git a/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff b/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff
new file mode 100644 (file)
index 0000000..ffdc85c
Binary files /dev/null and b/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff differ
diff --git a/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff2 b/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff2
new file mode 100644 (file)
index 0000000..008f4f9
Binary files /dev/null and b/extensions/PhabBugz/web/fonts/FontAwesome-DifferentialStatus.woff2 differ
diff --git a/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff b/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff
new file mode 100644 (file)
index 0000000..400014a
Binary files /dev/null and b/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff differ
diff --git a/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff2 b/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff2
new file mode 100644 (file)
index 0000000..4d13fc6
Binary files /dev/null and b/extensions/PhabBugz/web/fonts/fontawesome-webfont.woff2 differ
diff --git a/extensions/PhabBugz/web/js/phabricator.js b/extensions/PhabBugz/web/js/phabricator.js
new file mode 100644 (file)
index 0000000..a4ceff7
--- /dev/null
@@ -0,0 +1,115 @@
+/* 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.
+ */
+
+var Phabricator = {};
+
+Phabricator.getBugRevisions = function() {
+    var phabUrl = $('.phabricator-revisions').data('phabricator-base-uri');
+    var tr      = $('<tr/>');
+    var td      = $('<td/>');
+    var link    = $('<a/>');
+    var span    = $('<span/>');
+    var table   = $('<table/>');
+
+    function revisionRow(revision) {
+        var trRevision     = tr.clone();
+        var tdId           = td.clone();
+        var tdTitle        = td.clone();
+        var tdAuthor       = td.clone();
+        var tdRevisionStatus       = td.clone();
+        var tdReviewers    = td.clone();
+        var tableReviews   = table.clone();
+
+        var spanRevisionStatus     = span.clone();
+        var spanRevisionStatusIcon = span.clone();
+        var spanRevisionStatusText = span.clone();
+
+        var revLink = link.clone();
+        revLink.attr('href', phabUrl + '/' + revision.id);
+        revLink.text(revision.id);
+        tdId.append(revLink);
+
+        tdTitle.text(revision.title);
+        tdTitle.addClass('phabricator-title');
+
+        tdAuthor.text(revision.author);
+
+        spanRevisionStatusIcon.addClass('revision-status-icon-' + revision.status);
+        spanRevisionStatus.append(spanRevisionStatusIcon);
+        spanRevisionStatusText.text(revision.long_status);
+        spanRevisionStatus.append(spanRevisionStatusText);
+        spanRevisionStatus.addClass('revision-status-box-' + revision.status);
+        tdRevisionStatus.append(spanRevisionStatus);
+
+        var i = 0, l = revision.reviews.length;
+        for (; i < l; i++) {
+            var trReview             = tr.clone();
+            var tdReviewStatus       = td.clone();
+            var tdReviewer           = td.clone();
+            var spanReviewStatusIcon = span.clone();
+            spanReviewStatusIcon.addClass('review-status-icon-' + revision.reviews[i].status);
+            spanReviewStatusIcon.prop('title', revision.reviews[i].long_status);
+            tdReviewStatus.append(spanReviewStatusIcon);
+            tdReviewer.text(revision.reviews[i].user);
+            tdReviewer.addClass('review-reviewer');
+            trReview.append(tdReviewStatus, tdReviewer);
+            tableReviews.append(trReview);
+        }
+        tableReviews.addClass('phabricator-reviewers');
+        tdReviewers.append(tableReviews);
+
+        trRevision.append(
+            tdId,
+            tdTitle,
+            tdAuthor,
+            tdRevisionStatus,
+            tdReviewers
+        );
+
+        return trRevision;
+    }
+
+    var tbody = $('tbody.phabricator-revision');
+
+    function displayLoadError(errStr) {
+        var errRow = tbody.find('.phabricator-loading-error-row');
+        errRow.find('.phabricator-load-error-string').text(errStr);
+        errRow.removeClass('bz_default_hidden');
+    }
+
+    var $getUrl = '/rest/phabbugz/bug_revisions/' + BUGZILLA.bug_id +
+                  '?Bugzilla_api_token=' + BUGZILLA.api_token;
+
+    $.getJSON($getUrl, function(data) {
+        if (data.revisions.length === 0) {
+            displayLoadError('none returned from server');
+        } else {
+            var i = 0;
+            for (; i < data.revisions.length; i++) {
+                tbody.append(revisionRow(data.revisions[i]));
+            }
+        }
+        tbody.find('.phabricator-loading-row').addClass('bz_default_hidden');
+    }).fail(function(jqXHR, textStatus, errorThrown) {
+        var errStr;
+        if (jqXHR.responseJSON && jqXHR.responseJSON.err &&
+            jqXHR.responseJSON.err.msg) {
+            errStr = jqXHR.responseJSON.err.msg;
+        } else if (errorThrown) {
+            errStr = errorThrown;
+        } else {
+            errStr = 'unknown';
+        }
+        displayLoadError(errStr);
+        tbody.find('.phabricator-loading-row').addClass('bz_default_hidden');
+    });
+};
+
+$().ready(function() {
+    Phabricator.getBugRevisions();
+});
diff --git a/extensions/PhabBugz/web/style/phabricator.css b/extensions/PhabBugz/web/style/phabricator.css
new file mode 100644 (file)
index 0000000..bbc865b
--- /dev/null
@@ -0,0 +1,180 @@
+/* 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. */
+
+@font-face {
+  font-family: FontAwesome-DifferentialStatus;
+  font-style: normal;
+  font-weight: normal;
+  src: url(../fonts/FontAwesome-DifferentialStatus.woff2?v=4.7) format('woff2'),
+       url(../fonts/FontAwesome-DifferentialStatus.woff?v=4.7) format('woff');
+}
+
+.phabricator-table {
+    background: #fff;
+    border: none;
+    border-collapse: collapse;
+    border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+
+.phabricator-table th {
+    text-align: left;
+    padding: 4px;
+}
+
+.phabricator-table td {
+    vertical-align: middle !important;
+    padding: 4px !important;
+}
+
+.phabricator-table thead, .phabricator-table tfoot {
+    background-color: #eee;
+    color: #404040;
+}
+
+.phabricator-revisions {
+    background: #fff;
+    border: none;
+    border-collapse: collapse;
+}
+
+.phabricator-revisions th {
+    padding: 2px;
+}
+
+.phabricator-revisions td {
+    padding: 2px;
+    vertical-align: top;
+}
+
+.phabricator-revisions .phabricator-reviewers td {
+    padding: 1px;
+}
+
+span[class^="revision-status-box-"] {
+    border: none;
+    font-weight: normal;
+    padding: 3px 9px;
+    border-radius: 3px;
+    white-space: nowrap;
+    font-size: 14px;
+    margin-bottom: 5px;
+}
+
+span[class^="revision-status-icon-"]::before,
+span[class^="review-status-icon-"]::before
+{
+    display: inline-block;
+    font-variant: normal;
+    text-rendering: auto;
+    font-family: FontAwesome-DifferentialStatus;
+}
+
+.revision-status-icon-needs-review::before {
+  content: "\f121";
+}
+
+.revision-status-icon-needs-revision::before {
+  content: "\f021";
+}
+
+.revision-status-icon-changes-planned::before {
+  content: "\f025";
+}
+
+.revision-status-icon-accepted::before {
+  content: "\f00C";
+}
+
+.revision-status-icon-published::before {
+  content: "\f046";
+}
+
+.revision-status-icon-abandoned::before {
+  content: "\f072";
+}
+
+.revision-status-icon-draft::before {
+  content: "\f110";
+}
+
+.revision-status-box-needs-review {
+    background: rgba(71,87,120,0.1);
+    color: inherit;
+}
+
+.revision-status-box-accepted {
+    background: #ddefdd;
+    color: #326d34;
+}
+
+.revision-status-box-changes-planned,
+.revision-status-box-needs-revision {
+    background: #f7e6e6;
+    color: #a53737;
+}
+
+.revision-status-box-abandoned {
+    background: #eae6f7;
+    color: #6e5cb6;
+}
+
+.review-status-icon-accepted::before {
+    color: green;
+    content: "\f058";
+}
+
+.review-status-icon-accepted-prior::before {
+    color: grey;
+    content: "\f058";
+}
+
+.review-status-icon-added::before {
+    color: grey;
+    content: "\f10c";
+}
+
+.review-status-icon-blocking::before {
+    color: red;
+    content: "\f056";
+}
+
+.review-status-icon-rejected::before {
+    color: red;
+    content: "\f05c";
+}
+
+.review-status-icon-resigned::before {
+    color: rgba(55,55,55,0.3);
+    content: "\f024";
+}
+
+/* bug-modal specific */
+
+#module-phabricator-revisions .module-content {
+    padding: 0;
+}
+
+.bug_modal .phabricator-table {
+    width: 100%;
+}
+
+.bug_modal .phabricator-revision td {
+    padding: 8px;
+    vertical-align: top;
+    border-bottom: 1px dotted silver;
+}
+
+.bug_modal .phabricator-revisions th {
+    text-align: left;
+    padding-left: 8px;
+}
+
+.bug_modal .phabricator-revision .phabricator-reviewers td {
+    padding: 1px;
+    border: 0px;
+}