mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-20 04:20:55 +01:00
Publish feed stories from Maniphest
Summary: I didn't get around to this earlier; add Feed/Maniphest integration. This is partly motivated by wanting Projects to not be terrible. Pretty straightforward. Test Plan: - Created, updated, reassigned and closed a task. - Verified feed stories render reasonably. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, btrahan Maniphest Tasks: T681 Differential Revision: 1232
This commit is contained in:
parent
b5c9b9d059
commit
ee620bde6d
8 changed files with 269 additions and 9 deletions
|
@ -337,6 +337,7 @@ phutil_register_library_map(array(
|
||||||
'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__',
|
'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__',
|
||||||
'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__',
|
'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__',
|
||||||
'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__',
|
'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__',
|
||||||
|
'ManiphestAction' => 'applications/maniphest/constants/action',
|
||||||
'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default',
|
'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default',
|
||||||
'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base',
|
'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base',
|
||||||
'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception',
|
'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception',
|
||||||
|
@ -438,6 +439,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFeedStory' => 'applications/feed/story/base',
|
'PhabricatorFeedStory' => 'applications/feed/story/base',
|
||||||
'PhabricatorFeedStoryData' => 'applications/feed/storage/story',
|
'PhabricatorFeedStoryData' => 'applications/feed/storage/story',
|
||||||
'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential',
|
'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential',
|
||||||
|
'PhabricatorFeedStoryManiphest' => 'applications/feed/story/maniphest',
|
||||||
'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction',
|
'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction',
|
||||||
'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher',
|
'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher',
|
||||||
'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference',
|
'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference',
|
||||||
|
@ -1017,6 +1019,7 @@ phutil_register_library_map(array(
|
||||||
'JavelinViewExampleServerView' => 'AphrontView',
|
'JavelinViewExampleServerView' => 'AphrontView',
|
||||||
'LiskIsolationTestCase' => 'PhabricatorTestCase',
|
'LiskIsolationTestCase' => 'PhabricatorTestCase',
|
||||||
'LiskIsolationTestDAO' => 'LiskDAO',
|
'LiskIsolationTestDAO' => 'LiskDAO',
|
||||||
|
'ManiphestAction' => 'PhrictionConstants',
|
||||||
'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification',
|
'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification',
|
||||||
'ManiphestController' => 'PhabricatorController',
|
'ManiphestController' => 'PhabricatorController',
|
||||||
'ManiphestDAO' => 'PhabricatorLiskDAO',
|
'ManiphestDAO' => 'PhabricatorLiskDAO',
|
||||||
|
@ -1101,6 +1104,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController',
|
'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController',
|
||||||
'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
|
'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
|
||||||
'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory',
|
'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory',
|
||||||
|
'PhabricatorFeedStoryManiphest' => 'PhabricatorFeedStory',
|
||||||
'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory',
|
'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory',
|
||||||
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
|
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
|
||||||
'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory',
|
'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory',
|
||||||
|
|
|
@ -23,5 +23,6 @@ final class PhabricatorFeedStoryTypeConstants
|
||||||
const STORY_STATUS = 'PhabricatorFeedStoryStatus';
|
const STORY_STATUS = 'PhabricatorFeedStoryStatus';
|
||||||
const STORY_DIFFERENTIAL = 'PhabricatorFeedStoryDifferential';
|
const STORY_DIFFERENTIAL = 'PhabricatorFeedStoryDifferential';
|
||||||
const STORY_PHRICTION = 'PhabricatorFeedStoryPhriction';
|
const STORY_PHRICTION = 'PhabricatorFeedStoryPhriction';
|
||||||
|
const STORY_MANIPHEST = 'PhabricatorFeedStoryManiphest';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2011 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PhabricatorFeedStoryManiphest extends PhabricatorFeedStory {
|
||||||
|
|
||||||
|
public function getRequiredHandlePHIDs() {
|
||||||
|
$data = $this->getStoryData();
|
||||||
|
return array(
|
||||||
|
$this->getStoryData()->getAuthorPHID(),
|
||||||
|
$data->getValue('taskPHID'),
|
||||||
|
$data->getValue('ownerPHID'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredObjectPHIDs() {
|
||||||
|
return array(
|
||||||
|
$this->getStoryData()->getAuthorPHID(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderView() {
|
||||||
|
$data = $this->getStoryData();
|
||||||
|
|
||||||
|
$handles = $this->getHandles();
|
||||||
|
$author_phid = $data->getAuthorPHID();
|
||||||
|
$owner_phid = $data->getValue('ownerPHID');
|
||||||
|
$task_phid = $data->getValue('taskPHID');
|
||||||
|
|
||||||
|
$objects = $this->getObjects();
|
||||||
|
$action = $data->getValue('action');
|
||||||
|
|
||||||
|
$view = new PhabricatorFeedStoryView();
|
||||||
|
|
||||||
|
$verb = ManiphestAction::getActionPastTenseVerb($action);
|
||||||
|
$title =
|
||||||
|
'<strong>'.$handles[$author_phid]->renderLink().'</strong>'.
|
||||||
|
" {$verb} ".
|
||||||
|
'<strong>'.$handles[$task_phid]->renderLink().'</strong>';
|
||||||
|
switch ($action) {
|
||||||
|
case ManiphestAction::ACTION_ASSIGN:
|
||||||
|
$title .=
|
||||||
|
' to '.
|
||||||
|
'<strong>'.$handles[$owner_phid]->renderLink().'</strong>';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$title .= '.';
|
||||||
|
$view->setTitle($title);
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case ManiphestAction::ACTION_CREATE:
|
||||||
|
$full_size = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$full_size = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view->setEpoch($data->getEpoch());
|
||||||
|
|
||||||
|
if ($full_size) {
|
||||||
|
if (!empty($objects[$author_phid])) {
|
||||||
|
$image_phid = $objects[$author_phid]->getProfileImagePHID();
|
||||||
|
$image_uri = PhabricatorFileURI::getViewURIForPHID($image_phid);
|
||||||
|
$view->setImage($image_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = phutil_escape_html(
|
||||||
|
phutil_utf8_shorten($data->getValue('description'), 128));
|
||||||
|
$content = str_replace("\n", '<br />', $content);
|
||||||
|
|
||||||
|
$view->appendChild($content);
|
||||||
|
} else {
|
||||||
|
$view->setOneLineStory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
18
src/applications/feed/story/maniphest/__init__.php
Normal file
18
src/applications/feed/story/maniphest/__init__.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/feed/story/base');
|
||||||
|
phutil_require_module('phabricator', 'applications/feed/view/story');
|
||||||
|
phutil_require_module('phabricator', 'applications/files/uri');
|
||||||
|
phutil_require_module('phabricator', 'applications/maniphest/constants/action');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'markup');
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorFeedStoryManiphest.php');
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2011 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group maniphest
|
||||||
|
*/
|
||||||
|
class ManiphestAction extends PhrictionConstants {
|
||||||
|
|
||||||
|
const ACTION_CREATE = 'create';
|
||||||
|
const ACTION_CLOSE = 'close';
|
||||||
|
const ACTION_UPDATE = 'update';
|
||||||
|
const ACTION_ASSIGN = 'assign';
|
||||||
|
|
||||||
|
public static function getActionPastTenseVerb($action) {
|
||||||
|
static $map = array(
|
||||||
|
self::ACTION_CREATE => 'created',
|
||||||
|
self::ACTION_CLOSE => 'closed',
|
||||||
|
self::ACTION_UPDATE => 'updated',
|
||||||
|
self::ACTION_ASSIGN => 'assigned',
|
||||||
|
);
|
||||||
|
|
||||||
|
return idx($map, $action, "brazenly {$action}'d");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a group of transactions contain several actions, select the "strongest"
|
||||||
|
* action. For instance, a close is stronger than an update, because we want
|
||||||
|
* to render "User U closed task T" instead of "User U updated task T" when
|
||||||
|
* a user closes a task.
|
||||||
|
*/
|
||||||
|
public static function selectStrongestAction(array $actions) {
|
||||||
|
static $strengths = array(
|
||||||
|
self::ACTION_UPDATE => 0,
|
||||||
|
self::ACTION_ASSIGN => 1,
|
||||||
|
self::ACTION_CREATE => 2,
|
||||||
|
self::ACTION_CLOSE => 3,
|
||||||
|
);
|
||||||
|
|
||||||
|
$strongest = null;
|
||||||
|
$strength = -1;
|
||||||
|
foreach ($actions as $action) {
|
||||||
|
if ($strengths[$action] > $strength) {
|
||||||
|
$strength = $strengths[$action];
|
||||||
|
$strongest = $action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $strongest;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/applications/maniphest/constants/action/__init__.php
Normal file
14
src/applications/maniphest/constants/action/__init__.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/phriction/constants/base');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('ManiphestAction.php');
|
|
@ -171,6 +171,8 @@ class ManiphestTransactionEditor {
|
||||||
$email_cc,
|
$email_cc,
|
||||||
$task->getCCPHIDs());
|
$task->getCCPHIDs());
|
||||||
|
|
||||||
|
$this->publishFeedStory($task, $transactions);
|
||||||
|
|
||||||
// TODO: Do this offline via timeline
|
// TODO: Do this offline via timeline
|
||||||
PhabricatorSearchManiphestIndexer::indexTask($task);
|
PhabricatorSearchManiphestIndexer::indexTask($task);
|
||||||
|
|
||||||
|
@ -207,15 +209,7 @@ class ManiphestTransactionEditor {
|
||||||
$view->setHandles($handles);
|
$view->setHandles($handles);
|
||||||
list($action, $body) = $view->renderForEmail($with_date = false);
|
list($action, $body) = $view->renderForEmail($with_date = false);
|
||||||
|
|
||||||
$is_create = false;
|
$is_create = $this->isCreate($transactions);
|
||||||
foreach ($transactions as $transaction) {
|
|
||||||
$type = $transaction->getTransactionType();
|
|
||||||
if (($type == ManiphestTransactionType::TYPE_STATUS) &&
|
|
||||||
($transaction->getOldValue() === null) &&
|
|
||||||
($transaction->getNewValue() == ManiphestTaskStatus::STATUS_OPEN)) {
|
|
||||||
$is_create = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$task_uri = PhabricatorEnv::getURI('/T'.$task->getID());
|
$task_uri = PhabricatorEnv::getURI('/T'.$task->getID());
|
||||||
|
|
||||||
|
@ -276,4 +270,71 @@ class ManiphestTransactionEditor {
|
||||||
|
|
||||||
return $handler_object;
|
return $handler_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function publishFeedStory(ManiphestTask $task, array $transactions) {
|
||||||
|
$actions = array(ManiphestAction::ACTION_UPDATE);
|
||||||
|
$comments = null;
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
if ($transaction->hasComments()) {
|
||||||
|
$comments = $transaction->getComments();
|
||||||
|
}
|
||||||
|
switch ($transaction->getTransactionType()) {
|
||||||
|
case ManiphestTransactionType::TYPE_OWNER:
|
||||||
|
$actions[] = ManiphestAction::ACTION_ASSIGN;
|
||||||
|
break;
|
||||||
|
case ManiphestTransactionType::TYPE_STATUS:
|
||||||
|
if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) {
|
||||||
|
$actions[] = ManiphestAction::ACTION_CLOSE;
|
||||||
|
} else if ($this->isCreate($transactions)) {
|
||||||
|
$actions[] = ManiphestAction::ACTION_CREATE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$action_type = ManiphestAction::selectStrongestAction($actions);
|
||||||
|
$owner_phid = $task->getOwnerPHID();
|
||||||
|
$actor_phid = head($transactions)->getAuthorPHID();
|
||||||
|
$author_phid = $task->getAuthorPHID();
|
||||||
|
|
||||||
|
id(new PhabricatorFeedStoryPublisher())
|
||||||
|
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST)
|
||||||
|
->setStoryData(array(
|
||||||
|
'taskPHID' => $task->getPHID(),
|
||||||
|
'transactionIDs' => mpull($transactions, 'getID'),
|
||||||
|
'ownerPHID' => $owner_phid,
|
||||||
|
'action' => $action_type,
|
||||||
|
'comments' => $comments,
|
||||||
|
'description' => $task->getDescription(),
|
||||||
|
))
|
||||||
|
->setStoryTime(time())
|
||||||
|
->setStoryAuthorPHID($actor_phid)
|
||||||
|
->setRelatedPHIDs(
|
||||||
|
array_merge(
|
||||||
|
array_filter(
|
||||||
|
array(
|
||||||
|
$task->getPHID(),
|
||||||
|
$author_phid,
|
||||||
|
$actor_phid,
|
||||||
|
$owner_phid,
|
||||||
|
)),
|
||||||
|
$task->getProjectPHIDs()))
|
||||||
|
->publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isCreate(array $transactions) {
|
||||||
|
$is_create = false;
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
$type = $transaction->getTransactionType();
|
||||||
|
if (($type == ManiphestTransactionType::TYPE_STATUS) &&
|
||||||
|
($transaction->getOldValue() === null) &&
|
||||||
|
($transaction->getNewValue() == ManiphestTaskStatus::STATUS_OPEN)) {
|
||||||
|
$is_create = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $is_create;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/feed/constants/story');
|
||||||
|
phutil_require_module('phabricator', 'applications/feed/publisher');
|
||||||
|
phutil_require_module('phabricator', 'applications/maniphest/constants/action');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
|
||||||
phutil_require_module('phabricator', 'applications/maniphest/view/transactiondetail');
|
phutil_require_module('phabricator', 'applications/maniphest/view/transactiondetail');
|
||||||
|
|
Loading…
Reference in a new issue