diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a1893c9f65..4d0b655a1b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php b/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php index 1e46717a56..0d4d7eec45 100644 --- a/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php +++ b/src/applications/differential/field/selector/DifferentialDefaultFieldSelector.php @@ -35,6 +35,10 @@ final class DifferentialDefaultFieldSelector new DifferentialAsanaRepresentationFieldSpecification(), ); + if (PhabricatorAuthProviderOAuth1JIRA::getJIRAProvider()) { + $fields[] = new DifferentialJIRAIssuesFieldSpecification(); + } + return $fields; } diff --git a/src/applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php b/src/applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php new file mode 100644 index 0000000000..a80551f46f --- /dev/null +++ b/src/applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php @@ -0,0 +1,198 @@ +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(); + } + +} diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index ce6a86db55..d95c0add9f 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -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);