1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 08:42:41 +01:00

Use standard UI elements to render pull requests in Releeph

Summary:
Ref T3718. Ref T3644. Ref T3092. Switches from the Releeph UI elements to standard ones. I'll attach some screenshots.

Also fixes CSRF against the request action endpoint.

Test Plan:
  - Viewed request details.
  - Took actions on a request from detail page.
  - Viewed request list.
  - Took actions on a request from list page.
  - Used keyboard shortcuts to navigate list.
  - Used keyboard shortcuts to take actions.
  - Simulated errors.
  - Viewed on devices.

Reviewers: chad, btrahan

Reviewed By: btrahan

Subscribers: grp, FacebookPOC, mattlqx, tala, beng, LegNeato, epriestley

Maniphest Tasks: T3718, T3092, T3644

Differential Revision: https://secure.phabricator.com/D8771
This commit is contained in:
epriestley 2014-04-18 06:44:45 -07:00
parent 41ea90c686
commit 35df988036
37 changed files with 573 additions and 1468 deletions

View file

@ -18,7 +18,6 @@ return array(
'maniphest.pkg.css' => 'f1887d71',
'maniphest.pkg.js' => '2fe8af22',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
'rsrc/css/aphront/aphront-notes.css' => '6acadd3f',
'rsrc/css/aphront/context-bar.css' => '1c3b0529',
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
'rsrc/css/aphront/dialog-view.css' => 'c01d24b4',
@ -95,15 +94,10 @@ return array(
'rsrc/css/application/ponder/vote.css' => '8ed6ed8b',
'rsrc/css/application/profile/profile-view.css' => '33e6f703',
'rsrc/css/application/projects/project-tag.css' => '095c9404',
'rsrc/css/application/releeph/releeph-branch.css' => 'b8821d2d',
'rsrc/css/application/releeph/releeph-colors.css' => '2d2d6aa8',
'rsrc/css/application/releeph/releeph-core.css' => '140b959d',
'rsrc/css/application/releeph/releeph-intents.css' => '39065521',
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
'rsrc/css/application/releeph/releeph-project.css' => 'ee1f9f57',
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
'rsrc/css/application/releeph/releeph-status.css' => 'd119a005',
'rsrc/css/application/search/search-results.css' => 'f240504c',
'rsrc/css/application/settings/settings.css' => 'ea8f5915',
'rsrc/css/application/slowvote/slowvote.css' => '266df6a1',
@ -128,7 +122,7 @@ return array(
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2',
'rsrc/css/phui/calendar/phui-calendar.css' => '5e1ad989',
'rsrc/css/phui/phui-box.css' => 'a36cf3a5',
'rsrc/css/phui/phui-box.css' => '7b3a2eed',
'rsrc/css/phui/phui-button.css' => '653ac588',
'rsrc/css/phui/phui-document.css' => '3b078dc0',
'rsrc/css/phui/phui-feed-story.css' => '3a59c2cf',
@ -410,7 +404,7 @@ return array(
'rsrc/js/application/projects/behavior-project-boards.js' => 'd8e135db',
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '9eb2cedb',
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'fe7fc914',
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'd259e7c9',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'cd9e7094',
'rsrc/js/application/repository/repository-crossreference.js' => '8ab282be',
'rsrc/js/application/search/behavior-reorder-queries.js' => '37871df4',
@ -493,7 +487,6 @@ return array(
'aphront-error-view-css' => '9f1d5518',
'aphront-list-filter-view-css' => 'ef989c67',
'aphront-multi-column-view-css' => '12f65921',
'aphront-notes' => '6acadd3f',
'aphront-pager-view-css' => '2e3539af',
'aphront-panel-view-css' => '5846dfa2',
'aphront-request-failure-view-css' => 'da14df31',
@ -621,7 +614,7 @@ return array(
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-refresh-csrf' => 'c4b31646',
'javelin-behavior-releeph-preview-branch' => '9eb2cedb',
'javelin-behavior-releeph-request-state-change' => 'fe7fc914',
'javelin-behavior-releeph-request-state-change' => 'd259e7c9',
'javelin-behavior-releeph-request-typeahead' => 'cd9e7094',
'javelin-behavior-remarkup-preview' => 'f7379f45',
'javelin-behavior-repository-crossreference' => '8ab282be',
@ -743,7 +736,7 @@ return array(
'phortune-credit-card-form-css' => 'b25b4beb',
'phrequent-css' => 'ffc185ad',
'phriction-document-css' => '7d7f0071',
'phui-box-css' => 'a36cf3a5',
'phui-box-css' => '7b3a2eed',
'phui-button-css' => '653ac588',
'phui-calendar-css' => '5e1ad989',
'phui-calendar-day-css' => 'de035c8a',
@ -779,15 +772,10 @@ return array(
'raphael-core' => '51ee6b43',
'raphael-g' => '40dde778',
'raphael-g-line' => '40da039e',
'releeph-branch' => 'b8821d2d',
'releeph-colors' => '2d2d6aa8',
'releeph-core' => '140b959d',
'releeph-intents' => '39065521',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
'releeph-project' => 'ee1f9f57',
'releeph-request-differential-create-dialog' => '8d8b92cd',
'releeph-request-typeahead-css' => '667a48ae',
'releeph-status' => 'd119a005',
'setup-issue-css' => '69e640e7',
'sprite-actions-css' => '969ad0e5',
'sprite-apps-css' => '6973a52b',
@ -1727,6 +1715,15 @@ return array(
array(
0 => 'javelin-util',
),
'd259e7c9' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'javelin-workflow',
4 => 'javelin-util',
5 => 'phabricator-keyboard-shortcut',
),
'd4a14807' =>
array(
0 => 'javelin-install',
@ -1950,15 +1947,6 @@ return array(
4 => 'javelin-dom',
5 => 'javelin-magical-init',
),
'fe7fc914' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'phabricator-keyboard-shortcut',
5 => 'phabricator-notification',
),
'fe80fb6d' =>
array(
0 => 'javelin-behavior',

View file

@ -65,7 +65,6 @@ phutil_register_library_map(array(
'AphrontMoreView' => 'view/layout/AphrontMoreView.php',
'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php',
'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php',
'AphrontNoteView' => 'view/widget/AphrontNoteView.php',
'AphrontNullView' => 'view/AphrontNullView.php',
'AphrontPHPHTTPSink' => 'aphront/sink/AphrontPHPHTTPSink.php',
'AphrontPageView' => 'view/page/AphrontPageView.php',
@ -1716,7 +1715,6 @@ phutil_register_library_map(array(
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php',
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
'PhabricatorNoteExample' => 'applications/uiexample/examples/PhabricatorNoteExample.php',
'PhabricatorNotificationAdHocFeedStory' => 'applications/notification/feed/PhabricatorNotificationAdHocFeedStory.php',
'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php',
@ -2555,26 +2553,22 @@ phutil_register_library_map(array(
'ReleephRequestController' => 'applications/releeph/controller/request/ReleephRequestController.php',
'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php',
'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php',
'ReleephRequestHeaderListView' => 'applications/releeph/view/request/header/ReleephRequestHeaderListView.php',
'ReleephRequestHeaderView' => 'applications/releeph/view/request/header/ReleephRequestHeaderView.php',
'ReleephRequestIntentsView' => 'applications/releeph/view/request/ReleephRequestIntentsView.php',
'ReleephRequestMailReceiver' => 'applications/releeph/mail/ReleephRequestMailReceiver.php',
'ReleephRequestQuery' => 'applications/releeph/query/ReleephRequestQuery.php',
'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php',
'ReleephRequestSearchEngine' => 'applications/releeph/query/ReleephRequestSearchEngine.php',
'ReleephRequestStatus' => 'applications/releeph/constants/ReleephRequestStatus.php',
'ReleephRequestStatusView' => 'applications/releeph/view/request/ReleephRequestStatusView.php',
'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php',
'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php',
'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php',
'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php',
'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php',
'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php',
'ReleephRequestView' => 'applications/releeph/view/ReleephRequestView.php',
'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php',
'ReleephRequestorFieldSpecification' => 'applications/releeph/field/specification/ReleephRequestorFieldSpecification.php',
'ReleephRevisionFieldSpecification' => 'applications/releeph/field/specification/ReleephRevisionFieldSpecification.php',
'ReleephSeverityFieldSpecification' => 'applications/releeph/field/specification/ReleephSeverityFieldSpecification.php',
'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php',
'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php',
'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php',
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
@ -2662,7 +2656,6 @@ phutil_register_library_map(array(
'AphrontMoreView' => 'AphrontView',
'AphrontMultiColumnView' => 'AphrontView',
'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase',
'AphrontNoteView' => 'AphrontView',
'AphrontNullView' => 'AphrontView',
'AphrontPHPHTTPSink' => 'AphrontHTTPSink',
'AphrontPageView' => 'AphrontView',
@ -4531,7 +4524,6 @@ phutil_register_library_map(array(
1 => 'PhabricatorPolicyInterface',
),
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorNoteExample' => 'PhabricatorUIExample',
'PhabricatorNotificationAdHocFeedStory' => 'PhabricatorFeedStory',
'PhabricatorNotificationClearController' => 'PhabricatorNotificationController',
'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -5574,25 +5566,21 @@ phutil_register_library_map(array(
'ReleephRequestController' => 'ReleephController',
'ReleephRequestDifferentialCreateController' => 'ReleephController',
'ReleephRequestEditController' => 'ReleephBranchController',
'ReleephRequestHeaderListView' => 'AphrontView',
'ReleephRequestHeaderView' => 'AphrontView',
'ReleephRequestIntentsView' => 'AphrontView',
'ReleephRequestMailReceiver' => 'PhabricatorObjectMailReceiver',
'ReleephRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'ReleephRequestReplyHandler' => 'PhabricatorMailReplyHandler',
'ReleephRequestSearchEngine' => 'PhabricatorApplicationSearchEngine',
'ReleephRequestStatusView' => 'AphrontView',
'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction',
'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment',
'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor',
'ReleephRequestTypeaheadControl' => 'AphrontFormControl',
'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController',
'ReleephRequestView' => 'AphrontView',
'ReleephRequestViewController' => 'ReleephBranchController',
'ReleephRequestorFieldSpecification' => 'ReleephFieldSpecification',
'ReleephRevisionFieldSpecification' => 'ReleephFieldSpecification',
'ReleephSeverityFieldSpecification' => 'ReleephLevelFieldSpecification',
'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification',
'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
'ShellLogView' => 'AphrontView',
'SlowvoteEmbedView' => 'AphrontView',

View file

@ -14,20 +14,19 @@ final class PhabricatorApplicationReleephConfigOptions
public function getOptions() {
$default_fields = array(
new ReleephCommitMessageFieldSpecification(),
new ReleephSummaryFieldSpecification(),
new ReleephRequestorFieldSpecification(),
new ReleephSeverityFieldSpecification(),
new ReleephIntentFieldSpecification(),
new ReleephReasonFieldSpecification(),
new ReleephAuthorFieldSpecification(),
new ReleephRevisionFieldSpecification(),
new ReleephRequestorFieldSpecification(),
new ReleephSeverityFieldSpecification(),
new ReleephOriginalCommitFieldSpecification(),
new ReleephDiffMessageFieldSpecification(),
new ReleephStatusFieldSpecification(),
new ReleephIntentFieldSpecification(),
new ReleephBranchCommitFieldSpecification(),
new ReleephDiffSizeFieldSpecification(),
new ReleephDiffChurnFieldSpecification(),
new ReleephDiffMessageFieldSpecification(),
new ReleephCommitMessageFieldSpecification(),
);
$default = array();

View file

@ -15,9 +15,9 @@ final class ReleephRequestStatus {
self::STATUS_REQUESTED => pht('Requested'),
self::STATUS_REJECTED => pht('Rejected'),
self::STATUS_ABANDONED => pht('Abandoned'),
self::STATUS_PICKED => pht('Picked'),
self::STATUS_PICKED => pht('Pulled'),
self::STATUS_REVERTED => pht('Reverted'),
self::STATUS_NEEDS_PICK => pht('Needs Pick'),
self::STATUS_NEEDS_PICK => pht('Needs Pull'),
self::STATUS_NEEDS_REVERT => pht('Needs Revert'),
);
return idx($descriptions, $status, '??');

View file

@ -44,19 +44,40 @@ final class ReleephBranchViewController extends ReleephBranchController
assert_instances_of($requests, 'ReleephRequest');
$viewer = $this->getRequest()->getUser();
$branch = $this->getBranch();
// TODO: This is generally a bit sketchy, but we don't do this kind of
// thing elsewhere at the moment. For the moment it shouldn't be hugely
// costly, and we can batch things later. Generally, this commits fewer
// sins than the old code did.
// TODO: Really really gross.
$branch->populateReleephRequestHandles(
$viewer,
$requests);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
$list = id(new ReleephRequestHeaderListView())
->setUser($viewer)
->setAphrontRequest($this->getRequest())
->setReleephProject($branch->getProduct())
->setReleephBranch($branch)
->setReleephRequests($requests);
$list = array();
foreach ($requests as $pull) {
$field_list = PhabricatorCustomField::getObjectFields(
$pull,
PhabricatorCustomField::ROLE_VIEW);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($pull);
foreach ($field_list->getFields() as $field) {
if ($field->shouldMarkup()) {
$field->setMarkupEngine($engine);
}
}
$list[] = id(new ReleephRequestView())
->setUser($viewer)
->setCustomFields($field_list)
->setPullRequest($pull)
->setIsListView(true);
}
// This is quite sketchy, but the list has not actually rendered yet, so
// this still allows us to batch the markup rendering.
$engine->process();
return $list;
}

View file

@ -15,6 +15,8 @@ final class ReleephRequestActionController
$request = $this->getRequest();
$viewer = $request->getUser();
$request->validateCSRF();
$pull = id(new ReleephRequestQuery())
->setViewer($viewer)
->withIDs(array($this->requestID))
@ -26,8 +28,6 @@ final class ReleephRequestActionController
$branch = $pull->getBranch();
$product = $branch->getProduct();
$branch->populateReleephRequestHandles($viewer, array($pull));
$action = $this->action;
$origin_uri = '/'.$pull->getMonogram();
@ -93,22 +93,37 @@ final class ReleephRequestActionController
$editor->applyTransactions($pull, $xactions);
// If we're adding a new user to userIntents, we'll have to re-populate
// request handles to load that user's data.
//
// This is cheap enough to do every time.
$branch->populateReleephRequestHandles($viewer, array($pull));
if ($request->getBool('render')) {
$field_list = PhabricatorCustomField::getObjectFields(
$pull,
PhabricatorCustomField::ROLE_VIEW);
$list = id(new ReleephRequestHeaderListView())
->setReleephProject($product)
->setReleephBranch($branch)
->setReleephRequests(array($pull))
->setUser($viewer)
->setAphrontRequest($request);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($pull);
return id(new AphrontAjaxResponse())->setContent(
array(
'markup' => hsprintf('%s', $list->renderInner()),
));
// TODO: This should be more modern and general.
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($field_list->getFields() as $field) {
if ($field->shouldMarkup()) {
$field->setMarkupEngine($engine);
}
}
$engine->process();
$pull_box = id(new ReleephRequestView())
->setUser($viewer)
->setCustomFields($field_list)
->setPullRequest($pull)
->setIsListView(true);
return id(new AphrontAjaxResponse())->setContent(
array(
'markup' => hsprintf('%s', $pull_box),
));
}
return id(new AphrontRedirectResponse())->setURI($origin_uri);
}
}

View file

@ -33,21 +33,28 @@ final class ReleephRequestViewController
// TODO: Break this 1:1 stuff?
$branch = $pull->getBranch();
// TODO: Very gross.
$branch->populateReleephRequestHandles(
$viewer,
array($pull));
$field_list = PhabricatorCustomField::getObjectFields(
$pull,
PhabricatorCustomField::ROLE_VIEW);
$rq_view = id(new ReleephRequestHeaderListView())
->setReleephProject($branch->getProduct())
->setReleephBranch($branch)
->setReleephRequests(array($pull))
->setUser($viewer)
->setAphrontRequest($request)
->setReloadOnStateChange(true);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($pull);
// TODO: This should be more modern and general.
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer);
foreach ($field_list->getFields() as $field) {
if ($field->shouldMarkup()) {
$field->setMarkupEngine($engine);
}
}
$engine->process();
$pull_box = id(new ReleephRequestView())
->setUser($viewer)
->setCustomFields($field_list)
->setPullRequest($pull);
$xactions = id(new ReleephRequestTransactionQuery())
->setViewer($viewer)
@ -85,12 +92,15 @@ final class ReleephRequestViewController
return $this->buildStandardPageResponse(
array(
$crumbs,
$rq_view,
$pull_box,
$timeline,
$add_comment_form,
),
array(
'title' => $title
'title' => $title,
'device' => true,
));
}
}

View file

@ -37,7 +37,6 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
new ReleephFacebookSeverityFieldSpecification(),
new ReleephOriginalCommitFieldSpecification(),
new ReleephDiffMessageFieldSpecification(),
new ReleephStatusFieldSpecification(),
new ReleephIntentFieldSpecification(),
new ReleephBranchCommitFieldSpecification(),
new ReleephDiffSizeFieldSpecification(),
@ -57,7 +56,6 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
new ReleephSeverityFieldSpecification(),
new ReleephOriginalCommitFieldSpecification(),
new ReleephDiffMessageFieldSpecification(),
new ReleephStatusFieldSpecification(),
new ReleephIntentFieldSpecification(),
new ReleephBranchCommitFieldSpecification(),
new ReleephDiffSizeFieldSpecification(),
@ -66,91 +64,6 @@ final class ReleephDefaultFieldSelector extends ReleephFieldSelector {
}
}
public function arrangeFieldsForHeaderView(array $fields) {
if (self::isFacebook()) {
return array(
// Top group
array(
'left' => self::selectFields($fields, array(
'ReleephAuthorFieldSpecification',
'ReleephRevisionFieldSpecification',
'ReleephOriginalCommitFieldSpecification',
'ReleephDiffSizeFieldSpecification',
'ReleephDiffChurnFieldSpecification',
'ReleephDependsOnFieldSpecification',
'ReleephFacebookTasksFieldSpecification',
)),
'right' => self::selectFields($fields, array(
'ReleephRequestorFieldSpecification',
'ReleephFacebookKarmaFieldSpecification',
'ReleephFacebookSeverityFieldSpecification',
'ReleephFacebookTagFieldSpecification',
'ReleephStatusFieldSpecification',
'ReleephIntentFieldSpecification',
'ReleephBranchCommitFieldSpecification',
))
),
// Bottom group
array(
'left' => self::selectFields($fields, array(
'ReleephDiffMessageFieldSpecification',
)),
'right' => self::selectFields($fields, array(
'ReleephReasonFieldSpecification',
))
)
);
} else {
return array(
// Top group
array(
'left' => self::selectFields($fields, array(
'ReleephAuthorFieldSpecification',
'ReleephRevisionFieldSpecification',
'ReleephOriginalCommitFieldSpecification',
'ReleephDiffSizeFieldSpecification',
'ReleephDiffChurnFieldSpecification',
)),
'right' => self::selectFields($fields, array(
'ReleephRequestorFieldSpecification',
'ReleephSeverityFieldSpecification',
'ReleephStatusFieldSpecification',
'ReleephIntentFieldSpecification',
'ReleephBranchCommitFieldSpecification',
))
),
// Bottom group
array(
'left' => self::selectFields($fields, array(
'ReleephDiffMessageFieldSpecification',
)),
'right' => self::selectFields($fields, array(
'ReleephReasonFieldSpecification',
))
)
);
}
}
public function arrangeFieldsForSelectForm(array $fields) {
if (self::isFacebook()) {
return self::selectFields($fields, array(
'ReleephStatusFieldSpecification',
'ReleephFacebookSeverityFieldSpecification',
'ReleephRequestorFieldSpecification',
'ReleephFacebookTagFieldSpecification',
));
} else {
return self::selectFields($fields, array(
'ReleephStatusFieldSpecification',
'ReleephSeverityFieldSpecification',
'ReleephRequestorFieldSpecification',
));
}
}
public function sortFieldsForCommitMessage(array $fields) {
return self::selectFields($fields, array(
'ReleephCommitMessageFieldSpecification',

View file

@ -1,9 +1,5 @@
<?php
/**
* Control the rendering of ReleephRequestHeaderView, and the layout of the
* ReleephRequest search dialog (in ReleephBranchViewController.)
*/
abstract class ReleephFieldSelector {
final public function __construct() {
@ -12,10 +8,6 @@ abstract class ReleephFieldSelector {
abstract public function getFieldSpecifications();
abstract public function arrangeFieldsForHeaderView(array $fields);
abstract public function arrangeFieldsForSelectForm(array $fields);
public function sortFieldsForCommitMessage(array $fields) {
assert_instances_of($fields, 'ReleephFieldSpecification');
return $fields;

View file

@ -24,17 +24,23 @@ final class ReleephAuthorFieldSpecification
}
public function renderValueForHeaderView() {
$rr = $this->getReleephRequest();
$author_phid = idx(self::$authorMap, $rr->getPHID());
if ($author_phid) {
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->getUser())
->withPHIDs(array($author_phid))
->executeOne();
return $handle->renderLink();
} else {
return 'Unknown Author';
$pull = $this->getReleephRequest();
$commit = $pull->loadPhabricatorRepositoryCommit();
if (!$commit) {
return null;
}
$author_phid = $commit->getAuthorPHID();
if (!$author_phid) {
return null;
}
$handle = id(new PhabricatorHandleQuery())
->setViewer($this->getUser())
->withPHIDs(array($author_phid))
->executeOne();
return $handle->renderLink();
}
}

View file

@ -11,6 +11,10 @@ final class ReleephCommitMessageFieldSpecification
return '__only_for_commit_message!';
}
public function shouldAppearInPropertyView() {
return false;
}
public function shouldAppearOnCommitMessage() {
return true;
}

View file

@ -15,18 +15,17 @@ final class ReleephDiffMessageFieldSpecification
return null;
}
public function getStyleForPropertyView() {
return 'block';
}
public function renderValueForHeaderView() {
$markup = phutil_tag(
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$this->getMarkupEngineOutput());
return id(new AphrontNoteView())
->setTitle('Commit Message')
->appendChild($markup)
->render();
}
public function shouldMarkup() {

View file

@ -13,6 +13,30 @@ abstract class ReleephFieldSpecification
return $this;
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getName();
}
public function renderPropertyViewValue(array $handles) {
$value = $this->renderValueForHeaderView();
if ($value === '') {
return null;
}
return $value;
}
public function slowlyLoadHandle($phid) {
// TODO: Remove this, it's transitional as fields modernize.
return id(new PhabricatorHandleQuery())
->withPHIDs(array($phid))
->setViewer($this->getUser())
->executeOne();
}
abstract public function getName();
/* -( Storage )------------------------------------------------------------ */
@ -148,18 +172,30 @@ abstract class ReleephFieldSpecification
}
final public function getReleephProject() {
if (!$this->releephProject) {
return $this->getReleephBranch()->getProduct();
}
return $this->releephProject;
}
final public function getReleephBranch() {
if (!$this->releephBranch) {
return $this->getReleephRequest()->getBranch();
}
return $this->releephBranch;
}
final public function getReleephRequest() {
if (!$this->releephRequest) {
return $this->getObject();
}
return $this->releephRequest;
}
final public function getUser() {
if (!$this->user) {
return $this->getViewer();
}
return $this->user;
}

View file

@ -12,10 +12,67 @@ final class ReleephIntentFieldSpecification
}
public function renderValueForHeaderView() {
return id(new ReleephRequestIntentsView())
->setReleephRequest($this->getReleephRequest())
->setReleephProject($this->getReleephProject())
->render();
$pull = $this->getReleephRequest();
$intents = $pull->getUserIntents();
$product = $this->getReleephProject();
if (!$intents) {
return null;
}
$user_phids = array_keys($intents);
if ($user_phids) {
$handles = id(new PhabricatorHandleQuery())
->withPHIDs($user_phids)
->setViewer(PhabricatorUser::getOmnipotentUser())
->execute();
} else {
$handles = array();
}
$pushers = array();
$others = array();
foreach ($intents as $phid => $intent) {
if ($product->isAuthoritativePHID($phid)) {
$pushers[$phid] = $intent;
} else {
$others[$phid] = $intent;
}
}
$intents = $pushers + $others;
$view = id(new PHUIStatusListView());
foreach ($intents as $phid => $intent) {
switch ($intent) {
case ReleephRequest::INTENT_WANT:
$icon = 'accept-green';
$label = pht('Want');
break;
case ReleephRequest::INTENT_PASS:
$icon = 'reject-red';
$label = pht('Pass');
break;
default:
$icon = 'question';
$label = pht('Unknown Intent (%s)', $intent);
break;
}
$target = $handles[$phid]->renderLink();
if ($product->isAuthoritativePHID($phid)) {
$target = phutil_tag('strong', array(), $target);
}
$view->addItem(
id(new PHUIStatusItemView())
->setIcon($icon, $label)
->setTarget($target));
}
return $view;
}
public function shouldAppearOnCommitMessage() {

View file

@ -12,9 +12,8 @@ final class ReleephOriginalCommitFieldSpecification
}
public function renderValueForHeaderView() {
$rr = $this->getReleephRequest();
$handles = $rr->getHandles();
return $handles[$rr->getRequestCommitPHID()]->renderLink();
$pull = $this->getReleephRequest();
return $this->slowlyLoadHandle($pull->getRequestCommitPHID())->renderLink();
}
}

View file

@ -15,22 +15,25 @@ final class ReleephReasonFieldSpecification
return 'reason';
}
public function getStyleForPropertyView() {
return 'block';
}
public function renderLabelForHeaderView() {
return null;
}
public function getIconForPropertyView() {
return PHUIPropertyListView::ICON_SUMMARY;
}
public function renderValueForHeaderView() {
$markup = phutil_tag(
return phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$this->getMarkupEngineOutput());
return id(new AphrontNoteView())
->setTitle('Reason')
->appendChild($markup)
->render();
}
private $error = true;

View file

@ -1,20 +0,0 @@
<?php
final class ReleephStatusFieldSpecification
extends ReleephFieldSpecification {
public function getFieldKey() {
return 'status';
}
public function getName() {
return 'Status';
}
public function renderValueForHeaderView() {
return id(new ReleephRequestStatusView())
->setReleephRequest($this->getReleephRequest())
->render();
}
}

View file

@ -5,6 +5,10 @@ final class ReleephSummaryFieldSpecification
const MAX_SUMMARY_LENGTH = 60;
public function shouldAppearInPropertyView() {
return false;
}
public function getFieldKey() {
return 'summary';
}

View file

@ -187,38 +187,21 @@ final class ReleephRequest extends ReleephDAO
return $reason;
}
public function getSummary() {
/**
* Instead, you can use:
* - getDetail('summary') // the actual user-chosen summary
* - getSummaryForDisplay() // falls back to the original commit title
*
* Or for the fastidious:
* - id(new ReleephSummaryFieldSpecification())
* ->setReleephRequest($rr)
* ->getValue() // programmatic equivalent to getDetail()
*/
throw new Exception(
"getSummary() has been deprecated!");
}
/**
* Allow a null summary, and fall back to the title of the commit.
*/
public function getSummaryForDisplay() {
$summary = $this->getDetail('summary');
if (!$summary) {
$pr_commit_data = $this->loadPhabricatorRepositoryCommitData();
if ($pr_commit_data) {
$message_lines = explode("\n", $pr_commit_data->getCommitMessage());
$message_lines = array_filter($message_lines);
$summary = head($message_lines);
if (!strlen($summary)) {
$commit = $this->loadPhabricatorRepositoryCommit();
if ($commit) {
$summary = $commit->getSummary();
}
}
if (!$summary) {
$summary = '(no summary given and commit message empty or unparsed)';
if (!strlen($summary)) {
$summary = pht('None');
}
return $summary;

View file

@ -0,0 +1,249 @@
<?php
final class ReleephRequestView extends AphrontView {
private $pullRequest;
private $customFields;
private $isListView;
public function setIsListView($is_list_view) {
$this->isListView = $is_list_view;
return $this;
}
public function getIsListView() {
return $this->isListView;
}
public function setCustomFields(PhabricatorCustomFieldList $custom_fields) {
$this->customFields = $custom_fields;
return $this;
}
public function getCustomFields() {
return $this->customFields;
}
public function setPullRequest(ReleephRequest $pull_request) {
$this->pullRequest = $pull_request;
return $this;
}
public function getPullRequest() {
return $this->pullRequest;
}
public function render() {
$viewer = $this->getUser();
$field_list = $this->getCustomFields();
$pull = $this->getPullRequest();
$header = $this->buildHeader($pull);
$action_list = $this->buildActionList($pull);
$property_list = id(new PHUIPropertyListView())
->setUser($viewer)
->setActionList($action_list);
$field_list->appendFieldsToPropertyList(
$pull,
$viewer,
$property_list);
$warnings = $this->getWarnings($pull);
if ($this->getIsListView()) {
Javelin::initBehavior('releeph-request-state-change');
}
return id(new PHUIObjectBoxView())
->setHeader($header)
->setFormErrors($warnings)
->addSigil('releeph-request-box')
->setMetadata(array('uri' => '/'.$pull->getMonogram()))
->appendChild($property_list);
}
private function buildHeader(ReleephRequest $pull) {
$header_text = $pull->getSummaryForDisplay();
if ($this->getIsListView()) {
$header_text = phutil_tag(
'a',
array(
'href' => '/'.$pull->getMonogram(),
),
$header_text);
}
$header = id(new PHUIHeaderView())
->setHeader($header_text)
->setUser($this->getUser())
->setPolicyObject($pull);
switch ($pull->getStatus()) {
case ReleephRequestStatus::STATUS_REQUESTED:
$icon = 'open';
$color = null;
break;
case ReleephRequestStatus::STATUS_REJECTED:
$icon = 'reject';
$color = 'red';
break;
case ReleephRequestStatus::STATUS_PICKED:
$icon = 'accept';
$color = 'green';
break;
case ReleephRequestStatus::STATUS_REVERTED:
case ReleephRequestStatus::STATUS_ABANDONED:
$icon = 'reject';
$color = 'dark';
break;
case ReleephRequestStatus::STATUS_NEEDS_PICK:
$icon = 'warning';
$color = 'green';
break;
case ReleephRequestStatus::STATUS_NEEDS_REVERT:
$icon = 'warning';
$color = 'red';
break;
default:
$icon = 'question';
$color = null;
break;
}
$text = ReleephRequestStatus::getStatusDescriptionFor($pull->getStatus());
$header->setStatus($icon, $color, $text);
if ($this->getIsListView()) {
$header->setObjectName($pull->getMonogram());
}
return $header;
}
private function buildActionList(ReleephRequest $pull) {
$viewer = $this->getUser();
$id = $pull->getID();
$edit_uri = '/releeph/request/edit/'.$id.'/';
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$product = $pull->getBranch()->getProduct();
$viewer_is_pusher = $product->isAuthoritativePHID($viewer->getPHID());
$viewer_is_requestor = ($pull->getRequestUserPHID() == $viewer->getPHID());
if ($viewer_is_pusher) {
$yes_text = pht('Approve Pull');
$no_text = pht('Reject Pull');
$yes_icon = 'check';
$no_icon = 'delete';
} else if ($viewer_is_requestor) {
$yes_text = pht('Request Pull');
$no_text = pht('Cancel Pull');
$yes_icon = 'ok';
$no_icon = 'delete';
} else {
$yes_text = pht('Support Pull');
$no_text = pht('Discourage Pull');
$yes_icon = 'like';
$no_icon = 'dislike';
}
$yes_href = '/releeph/request/action/want/'.$id.'/';
$no_href = '/releeph/request/action/pass/'.$id.'/';
$intents = $pull->getUserIntents();
$current_intent = idx($intents, $viewer->getPHID());
$yes_disabled = ($current_intent == ReleephRequest::INTENT_WANT);
$no_disabled = ($current_intent == ReleephRequest::INTENT_PASS);
$use_workflow = (!$this->getIsListView());
$view->addAction(
id(new PhabricatorActionView())
->setName($yes_text)
->setHref($yes_href)
->setWorkflow($use_workflow)
->setRenderAsForm($use_workflow)
->setDisabled($yes_disabled)
->addSigil('releeph-request-state-change')
->addSigil('want')
->setIcon($yes_icon));
$view->addAction(
id(new PhabricatorActionView())
->setName($no_text)
->setHref($no_href)
->setWorkflow($use_workflow)
->setRenderAsForm($use_workflow)
->setDisabled($no_disabled)
->addSigil('releeph-request-state-change')
->addSigil('pass')
->setIcon($no_icon));
if ($viewer_is_pusher || $viewer_is_requestor) {
$pulled_href = '/releeph/request/action/mark-manually-picked/'.$id.'/';
$revert_href = '/releeph/request/action/mark-manually-reverted/'.$id.'/';
if ($pull->getInBranch()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Mark as Reverted'))
->setHref($revert_href)
->setWorkflow($use_workflow)
->setRenderAsForm($use_workflow)
->addSigil('releeph-request-state-change')
->addSigil('mark-manually-reverted')
->setIcon($no_icon));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Mark as Pulled'))
->setHref($pulled_href)
->setWorkflow($use_workflow)
->setRenderAsForm($use_workflow)
->addSigil('releeph-request-state-change')
->addSigil('mark-manually-picked')
->setIcon('warning'));
}
}
if (!$this->getIsListView()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Pull Request'))
->setIcon('edit')
->setHref($edit_uri));
}
return $view;
}
private function getWarnings(ReleephRequest $pull) {
$warnings = array();
switch ($pull->getStatus()) {
case ReleephRequestStatus::STATUS_NEEDS_PICK:
if ($pull->getPickStatus() == ReleephRequest::PICK_FAILED) {
$warnings[] = pht('Last pull failed!');
}
break;
case ReleephRequestStatus::STATUS_NEEDS_REVERT:
if ($pull->getPickStatus() == ReleephRequest::REVERT_FAILED) {
$warnings[] = pht('Last revert failed!');
}
break;
}
return $warnings;
}
}

View file

@ -1,99 +0,0 @@
<?php
final class ReleephRequestIntentsView extends AphrontView {
private $releephRequest;
private $releephProject;
public function setReleephRequest(ReleephRequest $rq) {
$this->releephRequest = $rq;
return $this;
}
public function setReleephProject(ReleephProject $rp) {
$this->releephProject = $rp;
return $this;
}
public function render() {
require_celerity_resource('releeph-intents');
return phutil_tag(
'div',
array(
'class' => 'releeph-intents',
),
array(
$this->renderIntentList(ReleephRequest::INTENT_WANT),
$this->renderIntentList(ReleephRequest::INTENT_PASS)
));
}
private function renderIntentList($render_intent) {
if (!$this->releephProject) {
throw new Exception("Must call setReleephProject() first!");
}
$project = $this->releephProject;
$request = $this->releephRequest;
$handles = $request->getHandles();
$pusher_links = array();
$user_links = array();
$intents = $request->getUserIntents();
foreach ($intents as $user_phid => $user_intent) {
if ($user_intent == $render_intent) {
if ($project->isAuthoritativePHID($user_phid)) {
$pusher_links[] = phutil_tag(
'span',
array(
'class' => 'pusher'
),
$handles[$user_phid]->renderLink());
} else {
$class = 'bystander';
if ($request->getRequestUserPHID() == $user_phid) {
$class = 'requestor';
}
$user_links[] = phutil_tag(
'span',
array(
'class' => $class,
),
$handles[$user_phid]->renderLink());
}
}
}
// Don't render anything
if (!$pusher_links && !$user_links) {
return null;
}
$links = array_merge($pusher_links, $user_links);
if ($links) {
$markup = $links;
} else {
$markup = array('&nbsp;');
}
// Stick an arrow up front
$arrow_class = 'arrow '.$render_intent;
array_unshift($markup, phutil_tag(
'div',
array(
'class' => $arrow_class,
),
''));
return phutil_tag(
'div',
array(
'class' => 'intents',
),
$markup);
}
}

View file

@ -1,53 +0,0 @@
<?php
final class ReleephRequestStatusView extends AphrontView {
private $releephRequest;
public function setReleephRequest(ReleephRequest $rq) {
$this->releephRequest = $rq;
return $this;
}
public function render() {
require_celerity_resource('releeph-status');
$request = $this->releephRequest;
$status = $request->getStatus();
$pick_status = $request->getPickStatus();
$description = ReleephRequestStatus::getStatusDescriptionFor($status);
$warning = null;
if ($status == ReleephRequestStatus::STATUS_NEEDS_PICK) {
if ($pick_status == ReleephRequest::PICK_FAILED) {
$warning = 'Last pick failed!';
}
} elseif ($status == ReleephRequestStatus::STATUS_NEEDS_REVERT) {
if ($pick_status == ReleephRequest::REVERT_FAILED) {
$warning = 'Last revert failed!';
}
}
return phutil_tag(
'div',
array(
'class' => 'releeph-status',
),
array(
phutil_tag(
'div',
array(
'class' => 'description',
),
$description),
phutil_tag(
'div',
array(
'class' => 'warning',
),
$warning)));
}
}

View file

@ -1,121 +0,0 @@
<?php
final class ReleephRequestHeaderListView
extends AphrontView {
private $releephProject;
private $releephBranch;
private $releephRequests;
private $aphrontRequest;
private $reload = false;
private $errors = array();
public function setReleephProject(ReleephProject $rp) {
$this->releephProject = $rp;
return $this;
}
public function setReleephBranch(ReleephBranch $rb) {
$this->releephBranch = $rb;
return $this;
}
public function setReleephRequests(array $requests) {
assert_instances_of($requests, 'ReleephRequest');
$this->releephRequests = $requests;
return $this;
}
public function setAphrontRequest(AphrontRequest $request) {
$this->aphrontRequest = $request;
return $this;
}
public function setReloadOnStateChange($bool) {
$this->reload = $bool;
return $this;
}
public function render() {
$views = $this->renderInner();
require_celerity_resource('phabricator-notification-css');
Javelin::initBehavior('releeph-request-state-change', array(
'reload' => $this->reload,
));
$error_view = null;
if ($this->errors) {
$error_view = id(new AphrontErrorView())
->setTitle('Bulk load errors')
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setErrors($this->errors)
->render();
}
$list = phutil_tag(
'div',
array(
'data-sigil' => 'releeph-request-header-list',
),
$views);
return array($error_view, $list);
}
/**
* Required for generating markup for ReleephRequestActionController.
*
* That controller just needs the markup, and doesn't need to start the
* javelin behavior.
*/
public function renderInner() {
$selector = $this->releephProject->getReleephFieldSelector();
$fields = $selector->getFieldSpecifications();
foreach ($fields as $field) {
$field
->setReleephProject($this->releephProject)
->setReleephBranch($this->releephBranch)
->setUser($this->user);
try {
$field->bulkLoad($this->releephRequests);
} catch (Exception $ex) {
$this->errors[] = $ex;
}
}
$engine = id(new PhabricatorMarkupEngine())
->setViewer($this->getUser());
$views = array();
foreach ($this->releephRequests as $releeph_request) {
$our_fields = array();
foreach ($fields as $key => $field) {
$our_fields[$key] = clone $field;
}
foreach ($our_fields as $field) {
if ($field->shouldMarkup()) {
$field
->setReleephRequest($releeph_request)
->setMarkupEngine($engine);
}
}
$our_field_groups = $selector->arrangeFieldsForHeaderView($our_fields);
$views[] = id(new ReleephRequestHeaderView())
->setUser($this->user)
->setAphrontRequest($this->aphrontRequest)
->setReleephProject($this->releephProject)
->setReleephBranch($this->releephBranch)
->setReleephRequest($releeph_request)
->setReleephFieldGroups($our_field_groups);
}
$engine->process();
return $views;
}
}

View file

@ -1,323 +0,0 @@
<?php
final class ReleephRequestHeaderView extends AphrontView {
const THROW_PARAM = '__releeph_throw';
private $aphrontRequest;
private $releephRequest;
private $releephBranch;
private $releephProject;
private $fieldGroups;
public function setAphrontRequest(AphrontRequest $request) {
$this->aphrontRequest = $request;
return $this;
}
public function setReleephProject(ReleephProject $rp) {
$this->releephProject = $rp;
return $this;
}
public function setReleephBranch(ReleephBranch $rb) {
$this->releephBranch = $rb;
return $this;
}
public function setReleephRequest(ReleephRequest $rr) {
$this->releephRequest = $rr;
return $this;
}
public function setReleephFieldGroups(array $field_groups) {
$this->fieldGroups = $field_groups;
return $this;
}
public function render() {
require_celerity_resource('releeph-core');
$all_properties_table = $this->renderFields();
require_celerity_resource('releeph-colors');
$status = $this->releephRequest->getStatus();
$rr_div_class =
'releeph-request-header '.
'releeph-request-header-border '.
'releeph-border-color-'.
ReleephRequestStatus::getStatusClassSuffixFor($status);
$hidden_link = phutil_tag(
'a',
array(
'href' => '/RQ'.$this->releephRequest->getID(),
'target' => '_blank',
'data-sigil' => 'hidden-link',
),
'');
$focus_char = phutil_tag(
'div',
array(
'class' => 'focus-char',
'data-sigil' => 'focus-char',
),
"\xE2\x98\x86");
$rr_div = phutil_tag(
'div',
array(
'data-sigil' => 'releeph-request-header',
'class' => $rr_div_class,
),
array(
phutil_tag(
'div',
array(),
array(
phutil_tag(
'h1',
array(),
array(
$focus_char,
$this->renderTitleLink(),
$hidden_link
)),
$all_properties_table,
)),
phutil_tag(
'div',
array(
'class' => 'button-divider',
),
$this->renderActionButtonsTable())));
return $rr_div;
}
private function renderFields() {
$field_row_groups = $this->fieldGroups;
$trs = array();
foreach ($field_row_groups as $field_column_group) {
$tds = array();
foreach ($field_column_group as $side => $fields) {
$rows = array();
foreach ($fields as $field) {
$rows[] = $this->renderOneField($field);
}
$pane = phutil_tag(
'table',
array(
'class' => 'fields',
),
$rows);
$tds[] = phutil_tag(
'td',
array(
'class' => 'side '.$side,
),
$pane);
}
$trs[] = phutil_tag(
'tr',
array(),
$tds);
}
return phutil_tag(
'table',
array(
'class' => 'panes',
),
$trs);
}
private function renderOneField(ReleephFieldSpecification $field) {
$field
->setUser($this->user)
->setReleephProject($this->releephProject)
->setReleephBranch($this->releephBranch)
->setReleephRequest($this->releephRequest);
$label = $field->renderLabelForHeaderView();
try {
$value = $field->renderValueForHeaderView();
} catch (Exception $ex) {
if ($this->aphrontRequest->getInt(self::THROW_PARAM)) {
throw $ex;
} else {
$value = $this->renderExceptionIcon($ex);
}
}
if ($value) {
if (!$label) {
return phutil_tag(
'tr',
array(),
phutil_tag('td', array('colspan' => 2), $value));
} else {
return phutil_tag(
'tr',
array(),
array(
phutil_tag('th', array(), $label),
phutil_tag('td', array(), $value)));
}
}
}
private function renderExceptionIcon(Exception $ex) {
Javelin::initBehavior('phabricator-tooltips');
require_celerity_resource('aphront-tooltip-css');
$throw_uri = $this
->aphrontRequest
->getRequestURI()
->setQueryParam(self::THROW_PARAM, 1);
$message = $ex->getMessage();
if (!$message) {
$message = get_class($ex).' with no message.';
}
return javelin_tag(
'a',
array(
'class' => 'releeph-field-error',
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $message,
'size' => 400,
'align' => 'E',
),
'href' => $throw_uri,
),
'!!!');
}
private function renderTitleLink() {
$rq_id = $this->releephRequest->getID();
$summary = $this->releephRequest->getSummaryForDisplay();
return phutil_tag(
'a',
array(
'href' => '/RQ'.$rq_id,
),
hsprintf(
'RQ%d: %s',
$rq_id,
$summary));
}
private function renderActionButtonsTable() {
$left_buttons = array();
$right_buttons = array();
$user_phid = $this->user->getPHID();
$is_pusher = $this->releephProject->isAuthoritativePHID($user_phid);
$is_requestor = $this->releephRequest->getRequestUserPHID() === $user_phid;
$current_intent = idx(
$this->releephRequest->getUserIntents(),
$this->user->getPHID());
if ($is_pusher) {
$left_buttons[] = $this->renderIntentButton(true, 'Approve', 'green');
$left_buttons[] = $this->renderIntentButton(false, 'Reject');
} else {
if ($is_requestor) {
$right_buttons[] = $this->renderIntentButton(true, 'Request');
$right_buttons[] = $this->renderIntentButton(false, 'Remove');
} else {
$right_buttons[] = $this->renderIntentButton(true, 'Want');
$right_buttons[] = $this->renderIntentButton(false, 'Pass');
}
}
// Allow the pusher to mark a request as manually picked or reverted.
if ($is_pusher || $is_requestor) {
if ($this->releephRequest->getInBranch()) {
$left_buttons[] = $this->renderActionButton(
'Mark Manually Reverted',
'mark-manually-reverted');
} else {
$left_buttons[] = $this->renderActionButton(
'Mark Manually Picked',
'mark-manually-picked');
}
}
$right_buttons[] = phutil_tag(
'a',
array(
'href' => '/releeph/request/edit/'.$this->releephRequest->getID().'/',
'class' => 'small blue button',
),
'Edit');
if (!$left_buttons && !$right_buttons) {
return;
}
$cells = array();
foreach ($left_buttons as $button) {
$cells[] = phutil_tag('td', array('align' => 'left'), $button);
}
$cells[] = phutil_tag('td', array('class' => 'wide'), '');
foreach ($right_buttons as $button) {
$cells[] = phutil_tag('td', array('align' => 'right'), $button);
}
$table = phutil_tag(
'table',
array(
'class' => 'buttons',
),
phutil_tag(
'tr',
array(),
$cells));
return $table;
}
private function renderIntentButton($want, $name, $class = null) {
$current_intent = idx(
$this->releephRequest->getUserIntents(),
$this->user->getPHID());
if ($current_intent) {
// If this is a "want" button, and they already want it, disable the
// button (and vice versa for the "pass" case.)
if (($want && $current_intent == ReleephRequest::INTENT_WANT) ||
(!$want && $current_intent == ReleephRequest::INTENT_PASS)) {
$class .= ' disabled';
}
}
$action = $want ? 'want' : 'pass';
return $this->renderActionButton($name, $action, $class);
}
private function renderActionButton($name, $action, $class=null) {
$attributes = array(
'class' => 'small button '.$class,
'sigil' => 'releeph-request-state-change '.$action,
'meta' => null,
);
if ($class != 'disabled') {
// NB the trailing slash on $uri is critical, otherwise the URI will
// redirect to one with a slash, which will turn our GET into a POST.
$attributes['meta'] = sprintf(
'/releeph/request/action/%s/%d/',
$action,
$this->releephRequest->getID());
}
return javelin_tag('a', $attributes, $name);
}
}

View file

@ -1,137 +0,0 @@
<?php
final class PhabricatorNoteExample extends PhabricatorUIExample {
public function getName() {
return "Notes";
}
public function getDescription() {
return pht('Bounded boxes of text.');
}
public function renderExample() {
$short_note = id(new AphrontNoteView())
->setTitle(pht('Short note'))
->appendChild('xxxx xx x xxxxx xxxx');
$longer_note = id(new AphrontNoteView())
->setTitle(pht('Longer note'))
->appendChild($this->buildParagraphs(2));
$wide_url = 'protocol://www.'.str_repeat('x', 100).'.com/';
$oversize_note = id(new AphrontNoteView())
->setTitle(pht('Oversize note'))
->appendChild(
$this->buildParagraphs(2).
$wide_url."\n\n".
$this->buildParagraphs(15));
$out = array();
$out[] = id(new AphrontPanelView())
->setHeader(pht('Unbounded Oversize Note'))
->appendChild($oversize_note);
$out[] = id(new AphrontPanelView())
->setHeader(pht('Short notes'))
->appendChild(
$this->renderTable(
array(array($short_note, $short_note))));
$out[] = id(new AphrontPanelView())
->setHeader(pht('Mixed notes'))
->appendChild(
$this->renderTable(
array(
array($longer_note, $short_note),
array($short_note, $short_note)
)));
$out[] = id(new AphrontPanelView())
->setHeader(pht('Oversize notes'))
->appendChild(
$this->renderTable(
array(
array($oversize_note, $short_note),
array($short_note, $oversize_note)
)));
return $out;
}
private function renderTable($rows) {
static $td_style = '
width: 50%;
max-width: 1em;
';
$trs = array();
foreach ($rows as $index => $row) {
$count = $index + 1;
list($left, $right) = $row;
$trs[] = phutil_tag(
'tr',
array(),
array(
phutil_tag(
'th',
array(),
"Row {$count}"),
phutil_tag('td')));
$trs[] = phutil_tag(
'tr',
array(),
array(
phutil_tag(
'td',
array(
'style' => $td_style,
),
$left->render()),
phutil_tag(
'td',
array(
'style' => $td_style,
),
$right->render())));
}
return phutil_tag(
'table',
array(
'style' => 'width: 80%;'
),
$trs);
}
private function buildParagraphs($num_paragraphs) {
$body = '';
for ($pp = 0; $pp < $num_paragraphs; $pp++) {
$scale = 50 * ($pp / 2);
$num_words = 30 + self::getRandom(0, $scale);
for ($ii = 0; $ii < $num_words; $ii++) {
$word = str_repeat('x', self::getRandom(3, 8));
$body .= $word.' ';
}
$body .= "\n\n";
}
return $body;
}
private static function getRandom($lower, $upper) {
// The ZX Spectrum's PRNG!
static $nn = 65537;
static $gg = 75;
static $ii = 1;
$ii = ($ii * $gg) % $nn;
if ($lower == $upper) {
return $lower;
} else {
return $lower + ($ii % ($upper - $lower));
}
}
}

View file

@ -11,6 +11,7 @@ final class PhabricatorActionView extends AphrontView {
private $renderAsForm;
private $download;
private $objectURI;
private $sigils = array();
public function setObjectURI($object_uri) {
$this->objectURI = $object_uri;
@ -34,6 +35,11 @@ final class PhabricatorActionView extends AphrontView {
return $this;
}
public function addSigil($sigil) {
$this->sigils[] = $sigil;
return $this;
}
/**
* If the user is not logged in and the action is relatively complicated,
* give them a generic login link that will re-direct to the page they're
@ -98,6 +104,21 @@ final class PhabricatorActionView extends AphrontView {
}
if ($this->href) {
$sigils = array();
if ($this->workflow) {
$sigils[] = 'workflow';
}
if ($this->download) {
$sigils[] = 'download';
}
if ($this->sigils) {
$sigils = array_merge($sigils, $this->sigils);
}
$sigils = $sigils ? implode(' ', $sigils) : null;
if ($this->renderAsForm) {
if (!$this->user) {
throw new Exception(
@ -111,20 +132,12 @@ final class PhabricatorActionView extends AphrontView {
),
$this->name);
$sigils = array();
if ($this->workflow) {
$sigils[] = 'workflow';
}
if ($this->download) {
$sigils[] = 'download';
}
$item = phabricator_form(
$this->user,
array(
'action' => $this->getHref(),
'method' => 'POST',
'sigil' => implode(' ', $sigils),
'sigil' => $sigils,
),
$item);
} else {
@ -133,7 +146,7 @@ final class PhabricatorActionView extends AphrontView {
array(
'href' => $this->getHref(),
'class' => 'phabricator-action-view-item',
'sigil' => $this->workflow ? 'workflow' : null,
'sigil' => $sigils,
),
$this->name);
}

View file

@ -11,10 +11,22 @@ final class PHUIObjectBoxView extends AphrontView {
private $header;
private $flush;
private $id;
private $sigils = array();
private $metadata;
private $tabs = array();
private $propertyLists = array();
public function addSigil($sigil) {
$this->sigils[] = $sigil;
return $this;
}
public function setMetadata(array $metadata) {
$this->metadata = $metadata;
return $this;
}
public function addPropertyList(
PHUIPropertyListView $property_list,
$tab = null) {
@ -246,6 +258,14 @@ final class PHUIObjectBoxView extends AphrontView {
$content->addClass('phui-object-box-flush');
}
foreach ($this->sigils as $sigil) {
$content->addSigil($sigil);
}
if ($this->metadata !== null) {
$content->setMetadata($this->metadata);
}
return $content;
}
}

View file

@ -1,38 +0,0 @@
<?php
final class AphrontNoteView extends AphrontView {
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function render() {
$title = phutil_tag(
'div',
array(
'class' => 'title',
),
$this->title);
$inner = phutil_tag(
'div',
array(
'class' => 'inner',
),
$this->renderChildren());
require_celerity_resource('aphront-notes');
return phutil_tag(
'div',
array(
'class' => 'aphront-note',
),
array(
$title,
$inner));
}
}

View file

@ -1,26 +0,0 @@
/**
* @provides aphront-notes
*/
div.aphront-note {
margin: 1em;
background: #ffc;
border: 1px solid #ccc;
}
div.aphront-note div.title {
margin: 0;
padding: 0.2em 0.5em 0.2em;
background: #ffd;
border-bottom: 1px solid #ccc;
color: {$lightgreytext};
font-size: smaller;
}
div.aphront-note div.inner {
overflow: auto;
padding: 0.5em;
max-height: 25em;
color: #333;
font-size: smaller;
}

View file

@ -1,79 +0,0 @@
/**
* @provides releeph-branch
*/
.releeph-branch-box {
margin-bottom: .5em;
padding: .5em .5em .5em;
border: 2px solid #d5d5d5;
/*border-top-color: #D5D5D5;
border-right-color: #BBB;
border-bottom-color: #A4A4A4;
border-left-color: #BBB;*/
background: #bbb;
}
/* Types of branch */
.releeph-branch-box-named {
background: #ddd;
}
.releeph-branch-box-latest {
background: #ffd;
}
/* Branch symbolic name and full name */
.releeph-branch-box .names {
width: 25em;
float: left;
margin-bottom: 1em;
}
.releeph-branch-box .names h1 {
font-size: 125%;
padding: 0px;
}
.releeph-branch-box .names h2 {
font-weight: normal;
font-size: 85%;
}
/* Date info */
.releeph-branch-box .date-info {
width: 10%;
float: left;
color: #555;
margin-bottom: .3em;
}
/* Statistics table */
.releeph-branch-box .request-statistics {
float: right;
padding-right: 2em;
font-size: 85%;
}
.releeph-branch-box .request-statistics th {
width: 1em;
text-align: right;
padding-right: .4em;
padding-left: .4em;
}
.releeph-branch-box .request-statistics td {
white-space: nowrap;
font-style: italic;
}
/* Buttons */
.releeph-branch-box .buttons {
float: right;
}

View file

@ -1,35 +0,0 @@
/**
* @provides releeph-colors
*/
.releeph-border-color-failed {
border-color: #d2d;
}
.releeph-border-color-requested {
border-color: #ddd;
}
.releeph-border-color-comment {
border-color: #ddd;
}
.releeph-border-color-needs-pick {
border-color: #096;
}
.releeph-border-color-rejected {
border-color: #d00;
}
.releeph-border-color-needs-revert {
border-color: #d00;
}
.releeph-border-color-abandoned {
border-color: #222;
}
.releeph-border-color-picked {
border-color: #069;
}

View file

@ -2,119 +2,6 @@
* @provides releeph-core
*/
.releeph-request-header {
margin: .5em 2em 3em;
/**
* Copied from the old .differential-panel, present in commit
* f04d8ab1a747dc9719d378d9286088b677ce224c
*
* (As is the <h1> code below)
*/
max-width: 1120px;
border: 1px solid {$greytext}622;
background: #efefdf;
padding: 15px 20px;
font-size: 13px;
}
.releeph-request-header h1 {
width: 100%;
border-bottom: 1px solid #aaaa99;
padding-bottom: 8px;
margin-bottom: 8px;
position: relative;
}
.releeph-request-header .focus-char {
left: -10px;
display: none;
float: left;
position: absolute;
top: 0px;
left: -1em;
font-weight: bold;
color: #880;
font-family: "Hiragino Kaku Gothic Pro", "Osaka", "Zapf Dingbats";
}
.releeph-request-header.focus .focus-char {
display: block;
}
.releeph-request-header-border {
border-width: 1px 10px 1px;
border-color: #ddd;
}
/* Laying out properties / fields */
.releeph-request-header table.panes {
width: 100%;
}
.releeph-request-header table.panes td.side {
width: 50%;
max-width: 1em;
}
.releeph-request-header table.panes td.side.left {
padding-right: 20px;
border-right: 3px solid #bbb;
}
.releeph-request-header table.panes td.side.right {
padding-left: 20px;
}
.releeph-request-header table.panes td.side table.fields {
width: 100%;
}
.releeph-request-header table.panes td.side table.fields tr {
vertical-align: middle;
}
.releeph-request-header table.panes td.side table.fields th {
font-weight: bold;
text-align: right;
padding-right: 1em;
white-space: nowrap;
}
.releeph-request-header table.panes td.side table.fields td {
width: 100%; /* wide! */
max-width: 1em;
}
/* Buttons */
.releeph-request-header .button-divider {
clear: both;
margin-top: 1.5em;
border-top: 1px solid #bbb;
}
.releeph-request-header .buttons {
width: 100%;
}
.releeph-request-header .buttons tr {
padding: 1em;
margin: 3em;
}
.releeph-request-header .buttons td {
padding: 1em .5em 0.2em;
}
.releeph-request-header .buttons td.wide {
width: 100%;
}
/* Colors: match differential colors */
@ -133,11 +20,11 @@
/* The diff size bar */
.releeph-request-header .diff-bar {
.diff-bar {
border: 0px;
}
.releeph-request-header .diff-bar div {
.diff-bar div {
width: 100px;
border: 1px solid;
border-top-color: #A4A4A4;
@ -149,51 +36,10 @@
margin-right: 1em;
}
.releeph-request-header .diff-bar div div {
.diff-bar div div {
height: 10px;
}
.releeph-request-header .diff-bar span {
.diff-bar span {
color: #555;
}
/* Rendering pick / commit errors, etc. */
.releeph-request-pick-failed-event h1:before {
content: '\2014 ';
}
.releeph-request-pick-failed-event h1:after {
content: ' \2014';
}
.releeph-request-pick-failed-event h1 {
padding: 3px 10px 3px;
margin-bottom: 0.5em;
background: #ffb;
font-size: small;
}
.releeph-request-pick-failed-event div {
font-family: monospace;
margin-bottom: 1.5em;
padding-left: 1em;
width: 70em;
}
/* History view of request */
.releeph-request-event-list {
margin: .5em 2em .5em;
}
/* Shorten long header-text */
.releeph-header-text-truncated {
width: 100%;
float: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -1,37 +0,0 @@
/**
* @provides releeph-intents
*/
.releeph-intents .intents {
clear: left;
width: 100%;
margin-top: 3px;
}
.releeph-intents .arrow {
float: left;
clear: left;
margin-right: 0.4em;
padding: 8px;
background: transparent 0 0 no-repeat;
}
.releeph-intents .arrow.want {
/* TODO: Icon. */
}
.releeph-intents .arrow.pass {
/* TODO: Icon. */
}
.releeph-intents a {
margin-right: 0.4em;
}
.releeph-intents .pusher {
font-weight: bold;
}
.releeph-intents .requestor {
font-weight: normal;
}

View file

@ -1,25 +0,0 @@
/**
* @provides releeph-project
*/
/**
* ...from aphront-transaction.css
*/
.releeph-pusher {
background: 2px 2px no-repeat;
margin-top: 1em;
margin-bottom: 1.25em;
margin-right: 1em;
min-height: 50px;
padding: 2px 0px;
background-color: white;
border: 2px solid gray;
float: left;
}
.releeph-pusher-body {
margin-left: 54px;
padding: 1em;
}

View file

@ -1,27 +0,0 @@
/**
* @provides releeph-status
*/
.releeph-status .description {
background: #d3d3d3;
padding: 2px 6px 3px;
margin-right: 4px;
margin-bottom: 5px;
display: block;
float: left;
border-radius: 8px;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
text-decoration: none;
}
.releeph-status .warning {
margin-top: 2px;
margin-left: 0.8em;
float: left;
padding-left: 22px;
background-repeat: no-repeat;
background-size: 16px auto;
/* TODO: This had a background that's still at Facebook? */
}

View file

@ -7,3 +7,7 @@
border-bottom: 1px solid {$blueborder};
background-color: #fff;
}
.phui-box.focus {
box-shadow: 0 0 5px 5px rgba(255, 255, 0, 0.90);
}

View file

@ -3,23 +3,17 @@
* @requires javelin-behavior
* javelin-dom
* javelin-stratcom
* javelin-request
* javelin-workflow
* javelin-util
* phabricator-keyboard-shortcut
* phabricator-notification
*/
JX.behavior('releeph-request-state-change', function(config) {
var root = JX.DOM.find(document, 'div', 'releeph-request-header-list');
function getRequestHeaderNodes() {
return JX.DOM.scry(root, 'div', 'releeph-request-header');
return JX.DOM.scry(document.body, 'div', 'releeph-request-box');
}
/**
* Keyboard navigation
*/
var keynav_cursor = -1;
var notification = new JX.Notification();
function keynavJump(manager, delta) {
// Calculate this everytime, because the DOM changes.
@ -68,7 +62,7 @@ JX.behavior('releeph-request-state-change', function(config) {
function keynavNavigateToRequestPage() {
var headers = getRequestHeaderNodes();
var header = headers[keynav_cursor];
JX.DOM.find(header, 'a', 'hidden-link').click();
window.open(JX.Stratcom.getData(header).uri);
}
new JX.KeyboardShortcut('j', 'Jump to next request.')
@ -95,51 +89,33 @@ JX.behavior('releeph-request-state-change', function(config) {
})
.register();
new JX.KeyboardShortcut('g', "Open selected request's page in a new tab.")
new JX.KeyboardShortcut(
['g', 'return'],
"Open selected request's page in a new tab.")
.setHandler(function(manager) {
keynavNavigateToRequestPage();
})
.register();
/**
* AJAXy state changes for request buttons.
*/
function request_action(node, url) {
var request = new JX.Request(url, function(response) {
if (config.reload) {
window.location.reload();
} else {
var markup = JX.$H(response.markup);
JX.DOM.replace(node, markup);
keynavMarkup();
}
});
request.send();
function onresponse(box, response) {
JX.DOM.replace(box, JX.$H(response.markup));
keynavMarkup();
}
JX.Stratcom.listen(
'click',
'releeph-request-state-change',
function(e) {
var button = e.getNode('releeph-request-state-change');
var node = e.getNode('releeph-request-header');
var url = e.getNodeData('releeph-request-state-change');
e.kill();
// If this button has no action, or we've already responded to the first
// click...
if (!url || button.disabled) {
return;
}
var box = e.getNode('releeph-request-box');
var link = e.getNode('releeph-request-state-change');
// There's a race condition here though :(
box.style.opacity = '0.5';
JX.DOM.alterClass(button, 'disabled', true);
button.disabled = true;
e.prevent();
request_action(node, url);
}
);
JX.Workflow.newFromLink(link)
.setData({render: true})
.setHandler(JX.bind(null, onresponse, box))
.start();
});
});