mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Add "JIRA Issues" field to Differential
Summary: Ref T3687. This adds a field which allows you to link Differential Revisions to JIRA issues. This is just about as basic as it can get, but gets the job done. The field enables itself if you have a JIRA auth provide. You enter JIRA issues in a comma-delimited format and it generates appropriate edges. Nothing is pushed to the issues yet. The only real rough part here is that if you commandeer a revision which is linked to issues you can't see, editing it is difficult via the CLI. This seems pretty much like a non-issue, but at some point we can let the field throw some kind of "RecoverableInvalidFieldException" which just warns the user. The "no reviewers, continue anyway?" prompt could then use that too. Test Plan: - Edited via web UI, tried valid/invalid edits, checked that edges showed up in the database, added/removed issues, clicked issue links. - Edited via CLI, tried valid/invalid edits. Reviewers: btrahan Reviewed By: btrahan CC: aran Maniphest Tasks: T3687 Differential Revision: https://secure.phabricator.com/D6879
This commit is contained in:
parent
825fb9c85a
commit
853544b54a
4 changed files with 210 additions and 0 deletions
|
@ -371,6 +371,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/DifferentialInlineCommentPreviewController.php',
|
||||
'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php',
|
||||
'DifferentialInlineCommentView' => 'applications/differential/view/DifferentialInlineCommentView.php',
|
||||
'DifferentialJIRAIssuesFieldSpecification' => 'applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php',
|
||||
'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/DifferentialLinesFieldSpecification.php',
|
||||
'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/DifferentialLintFieldSpecification.php',
|
||||
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
|
||||
|
@ -2411,6 +2412,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
|
||||
'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'DifferentialInlineCommentView' => 'AphrontView',
|
||||
'DifferentialJIRAIssuesFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLocalCommitsView' => 'AphrontView',
|
||||
|
|
|
@ -35,6 +35,10 @@ final class DifferentialDefaultFieldSelector
|
|||
new DifferentialAsanaRepresentationFieldSpecification(),
|
||||
);
|
||||
|
||||
if (PhabricatorAuthProviderOAuth1JIRA::getJIRAProvider()) {
|
||||
$fields[] = new DifferentialJIRAIssuesFieldSpecification();
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialJIRAIssuesFieldSpecification
|
||||
extends DifferentialFieldSpecification {
|
||||
|
||||
private $value;
|
||||
private $error;
|
||||
|
||||
public function getStorageKey() {
|
||||
return 'phabricator:jira-issues';
|
||||
}
|
||||
|
||||
public function getValueForStorage() {
|
||||
return json_encode($this->value);
|
||||
}
|
||||
|
||||
public function setValueFromStorage($value) {
|
||||
if (!strlen($value)) {
|
||||
$this->value = array();
|
||||
} else {
|
||||
$this->value = json_decode($value, true);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldAppearOnEdit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueFromRequest(AphrontRequest $request) {
|
||||
$this->value = $request->getStrList($this->getStorageKey());
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function renderEditControl() {
|
||||
return id(new AphrontFormTextControl())
|
||||
->setLabel(pht('JIRA Issues'))
|
||||
->setCaption(
|
||||
pht('Example: %s', phutil_tag('tt', array(), 'JIS-3, JIS-9')))
|
||||
->setName($this->getStorageKey())
|
||||
->setValue(implode(', ', $this->value))
|
||||
->setError($this->error);
|
||||
}
|
||||
|
||||
public function shouldAppearOnRevisionView() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renderLabelForRevisionView() {
|
||||
return pht('JIRA Issues:');
|
||||
}
|
||||
|
||||
public function renderValueForRevisionView() {
|
||||
$xobjs = $this->loadDoorkeeperExternalObjects();
|
||||
if (!$xobjs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$links = array();
|
||||
foreach ($xobjs as $xobj) {
|
||||
$links[] = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => $xobj->getObjectURI(),
|
||||
'target' => '_blank',
|
||||
),
|
||||
$xobj->getObjectID());
|
||||
}
|
||||
|
||||
return phutil_implode_html(', ', $links);
|
||||
}
|
||||
|
||||
public function shouldAppearOnConduitView() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getValueForConduit() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function shouldAppearOnCommitMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCommitMessageKey() {
|
||||
return 'jira.issues';
|
||||
}
|
||||
|
||||
public function setValueFromParsedCommitMessage($value) {
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldOverwriteWhenCommitMessageIsEdited() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renderLabelForCommitMessage() {
|
||||
return 'JIRA Issues';
|
||||
}
|
||||
|
||||
public function renderValueForCommitMessage($is_edit) {
|
||||
return implode(', ', $this->value);
|
||||
}
|
||||
|
||||
public function getSupportedCommitMessageLabels() {
|
||||
return array(
|
||||
'JIRA',
|
||||
'JIRA Issues',
|
||||
'JIRA Issue',
|
||||
);
|
||||
}
|
||||
|
||||
public function parseValueFromCommitMessage($value) {
|
||||
return preg_split('/[\s,]+/', $value, $limit = -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
public function validateField() {
|
||||
if ($this->value) {
|
||||
$refs = id(new DoorkeeperImportEngine())
|
||||
->setViewer($this->getUser())
|
||||
->setRefs($this->buildDoorkeeperRefs())
|
||||
->execute();
|
||||
|
||||
$bad = array();
|
||||
foreach ($refs as $ref) {
|
||||
if (!$ref->getIsVisible()) {
|
||||
$bad[] = $ref->getObjectID();
|
||||
}
|
||||
}
|
||||
|
||||
if ($bad) {
|
||||
$bad = implode(', ', $bad);
|
||||
$this->error = pht('Invalid');
|
||||
throw new DifferentialFieldValidationException(
|
||||
pht(
|
||||
"Some JIRA issues could not be loaded. They may not exist, or ".
|
||||
"you may not have permission to view them: %s",
|
||||
$bad));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function buildDoorkeeperRefs() {
|
||||
$provider = PhabricatorAuthProviderOAuth1JIRA::getJIRAProvider();
|
||||
|
||||
$refs = array();
|
||||
foreach ($this->value as $jira_key) {
|
||||
$refs[] = id(new DoorkeeperObjectRef())
|
||||
->setApplicationType(DoorkeeperBridgeJIRA::APPTYPE_JIRA)
|
||||
->setApplicationDomain($provider->getProviderDomain())
|
||||
->setObjectType(DoorkeeperBridgeJIRA::OBJTYPE_ISSUE)
|
||||
->setObjectID($jira_key);
|
||||
}
|
||||
|
||||
return $refs;
|
||||
}
|
||||
|
||||
private function loadDoorkeeperExternalObjects() {
|
||||
$refs = $this->buildDoorkeeperRefs();
|
||||
if (!$refs) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$xobjs = id(new DoorkeeperExternalObjectQuery())
|
||||
->setViewer($this->getUser())
|
||||
->withObjectKeys(mpull($refs, 'getObjectKey'))
|
||||
->execute();
|
||||
|
||||
return $xobjs;
|
||||
}
|
||||
|
||||
public function didWriteRevision(DifferentialRevisionEditor $editor) {
|
||||
$revision = $editor->getRevision();
|
||||
$revision_phid = $revision->getPHID();
|
||||
|
||||
$edge_type = PhabricatorEdgeConfig::TYPE_PHOB_HAS_JIRAISSUE;
|
||||
$edge_dsts = mpull($this->loadDoorkeeperExternalObjects(), 'getPHID');
|
||||
|
||||
$edges = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||
$revision_phid,
|
||||
$edge_type);
|
||||
|
||||
$editor = id(new PhabricatorEdgeEditor())
|
||||
->setActor($this->getUser());
|
||||
|
||||
foreach (array_diff($edges, $edge_dsts) as $rem_edge) {
|
||||
$editor->removeEdge($revision_phid, $edge_type, $rem_edge);
|
||||
}
|
||||
|
||||
foreach (array_diff($edge_dsts, $edges) as $add_edge) {
|
||||
$editor->addEdge($revision_phid, $edge_type, $add_edge);
|
||||
}
|
||||
|
||||
$editor->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -68,6 +68,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
const TYPE_PHOB_HAS_ASANASUBTASK = 80003;
|
||||
const TYPE_ASANASUBTASK_HAS_PHOB = 80002;
|
||||
|
||||
const TYPE_PHOB_HAS_JIRAISSUE = 80004;
|
||||
const TYPE_JIRAISSUE_HAS_PHOB = 80005;
|
||||
|
||||
public static function getInverse($edge_type) {
|
||||
static $map = array(
|
||||
self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK,
|
||||
|
@ -129,6 +132,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
|
||||
self::TYPE_DREV_HAS_REVIEWER => self::TYPE_REVIEWER_FOR_DREV,
|
||||
self::TYPE_REVIEWER_FOR_DREV => self::TYPE_DREV_HAS_REVIEWER,
|
||||
|
||||
self::TYPE_PHOB_HAS_JIRAISSUE => self::TYPE_JIRAISSUE_HAS_PHOB,
|
||||
self:: TYPE_JIRAISSUE_HAS_PHOB => self::TYPE_PHOB_HAS_JIRAISSUE
|
||||
);
|
||||
|
||||
return idx($map, $edge_type);
|
||||
|
|
Loading…
Reference in a new issue