From 8692f4857bed240f0874fcecb7e18fa8c9a162ce Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 06:59:18 -0700 Subject: [PATCH 01/41] Fix an issue with ClassMap handling of DivinerAtomizers Fixes T9193. --- .../diviner/workflow/DivinerGenerateWorkflow.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php index 78f43a826f..d448de69c0 100644 --- a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php +++ b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php @@ -420,9 +420,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow { $atomizer_versions = array(); foreach ($atomizers as $atomizer) { - $atomizer_versions[$atomizer['name']] = call_user_func( + $name = get_class($atomizer); + $atomizer_versions[$name] = call_user_func( array( - $atomizer['name'], + $name, 'getAtomizerVersion', )); } From 57b0353034c582e1fc44390b832abde2dc30af67 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 07:28:56 -0700 Subject: [PATCH 02/41] Add `harbormaster.createartifact` Summary: Ref T8659. In the general case, this eventually allows build processes to do things like: - Upload build results (like a ".app" or ".exe" or other binary). - Pass complex results between build steps (e.g., build step A does something hard and build step B uses it to do something else). Today, we're a long way away from having the infrastructure for that. However, it is useful to let third party build processes (like Jenkins) upload URIs that link back to the external build results. This adds `harbormaster.createartifact` so they can do that. The only useful thing to do with this method today is have your Jenkins build do this: params = array( "uri": "https://jenkins.mycompany.com/build/23923/details/", "name": "View Build Results in Jenkins", "ui.external": true, ); harbormaster.createartifact(target, 'uri', params); Then (after the next diff) we'll show a link in Differential and a prominent link in Harbormaster. I didn't actually do the UI stuff in this diff since it's already pretty big. This change moves a lot of code around, too: - Adds PHIDs to artifacts. - It modularizes build artifact types (currently "file", "host" and "URI"). - It formalizes build artifact parameters and construction: - This lets me generate usable documentation about how to create artifacts. - This prevents users from doing dangerous or policy-violating things. - It does some other general modernization. Test Plan: {F715633} {F715634} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8659 Differential Revision: https://secure.phabricator.com/D13900 --- .../20150814.harbormater.artifact.phid.sql | 5 + src/__phutil_library_map__.php | 12 ++ .../drydock/query/DrydockLeaseQuery.php | 41 ++--- .../artifact/HarbormasterArtifact.php | 88 +++++++++++ .../artifact/HarbormasterFileArtifact.php | 66 ++++++++ .../artifact/HarbormasterHostArtifact.php | 74 +++++++++ .../artifact/HarbormasterURIArtifact.php | 100 ++++++++++++ .../conduit/HarbormasterConduitAPIMethod.php | 12 ++ ...ormasterCreateArtifactConduitAPIMethod.php | 129 +++++++++++++++ .../HarbormasterBuildViewController.php | 56 ++++--- .../engine/HarbormasterBuildEngine.php | 2 +- .../HarbormasterBuildArtifactPHIDType.php | 35 ++++ .../query/HarbormasterBuildArtifactQuery.php | 60 +++---- ...ormasterCommandBuildStepImplementation.php | 12 +- ...masterLeaseHostBuildStepImplementation.php | 15 +- ...PublishFragmentBuildStepImplementation.php | 17 +- ...rUploadArtifactBuildStepImplementation.php | 20 ++- .../storage/build/HarbormasterBuild.php | 30 ---- .../build/HarbormasterBuildArtifact.php | 149 ++++++------------ .../storage/build/HarbormasterBuildTarget.php | 48 ++++++ 20 files changed, 722 insertions(+), 249 deletions(-) create mode 100644 resources/sql/autopatches/20150814.harbormater.artifact.phid.sql create mode 100644 src/applications/harbormaster/artifact/HarbormasterArtifact.php create mode 100644 src/applications/harbormaster/artifact/HarbormasterFileArtifact.php create mode 100644 src/applications/harbormaster/artifact/HarbormasterHostArtifact.php create mode 100644 src/applications/harbormaster/artifact/HarbormasterURIArtifact.php create mode 100644 src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php create mode 100644 src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php diff --git a/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql b/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql new file mode 100644 index 0000000000..1f03268ff0 --- /dev/null +++ b/resources/sql/autopatches/20150814.harbormater.artifact.phid.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact + ADD phid VARBINARY(64) NOT NULL AFTER id; + +UPDATE {$NAMESPACE}_harbormaster.harbormaster_buildartifact + SET phid = CONCAT('PHID-HMBA-', id) WHERE phid = ''; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3bb3ad8c5b..4b6d8ac47e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -915,12 +915,14 @@ phutil_register_library_map(array( 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', + 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', 'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php', 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', 'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php', 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', + 'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php', 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php', 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', @@ -980,9 +982,12 @@ phutil_register_library_map(array( 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', + 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', + 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', + 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', @@ -1018,6 +1023,7 @@ phutil_register_library_map(array( 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', + 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', @@ -4603,6 +4609,7 @@ phutil_register_library_map(array( 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterArtifact' => 'Phobject', 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase', 'HarbormasterBuild' => array( 'HarbormasterDAO', @@ -4616,6 +4623,7 @@ phutil_register_library_map(array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), + 'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildAutoplan' => 'Phobject', 'HarbormasterBuildCommand' => 'HarbormasterDAO', @@ -4700,9 +4708,12 @@ phutil_register_library_map(array( 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', + 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', + 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', + 'HarbormasterHostArtifact' => 'HarbormasterArtifact', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintPropertyView' => 'AphrontView', @@ -4738,6 +4749,7 @@ phutil_register_library_map(array( 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', + 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 'HarbormasterUnitMessagesController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', diff --git a/src/applications/drydock/query/DrydockLeaseQuery.php b/src/applications/drydock/query/DrydockLeaseQuery.php index d5d62f950e..23cad90e9c 100644 --- a/src/applications/drydock/query/DrydockLeaseQuery.php +++ b/src/applications/drydock/query/DrydockLeaseQuery.php @@ -27,19 +27,12 @@ final class DrydockLeaseQuery extends DrydockQuery { return $this; } + public function newResultObject() { + return new DrydockLease(); + } + protected function loadPage() { - $table = new DrydockLease(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT lease.* FROM %T lease %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $leases) { @@ -69,40 +62,38 @@ final class DrydockLeaseQuery extends DrydockQuery { return $leases; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->resourceIDs) { + if ($this->resourceIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'resourceID IN (%Ld)', $this->resourceIDs); } - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->statuses) { + if ($this->statuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'status IN (%Ld)', $this->statuses); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } } diff --git a/src/applications/harbormaster/artifact/HarbormasterArtifact.php b/src/applications/harbormaster/artifact/HarbormasterArtifact.php new file mode 100644 index 0000000000..a0d3a86101 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterArtifact.php @@ -0,0 +1,88 @@ +getArtifactTypeDescription(); + } + + abstract public function getArtifactTypeDescription(); + abstract public function getArtifactParameterSpecification(); + abstract public function getArtifactParameterDescriptions(); + abstract public function willCreateArtifact(PhabricatorUser $actor); + + public function validateArtifactData(array $artifact_data) { + $artifact_spec = $this->getArtifactParameterSpecification(); + PhutilTypeSpec::checkMap($artifact_data, $artifact_spec); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + return null; + } + + public function releaseArtifact(PhabricatorUser $actor) { + return; + } + + public function getArtifactDataExample() { + return null; + } + + public function setBuildArtifact(HarbormasterBuildArtifact $build_artifact) { + $this->buildArtifact = $build_artifact; + return $this; + } + + public function getBuildArtifact() { + return $this->buildArtifact; + } + + final public function getArtifactConstant() { + $class = new ReflectionClass($this); + + $const = $class->getConstant('ARTIFACTCONST'); + if ($const === false) { + throw new Exception( + pht( + '"%s" class "%s" must define a "%s" property.', + __CLASS__, + get_class($this), + 'ARTIFACTCONST')); + } + + $limit = self::getArtifactConstantByteLimit(); + if (!is_string($const) || (strlen($const) > $limit)) { + throw new Exception( + pht( + '"%s" class "%s" has an invalid "%s" property. Action constants '. + 'must be strings and no more than %s bytes in length.', + __CLASS__, + get_class($this), + 'ARTIFACTCONST', + new PhutilNumber($limit))); + } + + return $const; + } + + final public static function getArtifactConstantByteLimit() { + return 32; + } + + final public static function getAllArtifactTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getArtifactConstant') + ->execute(); + } + + final public static function getArtifactType($type) { + return idx(self::getAllArtifactTypes(), $type); + } + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php new file mode 100644 index 0000000000..91fc025c21 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterFileArtifact.php @@ -0,0 +1,66 @@ + 'string', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'filePHID' => pht('File to create an artifact from.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'filePHID' => 'PHID-FILE-abcdefghijklmnopqrst', + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('filePHID'); + return $viewer->renderHandle($file_phid); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + // NOTE: This is primarily making sure the actor has permission to view the + // file. We don't want to let you run builds using files you don't have + // permission to see, since this could let you violate permissions. + $this->loadArtifactFile($actor); + } + + public function loadArtifactFile(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('filePHID'); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'File PHID "%s" does not correspond to a valid file.', + $file_phid)); + } + + return $file; + } + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php new file mode 100644 index 0000000000..73d00844af --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterHostArtifact.php @@ -0,0 +1,74 @@ + 'string', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'drydockLeasePHID' => pht( + 'Drydock host lease to create an artifact from.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'drydockLeasePHID' => 'PHID-DRYL-abcdefghijklmnopqrst', + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $file_phid = $artifact->getProperty('drydockLeasePHID'); + return $viewer->renderHandle($file_phid); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + $this->loadArtifactLease($actor); + } + + public function loadArtifactLease(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $lease_phid = $artifact->getProperty('drydockLeasePHID'); + + $lease = id(new DrydockLeaseQuery()) + ->setViewer($viewer) + ->withPHIDs(array($lease_phid)) + ->executeOne(); + if (!$lease) { + throw new Exception( + pht( + 'Drydock lease PHID "%s" does not correspond to a valid lease.', + $lease_phid)); + } + + return $lease; + } + + public function releaseArtifact(PhabricatorUser $actor) { + $lease = $this->loadArtifactLease($actor); + $resource = $lease->getResource(); + $blueprint = $resource->getBlueprint(); + + if ($lease->isActive()) { + $blueprint->releaseLease($resource, $lease); + } + } + + +} diff --git a/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php new file mode 100644 index 0000000000..53d3241711 --- /dev/null +++ b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php @@ -0,0 +1,100 @@ + 'string', + 'name' => 'optional string', + 'ui.external' => 'optional bool', + ); + } + + public function getArtifactParameterDescriptions() { + return array( + 'uri' => pht('The URI to store.'), + 'name' => pht('Optional label for this URI.'), + 'ui.external' => pht( + 'If true, display this URI in the UI as an link to '. + 'additional build details in an external build system.'), + ); + } + + public function getArtifactDataExample() { + return array( + 'uri' => 'https://buildserver.mycompany.com/build/123/', + 'name' => pht('View External Build Results'), + 'ui.external' => true, + ); + } + + public function renderArtifactSummary(PhabricatorUser $viewer) { + $artifact = $this->getBuildArtifact(); + $uri = $artifact->getProperty('uri'); + + try { + $this->validateURI($uri); + } catch (Exception $ex) { + return pht(''); + } + + $name = $artifact->getProperty('name', $uri); + + return phutil_tag( + 'a', + array( + 'href' => $uri, + 'target' => '_blank', + ), + $name); + } + + public function willCreateArtifact(PhabricatorUser $actor) { + $artifact = $this->getBuildArtifact(); + $uri = $artifact->getProperty('uri'); + $this->validateURI($uri); + } + + private function validateURI($raw_uri) { + $uri = new PhutilURI($raw_uri); + + $protocol = $uri->getProtocol(); + if (!strlen($protocol)) { + throw new Exception( + pht( + 'Unable to identify the protocol for URI "%s". URIs must be '. + 'fully qualified and have an identifiable protocol.', + $raw_uri)); + } + + $protocol_key = 'uri.allowed-protocols'; + $protocols = PhabricatorEnv::getEnvConfig($protocol_key); + if (empty($protocols[$protocol])) { + throw new Exception( + pht( + 'URI "%s" does not have an allowable protocol. Configure '. + 'protocols in `%s`. Allowed protocols are: %s.', + $raw_uri, + $protocol_key, + implode(', ', array_keys($protocols)))); + } + } + +} diff --git a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php index 33d239dc12..ccd76d2bbc 100644 --- a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php @@ -15,4 +15,16 @@ abstract class HarbormasterConduitAPIMethod extends ConduitAPIMethod { return pht('All Harbormaster APIs are new and subject to change.'); } + protected function returnArtifactList(array $artifacts) { + $list = array(); + + foreach ($artifacts as $artifact) { + $list[] = array( + 'phid' => $artifact->getPHID(), + ); + } + + return $list; + } + } diff --git a/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php new file mode 100644 index 0000000000..56702bf99e --- /dev/null +++ b/src/applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php @@ -0,0 +1,129 @@ +getArtifactTypeName(); + $type_const = $type->getArtifactConstant(); + $type_summary = $type->getArtifactTypeSummary(); + $out[] = "| `{$type_const}` | **{$type_name}** | {$type_summary} |"; + } + + $out[] = null; + $out[] = pht( + 'Each artifact also needs an `artifactKey`, which names the artifact. '. + 'Finally, you will provide some `artifactData` to fill in the content '. + 'of the artifact. The data you provide depends on what type of artifact '. + 'you are creating.'); + + foreach ($types as $type) { + $type_name = $type->getArtifactTypeName(); + $type_const = $type->getArtifactConstant(); + + $out[] = $type_name; + $out[] = '--------------------------'; + $out[] = null; + $out[] = $type->getArtifactTypeDescription(); + $out[] = null; + $out[] = pht( + 'Create an artifact of this type by passing `%s` as the '. + '`artifactType`. When creating an artifact of this type, provide '. + 'these parameters as a dictionary to `artifactData`:', + $type_const); + + $spec = $type->getArtifactParameterSpecification(); + $desc = $type->getArtifactParameterDescriptions(); + $out[] = "| {$head_key} | {$head_type} | {$head_desc} |"; + $out[] = '|-------------|--------------|--------------|'; + foreach ($spec as $key => $key_type) { + $key_desc = idx($desc, $key); + $out[] = "| `{$key}` | //{$key_type}// | {$key_desc} |"; + } + + $example = $type->getArtifactDataExample(); + if ($example !== null) { + $json = new PhutilJSON(); + $rendered = $json->encodeFormatted($example); + + $out[] = pht('For example:'); + $out[] = '```lang=json'; + $out[] = $rendered; + $out[] = '```'; + } + } + + return implode("\n", $out); + } + + protected function defineParamTypes() { + return array( + 'buildTargetPHID' => 'phid', + 'artifactKey' => 'string', + 'artifactType' => 'string', + 'artifactData' => 'map', + ); + } + + protected function defineReturnType() { + return 'wild'; + } + + protected function execute(ConduitAPIRequest $request) { + $viewer = $request->getUser(); + + $build_target_phid = $request->getValue('buildTargetPHID'); + $build_target = id(new HarbormasterBuildTargetQuery()) + ->setViewer($viewer) + ->withPHIDs(array($build_target_phid)) + ->executeOne(); + if (!$build_target) { + throw new Exception( + pht( + 'No such build target "%s"!', + $build_target_phid)); + } + + $artifact = $build_target->createArtifact( + $viewer, + $request->getValue('artifactKey'), + $request->getValue('artifactType'), + $request->getValue('artifactData')); + + return array( + 'data' => $this->returnArtifactList(array($artifact)), + ); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index cdc5baec60..189fa76ec2 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -3,17 +3,11 @@ final class HarbormasterBuildViewController extends HarbormasterController { - private $id; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; - } - - public function processRequest() { + public function handleRequest(AphrontRequest $request) { $request = $this->getRequest(); $viewer = $request->getUser(); - $id = $this->id; + $id = $request->getURIData('id'); $generation = $request->getInt('g'); $build = id(new HarbormasterBuildQuery()) @@ -224,29 +218,51 @@ final class HarbormasterBuildViewController )); } - private function buildArtifacts( - HarbormasterBuildTarget $build_target) { - - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildArtifacts(HarbormasterBuildTarget $build_target) { + $viewer = $this->getViewer(); $artifacts = id(new HarbormasterBuildArtifactQuery()) ->setViewer($viewer) ->withBuildTargetPHIDs(array($build_target->getPHID())) ->execute(); - $list = id(new PHUIObjectItemListView()) - ->setNoDataString(pht('This target has no associated artifacts.')) - ->setFlush(true); + $artifacts = msort($artifacts, 'getArtifactKey'); + $rows = array(); foreach ($artifacts as $artifact) { - $item = $artifact->getObjectItemView($viewer); - if ($item !== null) { - $list->addItem($item); + $impl = $artifact->getArtifactImplementation(); + + if ($impl) { + $summary = $impl->renderArtifactSummary($viewer); + $type_name = $impl->getArtifactTypeName(); + } else { + $summary = pht(''); + $type_name = $artifact->getType(); } + + $rows[] = array( + $artifact->getArtifactKey(), + $type_name, + $summary, + ); } - return $list; + $table = id(new AphrontTableView($rows)) + ->setNoDataString(pht('This target has no associated artifacts.')) + ->setHeaders( + array( + pht('Key'), + pht('Type'), + pht('Summary'), + )) + ->setColumnClasses( + array( + 'pri', + '', + 'wide', + )); + + return $table; } private function buildLog( diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index fd895979cd..f2ed44bb0d 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -483,7 +483,7 @@ final class HarbormasterBuildEngine extends Phobject { ->execute(); foreach ($artifacts as $artifact) { - $artifact->release(); + $artifact->releaseArtifact(); } } diff --git a/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php new file mode 100644 index 0000000000..5c3eb992f7 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php @@ -0,0 +1,35 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $artifact = $objects[$phid]; + $artifact_id = $artifact->getID(); + $handle->setName(pht('Build Artifact %d', $artifact_id)); + } + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php index 553780aa76..8f50aa1bb2 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildArtifactQuery.php @@ -6,7 +6,7 @@ final class HarbormasterBuildArtifactQuery private $ids; private $buildTargetPHIDs; private $artifactTypes; - private $artifactKeys; + private $artifactIndexes; private $keyBuildPHID; private $keyBuildGeneration; @@ -25,29 +25,17 @@ final class HarbormasterBuildArtifactQuery return $this; } - public function withArtifactKeys( - $build_phid, - $build_gen, - array $artifact_keys) { - $this->keyBuildPHID = $build_phid; - $this->keyBuildGeneration = $build_gen; - $this->artifactKeys = $artifact_keys; + public function withArtifactIndexes(array $artifact_indexes) { + $this->artifactIndexes = $artifact_indexes; return $this; } + public function newResultObject() { + return new HarbormasterBuildArtifact(); + } + protected function loadPage() { - $table = new HarbormasterBuildArtifact(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { @@ -75,46 +63,38 @@ final class HarbormasterBuildArtifactQuery return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->buildTargetPHIDs) { + if ($this->buildTargetPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildTargetPHID IN (%Ls)', $this->buildTargetPHIDs); } - if ($this->artifactTypes) { + if ($this->artifactTypes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'artifactType in (%Ls)', $this->artifactTypes); } - if ($this->artifactKeys) { - $indexes = array(); - foreach ($this->artifactKeys as $key) { - $indexes[] = PhabricatorHash::digestForIndex( - $this->keyBuildPHID.$this->keyBuildGeneration.$key); - } - + if ($this->artifactIndexes !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'artifactIndex IN (%Ls)', - $indexes); + $this->artifactIndexes); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php index 931ade75a7..2a6b633a72 100644 --- a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php @@ -43,9 +43,9 @@ final class HarbormasterCommandBuildStepImplementation $settings = $this->getSettings(); $variables = $build_target->getVariables(); - $artifact = $build->loadArtifact($settings['hostartifact']); - - $lease = $artifact->loadDrydockLease(); + $artifact = $build_target->loadArtifact($settings['hostartifact']); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease(); $this->platform = $lease->getAttribute('platform'); @@ -120,9 +120,9 @@ final class HarbormasterCommandBuildStepImplementation public function getArtifactInputs() { return array( array( - 'name' => pht('Run on Host'), - 'key' => $this->getSetting('hostartifact'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'name' => pht('Run on Host'), + 'key' => $this->getSetting('hostartifact'), + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php index d5f461506a..127025fd26 100644 --- a/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php @@ -36,14 +36,13 @@ final class HarbormasterLeaseHostBuildStepImplementation $lease->waitUntilActive(); // Create the associated artifact. - $artifact = $build->createArtifact( - $build_target, + $artifact = $build_target->createArtifact( + PhabricatorUser::getOmnipotentUser(), $settings['name'], - HarbormasterBuildArtifact::TYPE_HOST); - $artifact->setArtifactData(array( - 'drydock-lease' => $lease->getID(), - )); - $artifact->save(); + HarbormasterHostArtifact::ARTIFACTCONST, + array( + 'drydockLeasePHID' => $lease->getPHID(), + )); } public function getArtifactOutputs() { @@ -51,7 +50,7 @@ final class HarbormasterLeaseHostBuildStepImplementation array( 'name' => pht('Leased Host'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php index dc59717a23..fc0c830150 100644 --- a/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php @@ -29,33 +29,34 @@ final class HarbormasterPublishFragmentBuildStepImplementation $settings = $this->getSettings(); $variables = $build_target->getVariables(); + $viewer = PhabricatorUser::getOmnipotentUser(); $path = $this->mergeVariables( 'vsprintf', $settings['path'], $variables); - $artifact = $build->loadArtifact($settings['artifact']); - - $file = $artifact->loadPhabricatorFile(); + $artifact = $build_target->loadArtifact($settings['artifact']); + $impl = $artifact->getArtifactImplementation(); + $file = $impl->loadArtifactFile($viewer); $fragment = id(new PhragmentFragmentQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withPaths(array($path)) ->executeOne(); if ($fragment === null) { PhragmentFragment::createFromFile( - PhabricatorUser::getOmnipotentUser(), + $viewer, $file, $path, PhabricatorPolicies::getMostOpenPolicy(), PhabricatorPolicies::POLICY_USER); } else { if ($file->getMimeType() === 'application/zip') { - $fragment->updateFromZIP(PhabricatorUser::getOmnipotentUser(), $file); + $fragment->updateFromZIP($viewer, $file); } else { - $fragment->updateFromFile(PhabricatorUser::getOmnipotentUser(), $file); + $fragment->updateFromFile($viewer, $file); } } } @@ -65,7 +66,7 @@ final class HarbormasterPublishFragmentBuildStepImplementation array( 'name' => pht('Publishes File'), 'key' => $this->getSetting('artifact'), - 'type' => HarbormasterBuildArtifact::TYPE_FILE, + 'type' => HarbormasterFileArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php index 75b48eb3c6..88651826e7 100644 --- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php @@ -34,8 +34,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation $settings['path'], $variables); - $artifact = $build->loadArtifact($settings['hostartifact']); - + $artifact = $build_target->loadArtifact($settings['hostartifact']); $lease = $artifact->loadDrydockLease(); $interface = $lease->getInterface('filesystem'); @@ -44,14 +43,13 @@ final class HarbormasterUploadArtifactBuildStepImplementation $file = $interface->saveFile($path, $settings['name']); // Insert the artifact record. - $artifact = $build->createArtifact( - $build_target, + $artifact = $build_target->createArtifact( + PhabricatorUser::getOmnipotentUser(), $settings['name'], - HarbormasterBuildArtifact::TYPE_FILE); - $artifact->setArtifactData(array( - 'filePHID' => $file->getPHID(), - )); - $artifact->save(); + HarbormasterFileArtifact::ARTIFACTCONST, + array( + 'filePHID' => $file->getPHID(), + )); } public function getArtifactInputs() { @@ -59,7 +57,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation array( 'name' => pht('Upload From Host'), 'key' => $this->getSetting('hostartifact'), - 'type' => HarbormasterBuildArtifact::TYPE_HOST, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } @@ -69,7 +67,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation array( 'name' => pht('Uploaded File'), 'key' => $this->getSetting('name'), - 'type' => HarbormasterBuildArtifact::TYPE_FILE, + 'type' => HarbormasterHostArtifact::ARTIFACTCONST, ), ); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 5ea9f2f07b..e35744ecc9 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -235,36 +235,6 @@ final class HarbormasterBuild extends HarbormasterDAO return $log; } - public function createArtifact( - HarbormasterBuildTarget $build_target, - $artifact_key, - $artifact_type) { - - $artifact = - HarbormasterBuildArtifact::initializeNewBuildArtifact($build_target); - $artifact->setArtifactKey( - $this->getPHID(), - $this->getBuildGeneration(), - $artifact_key); - $artifact->setArtifactType($artifact_type); - $artifact->save(); - return $artifact; - } - - public function loadArtifact($name) { - $artifact = id(new HarbormasterBuildArtifactQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withArtifactKeys( - $this->getPHID(), - $this->getBuildGeneration(), - array($name)) - ->executeOne(); - if ($artifact === null) { - throw new Exception(pht('Artifact not found!')); - } - return $artifact; - } - public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php index f96ed6243c..0b195db88f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -10,19 +10,18 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO protected $artifactData = array(); private $buildTarget = self::ATTACHABLE; - - const TYPE_FILE = 'file'; - const TYPE_HOST = 'host'; - const TYPE_URI = 'uri'; + private $artifactImplementation; public static function initializeNewBuildArtifact( HarbormasterBuildTarget $build_target) { return id(new HarbormasterBuildArtifact()) + ->attachBuildTarget($build_target) ->setBuildTargetPHID($build_target->getPHID()); } protected function getConfiguration() { return array( + self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'artifactData' => self::SERIALIZATION_JSON, ), @@ -43,6 +42,11 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO ) + parent::getConfiguration(); } + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterBuildArtifactPHIDType::TYPECONST); + } + public function attachBuildTarget(HarbormasterBuildTarget $build_target) { $this->buildTarget = $build_target; return $this; @@ -52,113 +56,58 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO return $this->assertAttached($this->buildTarget); } - public function setArtifactKey($build_phid, $build_gen, $key) { - $this->artifactIndex = - PhabricatorHash::digestForIndex($build_phid.$build_gen.$key); + public function setArtifactKey($key) { + $target = $this->getBuildTarget(); + $this->artifactIndex = self::getArtifactIndex($target, $key); $this->artifactKey = $key; return $this; } - public function getObjectItemView(PhabricatorUser $viewer) { - $data = $this->getArtifactData(); - switch ($this->getArtifactType()) { - case self::TYPE_FILE: - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs($data) - ->executeOne(); + public static function getArtifactIndex( + HarbormasterBuildTarget $target, + $artifact_key) { - return id(new PHUIObjectItemView()) - ->setObjectName(pht('File')) - ->setHeader($handle->getFullName()) - ->setHref($handle->getURI()); - case self::TYPE_HOST: - $leases = id(new DrydockLeaseQuery()) - ->setViewer($viewer) - ->withIDs(array($data['drydock-lease'])) - ->execute(); - $lease = idx($leases, $data['drydock-lease']); + $build = $target->getBuild(); - return id(new PHUIObjectItemView()) - ->setObjectName(pht('Drydock Lease')) - ->setHeader($lease->getID()) - ->setHref('/drydock/lease/'.$lease->getID()); - case self::TYPE_URI: - return id(new PHUIObjectItemView()) - ->setObjectName($data['name']) - ->setHeader($data['uri']) - ->setHref($data['uri']); - default: + $parts = array( + $build->getPHID(), + $target->getBuildGeneration(), + $artifact_key, + ); + $parts = implode("\0", $parts); + + return PhabricatorHash::digestForIndex($parts); + } + + public function releaseArtifact() { + $impl = $this->getArtifactImplementation(); + + if ($impl) { + $impl->releaseArtifact(PhabricatorUser::getOmnipotentUser()); + } + + return null; + } + + public function getArtifactImplementation() { + if ($this->artifactImplementation === null) { + $type = $this->getArtifactType(); + $impl = HarbormasterArtifact::getArtifactType($type); + if (!$impl) { return null; + } + + $impl = clone $impl; + $impl->setBuildArtifact($this); + $this->artifactImplementation = $impl; } + + return $this->artifactImplementation; } - public function loadDrydockLease() { - if ($this->getArtifactType() !== self::TYPE_HOST) { - throw new Exception( - pht( - '`%s` may only be called on host artifacts.', - __FUNCTION__)); - } - $data = $this->getArtifactData(); - - // FIXME: Is there a better way of doing this? - // TODO: Policy stuff, etc. - $lease = id(new DrydockLease())->load( - $data['drydock-lease']); - if ($lease === null) { - throw new Exception(pht('Associated Drydock lease not found!')); - } - $resource = id(new DrydockResource())->load( - $lease->getResourceID()); - if ($resource === null) { - throw new Exception(pht('Associated Drydock resource not found!')); - } - $lease->attachResource($resource); - - return $lease; - } - - public function loadPhabricatorFile() { - if ($this->getArtifactType() !== self::TYPE_FILE) { - throw new Exception( - pht( - '`%s` may only be called on file artifacts.', - __FUNCTION__)); - } - - $data = $this->getArtifactData(); - - // The data for TYPE_FILE is an array with a single PHID in it. - $phid = $data['filePHID']; - - $file = id(new PhabricatorFileQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(array($phid)) - ->executeOne(); - if ($file === null) { - throw new Exception(pht('Associated file not found!')); - } - return $file; - } - - public function release() { - switch ($this->getArtifactType()) { - case self::TYPE_HOST: - $this->releaseDrydockLease(); - break; - } - } - - public function releaseDrydockLease() { - $lease = $this->loadDrydockLease(); - $resource = $lease->getResource(); - $blueprint = $resource->getBlueprint(); - - if ($lease->isActive()) { - $blueprint->releaseLease($resource, $lease); - } + public function getProperty($key, $default = null) { + return idx($this->artifactData, $key, $default); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index b2e0a2aa83..7f08292787 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -201,6 +201,54 @@ final class HarbormasterBuildTarget extends HarbormasterDAO ); } + public function createArtifact( + PhabricatorUser $actor, + $artifact_key, + $artifact_type, + array $artifact_data) { + + $impl = HarbormasterArtifact::getArtifactType($artifact_type); + if (!$impl) { + throw new Exception( + pht( + 'There is no implementation available for artifacts of type "%s".', + $artifact_type)); + } + + $impl->validateArtifactData($artifact_data); + + $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this) + ->setArtifactKey($artifact_key) + ->setArtifactType($artifact_type) + ->setArtifactData($artifact_data); + + $impl = $artifact->getArtifactImplementation(); + $impl->willCreateArtifact($actor); + + return $artifact->save(); + } + + public function loadArtifact($artifact_key) { + $indexes = array(); + + $indexes[] = HarbormasterBuildArtifact::getArtifactIndex( + $this, + $artifact_key); + + $artifact = id(new HarbormasterBuildArtifactQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withArtifactIndexes($indexes) + ->executeOne(); + if ($artifact === null) { + throw new Exception( + pht( + 'Artifact "%s" not found!', + $artifact_key)); + } + + return $artifact; + } + /* -( Status )------------------------------------------------------------- */ From 74bf0d6ec692d76a0497d76b4e4f78d2068d64e0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 07:29:26 -0700 Subject: [PATCH 03/41] Show external build links in applications Summary: Fixes T8659. This isn't //explicitly// documented but I'm going to wait for a bit until the "Harbormaster" doc splits into internal/external builds to add docs for it. There's other similar stuff coming soon anyway. Test Plan: {F716439} {F716440} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8659 Differential Revision: https://secure.phabricator.com/D13903 --- .../artifact/HarbormasterURIArtifact.php | 9 ++++ .../HarbormasterBuildViewController.php | 48 ++++++++++++++---- .../event/HarbormasterUIEventListener.php | 49 ++++++++++++++++--- .../query/HarbormasterBuildQuery.php | 35 +++++-------- .../query/HarbormasterBuildTargetQuery.php | 43 +++++++--------- 5 files changed, 118 insertions(+), 66 deletions(-) diff --git a/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php index 53d3241711..311492de20 100644 --- a/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php +++ b/src/applications/harbormaster/artifact/HarbormasterURIArtifact.php @@ -46,6 +46,15 @@ final class HarbormasterURIArtifact extends HarbormasterArtifact { } public function renderArtifactSummary(PhabricatorUser $viewer) { + return $this->renderLink(); + } + + public function isExternalLink() { + $artifact = $this->getBuildArtifact(); + return (bool)$artifact->getProperty('ui.external', false); + } + + public function renderLink() { $artifact = $this->getBuildArtifact(); $uri = $artifact->getProperty('uri'); diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 189fa76ec2..1a9832c588 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -67,6 +67,18 @@ final class HarbormasterBuildViewController $messages = array(); } + if ($build_targets) { + $artifacts = id(new HarbormasterBuildArtifactQuery()) + ->setViewer($viewer) + ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID')) + ->execute(); + $artifacts = msort($artifacts, 'getArtifactKey'); + $artifacts = mgroup($artifacts, 'getBuildTargetPHID'); + } else { + $artifacts = array(); + } + + $targets = array(); foreach ($build_targets as $build_target) { $header = id(new PHUIHeaderView()) @@ -77,6 +89,27 @@ final class HarbormasterBuildViewController ->setHeader($header); $properties = new PHUIPropertyListView(); + + $target_artifacts = idx($artifacts, $build_target->getPHID(), array()); + + $links = array(); + $type_uri = HarbormasterURIArtifact::ARTIFACTCONST; + foreach ($target_artifacts as $artifact) { + if ($artifact->getArtifactType() == $type_uri) { + $impl = $artifact->getArtifactImplementation(); + if ($impl->isExternalLink()) { + $links[] = $impl->renderLink(); + } + } + } + + if ($links) { + $links = phutil_implode_html(phutil_tag('br'), $links); + $properties->addProperty( + pht('External Link'), + $links); + } + $status_view = new PHUIStatusListView(); $item = new PHUIStatusItemView(); @@ -177,9 +210,9 @@ final class HarbormasterBuildViewController $properties->addRawContent($this->buildProperties($variables)); $target_box->addPropertyList($properties, pht('Variables')); - $artifacts = $this->buildArtifacts($build_target); + $artifacts_tab = $this->buildArtifacts($build_target, $target_artifacts); $properties = new PHUIPropertyListView(); - $properties->addRawContent($artifacts); + $properties->addRawContent($artifacts_tab); $target_box->addPropertyList($properties, pht('Artifacts')); $build_messages = idx($messages, $build_target->getPHID(), array()); @@ -218,16 +251,11 @@ final class HarbormasterBuildViewController )); } - private function buildArtifacts(HarbormasterBuildTarget $build_target) { + private function buildArtifacts( + HarbormasterBuildTarget $build_target, + array $artifacts) { $viewer = $this->getViewer(); - $artifacts = id(new HarbormasterBuildArtifactQuery()) - ->setViewer($viewer) - ->withBuildTargetPHIDs(array($build_target->getPHID())) - ->execute(); - - $artifacts = msort($artifacts, 'getArtifactKey'); - $rows = array(); foreach ($artifacts as $artifact) { $impl = $artifact->getArtifactImplementation(); diff --git a/src/applications/harbormaster/event/HarbormasterUIEventListener.php b/src/applications/harbormaster/event/HarbormasterUIEventListener.php index d0a79f4b12..82ffcad560 100644 --- a/src/applications/harbormaster/event/HarbormasterUIEventListener.php +++ b/src/applications/harbormaster/event/HarbormasterUIEventListener.php @@ -16,7 +16,7 @@ final class HarbormasterUIEventListener } private function handlePropertyEvent($ui_event) { - $user = $ui_event->getUser(); + $viewer = $ui_event->getUser(); $object = $ui_event->getValue('object'); if (!$object || !$object->getPHID()) { @@ -52,10 +52,11 @@ final class HarbormasterUIEventListener } $buildable = id(new HarbormasterBuildableQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withManualBuildables(false) ->withBuildablePHIDs(array($buildable_phid)) ->needBuilds(true) + ->needTargets(true) ->executeOne(); if (!$buildable) { return; @@ -63,10 +64,26 @@ final class HarbormasterUIEventListener $builds = $buildable->getBuilds(); - $build_handles = id(new PhabricatorHandleQuery()) - ->setViewer($user) - ->withPHIDs(mpull($builds, 'getPHID')) - ->execute(); + $targets = array(); + foreach ($builds as $build) { + foreach ($build->getBuildTargets() as $target) { + $targets[] = $target; + } + } + + if ($targets) { + $artifacts = id(new HarbormasterBuildArtifactQuery()) + ->setViewer($viewer) + ->withBuildTargetPHIDs(mpull($targets, 'getPHID')) + ->withArtifactTypes( + array( + HarbormasterURIArtifact::ARTIFACTCONST, + )) + ->execute(); + $artifacts = mgroup($artifacts, 'getBuildTargetPHID'); + } else { + $artifacts = array(); + } $status_view = new PHUIStatusListView(); @@ -87,6 +104,7 @@ final class HarbormasterUIEventListener $target = phutil_tag('strong', array(), $target); + $status_view ->addItem( id(new PHUIStatusItemView()) @@ -95,7 +113,23 @@ final class HarbormasterUIEventListener foreach ($builds as $build) { $item = new PHUIStatusItemView(); - $item->setTarget($build_handles[$build->getPHID()]->renderLink()); + $item->setTarget($viewer->renderHandle($build->getPHID())); + + $links = array(); + foreach ($build->getBuildTargets() as $build_target) { + $uris = idx($artifacts, $build_target->getPHID(), array()); + foreach ($uris as $uri) { + $impl = $uri->getArtifactImplementation(); + if ($impl->isExternalLink()) { + $links[] = $impl->renderLink(); + } + } + } + + if ($links) { + $links = phutil_implode_html(" \xC2\xB7 ", $links); + $item->setNote($links); + } $status = $build->getBuildStatus(); $status_name = HarbormasterBuild::getBuildStatusName($status); @@ -104,7 +138,6 @@ final class HarbormasterUIEventListener $item->setIcon($icon, $color, $status_name); - $status_view->addItem($item); } diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php index d0b1e7420c..1ee1ecb3a8 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php @@ -40,19 +40,12 @@ final class HarbormasterBuildQuery return $this; } + public function newResultObject() { + return new HarbormasterBuild(); + } + protected function loadPage() { - $table = new HarbormasterBuild(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { @@ -136,47 +129,45 @@ final class HarbormasterBuildQuery return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid in (%Ls)', $this->phids); } if ($this->buildStatuses !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildStatus in (%Ls)', $this->buildStatuses); } if ($this->buildablePHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildablePHID IN (%Ls)', $this->buildablePHIDs); } if ($this->buildPlanPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildPlanPHID IN (%Ls)', $this->buildPlanPHIDs); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php index c61c0bd8b0..6872be835b 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php @@ -34,55 +34,46 @@ final class HarbormasterBuildTargetQuery return $this; } - protected function loadPage() { - $table = new HarbormasterBuildTarget(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + public function newResultObject() { + return new HarbormasterBuildTarget(); } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } - if ($this->ids) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'phid in (%Ls)', $this->phids); } - if ($this->buildPHIDs) { + if ($this->buildPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildPHID in (%Ls)', $this->buildPHIDs); } - if ($this->buildGenerations) { + if ($this->buildGenerations !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'buildGeneration in (%Ld)', $this->buildGenerations); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } protected function didFilterPage(array $page) { From bb0d13345d6a7b0c9741ae28a4db2d0d8ffc30e8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 10:54:27 -0700 Subject: [PATCH 04/41] Remove unused revisionID and whitespace properties from diff TOC view Summary: Ref T2183. These properties are not used and not useful. Test Plan: Grepped for callsites and uses. Loaded revisions. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2183 Differential Revision: https://secure.phabricator.com/D13906 --- .../DifferentialRevisionViewController.php | 2 -- .../view/DifferentialDiffTableOfContentsView.php | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 085e888fe5..aa8daa76d6 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -359,8 +359,6 @@ final class DifferentialRevisionViewController extends DifferentialController { } $toc_view->setDiff($target); $toc_view->setUser($user); - $toc_view->setRevisionID($revision->getID()); - $toc_view->setWhitespace($whitespace); $comment_form = null; if (!$viewer_is_anonymous) { diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php index 26161096fe..3bbe588ed0 100644 --- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php +++ b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php @@ -8,8 +8,6 @@ final class DifferentialDiffTableOfContentsView extends AphrontView { private $repository; private $diff; private $renderURI = '/differential/changeset/'; - private $revisionID; - private $whitespace; private $coverageMap; public function setChangesets($changesets) { @@ -42,16 +40,6 @@ final class DifferentialDiffTableOfContentsView extends AphrontView { return $this; } - public function setRevisionID($revision_id) { - $this->revisionID = $revision_id; - return $this; - } - - public function setWhitespace($whitespace) { - $this->whitespace = $whitespace; - return $this; - } - public function render() { $this->requireResource('differential-core-view-css'); From 7a1bbe6634efbeb02d742f6df5d189e1cc5e93d5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 10:54:33 -0700 Subject: [PATCH 05/41] Add basic support for Herald outbound rules Summary: Ref T5791. This is still very basic (no global actions, no support for matching headers/bodies/recipients/etc) but gets the core in. Test Plan: {F715209} {F715211} Reviewers: chad Reviewed By: chad Maniphest Tasks: T5791 Differential Revision: https://secure.phabricator.com/D13897 --- src/__phutil_library_map__.php | 16 ++ .../HeraldTestConsoleController.php | 3 + .../controller/HeraldTranscriptController.php | 12 +- .../constants/PhabricatorMailRoutingRule.php | 51 ++++++ .../PhabricatorMetaMTAMailViewController.php | 150 ++++++++++++++++-- .../PhabricatorMailEmailHeraldField.php | 14 ++ .../PhabricatorMailEmailHeraldFieldGroup.php | 15 ++ ...PhabricatorMailEmailSubjectHeraldField.php | 20 +++ ...abricatorMailOutboundMailHeraldAdapter.php | 62 ++++++++ ...ricatorMailOutboundRoutingHeraldAction.php | 46 ++++++ ...ilOutboundRoutingSelfEmailHeraldAction.php | 34 ++++ ...undRoutingSelfNotificationHeraldAction.php | 34 ++++ .../PhabricatorMetaMTAEmailHeraldAction.php | 4 + .../phid/PhabricatorMetaMTAMailPHIDType.php | 2 +- .../metamta/query/PhabricatorMetaMTAActor.php | 11 ++ .../storage/PhabricatorMetaMTAMail.php | 98 +++++++++++- ...habricatorApplicationTransactionEditor.php | 15 ++ 17 files changed, 570 insertions(+), 17 deletions(-) create mode 100644 src/applications/metamta/constants/PhabricatorMailRoutingRule.php create mode 100644 src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php create mode 100644 src/applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php create mode 100644 src/applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php create mode 100644 src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php create mode 100644 src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php create mode 100644 src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php create mode 100644 src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4b6d8ac47e..95678635b3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2247,6 +2247,9 @@ phutil_register_library_map(array( 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', + 'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php', + 'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php', + 'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', @@ -2263,10 +2266,15 @@ phutil_register_library_map(array( 'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php', 'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php', 'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php', + 'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php', + 'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php', + 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php', + 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php', 'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php', 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', + 'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php', 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', @@ -6178,6 +6186,9 @@ phutil_register_library_map(array( 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorMacroViewController' => 'PhabricatorMacroController', + 'PhabricatorMailEmailHeraldField' => 'HeraldField', + 'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup', + 'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField', 'PhabricatorMailImplementationAdapter' => 'Phobject', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', @@ -6194,10 +6205,15 @@ phutil_register_library_map(array( 'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', + 'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter', + 'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction', + 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', + 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 'PhabricatorMailOutboundStatus' => 'Phobject', 'PhabricatorMailReceiver' => 'Phobject', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorMailReplyHandler' => 'Phobject', + 'PhabricatorMailRoutingRule' => 'Phobject', 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 9b5091664f..64b7c5867b 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -48,6 +48,9 @@ final class HeraldTestConsoleController extends HeraldController { } else if ($object instanceof PonderQuestion) { $adapter = id(new HeraldPonderQuestionAdapter()) ->setQuestion($object); + } else if ($object instanceof PhabricatorMetaMTAMail) { + $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) + ->setObject($object); } else { throw new Exception(pht('Can not build adapter for object!')); } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 64563b7ef8..3e4b0b074c 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -356,7 +356,17 @@ final class HeraldTranscriptController extends HeraldController { // Handle older transcripts which used a static string to record // action results. - if (!is_array($log)) { + + if ($xscript->getDryRun()) { + $action_list->addItem( + id(new PHUIStatusItemView()) + ->setIcon('fa-ban', 'grey') + ->setTarget(pht('Dry Run')) + ->setNote( + pht( + 'This was a dry run, so no actions were taken.'))); + continue; + } else if (!is_array($log)) { $action_list->addItem( id(new PHUIStatusItemView()) ->setIcon('fa-clock-o', 'grey') diff --git a/src/applications/metamta/constants/PhabricatorMailRoutingRule.php b/src/applications/metamta/constants/PhabricatorMailRoutingRule.php new file mode 100644 index 0000000000..1e0e1b9e26 --- /dev/null +++ b/src/applications/metamta/constants/PhabricatorMailRoutingRule.php @@ -0,0 +1,51 @@ + $strength_v); + } + + public static function getRuleStrength($const) { + $strength = array( + self::ROUTE_AS_NOTIFICATION => 1, + self::ROUTE_AS_MAIL => 2, + ); + + return idx($strength, $const, 0); + } + + public static function getRuleName($const) { + $names = array( + self::ROUTE_AS_NOTIFICATION => pht('Route as Notification'), + self::ROUTE_AS_MAIL => pht('Route as Mail'), + ); + + return idx($names, $const, $const); + } + + public static function getRuleIcon($const) { + $icons = array( + self::ROUTE_AS_NOTIFICATION => 'fa-bell', + self::ROUTE_AS_MAIL => 'fa-envelope', + ); + + return idx($icons, $const, 'fa-question-circle'); + } + + public static function getRuleColor($const) { + $colors = array( + self::ROUTE_AS_NOTIFICATION => 'grey', + self::ROUTE_AS_MAIL => 'grey', + ); + + return idx($colors, $const, 'yellow'); + } + +} diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php index faaa08e472..7cb1ad71d6 100644 --- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php +++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php @@ -86,6 +86,10 @@ final class PhabricatorMetaMTAMailViewController pht('Cc'), $cc_list); + $properties->addProperty( + pht('Sent'), + phabricator_datetime($mail->getDateCreated(), $viewer)); + $properties->addSectionHeader( pht('Message'), PHUIPropertyListView::ICON_SUMMARY); @@ -144,23 +148,16 @@ final class PhabricatorMetaMTAMailViewController $actors = $mail->getDeliveredActors(); $reasons = null; if (!$actors) { - // TODO: We can get rid of this special-cased message after these changes - // have been live for a while, but provide a more tailored message for - // now so things are a little less confusing for users. - if ($mail->getStatus() == PhabricatorMetaMTAMail::STATUS_SENT) { - $delivery = phutil_tag( - 'em', - array(), - pht( - 'This is an older message that predates recording delivery '. - 'information, so none is available.')); - } else { - $delivery = phutil_tag( - 'em', - array(), + if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) { + $delivery = $this->renderEmptyMessage( pht( 'This message has not been delivered yet, so delivery information '. 'is not available.')); + } else { + $delivery = $this->renderEmptyMessage( + pht( + 'This is an older message that predates recording delivery '. + 'information, so none is available.')); } } else { $actor = idx($actors, $viewer->getPHID()); @@ -214,6 +211,127 @@ final class PhabricatorMetaMTAMailViewController $properties->addProperty(pht('Delivery'), $delivery); if ($reasons) { $properties->addProperty(pht('Reasons'), $reasons); + $properties->addProperty( + null, + $this->renderEmptyMessage( + pht( + 'Delivery reasons are listed from weakest to strongest.'))); + } + + $properties->addSectionHeader(pht('Routing Rules')); + + $map = $mail->getDeliveredRoutingMap(); + $routing_detail = null; + if ($map === null) { + if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) { + $routing_result = $this->renderEmptyMessage( + pht( + 'This message has not been sent yet, so routing rules have '. + 'not been computed.')); + } else { + $routing_result = $this->renderEmptyMessage( + pht( + 'This is an older message which predates routing rules.')); + } + } else { + $rule = idx($map, $viewer->getPHID()); + if ($rule === null) { + $rule = idx($map, 'default'); + } + + if ($rule === null) { + $routing_result = $this->renderEmptyMessage( + pht( + 'No routing rules applied when delivering this message to you.')); + } else { + $rule_const = $rule['rule']; + $reason_phid = $rule['reason']; + switch ($rule_const) { + case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION: + $routing_result = pht( + 'This message was routed as a notification because it '. + 'matched %s.', + $viewer->renderHandle($reason_phid)->render()); + break; + case PhabricatorMailRoutingRule::ROUTE_AS_MAIL: + $routing_result = pht( + 'This message was routed as an email because it matched %s.', + $viewer->renderHandle($reason_phid)->render()); + break; + default: + $routing_result = pht('Unknown routing rule "%s".', $rule_const); + break; + } + } + + $routing_rules = $mail->getDeliveredRoutingRules(); + if ($routing_rules) { + $rules = array(); + foreach ($routing_rules as $rule) { + $phids = idx($rule, 'phids'); + if ($phids === null) { + $rules[] = $rule; + } else if (in_array($viewer->getPHID(), $phids)) { + $rules[] = $rule; + } + } + + // Reorder rules by strength. + foreach ($rules as $key => $rule) { + $const = $rule['routingRule']; + $phids = $rule['phids']; + + if ($phids === null) { + $type = 'A'; + } else { + $type = 'B'; + } + + $rules[$key]['strength'] = sprintf( + '~%s%08d', + $type, + PhabricatorMailRoutingRule::getRuleStrength($const)); + } + $rules = isort($rules, 'strength'); + + $routing_detail = id(new PHUIStatusListView()); + foreach ($rules as $rule) { + $const = $rule['routingRule']; + $phids = $rule['phids']; + + $name = PhabricatorMailRoutingRule::getRuleName($const); + + $icon = PhabricatorMailRoutingRule::getRuleIcon($const); + $color = PhabricatorMailRoutingRule::getRuleColor($const); + + if ($phids === null) { + $kind = pht('Global'); + } else { + $kind = pht('Personal'); + } + + $target = array($kind, ': ', $name); + $target = phutil_tag('strong', array(), $target); + + $item = id(new PHUIStatusItemView()) + ->setTarget($target) + ->setNote($viewer->renderHandle($rule['reasonPHID'])) + ->setIcon($icon, $color); + + $routing_detail->addItem($item); + } + } + } + + $properties->addProperty(pht('Effective Rule'), $routing_result); + + if ($routing_detail !== null) { + $properties->addProperty(pht('All Matching Rules'), $routing_detail); + $properties->addProperty( + null, + $this->renderEmptyMessage( + pht( + 'Matching rules are listed from weakest to strongest.'))); } return $properties; @@ -252,4 +370,8 @@ final class PhabricatorMetaMTAMailViewController return $properties; } + private function renderEmptyMessage($message) { + return phutil_tag('em', array(), $message); + } + } diff --git a/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php b/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php new file mode 100644 index 0000000000..815ddef69a --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailEmailHeraldField.php @@ -0,0 +1,14 @@ +getSubject(); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_TEXT; + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php new file mode 100644 index 0000000000..43c0a63300 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php @@ -0,0 +1,62 @@ +mail = $this->newObject(); + } + + protected function newObject() { + return new PhabricatorMetaMTAMail(); + } + + public function getObject() { + return $this->mail; + } + + public function setObject(PhabricatorMetaMTAMail $mail) { + $this->mail = $mail; + return $this; + } + + public function getAdapterContentName() { + return pht('Outbound Mail'); + } + + public function isSingleEventAdapter() { + return true; + } + + public function getRepetitionOptions() { + return array( + HeraldRepetitionPolicyConfig::FIRST, + ); + } + + public function supportsRuleType($rule_type) { + switch ($rule_type) { + case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL: + case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL: + return true; + case HeraldRuleTypeConfig::RULE_TYPE_OBJECT: + default: + return false; + } + } + + public function getHeraldName() { + return pht('Mail %d', $this->getObject()->getID()); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php new file mode 100644 index 0000000000..9813110538 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php @@ -0,0 +1,46 @@ +getAdapter(); + $mail = $adapter->getObject(); + $mail->addRoutingRule($route, $phids, $rule->getPHID()); + + $this->logEffect( + self::DO_ROUTE, + array( + 'route' => $route, + 'phids' => $phids, + )); + } + + protected function getActionEffectMap() { + return array( + self::DO_ROUTE => array( + 'icon' => 'fa-arrow-right', + 'color' => 'green', + 'name' => pht('Routed Message'), + ), + ); + } + + protected function renderActionEffectDescription($type, $data) { + switch ($type) { + case self::DO_ROUTE: + return pht('Routed mail.'); + } + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php new file mode 100644 index 0000000000..d39128cf67 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php @@ -0,0 +1,34 @@ +getRule(); + $author_phid = $rule->getAuthorPHID(); + + $this->applyRouting( + $rule, + PhabricatorMailRoutingRule::ROUTE_AS_MAIL, + array($author_phid)); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Deliver as email.'); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php new file mode 100644 index 0000000000..24838b7a79 --- /dev/null +++ b/src/applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php @@ -0,0 +1,34 @@ +getRule(); + $author_phid = $rule->getAuthorPHID(); + + $this->applyRouting( + $rule, + PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION, + array($author_phid)); + } + + public function getHeraldActionStandardType() { + return self::STANDARD_NONE; + } + + public function renderActionDescription($value) { + return pht('Deliver as notification.'); + } + +} diff --git a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php index 835bde8d4d..ec7036ebe0 100644 --- a/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php +++ b/src/applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php @@ -14,6 +14,10 @@ abstract class PhabricatorMetaMTAEmailHeraldAction return false; } + if ($object instanceof PhabricatorMetaMTAMail) { + return false; + } + return true; } diff --git a/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php b/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php index 8159045dbd..7117b50f8e 100644 --- a/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php +++ b/src/applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php @@ -37,7 +37,7 @@ final class PhabricatorMetaMTAMailPHIDType extends PhabricatorPHIDType { $handle ->setName($name) - ->setFullName($name); + ->setURI('/mail/detail/'.$id.'/'); } } } diff --git a/src/applications/metamta/query/PhabricatorMetaMTAActor.php b/src/applications/metamta/query/PhabricatorMetaMTAActor.php index 59e3f297de..a15a5b2390 100644 --- a/src/applications/metamta/query/PhabricatorMetaMTAActor.php +++ b/src/applications/metamta/query/PhabricatorMetaMTAActor.php @@ -18,6 +18,8 @@ final class PhabricatorMetaMTAActor extends Phobject { const REASON_BOT = 'bot'; const REASON_FORCE = 'force'; const REASON_FORCE_HERALD = 'force-herald'; + const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification'; + const REASON_ROUTE_AS_MAIL = 'route-as-mail'; private $phid; private $emailAddress; @@ -77,6 +79,7 @@ final class PhabricatorMetaMTAActor extends Phobject { case self::REASON_NONE: case self::REASON_FORCE: case self::REASON_FORCE_HERALD: + case self::REASON_ROUTE_AS_MAIL: return true; default: // All other reasons cause the message to not be delivered. @@ -99,6 +102,8 @@ final class PhabricatorMetaMTAActor extends Phobject { self::REASON_UNLOADABLE => pht('Bad Recipient'), self::REASON_FORCE => pht('Forced Mail'), self::REASON_FORCE_HERALD => pht('Forced by Herald'), + self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'), + self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'), ); return idx($names, $reason, pht('Unknown ("%s")', $reason)); @@ -147,6 +152,12 @@ final class PhabricatorMetaMTAActor extends Phobject { self::REASON_FORCE_HERALD => pht( 'This recipient was added by a "Send me an Email" rule in Herald, '. 'which overrides some delivery settings.'), + self::REASON_ROUTE_AS_NOTIFICATION => pht( + 'This message was downgraded to a notification by outbound mail '. + 'rules in Herald.'), + self::REASON_ROUTE_AS_MAIL => pht( + 'This message was upgraded to email by outbound mail rules '. + 'in Herald.'), ); return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason)); diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php index b68ac93c5c..abcbb7a082 100644 --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -16,6 +16,7 @@ final class PhabricatorMetaMTAMail protected $relatedPHID; private $recipientExpansionMap; + private $routingMap; public function __construct() { @@ -656,6 +657,9 @@ final class PhabricatorMetaMTAMail } $this->setParam('actors.sent', $actor_list); + $this->setParam('routing.sent', $this->getParam('routing')); + $this->setParam('routingmap.sent', $this->getRoutingRuleMap()); + if (!$add_to && !$add_cc) { $this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID); $this->setMessage( @@ -963,9 +967,25 @@ final class PhabricatorMetaMTAMail } } + foreach ($deliverable as $phid) { + switch ($this->getRoutingRule($phid)) { + case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION: + $actors[$phid]->setUndeliverable( + PhabricatorMetaMTAActor::REASON_ROUTE_AS_NOTIFICATION); + break; + case PhabricatorMailRoutingRule::ROUTE_AS_MAIL: + $actors[$phid]->setDeliverable( + PhabricatorMetaMTAActor::REASON_ROUTE_AS_MAIL); + break; + default: + // No change. + break; + } + } + // If recipients were initially deliverable and were added by "Send me an // email" Herald rules, annotate them as such and make them deliverable - // again, overriding any changes made by the "self mail" and "mail tags" + // again, overriding any changes made by the "self mail" and "mail tags" // settings. $force_recipients = $this->getForceHeraldMailRecipientPHIDs(); $force_recipients = array_fuse($force_recipients); @@ -1065,6 +1085,82 @@ final class PhabricatorMetaMTAMail return $this->getParam('actors.sent'); } + public function getDeliveredRoutingRules() { + return $this->getParam('routing.sent'); + } + + public function getDeliveredRoutingMap() { + return $this->getParam('routingmap.sent'); + } + + +/* -( Routing )------------------------------------------------------------ */ + + + public function addRoutingRule($routing_rule, $phids, $reason_phid) { + $routing = $this->getParam('routing', array()); + $routing[] = array( + 'routingRule' => $routing_rule, + 'phids' => $phids, + 'reasonPHID' => $reason_phid, + ); + $this->setParam('routing', $routing); + + // Throw the routing map away so we rebuild it. + $this->routingMap = null; + + return $this; + } + + private function getRoutingRule($phid) { + $map = $this->getRoutingRuleMap(); + + $info = idx($map, $phid, idx($map, 'default')); + if ($info) { + return idx($info, 'rule'); + } + + return null; + } + + private function getRoutingRuleMap() { + if ($this->routingMap === null) { + $map = array(); + + $routing = $this->getParam('routing', array()); + foreach ($routing as $route) { + $phids = $route['phids']; + if ($phids === null) { + $phids = array('default'); + } + + foreach ($phids as $phid) { + $new_rule = $route['routingRule']; + + $current_rule = idx($map, $phid); + if ($current_rule === null) { + $is_stronger = true; + } else { + $is_stronger = PhabricatorMailRoutingRule::isStrongerThan( + $new_rule, + $current_rule); + } + + if ($is_stronger) { + $map[$phid] = array( + 'rule' => $new_rule, + 'reason' => $route['reasonPHID'], + ); + } + } + } + + $this->routingMap = $map; + } + + return $this->routingMap; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 45782758f8..a5044360de 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1055,6 +1055,7 @@ abstract class PhabricatorApplicationTransactionEditor } if ($this->shouldPublishFeedStory($object, $xactions)) { + $mailed = array(); foreach ($messages as $mail) { foreach ($mail->buildRecipientList() as $phid) { @@ -2299,6 +2300,8 @@ abstract class PhabricatorApplicationTransactionEditor } } + $this->runHeraldMailRules($messages); + return $messages; } @@ -3140,4 +3143,16 @@ abstract class PhabricatorApplicationTransactionEditor ); } + private function runHeraldMailRules(array $messages) { + foreach ($messages as $message) { + $engine = new HeraldEngine(); + $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) + ->setObject($message); + + $rules = $engine->loadRulesForAdapter($adapter); + $effects = $engine->applyRules($rules, $adapter); + $engine->applyEffects($effects, $adapter, $rules); + } + } + } From fca716d69969c1204bc6a6fdd46f01b8658b410c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 15 Aug 2015 10:55:38 -0700 Subject: [PATCH 06/41] More Ponder Answer polish Summary: Fixes T9099, I think this is as much as I can come up with for unbeta. Cleans up the answer header (profile image, smaller font, smaller header). Cleans up voting (new, with color), and makes it a bit more readable. Test Plan: Review a number of answers in ponder with and without votes, comments. {F720189} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9099 Differential Revision: https://secure.phabricator.com/D13907 --- resources/celerity/map.php | 4 +-- .../PonderQuestionViewController.php | 4 +++ .../ponder/view/PonderAnswerView.php | 35 ++++++++++++------- .../ponder/view/PonderFooterView.php | 12 ++++--- .../PhabricatorUSEnglishTranslation.php | 1 + .../css/application/ponder/ponder-view.css | 27 ++++++++++++-- 6 files changed, 63 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 64d3ff99f3..cf7a8dfcb9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -93,7 +93,7 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/ponder-view.css' => '6a399881', + 'rsrc/css/application/ponder/ponder-view.css' => '870153f4', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -811,7 +811,7 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-view-css' => '6a399881', + 'ponder-view-css' => '870153f4', 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 06f924510f..94830f176d 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -223,16 +223,20 @@ final class PonderQuestionViewController extends PonderController { $engine->process(); $xaction_groups = mgroup($xactions, 'getObjectPHID'); + $author_phids = mpull($answers, 'getAuthorPHID'); + $handles = $this->loadViewerHandles($author_phids); $view = array(); foreach ($answers as $answer) { $xactions = idx($xaction_groups, $answer->getPHID(), array()); $id = $answer->getID(); + $handle = $handles[$answer->getAuthorPHID()]; $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) ->setTransactions($xactions) + ->setHandle($handle) ->setMarkupEngine($engine); } diff --git a/src/applications/ponder/view/PonderAnswerView.php b/src/applications/ponder/view/PonderAnswerView.php index 532bc9f2cd..5f2b1f5a70 100644 --- a/src/applications/ponder/view/PonderAnswerView.php +++ b/src/applications/ponder/view/PonderAnswerView.php @@ -5,6 +5,7 @@ final class PonderAnswerView extends AphrontTagView { private $answer; private $transactions; private $engine; + private $handle; public function setAnswer($answer) { $this->answer = $answer; @@ -21,6 +22,11 @@ final class PonderAnswerView extends AphrontTagView { return $this; } + public function setHandle($handle) { + $this->handle = $handle; + return $this; + } + protected function getTagAttributes() { return array( 'class' => 'ponder-answer-view', @@ -34,6 +40,7 @@ final class PonderAnswerView extends AphrontTagView { $status = $answer->getStatus(); $author_phid = $answer->getAuthorPHID(); $actions = $this->buildAnswerActions(); + $handle = $this->handle; $id = $answer->getID(); if ($status == PonderAnswerStatus::ANSWER_STATUS_HIDDEN) { @@ -73,8 +80,10 @@ final class PonderAnswerView extends AphrontTagView { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setEpoch($answer->getDateCreated()) - ->setHeader($viewer->renderHandle($author_phid)) - ->addActionLink($action_button); + ->setHeader($handle->getName()) + ->addActionLink($action_button) + ->setImage($handle->getImageURI()) + ->setImageURL($handle->getURI()); $content = phutil_tag( 'div', @@ -95,17 +104,19 @@ final class PonderAnswerView extends AphrontTagView { ->setCount(count($this->transactions)); $votes = $answer->getVoteCount(); - if ($votes) { - $icon = id(new PHUIIconView()) - ->setIconFont('fa-thumbs-up'); - $helpful = phutil_tag( - 'span', - array( - 'class' => 'ponder-footer-action', - ), - array($votes, $icon)); - $footer->addAction($helpful); + $vote_class = null; + if ($votes > 0) { + $vote_class = 'ponder-footer-action-helpful'; } + $icon = id(new PHUIIconView()) + ->setIconFont('fa-thumbs-up msr'); + $helpful = phutil_tag( + 'span', + array( + 'class' => 'ponder-footer-action '.$vote_class, + ), + array($icon, $votes)); + $footer->addAction($helpful); $answer_view = id(new PHUIObjectBoxView()) ->setHeader($header) diff --git a/src/applications/ponder/view/PonderFooterView.php b/src/applications/ponder/view/PonderFooterView.php index 8f55b86aeb..b1f346b147 100644 --- a/src/applications/ponder/view/PonderFooterView.php +++ b/src/applications/ponder/view/PonderFooterView.php @@ -36,9 +36,13 @@ final class PonderFooterView extends AphrontTagView { $content_id = $this->contentID; if ($this->count == 0) { + $icon = id(new PHUIIconView()) + ->setIconFont('fa-plus-circle msr'); $text = pht('Add a Comment'); } else { - $text = pht('Show %s Comments', new PhutilNumber($this->count)); + $icon = id(new PHUIIconView()) + ->setIconFont('fa-comments msr'); + $text = pht('Show %d Comment(s)', new PhutilNumber($this->count)); } $actions = array(); @@ -54,7 +58,7 @@ final class PonderFooterView extends AphrontTagView { 'hideIDs' => array($hide_action_id), ), ), - $text); + array($icon, $text)); $show_action = javelin_tag( 'a', @@ -69,12 +73,12 @@ final class PonderFooterView extends AphrontTagView { 'hideIDs' => array($content_id, $show_action_id), ), ), - pht('Hide Comments')); + array($icon, pht('Hide Comments'))); $actions[] = $hide_action; $actions[] = $show_action; - return array($actions, $this->actions); + return array($this->actions, $actions); } } diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 76f2bab8ba..9ac0d0b591 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -33,6 +33,7 @@ final class PhabricatorUSEnglishTranslation '%d diff(s)' => array('%d diff', '%d diffs'), '%d Answer(s)' => array('%d Answer', '%d Answers'), + 'Show %d Comment(s)' => array('Show %d Comment', 'Show %d Comments'), '%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'), 'You successfully created %d diff(s).' => array( diff --git a/webroot/rsrc/css/application/ponder/ponder-view.css b/webroot/rsrc/css/application/ponder/ponder-view.css index 867009c4bb..d1b599d529 100644 --- a/webroot/rsrc/css/application/ponder/ponder-view.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -31,22 +31,45 @@ margin-left: 12px; } +.ponder-answer-view .phui-header-shell { + padding-bottom: 8px; +} + +.ponder-answer-view .phui-header-view .phui-header-header { + font-size: 16px; +} + +.ponder-answer-view .phui-header-col1 { + width: 45px; +} + +.ponder-answer-view .phui-header-image { + height: 35px; + width: 35px; + border-radius: 3px; +} + .ponder-footer-view { margin: 0 4px -4px; + text-align: right; } .ponder-footer-view .ponder-footer-action { padding: 4px 8px; - margin-right: 8px; + margin-left: 8px; color: {$bluetext}; display: inline-block; background-color: rgba(71, 87, 120, 0.06); font-size: {$smallerfontsize}; } +.ponder-footer-view .ponder-footer-action.ponder-footer-action-helpful { + background-color: {$lightyellow}; +} + .ponder-footer-view .ponder-footer-action .phui-icon-view { color: {$bluetext}; - margin-left: 4px; + font-size: {$smallerfontsize}; } .ponder-footer-view a:hover { From cb912d17352c147d11c0efe71b567f03844be578 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 11:43:12 -0700 Subject: [PATCH 07/41] Convert DifferentialRevision view to new PHUIDiffTableOfContentsListView Summary: Ref T2183. Introduces a new View which can (in theory) unify the Revision, Diff and Commit table of contents views. This has the same behavior as before, but accepts slightly more general primitives and parameters and has somewhat cleaner code. I've made one intentinoal behavior change: removing the "Open All in Editor" button. I suspect this is essentially unused, and is a pain to keep around. We can look at restoring it if anyone notices. Test Plan: Looked at a bunch of revisions, no changes from before. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2183 Differential Revision: https://secure.phabricator.com/D13908 --- src/__phutil_library_map__.php | 4 + .../DifferentialRevisionViewController.php | 43 ++- .../view/PHUIDiffTableOfContentsItemView.php | 295 ++++++++++++++++++ .../view/PHUIDiffTableOfContentsListView.php | 80 +++++ 4 files changed, 412 insertions(+), 10 deletions(-) create mode 100644 src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php create mode 100644 src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 95678635b3..e88762b70b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1362,6 +1362,8 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', + 'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php', + 'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', @@ -5157,6 +5159,8 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffRevealIconView' => 'AphrontView', + 'PHUIDiffTableOfContentsItemView' => 'AphrontView', + 'PHUIDiffTableOfContentsListView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentExample' => 'PhabricatorUIExample', 'PHUIDocumentView' => 'AphrontTagView', diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index aa8daa76d6..c853cc278c 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -349,16 +349,10 @@ final class DifferentialRevisionViewController extends DifferentialController { $other_view = $this->renderOtherRevisions($other_revisions); } - $toc_view = new DifferentialDiffTableOfContentsView(); - $toc_view->setChangesets($changesets); - $toc_view->setVisibleChangesets($visible_changesets); - $toc_view->setRenderingReferences($rendering_references); - $toc_view->setCoverageMap($target->loadCoverageMap($user)); - if ($repository) { - $toc_view->setRepository($repository); - } - $toc_view->setDiff($target); - $toc_view->setUser($user); + $toc_view = $this->buildTableOfContents( + $changesets, + $visible_changesets, + $target->loadCoverageMap($user)); $comment_form = null; if (!$viewer_is_anonymous) { @@ -1042,5 +1036,34 @@ final class DifferentialRevisionViewController extends DifferentialController { return $view; } + private function buildTableOfContents( + array $changesets, + array $visible_changesets, + array $coverage) { + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setUser($viewer); + + foreach ($changesets as $changeset_id => $changeset) { + $is_visible = isset($visible_changesets[$changeset_id]); + $anchor = $changeset->getAnchorName(); + + $filename = $changeset->getFilename(); + $coverage_id = 'differential-mcoverage-'.md5($filename); + + $item = id(new PHUIDiffTableOfContentsItemView()) + ->setChangeset($changeset) + ->setIsVisible($is_visible) + ->setAnchor($anchor) + ->setCoverage(idx($coverage, $filename)) + ->setCoverageID($coverage_id); + + $toc_view->addItem($item); + } + + return $toc_view; + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php new file mode 100644 index 0000000000..638a9c21eb --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php @@ -0,0 +1,295 @@ +changeset = $changeset; + return $this; + } + + public function getChangeset() { + return $this->changeset; + } + + public function setIsVisible($is_visible) { + $this->isVisible = $is_visible; + return $this; + } + + public function getIsVisible() { + return $this->isVisible; + } + + public function setAnchor($anchor) { + $this->anchor = $anchor; + return $this; + } + + public function getAnchor() { + return $this->anchor; + } + + public function setCoverage($coverage) { + $this->coverage = $coverage; + return $this; + } + + public function getCoverage() { + return $this->coverage; + } + + public function setCoverageID($coverage_id) { + $this->coverageID = $coverage_id; + return $this; + } + + public function getCoverageID() { + return $this->coverageID; + } + + public function render() { + $changeset = $this->getChangeset(); + + $cells = array(); + + $cells[] = $this->renderPathChangeCharacter(); + $cells[] = $this->renderPropertyChangeCharacter(); + $cells[] = $this->renderPropertyChangeDescription(); + + $link = $this->renderChangesetLink(); + $lines = $this->renderChangesetLines(); + $meta = $this->renderChangesetMetadata(); + + $cells[] = array( + $link, + $lines, + $meta, + ); + + $cells[] = $this->renderCoverage(); + $cells[] = $this->renderModifiedCoverage(); + + return $cells; + } + + private function renderPathChangeCharacter() { + $changeset = $this->getChangeset(); + $type = $changeset->getChangeType(); + + $color = DifferentialChangeType::getSummaryColorForChangeType($type); + $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); + $title = DifferentialChangeType::getFullNameForChangeType($type); + + return javelin_tag( + 'span', + array( + 'sigil' => 'has-tip', + 'meta' => array( + 'tip' => $title, + 'align' => 'E', + ), + 'class' => 'phui-text-'.$color, + ), + $char); + } + + private function renderPropertyChangeCharacter() { + $changeset = $this->getChangeset(); + + $old = $changeset->getOldProperties(); + $new = $changeset->getNewProperties(); + + if ($old === $new) { + return null; + } + + return javelin_tag( + 'span', + array( + 'sigil' => 'has-tip', + 'meta' => array( + 'tip' => pht('Properties Modified'), + 'align' => 'E', + ), + ), + 'M'); + } + + private function renderPropertyChangeDescription() { + $changeset = $this->getChangeset(); + + $file_type = $changeset->getFileType(); + + $desc = DifferentialChangeType::getShortNameForFileType($file_type); + if ($desc === null) { + return null; + } + + return pht('(%s)', $desc); + } + + private function renderChangesetLink() { + $anchor = $this->getAnchor(); + + $changeset = $this->getChangeset(); + $name = $changeset->getDisplayFilename(); + + $change_type = $changeset->getChangeType(); + if (DifferentialChangeType::isOldLocationChangeType($change_type)) { + $away = $changeset->getAwayPaths(); + if (count($away) == 1) { + if ($change_type == DifferentialChangeType::TYPE_MOVE_AWAY) { + $right_arrow = "\xE2\x86\x92"; + $name = $this->renderRename($name, head($away), $right_arrow); + } + } + } else if ($change_type == DifferentialChangeType::TYPE_MOVE_HERE) { + $left_arrow = "\xE2\x86\x90"; + $name = $this->renderRename($name, $changeset->getOldFile(), $left_arrow); + } + + return javelin_tag( + 'a', + array( + 'href' => '#'.$anchor, + 'sigil' => 'differential-load', + 'meta' => array( + 'id' => 'diff-'.$anchor, + ), + ), + $name); + } + + private function renderChangesetLines() { + $changeset = $this->getChangeset(); + + $line_count = $changeset->getAffectedLineCount(); + if (!$line_count) { + return null; + } + + return ' '.pht('(%d line(s))', $line_count); + } + + private function renderCoverage() { + $not_applicable = '-'; + + $coverage = $this->getCoverage(); + if (!strlen($coverage)) { + return $not_applicable; + } + + $covered = substr_count($coverage, 'C'); + $not_covered = substr_count($coverage, 'U'); + + if (!$not_covered && !$covered) { + return $not_applicable; + } + + return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); + } + + private function renderModifiedCoverage() { + $not_applicable = '-'; + + $coverage = $this->getCoverage(); + if (!strlen($coverage)) { + return $not_applicable; + } + + if ($this->getIsVisible()) { + $label = pht('Loading...'); + } else { + $label = pht('?'); + } + + return phutil_tag( + 'div', + array( + 'id' => $this->getCoverageID(), + 'class' => 'differential-mcoverage-loading', + ), + $label); + } + + private function renderChangesetMetadata() { + $changeset = $this->getChangeset(); + $type = $changeset->getChangeType(); + + $meta = array(); + if (DifferentialChangeType::isOldLocationChangeType($type)) { + $away = $changeset->getAwayPaths(); + if (count($away) > 1) { + if ($type == DifferentialChangeType::TYPE_MULTICOPY) { + $meta[] = pht('Deleted after being copied to multiple locations:'); + } else { + $meta[] = pht('Copied to multiple locations:'); + } + foreach ($away as $path) { + $meta[] = $path; + } + } else { + if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { + // This case is handled when we render the path. + } else { + $meta[] = pht('Copied to %s', head($away)); + } + } + } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { + $meta = pht('Copied from %s', $changeset->getOldFile()); + } + + if (!$meta) { + return null; + } + + $meta = phutil_implode_html(phutil_tag('br'), $meta); + + return phutil_tag( + 'div', + array( + 'class' => 'differential-toc-meta', + ), + $meta); + } + + private function renderRename($self, $other, $arrow) { + $old = explode('/', $self); + $new = explode('/', $other); + + $start = count($old); + foreach ($old as $index => $part) { + if (!isset($new[$index]) || $part != $new[$index]) { + $start = $index; + break; + } + } + + $end = count($old); + foreach (array_reverse($old) as $from_end => $part) { + $index = count($new) - $from_end - 1; + if (!isset($new[$index]) || $part != $new[$index]) { + $end = $from_end; + break; + } + } + + $rename = + '{'. + implode('/', array_slice($old, $start, count($old) - $end - $start)). + ' '.$arrow.' '. + implode('/', array_slice($new, $start, count($new) - $end - $start)). + '}'; + + array_splice($new, $start, count($new) - $end - $start, $rename); + + return implode('/', $new); + } + +} diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php new file mode 100644 index 0000000000..6ed2e8343e --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -0,0 +1,80 @@ +items[] = $item; + return $this; + } + + public function render() { + $this->requireResource('differential-core-view-css'); + $this->requireResource('differential-table-of-contents-css'); + $this->requireResource('phui-text-css'); + + $items = $this->items; + + $rows = array(); + foreach ($items as $item) { + $rows[] = $item->render(); + } + + $reveal_link = javelin_tag( + 'a', + array( + 'sigil' => 'differential-reveal-all', + 'mustcapture' => true, + 'class' => 'button differential-toc-reveal-all', + ), + pht('Show All Context')); + + $buttons = phutil_tag( + 'div', + array( + 'class' => 'differential-toc-buttons grouped', + ), + $reveal_link); + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + '', + '', + '', + pht('Path'), + pht('Coverage (All)'), + pht('Coverage (Touched)'), + )) + ->setColumnClasses( + array( + 'differential-toc-char center', + 'differential-toc-prop center', + 'differential-toc-ftype center', + 'differential-toc-file wide', + 'differential-toc-cov', + 'differential-toc-cov', + )) + ->setDeviceVisibility( + array( + true, + true, + true, + true, + false, + false, + )); + + $anchor = id(new PhabricatorAnchorView()) + ->setAnchorName('toc') + ->setNavigationMarker(true); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Table of Contents')) + ->setTable($table) + ->appendChild($anchor) + ->appendChild($buttons); + } + +} From 8149399312b074af836f6d6a247f9fc2bbf6007d Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 12:57:20 -0700 Subject: [PATCH 08/41] Convert commits to use unified table of contents Summary: Fixes T2183. We now use the same rendering element in both places. Intentional changes: - Package highlighting is out, coming back to both apps in next diff. - removed redundant-feeling "Change" link. The information is now shown with a character ("M", "V", etc.) and the page is a click away under "History". Clicking the path also jumps you to substantially similar content. (We could restore it fairly easily, I just think it's probably the least useful thing in the table right now.) Test Plan: Viewed a bunch of commits in Diffusion. Reviewers: chad Reviewed By: chad Maniphest Tasks: T2183 Differential Revision: https://secure.phabricator.com/D13910 --- src/__phutil_library_map__.php | 4 - .../controller/DifferentialController.php | 29 ++ .../DifferentialDiffViewController.php | 8 +- .../DifferentialRevisionViewController.php | 30 -- .../DifferentialDiffTableOfContentsView.php | 300 ------------------ .../controller/DiffusionCommitController.php | 79 +++-- .../view/DiffusionCommitChangeTableView.php | 103 ------ .../view/PHUIDiffTableOfContentsItemView.php | 41 ++- .../view/PHUIDiffTableOfContentsListView.php | 45 ++- 9 files changed, 162 insertions(+), 477 deletions(-) delete mode 100644 src/applications/differential/view/DifferentialDiffTableOfContentsView.php delete mode 100644 src/applications/diffusion/view/DiffusionCommitChangeTableView.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e88762b70b..e589155500 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -367,7 +367,6 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php', 'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php', 'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php', - 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/DifferentialDiffTableOfContentsView.php', 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', @@ -519,7 +518,6 @@ phutil_register_library_map(array( 'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php', 'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php', 'DiffusionCommitBranchesHeraldField' => 'applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php', - 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/DiffusionCommitChangeTableView.php', 'DiffusionCommitCommitterHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterHeraldField.php', 'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php', 'DiffusionCommitDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentAddedHeraldField.php', @@ -4006,7 +4004,6 @@ phutil_register_library_map(array( 'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField', - 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffTestCase' => 'PhutilTestCase', 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -4178,7 +4175,6 @@ phutil_register_library_map(array( 'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitBranchesController' => 'DiffusionController', 'DiffusionCommitBranchesHeraldField' => 'DiffusionCommitHeraldField', - 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitCommitterHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitDiffContentAddedHeraldField' => 'DiffusionCommitHeraldField', diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 30e8c92d39..0ae33f4e59 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -21,4 +21,33 @@ abstract class DifferentialController extends PhabricatorController { return $this->buildSideNavView(true)->getMenu(); } + protected function buildTableOfContents( + array $changesets, + array $visible_changesets, + array $coverage) { + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setUser($viewer); + + foreach ($changesets as $changeset_id => $changeset) { + $is_visible = isset($visible_changesets[$changeset_id]); + $anchor = $changeset->getAnchorName(); + + $filename = $changeset->getFilename(); + $coverage_id = 'differential-mcoverage-'.md5($filename); + + $item = id(new PHUIDiffTableOfContentsItemView()) + ->setChangeset($changeset) + ->setIsVisible($is_visible) + ->setAnchor($anchor) + ->setCoverage(idx($coverage, $filename)) + ->setCoverageID($coverage_id); + + $toc_view->addItem($item); + } + + return $toc_view; + } + } diff --git a/src/applications/differential/controller/DifferentialDiffViewController.php b/src/applications/differential/controller/DifferentialDiffViewController.php index 28e7c2fc54..6ffd57396c 100644 --- a/src/applications/differential/controller/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/DifferentialDiffViewController.php @@ -116,10 +116,10 @@ final class DifferentialDiffViewController extends DifferentialController { $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); - $table_of_contents = id(new DifferentialDiffTableOfContentsView()) - ->setChangesets($changesets) - ->setVisibleChangesets($changesets) - ->setCoverageMap($diff->loadCoverageMap($viewer)); + $table_of_contents = $this->buildTableOfContents( + $changesets, + $changesets, + $diff->loadCoverageMap($viewer)); $refs = array(); foreach ($changesets as $changeset) { diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index c853cc278c..4af487252c 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1036,34 +1036,4 @@ final class DifferentialRevisionViewController extends DifferentialController { return $view; } - private function buildTableOfContents( - array $changesets, - array $visible_changesets, - array $coverage) { - $viewer = $this->getViewer(); - - $toc_view = id(new PHUIDiffTableOfContentsListView()) - ->setUser($viewer); - - foreach ($changesets as $changeset_id => $changeset) { - $is_visible = isset($visible_changesets[$changeset_id]); - $anchor = $changeset->getAnchorName(); - - $filename = $changeset->getFilename(); - $coverage_id = 'differential-mcoverage-'.md5($filename); - - $item = id(new PHUIDiffTableOfContentsItemView()) - ->setChangeset($changeset) - ->setIsVisible($is_visible) - ->setAnchor($anchor) - ->setCoverage(idx($coverage, $filename)) - ->setCoverageID($coverage_id); - - $toc_view->addItem($item); - } - - return $toc_view; - } - - } diff --git a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/DifferentialDiffTableOfContentsView.php deleted file mode 100644 index 3bbe588ed0..0000000000 --- a/src/applications/differential/view/DifferentialDiffTableOfContentsView.php +++ /dev/null @@ -1,300 +0,0 @@ -changesets = $changesets; - return $this; - } - - public function setVisibleChangesets($visible_changesets) { - $this->visibleChangesets = $visible_changesets; - return $this; - } - - public function setRenderingReferences(array $references) { - $this->references = $references; - return $this; - } - - public function setRepository(PhabricatorRepository $repository) { - $this->repository = $repository; - return $this; - } - - public function setDiff(DifferentialDiff $diff) { - $this->diff = $diff; - return $this; - } - - public function setCoverageMap(array $coverage_map) { - $this->coverageMap = $coverage_map; - return $this; - } - - public function render() { - - $this->requireResource('differential-core-view-css'); - $this->requireResource('differential-table-of-contents-css'); - $this->requireResource('phui-text-css'); - - $rows = array(); - - $changesets = $this->changesets; - $paths = array(); - foreach ($changesets as $id => $changeset) { - $type = $changeset->getChangeType(); - $ftype = $changeset->getFileType(); - $ref = idx($this->references, $id); - $display_file = $changeset->getDisplayFilename(); - - $meta = null; - if (DifferentialChangeType::isOldLocationChangeType($type)) { - $away = $changeset->getAwayPaths(); - if (count($away) > 1) { - $meta = array(); - if ($type == DifferentialChangeType::TYPE_MULTICOPY) { - $meta[] = pht('Deleted after being copied to multiple locations:'); - } else { - $meta[] = pht('Copied to multiple locations:'); - } - foreach ($away as $path) { - $meta[] = $path; - } - $meta = phutil_implode_html(phutil_tag('br'), $meta); - } else { - if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { - $display_file = $this->renderRename( - $display_file, - reset($away), - "\xE2\x86\x92"); - } else { - $meta = pht('Copied to %s', reset($away)); - } - } - } else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { - $old_file = $changeset->getOldFile(); - $display_file = $this->renderRename( - $display_file, - $old_file, - "\xE2\x86\x90"); - } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { - $meta = pht('Copied from %s', $changeset->getOldFile()); - } - - $link = $this->renderChangesetLink($changeset, $ref, $display_file); - - $line_count = $changeset->getAffectedLineCount(); - if ($line_count == 0) { - $lines = ''; - } else { - $lines = ' '.pht('(%d line(s))', $line_count); - } - - $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); - $chartitle = DifferentialChangeType::getFullNameForChangeType($type); - $desc = DifferentialChangeType::getShortNameForFileType($ftype); - $color = DifferentialChangeType::getSummaryColorForChangeType($type); - if ($desc) { - $desc = '('.$desc.')'; - } - $pchar = - ($changeset->getOldProperties() === $changeset->getNewProperties()) - ? '' - : phutil_tag( - 'span', - array('title' => pht('Properties Changed')), - 'M'); - - $fname = $changeset->getFilename(); - $cov = $this->renderCoverage($this->coverageMap, $fname); - if ($cov === null) { - $mcov = $cov = phutil_tag('em', array(), '-'); - } else { - $mcov = phutil_tag( - 'div', - array( - 'id' => 'differential-mcoverage-'.md5($fname), - 'class' => 'differential-mcoverage-loading', - ), - (isset($this->visibleChangesets[$id]) ? - pht('Loading...') : pht('?'))); - } - - if ($meta) { - $meta = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-meta', - ), - $meta); - } - - if ($this->diff && $this->repository) { - $paths[] = - $changeset->getAbsoluteRepositoryPath($this->repository, $this->diff); - } - - $char = phutil_tag('span', array('class' => 'phui-text-'.$color), $char); - - $rows[] = array( - $char, - $pchar, - $desc, - array($link, $lines, $meta), - $cov, - $mcov, - ); - } - - $editor_link = null; - if ($paths && $this->user) { - $editor_link = $this->user->loadEditorLink( - $paths, - 1, // line number - $this->repository->getCallsign()); - if ($editor_link) { - $editor_link = - phutil_tag( - 'a', - array( - 'href' => $editor_link, - 'class' => 'button differential-toc-edit-all', - ), - pht('Open All in Editor')); - } - } - - $reveal_link = javelin_tag( - 'a', - array( - 'sigil' => 'differential-reveal-all', - 'mustcapture' => true, - 'class' => 'button differential-toc-reveal-all', - ), - pht('Show All Context')); - - $buttons = phutil_tag( - 'div', - array( - 'class' => 'differential-toc-buttons grouped', - ), - array( - $editor_link, - $reveal_link, - )); - - $table = id(new AphrontTableView($rows)); - $table->setHeaders( - array( - '', - '', - '', - pht('Path'), - pht('Coverage (All)'), - pht('Coverage (Touched)'), - )); - $table->setColumnClasses( - array( - 'differential-toc-char center', - 'differential-toc-prop center', - 'differential-toc-ftype center', - 'differential-toc-file wide', - 'differential-toc-cov', - 'differential-toc-cov', - )); - $table->setDeviceVisibility( - array( - true, - true, - true, - true, - false, - false, - )); - $anchor = id(new PhabricatorAnchorView()) - ->setAnchorName('toc') - ->setNavigationMarker(true); - - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Table of Contents')) - ->setTable($table) - ->appendChild($anchor) - ->appendChild($buttons); - } - - private function renderRename($display_file, $other_file, $arrow) { - $old = explode('/', $display_file); - $new = explode('/', $other_file); - - $start = count($old); - foreach ($old as $index => $part) { - if (!isset($new[$index]) || $part != $new[$index]) { - $start = $index; - break; - } - } - - $end = count($old); - foreach (array_reverse($old) as $from_end => $part) { - $index = count($new) - $from_end - 1; - if (!isset($new[$index]) || $part != $new[$index]) { - $end = $from_end; - break; - } - } - - $rename = - '{'. - implode('/', array_slice($old, $start, count($old) - $end - $start)). - ' '.$arrow.' '. - implode('/', array_slice($new, $start, count($new) - $end - $start)). - '}'; - - array_splice($new, $start, count($new) - $end - $start, $rename); - return implode('/', $new); - } - - private function renderCoverage(array $coverage, $file) { - $info = idx($coverage, $file); - if (!$info) { - return null; - } - - $not_covered = substr_count($info, 'U'); - $covered = substr_count($info, 'C'); - - if (!$not_covered && !$covered) { - return null; - } - - return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered))); - } - - - private function renderChangesetLink( - DifferentialChangeset $changeset, - $ref, - $display_file) { - - return javelin_tag( - 'a', - array( - 'href' => '#'.$changeset->getAnchorName(), - 'sigil' => 'differential-load', - 'meta' => array( - 'id' => 'diff-'.$changeset->getAnchorName(), - ), - ), - $display_file); - } - -} diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 74441be360..927ad7f453 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -75,7 +75,6 @@ final class DiffusionCommitController extends DiffusionController { $commit_data = $commit->getCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); - $changesets = null; if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); @@ -181,24 +180,6 @@ final class DiffusionCommitController extends DiffusionController { $user, $this->auditAuthorityPHIDs); - $owners_paths = array(); - if ($highlighted_audits) { - $packages = id(new PhabricatorOwnersPackage())->loadAllWhere( - 'phid IN (%Ls)', - mpull($highlighted_audits, 'getAuditorPHID')); - if ($packages) { - $owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere( - 'repositoryPHID = %s AND packageID IN (%Ld)', - $repository->getPHID(), - mpull($packages, 'getID')); - } - } - - $change_table = new DiffusionCommitChangeTableView(); - $change_table->setDiffusionRequest($drequest); - $change_table->setPathChanges($changes); - $change_table->setOwnersPaths($owners_paths); - $count = count($changes); $bad_commit = null; @@ -210,6 +191,7 @@ final class DiffusionCommitController extends DiffusionController { 'r'.$callsign.$commit->getCommitIdentifier()); } + $show_changesets = false; if ($bad_commit) { $content[] = $this->renderStatusMessage( pht('Bad Commit'), @@ -235,6 +217,8 @@ final class DiffusionCommitController extends DiffusionController { 'Changes are not shown.', $hard_limit)); } else { + $show_changesets = true; + // The user has clicked "Show All Changes", and we should show all the // changes inline even if there are more than the soft limit. $show_all_details = $request->getBool('show_all'); @@ -264,15 +248,20 @@ final class DiffusionCommitController extends DiffusionController { $header->addActionLink($button); } + $changesets = DiffusionPathChange::convertToDifferentialChangesets( + $user, + $changes); + + // TODO: This table and panel shouldn't really be separate, but we need + // to clean up the "Load All Files" interaction first. + $change_table = $this->buildTableOfContents( + $changesets); + $change_panel->setTable($change_table); $change_panel->setHeader($header); $content[] = $change_panel; - $changesets = DiffusionPathChange::convertToDifferentialChangesets( - $user, - $changes); - $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: @@ -353,12 +342,6 @@ final class DiffusionCommitController extends DiffusionController { $change_list->setInlineCommentControllerURI( '/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/'); - $change_references = array(); - foreach ($changesets as $key => $changeset) { - $change_references[$changeset->getID()] = $references[$key]; - } - $change_table->setRenderingReferences($change_references); - $content[] = $change_list->render(); } @@ -375,7 +358,7 @@ final class DiffusionCommitController extends DiffusionController { $show_filetree = $prefs->getPreference($pref_filetree); $collapsed = $prefs->getPreference($pref_collapse); - if ($changesets && $show_filetree) { + if ($show_changesets && $show_filetree) { $nav = id(new DifferentialChangesetFileTreeSideNavBuilder()) ->setTitle($short_name) ->setBaseURI(new PhutilURI('/'.$commit_id)) @@ -1082,4 +1065,40 @@ final class DiffusionCommitController extends DiffusionController { return $parser->processCorpus($corpus); } + private function buildTableOfContents(array $changesets) { + $viewer = $this->getViewer(); + + $toc_view = id(new PHUIDiffTableOfContentsListView()) + ->setUser($viewer); + + // TODO: This is hacky, we just want access to the linkX() methods on + // DiffusionView. + $diffusion_view = id(new DiffusionEmptyResultView()) + ->setDiffusionRequest($this->getDiffusionRequest()); + + // TODO: Restore package stuff here. + + foreach ($changesets as $changeset_id => $changeset) { + $path = $changeset->getFilename(); + $anchor = substr(md5($path), 0, 8); + + $history_link = $diffusion_view->linkHistory($path); + $browse_link = $diffusion_view->linkBrowse($path); + + $item = id(new PHUIDiffTableOfContentsItemView()) + ->setChangeset($changeset) + ->setAnchor($anchor) + ->setContext( + array( + $history_link, + ' ', + $browse_link, + )); + + $toc_view->addItem($item); + } + + return $toc_view; + } + } diff --git a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php b/src/applications/diffusion/view/DiffusionCommitChangeTableView.php deleted file mode 100644 index 538e225ae7..0000000000 --- a/src/applications/diffusion/view/DiffusionCommitChangeTableView.php +++ /dev/null @@ -1,103 +0,0 @@ -pathChanges = $path_changes; - return $this; - } - - public function setOwnersPaths(array $owners_paths) { - assert_instances_of($owners_paths, 'PhabricatorOwnersPath'); - $this->ownersPaths = $owners_paths; - return $this; - } - - public function setRenderingReferences(array $value) { - $this->renderingReferences = $value; - return $this; - } - - public function render() { - $rows = array(); - $rowc = array(); - - // TODO: Experiment with path stack rendering. - - // TODO: Copy Away and Move Away are rendered junkily still. - - foreach ($this->pathChanges as $id => $change) { - $path = $change->getPath(); - $hash = substr(md5($path), 0, 8); - if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { - $path .= '/'; - } - - if (isset($this->renderingReferences[$id])) { - $path_column = javelin_tag( - 'a', - array( - 'href' => '#'.$hash, - 'meta' => array( - 'id' => 'diff-'.$hash, - 'ref' => $this->renderingReferences[$id], - ), - 'sigil' => 'differential-load', - ), - $path); - } else { - $path_column = $path; - } - - $rows[] = array( - $this->linkHistory($change->getPath()), - $this->linkBrowse($change->getPath()), - $this->linkChange( - $change->getChangeType(), - $change->getFileType(), - $change->getPath()), - $path_column, - ); - - $row_class = null; - foreach ($this->ownersPaths as $owners_path) { - $excluded = $owners_path->getExcluded(); - $owners_path = $owners_path->getPath(); - if (strncmp('/'.$path, $owners_path, strlen($owners_path)) == 0) { - if ($excluded) { - $row_class = null; - break; - } - $row_class = 'highlighted'; - } - } - $rowc[] = $row_class; - } - - $view = new AphrontTableView($rows); - $view->setHeaders( - array( - pht('History'), - pht('Browse'), - pht('Change'), - pht('Path'), - )); - $view->setColumnClasses( - array( - '', - '', - '', - 'wide', - )); - $view->setRowClasses($rowc); - $view->setNoDataString(pht('This change has not been fully parsed yet.')); - - return $view->render(); - } - -} diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php index 638a9c21eb..a029916f86 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php @@ -3,10 +3,12 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { private $changeset; - private $isVisible; + private $isVisible = true; private $anchor; private $coverage; private $coverageID; + private $context; + private $package; public function setChangeset(DifferentialChangeset $changeset) { $this->changeset = $changeset; @@ -53,11 +55,31 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { return $this->coverageID; } + public function setContext($context) { + $this->context = $context; + return $this; + } + + public function getContext() { + return $this->context; + } + + public function setPackage(PhabricatorOwnersPackage $package) { + $this->package = $package; + return $this; + } + + public function getPackage() { + return $this->package; + } + public function render() { $changeset = $this->getChangeset(); $cells = array(); + $cells[] = $this->getContext(); + $cells[] = $this->renderPathChangeCharacter(); $cells[] = $this->renderPropertyChangeCharacter(); $cells[] = $this->renderPropertyChangeDescription(); @@ -75,6 +97,8 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { $cells[] = $this->renderCoverage(); $cells[] = $this->renderModifiedCoverage(); + $cells[] = $this->renderPackage(); + return $cells; } @@ -89,7 +113,7 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { return javelin_tag( 'span', array( - 'sigil' => 'has-tip', + 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $title, 'align' => 'E', @@ -112,10 +136,11 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { return javelin_tag( 'span', array( - 'sigil' => 'has-tip', + 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Properties Modified'), 'align' => 'E', + 'size' => 200, ), ), 'M'); @@ -259,6 +284,16 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { $meta); } + private function renderPackage() { + $package = $this->getPackage(); + + if (!$package) { + return null; + } + + return $this->getUser()->renderHandle($package->getPHID()); + } + private function renderRename($self, $other, $arrow) { $old = explode('/', $self); $new = explode('/', $other); diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index 6ed2e8343e..66677ee598 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -14,13 +14,35 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { $this->requireResource('differential-table-of-contents-css'); $this->requireResource('phui-text-css'); + Javelin::initBehavior('phabricator-tooltips'); + $items = $this->items; $rows = array(); foreach ($items as $item) { + $item->setUser($this->getUser()); $rows[] = $item->render(); } + // Check if any item has content in these columns. If no item does, we'll + // just hide them. + $any_coverage = false; + $any_context = false; + $any_package = false; + foreach ($items as $item) { + if ($item->getContext() !== null) { + $any_context = true; + } + + if (strlen($item->getCoverage())) { + $any_coverage = true; + } + + if ($item->getPackage() !== null) { + $any_package = true; + } + } + $reveal_link = javelin_tag( 'a', array( @@ -40,21 +62,36 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { $table = id(new AphrontTableView($rows)) ->setHeaders( array( - '', - '', - '', + null, + null, + null, + null, pht('Path'), pht('Coverage (All)'), pht('Coverage (Touched)'), + null, )) ->setColumnClasses( array( + 'center', 'differential-toc-char center', 'differential-toc-prop center', 'differential-toc-ftype center', 'differential-toc-file wide', 'differential-toc-cov', 'differential-toc-cov', + 'center', + )) + ->setColumnVisibility( + array( + $any_context, + true, + true, + true, + true, + $any_coverage, + $any_coverage, + $any_package, )) ->setDeviceVisibility( array( @@ -62,8 +99,10 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { true, true, true, + true, false, false, + true, )); $anchor = id(new PhabricatorAnchorView()) From 36a50ccbae1e004eb1bb20003d726593cba6d814 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 15 Aug 2015 13:06:10 -0700 Subject: [PATCH 09/41] Provide a more modern way to load packages owning a set of files Summary: Ref T8320. Ref T8004. This just tries to generally modernize It also replaces the nonfunctional "Find Owners" link with a new property that just shows owning packages. Test Plan: - Created and edited packages. {F720478} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8004, T8320 Differential Revision: https://secure.phabricator.com/D13911 --- .../controller/DiffusionBrowseController.php | 62 ++++--- .../PhabricatorOwnersDetailController.php | 3 +- .../PhabricatorOwnersPathsController.php | 3 +- ...bricatorOwnersPackageTransactionEditor.php | 6 +- .../query/PhabricatorOwnersPackageQuery.php | 151 +++++++++++++++++- .../storage/PhabricatorOwnersPackage.php | 14 +- .../owners/storage/PhabricatorOwnersPath.php | 32 ++++ 7 files changed, 242 insertions(+), 29 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 3d7bfb78fd..6fd164f2a7 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -111,25 +111,6 @@ abstract class DiffusionBrowseController extends DiffusionController { ->setIcon('fa-home') ->setDisabled(!$behind_head)); - // TODO: Ideally, this should live in Owners and be event-triggered, but - // there's no reasonable object for it to react to right now. - - $owners = 'PhabricatorOwnersApplication'; - if (PhabricatorApplication::isClassInstalled($owners)) { - $owners_uri = id(new PhutilURI('/owners/view/search/')) - ->setQueryParams( - array( - 'repository' => $drequest->getCallsign(), - 'path' => '/'.$drequest->getPath(), - )); - - $view->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Find Owners')) - ->setHref((string)$owners_uri) - ->setIcon('fa-users')); - } - return $view; } @@ -137,7 +118,7 @@ abstract class DiffusionBrowseController extends DiffusionController { DiffusionRequest $drequest, PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) @@ -180,6 +161,47 @@ abstract class DiffusionBrowseController extends DiffusionController { } } + $repository = $drequest->getRepository(); + + $owners = 'PhabricatorOwnersApplication'; + if (PhabricatorApplication::isClassInstalled($owners)) { + $package_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withControl( + $repository->getPHID(), + array( + $drequest->getPath(), + )); + + $package_query->execute(); + + $packages = $package_query->getControllingPackagesForPath( + $repository->getPHID(), + $drequest->getPath()); + + if ($packages) { + $ownership = id(new PHUIStatusListView()) + ->setUser($viewer); + + + + foreach ($packages as $package) { + $icon = 'fa-list-alt'; + $color = 'grey'; + + $item = id(new PHUIStatusItemView()) + ->setIcon($icon, $color) + ->setTarget($viewer->renderHandle($package->getPHID())); + + $ownership->addItem($item); + } + } else { + $ownership = phutil_tag('em', array(), pht('None')); + } + + $view->addProperty(pht('Packages'), $ownership); + } + return $view; } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 6ec31eadfc..c3f0ec42f4 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -13,12 +13,13 @@ final class PhabricatorOwnersDetailController $package = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) + ->needPaths(true) ->executeOne(); if (!$package) { return new Aphront404Response(); } - $paths = $package->loadPaths(); + $paths = $package->getPaths(); $repository_phids = array(); foreach ($paths as $path) { diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index 95df2cb807..ad278d5296 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -15,6 +15,7 @@ final class PhabricatorOwnersPathsController // TODO: Support this capability. // PhabricatorPolicyCapability::CAN_EDIT, )) + ->needPaths(true) ->executeOne(); if (!$package) { return new Aphront404Response(); @@ -66,7 +67,7 @@ final class PhabricatorOwnersPathsController return id(new AphrontRedirectResponse()) ->setURI('/owners/package/'.$package->getID().'/'); } else { - $paths = $package->loadPaths(); + $paths = $package->getPaths(); $path_refs = mpull($paths, 'getRef'); } diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 8ce1e48c29..311cf642a0 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -43,8 +43,7 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: return $object->getDescription(); case PhabricatorOwnersPackageTransaction::TYPE_PATHS: - // TODO: needPaths() this on the query - $paths = $object->loadPaths(); + $paths = $object->getPaths(); return mpull($paths, 'getRef'); } } @@ -152,8 +151,7 @@ final class PhabricatorOwnersPackageTransactionEditor $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); - // TODO: needPaths this - $paths = $object->loadPaths(); + $paths = $object->getPaths(); $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); list($rem, $add) = $diffs; diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 1d34d7be58..8dbb74e528 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -8,6 +8,10 @@ final class PhabricatorOwnersPackageQuery private $ownerPHIDs; private $repositoryPHIDs; private $namePrefix; + private $needPaths; + + private $controlMap = array(); + private $controlResults; /** * Owners are direct owners, and members of owning projects. @@ -32,19 +36,64 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withControl($repository_phid, array $paths) { + if (empty($this->controlMap[$repository_phid])) { + $this->controlMap[$repository_phid] = array(); + } + + foreach ($paths as $path) { + $this->controlMap[$repository_phid][$path] = $path; + } + + // We need to load paths to execute control queries. + $this->needPaths = true; + + return $this; + } + public function withNamePrefix($prefix) { $this->namePrefix = $prefix; return $this; } + public function needPaths($need_paths) { + $this->needPaths = $need_paths; + return $this; + } + public function newResultObject() { return new PhabricatorOwnersPackage(); } + protected function willExecute() { + $this->controlResults = array(); + } + protected function loadPage() { return $this->loadStandardPage(new PhabricatorOwnersPackage()); } + protected function didFilterPage(array $packages) { + if ($this->needPaths) { + $package_ids = mpull($packages, 'getID'); + + $paths = id(new PhabricatorOwnersPath())->loadAllWhere( + 'packageID IN (%Ld)', + $package_ids); + $paths = mgroup($paths, 'getPackageID'); + + foreach ($packages as $package) { + $package->attachPaths(idx($paths, $package->getID(), array())); + } + } + + if ($this->controlMap) { + $this->controlResults += mpull($packages, null, 'getID'); + } + + return $packages; + } + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); @@ -55,7 +104,7 @@ final class PhabricatorOwnersPackageQuery id(new PhabricatorOwnersOwner())->getTableName()); } - if ($this->repositoryPHIDs !== null) { + if ($this->shouldJoinOwnersPathTable()) { $joins[] = qsprintf( $conn, 'JOIN %T rpath ON rpath.packageID = p.id', @@ -115,11 +164,30 @@ final class PhabricatorOwnersPackageQuery phutil_utf8_strtolower($this->namePrefix)); } + if ($this->controlMap) { + $clauses = array(); + foreach ($this->controlMap as $repository_phid => $paths) { + $fragments = array(); + foreach ($paths as $path) { + foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) { + $fragments[$fragment] = $fragment; + } + } + + $clauses[] = qsprintf( + $conn, + '(rpath.repositoryPHID = %s AND rpath.path IN (%Ls))', + $repository_phid, + $fragments); + } + $where[] = implode(' OR ', $clauses); + } + return $where; } protected function shouldGroupQueryResultRows() { - if ($this->repositoryPHIDs) { + if ($this->shouldJoinOwnersPathTable()) { return true; } @@ -167,4 +235,83 @@ final class PhabricatorOwnersPackageQuery return 'p'; } + private function shouldJoinOwnersPathTable() { + if ($this->repositoryPHIDs !== null) { + return true; + } + + if ($this->controlMap) { + return true; + } + + return false; + } + + +/* -( Path Control )------------------------------------------------------- */ + + + /** + * Get the package which controls a path, if one exists. + * + * @return PhabricatorOwnersPackage|null Package, if one exists. + */ + public function getControllingPackageForPath($repository_phid, $path) { + $packages = $this->getControllingPackagesForPath($repository_phid, $path); + + if (!$packages) { + return null; + } + + return head($packages); + } + + + /** + * Get a list of all packages which control a path or its parent directories, + * ordered from weakest to strongest. + * + * The first package has the most specific claim on the path; the last + * package has the most general claim. + * + * @return list List of controlling packages. + */ + public function getControllingPackagesForPath($repository_phid, $path) { + if (!isset($this->controlMap[$repository_phid][$path])) { + throw new PhutilInvalidStateException('withControl'); + } + + if ($this->controlResults === null) { + throw new PhutilInvalidStateException('execute'); + } + + $packages = $this->controlResults; + + $matches = array(); + foreach ($packages as $package_id => $package) { + $best_match = null; + $include = false; + + foreach ($package->getPaths() as $package_path) { + $strength = $package_path->getPathMatchStrength($path); + if ($strength > $best_match) { + $best_match = $strength; + $include = !$package_path->getExcluded(); + } + } + + if ($best_match && $include) { + $matches[$package_id] = array( + 'strength' => $best_match, + 'package' => $package, + ); + } + } + + $matches = isort($matches, 'strength'); + $matches = array_reverse($matches); + + return array_values(ipull($matches, 'package')); + } + } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index ecf1862323..82c8ca9074 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -13,6 +13,8 @@ final class PhabricatorOwnersPackage protected $primaryOwnerPHID; protected $mailKey; + private $paths = self::ATTACHABLE; + public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) ->setAuditingEnabled(0) @@ -225,7 +227,7 @@ final class PhabricatorOwnersPackage return $ids; } - private static function splitPath($path) { + public static function splitPath($path) { $result = array('/'); $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $path = trim($path, '/'); @@ -238,6 +240,16 @@ final class PhabricatorOwnersPackage return $result; } + public function attachPaths(array $paths) { + assert_instances_of($paths, 'PhabricatorOwnersPath'); + $this->paths = $paths; + return $this; + } + + public function getPaths() { + return $this->assertAttached($this->paths); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/owners/storage/PhabricatorOwnersPath.php b/src/applications/owners/storage/PhabricatorOwnersPath.php index f65d6052db..33ab109719 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPath.php +++ b/src/applications/owners/storage/PhabricatorOwnersPath.php @@ -70,4 +70,36 @@ final class PhabricatorOwnersPath extends PhabricatorOwnersDAO { return isset($set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']]); } + /** + * Get the number of directory matches between this path specification and + * some real path. + */ + public function getPathMatchStrength($path) { + $this_path = $this->getPath(); + + if ($this_path === '/') { + // The root path "/" just matches everything with strength 1. + return 1; + } + + $self_fragments = PhabricatorOwnersPackage::splitPath($this_path); + $path_fragments = PhabricatorOwnersPackage::splitPath($path); + + $self_count = count($self_fragments); + $path_count = count($path_fragments); + if ($self_count > $path_count) { + // If this path is longer (and therefor more specific) than the target + // path, we don't match it at all. + return 0; + } + + for ($ii = 0; $ii < $self_count; $ii++) { + if ($self_fragments[$ii] != $path_fragments[$ii]) { + return 0; + } + } + + return $self_count; + } + } From 5590642a1df005663a1d7b3e48e5004591c017b3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:47:58 -0700 Subject: [PATCH 10/41] Simplify some transaction translations Summary: Ref T8700, I don't believe we need to be specific here about the object, since it displays on the object. Test Plan: Change policy a few times on a task, see new translation Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8700 Differential Revision: https://secure.phabricator.com/D13913 --- .../storage/PhabricatorApplicationTransaction.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index c7be284ecd..e33049dcc7 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -642,23 +642,20 @@ abstract class PhabricatorApplicationTransaction $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( - '%s changed the visibility of this %s from "%s" to "%s".', + '%s changed the visibility from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( - '%s changed the edit policy of this %s from "%s" to "%s".', + '%s changed the edit policy from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( - '%s changed the join policy of this %s from "%s" to "%s".', + '%s changed the join policy from "%s" to "%s".', $this->renderHandleLink($author_phid), - $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_SPACE: From 4c437049b8e51762289ff8169751e37c902d4aa6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:48:19 -0700 Subject: [PATCH 11/41] Update style of remarkup tt Summary: Standardize color and spacing. Test Plan: `Wrote a test plan` {F720873} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13916 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cf7a8dfcb9..a9e6eb704e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '33799ec4', + 'core.pkg.css' => '12dcd407', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '9451634c', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => 'de13dcbe', + 'rsrc/css/core/remarkup.css' => '66f5facd', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => '5a337049', @@ -737,7 +737,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '6920d200', - 'phabricator-remarkup-css' => 'de13dcbe', + 'phabricator-remarkup-css' => '66f5facd', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 457242c9ee..5821b3f482 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -52,9 +52,10 @@ } .phabricator-remarkup tt.remarkup-monospaced { - color: #333333; - background: #ebebeb; - padding: 0 4px; + color: #000; + background: rgba(71,87,120,0.1); + padding: 2px 4px; + border-radius: 3px; white-space: pre-wrap; } From 7212e825fad746cbb4577d7165ddbf40f6c4550b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:50:11 -0700 Subject: [PATCH 12/41] Fix pre-wrap in two-column-view Summary: Fixes T9154, tables have special needs with widths and pres Test Plan: Test a long piece of code in a Ponder question Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9154 Differential Revision: https://secure.phabricator.com/D13904 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-two-column-view.css | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a9e6eb704e..2c81af91f3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -148,7 +148,7 @@ return array( 'rsrc/css/phui/phui-tag-view.css' => '402691cc', 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => 'f1bccf73', - 'rsrc/css/phui/phui-two-column-view.css' => 'add0a7d1', + 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', 'rsrc/css/sprite-login.css' => '1ebb9bf9', @@ -802,7 +802,7 @@ return array( 'phui-text-css' => 'cf019f54', 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => 'f1bccf73', - 'phui-two-column-view-css' => 'add0a7d1', + 'phui-two-column-view-css' => '39ecafb1', 'phui-workboard-view-css' => '6704d68d', 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 4701955b49..7b6bc7e49b 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -31,3 +31,7 @@ .phui-side-column .phui-object-box:first-child { margin: 0 16px 16px 0; } + +.phui-two-column-view pre { + white-space: pre-wrap; +} From e29255650cf3d6403e2019970ece45a1eac80dbc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:50:23 -0700 Subject: [PATCH 13/41] Fix Macro active query Summary: When using the active query in Macro, all Macros are returned. Properly set the status query. Test Plan: Review active macros, don't see archived macros. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13912 --- src/applications/macro/query/PhabricatorMacroSearchEngine.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index 2000b3275a..039f396d5f 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -111,7 +111,9 @@ final class PhabricatorMacroSearchEngine switch ($query_key) { case 'active': - return $query; + return $query->setParameter( + 'status', + PhabricatorMacroQuery::STATUS_ACTIVE); case 'all': return $query->setParameter( 'status', From 7187a5351e4d66f480ecd341527ab3260978ecc3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:50:46 -0700 Subject: [PATCH 14/41] Mobile Slowvote Summary: Fixes T9180, removes the background color and wraps the text. Test Plan: View on a shrunked Chrome. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9180 Differential Revision: https://secure.phabricator.com/D13915 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/slowvote/slowvote.css | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2c81af91f3..1ee0891ef3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -100,7 +100,7 @@ return array( 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => '7dea472c', - 'rsrc/css/application/slowvote/slowvote.css' => '7c27f0f9', + 'rsrc/css/application/slowvote/slowvote.css' => '475b4bd2', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', @@ -741,7 +741,7 @@ return array( 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', - 'phabricator-slowvote-css' => '7c27f0f9', + 'phabricator-slowvote-css' => '475b4bd2', 'phabricator-source-code-view-css' => '5e0178de', 'phabricator-standard-page-view' => '4d176b67', 'phabricator-textareautils' => '5c93c52c', diff --git a/webroot/rsrc/css/application/slowvote/slowvote.css b/webroot/rsrc/css/application/slowvote/slowvote.css index 5c78ce6add..afa45a8032 100644 --- a/webroot/rsrc/css/application/slowvote/slowvote.css +++ b/webroot/rsrc/css/application/slowvote/slowvote.css @@ -52,6 +52,10 @@ background-color: {$sh-bluebackground}; } +.device-phone .slowvote-bar { + display: none; +} + .slowvote-control-offset { white-space: nowrap; position: absolute; @@ -62,6 +66,12 @@ text-shadow: {$whitetextshadow}; } +.device-phone .slowvote-control-offset { + white-space: normal; + position: static; + padding: 0 4px 8px; +} + .slowvote-option-label-group { margin: 0 0 12px; } From c9de213750c16d02190734866a69101dd864cd0d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 16 Aug 2015 17:53:02 -0700 Subject: [PATCH 15/41] Fix mobile previous revision inline comments Summary: Fixes T9016, adds additonal space around icons. Test Plan: Visit an old inline comment, no overlapping icon. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9016 Differential Revision: https://secure.phabricator.com/D13914 --- resources/celerity/map.php | 6 +++--- .../css/application/differential/phui-inline-comment.css | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1ee0891ef3..af179cf770 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'core.pkg.css' => '12dcd407', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '9451634c', + 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => 'ebef29b1', 'diffusion.pkg.css' => '385e85b3', 'diffusion.pkg.js' => '0115b37c', @@ -60,7 +60,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '9fadd6b8', + 'rsrc/css/application/differential/phui-inline-comment.css' => '0fdb3667', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -788,7 +788,7 @@ return array( 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '5b16bac6', - 'phui-inline-comment-view-css' => '9fadd6b8', + 'phui-inline-comment-view-css' => '0fdb3667', 'phui-list-view-css' => '125599df', 'phui-object-box-css' => '407eaf5a', 'phui-object-item-list-view-css' => '36ce366c', diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 4bb3b3d770..02998aaaf0 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -83,17 +83,13 @@ } .device-phone .differential-inline-comment .inline-head-right { - float: none; + margin: 12px 0 4px 4px; } .device-phone .differential-inline-comment .inline-head-right .mml { margin: 0 4px 0 0; } -.device-phone .differential-inline-comment .differential-inline-comment-head { - padding: 0px 0px 4px 8px; -} - /* - Sythetic Comment --------------------------------------------------------- From cfd1348975019ceb2e4d0838f711aa3d64bd6d1c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 16 Aug 2015 17:59:02 -0700 Subject: [PATCH 16/41] Fix an issue with Table of Contents construction for copied files Summary: Ref T9201. Test Plan: Inspection. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9201 Differential Revision: https://secure.phabricator.com/D13918 --- .../diff/view/PHUIDiffTableOfContentsItemView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php index a029916f86..13ab61de64 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php @@ -267,7 +267,7 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { } } } else if ($type == DifferentialChangeType::TYPE_COPY_HERE) { - $meta = pht('Copied from %s', $changeset->getOldFile()); + $meta[] = pht('Copied from %s', $changeset->getOldFile()); } if (!$meta) { From a3393c3ecb92753277cd7bf447631ebb510f59ce Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 16 Aug 2015 17:59:14 -0700 Subject: [PATCH 17/41] Fix Owners lookups for the repository root Summary: Ref T9201. At the root of a repository the current path is `null`, but the OwnersQuery wants strings. This could be resolved a couple different ways, but just cast the arguments to strings since that seems reasonable enough. Test Plan: Browsed root of a repository. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9201 Differential Revision: https://secure.phabricator.com/D13919 --- .../diffusion/controller/DiffusionBrowseController.php | 2 -- .../owners/query/PhabricatorOwnersPackageQuery.php | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 6fd164f2a7..7450c3921a 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -183,8 +183,6 @@ abstract class DiffusionBrowseController extends DiffusionController { $ownership = id(new PHUIStatusListView()) ->setUser($viewer); - - foreach ($packages as $package) { $icon = 'fa-list-alt'; $color = 'grey'; diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 8dbb74e528..f2691ea3be 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -42,6 +42,7 @@ final class PhabricatorOwnersPackageQuery } foreach ($paths as $path) { + $path = (string)$path; $this->controlMap[$repository_phid][$path] = $path; } @@ -277,6 +278,8 @@ final class PhabricatorOwnersPackageQuery * @return list List of controlling packages. */ public function getControllingPackagesForPath($repository_phid, $path) { + $path = (string)$path; + if (!isset($this->controlMap[$repository_phid][$path])) { throw new PhutilInvalidStateException('withControl'); } From fd61702d6e961a37ccf8a344bb1a03c26eccc053 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Aug 2015 10:08:54 -0700 Subject: [PATCH 18/41] Remove "Primary Owner" from Owners Summary: Ref T8320. This field doesn't have any special effects and has no reason to exist. Primary owners are always already owners. This makes one minor implicit change to Owners: it is now possible to create a package with no owners. That seems fine; it is reasonable to create a package purely as an organizational tool and use it in, e.g., Herald. Test Plan: Created and edited packages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13921 --- .../conduit/OwnersQueryConduitAPIMethod.php | 1 - .../PhabricatorOwnersDetailController.php | 8 ------ .../PhabricatorOwnersEditController.php | 28 ------------------- ...bricatorOwnersPackageTransactionEditor.php | 25 ----------------- .../storage/PhabricatorOwnersPackage.php | 3 +- .../PhabricatorOwnersPackageTransaction.php | 17 ++--------- 6 files changed, 4 insertions(+), 78 deletions(-) diff --git a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php index 5473ec6e00..6ce996a002 100644 --- a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php +++ b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php @@ -110,7 +110,6 @@ final class OwnersQueryConduitAPIMethod extends OwnersConduitAPIMethod { 'phid' => $package->getPHID(), 'name' => $package->getName(), 'description' => $package->getDescription(), - 'primaryOwner' => $package->getPrimaryOwnerPHID(), 'owners' => $owners, 'paths' => $paths, ); diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index c3f0ec42f4..168d7cd07e 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -150,14 +150,6 @@ final class PhabricatorOwnersDetailController $view = id(new PHUIPropertyListView()) ->setUser($viewer); - $primary_phid = $package->getPrimaryOwnerPHID(); - if ($primary_phid) { - $primary_owner = $viewer->renderHandle($primary_phid); - } else { - $primary_owner = phutil_tag('em', array(), pht('None')); - } - $view->addProperty(pht('Primary Owner'), $primary_owner); - // TODO: needOwners() this on the Query. $owners = $package->loadOwners(); if ($owners) { diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 9f1987814d..618f99456d 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -28,10 +28,8 @@ final class PhabricatorOwnersEditController } $e_name = true; - $e_primary = true; $v_name = $package->getName(); - $v_primary = $package->getPrimaryOwnerPHID(); // TODO: Pull these off needOwners() on the Query. $v_owners = mpull($package->loadOwners(), 'getUserPHID'); $v_auditing = $package->getAuditingEnabled(); @@ -43,18 +41,11 @@ final class PhabricatorOwnersEditController $xactions = array(); $v_name = $request->getStr('name'); - $v_primary = head($request->getArr('primary')); $v_owners = $request->getArr('owners'); $v_auditing = ($request->getStr('auditing') == 'enabled'); $v_description = $request->getStr('description'); - if ($v_primary) { - $v_owners[] = $v_primary; - $v_owners = array_unique($v_owners); - } - $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; @@ -63,10 +54,6 @@ final class PhabricatorOwnersEditController ->setTransactionType($type_name) ->setNewValue($v_name); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_primary) - ->setNewValue($v_primary); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_owners) ->setNewValue($v_owners); @@ -102,16 +89,9 @@ final class PhabricatorOwnersEditController $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); - $e_primary = $ex->getShortMessage($type_primary); } } - if ($v_primary) { - $value_primary_owner = array($v_primary); - } else { - $value_primary_owner = array(); - } - if ($is_new) { $cancel_uri = '/owners/'; $title = pht('New Package'); @@ -130,14 +110,6 @@ final class PhabricatorOwnersEditController ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLabel(pht('Primary Owner')) - ->setName('primary') - ->setLimit(1) - ->setValue($value_primary_owner) - ->setError($e_primary)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorProjectOrUserDatasource()) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 311cf642a0..79cce9e9af 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -15,7 +15,6 @@ final class PhabricatorOwnersPackageTransactionEditor $types = parent::getTransactionTypes(); $types[] = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $types[] = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; $types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; @@ -31,8 +30,6 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: return $object->getName(); - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - return $object->getPrimaryOwnerPHID(); case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: // TODO: needOwners() this on the Query. $phids = mpull($object->loadOwners(), 'getUserPHID'); @@ -54,7 +51,6 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return $xaction->getNewValue(); @@ -94,9 +90,6 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $object->setPrimaryOwnerPHID($xaction->getNewValue()); - return; case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; @@ -117,7 +110,6 @@ final class PhabricatorOwnersPackageTransactionEditor switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return; @@ -200,22 +192,6 @@ final class PhabricatorOwnersPackageTransactionEditor $errors[] = $error; } break; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $missing = $this->validateIsEmptyTextField( - $object->getPrimaryOwnerPHID(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Packages must have a primary owner.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; } return $errors; @@ -245,7 +221,6 @@ final class PhabricatorOwnersPackageTransactionEditor protected function getMailTo(PhabricatorLiskDAO $object) { return array( - $object->getPrimaryOwnerPHID(), $this->requireActor()->getPHID(), ); } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 82c8ca9074..bfba5856ca 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -17,8 +17,7 @@ final class PhabricatorOwnersPackage public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) - ->setAuditingEnabled(0) - ->setPrimaryOwnerPHID($actor->getPHID()); + ->setAuditingEnabled(0); } public function getCapabilities() { diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 3686125887..bf676f941b 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -25,14 +25,6 @@ final class PhabricatorOwnersPackageTransaction $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_PRIMARY: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; case self::TYPE_OWNERS: $add = array_diff($new, $old); foreach ($add as $phid) { @@ -55,6 +47,9 @@ final class PhabricatorOwnersPackageTransaction switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($old === null); + case self::TYPE_PRIMARY: + // TODO: Eventually, remove these transactions entirely. + return true; } } @@ -76,12 +71,6 @@ final class PhabricatorOwnersPackageTransaction $old, $new); } - case self::TYPE_PRIMARY: - return pht( - '%s changed the primary owner for this package from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); case self::TYPE_OWNERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); From f5ea5ba9fe7b1b6e0f5ee44a54562e8e3d77ac66 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Aug 2015 10:08:52 -0700 Subject: [PATCH 19/41] Simplify Countdown mailtags Summary: These were a little silly, simplify them Test Plan: New countdown, edit preferences, etc. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13888 --- .../editor/PhabricatorCountdownEditor.php | 10 ++++------ .../storage/PhabricatorCountdownTransaction.php | 14 ++++++-------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/applications/countdown/editor/PhabricatorCountdownEditor.php b/src/applications/countdown/editor/PhabricatorCountdownEditor.php index 97f8e5b947..f0962beae2 100644 --- a/src/applications/countdown/editor/PhabricatorCountdownEditor.php +++ b/src/applications/countdown/editor/PhabricatorCountdownEditor.php @@ -147,12 +147,10 @@ final class PhabricatorCountdownEditor public function getMailTagsMap() { return array( - PhabricatorCountdownTransaction::MAILTAG_TITLE => - pht('Someone changes the countdown title.'), - PhabricatorCountdownTransaction::MAILTAG_DESCRIPTION => - pht('Someone changes the countdown description.'), - PhabricatorCountdownTransaction::MAILTAG_EPOCH => - pht('Someone changes the countdown end date.'), + PhabricatorCountdownTransaction::MAILTAG_DETAILS => + pht('Someone changes the countdown details.'), + PhabricatorCountdownTransaction::MAILTAG_COMMENT => + pht('Someone comments on a countdown.'), PhabricatorCountdownTransaction::MAILTAG_OTHER => pht('Other countdown activity not listed above occurs.'), ); diff --git a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php index eace665a89..72303a369f 100644 --- a/src/applications/countdown/storage/PhabricatorCountdownTransaction.php +++ b/src/applications/countdown/storage/PhabricatorCountdownTransaction.php @@ -7,9 +7,8 @@ final class PhabricatorCountdownTransaction const TYPE_EPOCH = 'countdown:epoch'; const TYPE_DESCRIPTION = 'countdown:description'; - const MAILTAG_TITLE = 'countdown:title'; - const MAILTAG_EPOCH = 'countdown:epoch'; - const MAILTAG_DESCRIPTION = 'countdown:description'; + const MAILTAG_DETAILS = 'countdown:details'; + const MAILTAG_COMMENT = 'countdown:comment'; const MAILTAG_OTHER = 'countdown:other'; public function getApplicationName() { @@ -135,14 +134,13 @@ final class PhabricatorCountdownTransaction $tags = parent::getMailTags(); switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + $tags[] = self::MAILTAG_COMMENT; + break; case self::TYPE_TITLE: - $tags[] = self::MAILTAG_TITLE; - break; case self::TYPE_EPOCH: - $tags[] = self::MAILTAG_EPOCH; - break; case self::TYPE_DESCRIPTION: - $tags[] = self::MAILTAG_DESCRIPTION; + $tags[] = self::MAILTAG_DETAILS; break; default: $tags[] = self::MAILTAG_OTHER; From 6cfeb9e5401436a2e3ffd2ccfcd63a1e256581a9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Aug 2015 10:09:17 -0700 Subject: [PATCH 20/41] Further modernize OwnersPackageQuery Summary: Ref T8320. - Add needOwners(). - Split withOwnerPHIDs() [exact owners] and withAuthorityPHIDs() [indirect authority] apart. - Restore searching by path. Test Plan: Browsed pacakges, edited packages, edited paths. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13922 --- .../editor/PhabricatorAuditCommentEditor.php | 2 +- .../conduit/OwnersQueryConduitAPIMethod.php | 2 +- .../PhabricatorOwnersDetailController.php | 4 +- .../PhabricatorOwnersEditController.php | 4 +- .../query/PhabricatorOwnersPackageQuery.php | 127 ++++++++++++++---- .../PhabricatorOwnersPackageSearchEngine.php | 24 ++-- .../storage/PhabricatorOwnersPackage.php | 15 ++- 7 files changed, 138 insertions(+), 40 deletions(-) diff --git a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php index 9f4bd42f29..03476f70a7 100644 --- a/src/applications/audit/editor/PhabricatorAuditCommentEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditCommentEditor.php @@ -20,7 +20,7 @@ final class PhabricatorAuditCommentEditor extends PhabricatorEditor { $owned_packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($user) - ->withOwnerPHIDs(array($user->getPHID())) + ->withAuthorityPHIDs(array($user->getPHID())) ->execute(); foreach ($owned_packages as $package) { $phids[$package->getPHID()] = true; diff --git a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php index 6ce996a002..a3b7b52cab 100644 --- a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php +++ b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php @@ -141,7 +141,7 @@ final class OwnersQueryConduitAPIMethod extends OwnersConduitAPIMethod { $query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($request->getUser()); - $query->withOwnerPHIDs(array($request->getValue('userAffiliated'))); + $query->withAuthorityPHIDs(array($request->getValue('userAffiliated'))); $packages = $query->execute(); } else if ($is_owner_query) { diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 168d7cd07e..008cb715e5 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -14,6 +14,7 @@ final class PhabricatorOwnersDetailController ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->needPaths(true) + ->needOwners(true) ->executeOne(); if (!$package) { return new Aphront404Response(); @@ -150,8 +151,7 @@ final class PhabricatorOwnersDetailController $view = id(new PHUIPropertyListView()) ->setUser($viewer); - // TODO: needOwners() this on the Query. - $owners = $package->loadOwners(); + $owners = $package->getOwners(); if ($owners) { $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); } else { diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 618f99456d..f601cd3115 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -17,6 +17,7 @@ final class PhabricatorOwnersEditController // TODO: Support this capability. // PhabricatorPolicyCapability::CAN_EDIT, )) + ->needOwners(true) ->executeOne(); if (!$package) { return new Aphront404Response(); @@ -30,8 +31,7 @@ final class PhabricatorOwnersEditController $e_name = true; $v_name = $package->getName(); - // TODO: Pull these off needOwners() on the Query. - $v_owners = mpull($package->loadOwners(), 'getUserPHID'); + $v_owners = mpull($package->getOwners(), 'getUserPHID'); $v_auditing = $package->getAuditingEnabled(); $v_description = $package->getDescription(); diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index f2691ea3be..74f77f79b7 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -6,21 +6,37 @@ final class PhabricatorOwnersPackageQuery private $ids; private $phids; private $ownerPHIDs; + private $authorityPHIDs; private $repositoryPHIDs; + private $paths; private $namePrefix; - private $needPaths; private $controlMap = array(); private $controlResults; + private $needPaths; + private $needOwners; + + /** - * Owners are direct owners, and members of owning projects. + * Query owner PHIDs exactly. This does not expand authorities, so a user + * PHID will not match projects the user is a member of. */ public function withOwnerPHIDs(array $phids) { $this->ownerPHIDs = $phids; return $this; } + /** + * Query owner authority. This will expand authorities, so a user PHID will + * match both packages they own directly and packages owned by a project they + * are a member of. + */ + public function withAuthorityPHIDs(array $phids) { + $this->authorityPHIDs = $phids; + return $this; + } + public function withPHIDs(array $phids) { $this->phids = $phids; return $this; @@ -36,6 +52,11 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withPaths(array $paths) { + $this->paths = $paths; + return $this; + } + public function withControl($repository_phid, array $paths) { if (empty($this->controlMap[$repository_phid])) { $this->controlMap[$repository_phid] = array(); @@ -62,6 +83,11 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function needOwners($need_owners) { + $this->needOwners = $need_owners; + return $this; + } + public function newResultObject() { return new PhabricatorOwnersPackage(); } @@ -88,6 +114,19 @@ final class PhabricatorOwnersPackageQuery } } + if ($this->needOwners) { + $package_ids = mpull($packages, 'getID'); + + $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( + 'packageID IN (%Ld)', + $package_ids); + $owners = mgroup($owners, 'getPackageID'); + + foreach ($packages as $package) { + $package->attachOwners(idx($owners, $package->getID(), array())); + } + } + if ($this->controlMap) { $this->controlResults += mpull($packages, null, 'getID'); } @@ -98,14 +137,14 @@ final class PhabricatorOwnersPackageQuery protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $joins = parent::buildJoinClauseParts($conn); - if ($this->ownerPHIDs !== null) { + if ($this->shouldJoinOwnersTable()) { $joins[] = qsprintf( $conn, 'JOIN %T o ON o.packageID = p.id', id(new PhabricatorOwnersOwner())->getTableName()); } - if ($this->shouldJoinOwnersPathTable()) { + if ($this->shouldJoinPathTable()) { $joins[] = qsprintf( $conn, 'JOIN %T rpath ON rpath.packageID = p.id', @@ -139,21 +178,26 @@ final class PhabricatorOwnersPackageQuery $this->repositoryPHIDs); } - if ($this->ownerPHIDs !== null) { - $base_phids = $this->ownerPHIDs; - - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($this->getViewer()) - ->withMemberPHIDs($base_phids) - ->execute(); - $project_phids = mpull($projects, 'getPHID'); - - $all_phids = array_merge($base_phids, $project_phids); - + if ($this->authorityPHIDs !== null) { + $authority_phids = $this->expandAuthority($this->authorityPHIDs); $where[] = qsprintf( $conn, 'o.userPHID IN (%Ls)', - $all_phids); + $authority_phids); + } + + if ($this->ownerPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'o.userPHID IN (%Ls)', + $this->ownerPHIDs); + } + + if ($this->paths !== null) { + $where[] = qsprintf( + $conn, + 'rpath.path IN (%Ls)', + $this->getFragmentsForPaths($this->paths)); } if (strlen($this->namePrefix)) { @@ -168,12 +212,7 @@ final class PhabricatorOwnersPackageQuery if ($this->controlMap) { $clauses = array(); foreach ($this->controlMap as $repository_phid => $paths) { - $fragments = array(); - foreach ($paths as $path) { - foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) { - $fragments[$fragment] = $fragment; - } - } + $fragments = $this->getFragmentsForPaths($paths); $clauses[] = qsprintf( $conn, @@ -188,11 +227,11 @@ final class PhabricatorOwnersPackageQuery } protected function shouldGroupQueryResultRows() { - if ($this->shouldJoinOwnersPathTable()) { + if ($this->shouldJoinOwnersTable()) { return true; } - if ($this->ownerPHIDs) { + if ($this->shouldJoinPathTable()) { return true; } @@ -236,11 +275,27 @@ final class PhabricatorOwnersPackageQuery return 'p'; } - private function shouldJoinOwnersPathTable() { + private function shouldJoinOwnersTable() { + if ($this->ownerPHIDs !== null) { + return true; + } + + if ($this->authorityPHIDs !== null) { + return true; + } + + return false; + } + + private function shouldJoinPathTable() { if ($this->repositoryPHIDs !== null) { return true; } + if ($this->paths !== null) { + return true; + } + if ($this->controlMap) { return true; } @@ -248,6 +303,28 @@ final class PhabricatorOwnersPackageQuery return false; } + private function expandAuthority(array $phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($this->getViewer()) + ->withMemberPHIDs($phids) + ->execute(); + $project_phids = mpull($projects, 'getPHID'); + + return array_fuse($phids) + array_fuse($project_phids); + } + + private function getFragmentsForPaths(array $paths) { + $fragments = array(); + + foreach ($paths as $path) { + foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) { + $fragments[$fragment] = $fragment; + } + } + + return $fragments; + } + /* -( Path Control )------------------------------------------------------- */ diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php index fc2f8f620f..a26a9b3c56 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -18,29 +18,37 @@ final class PhabricatorOwnersPackageSearchEngine protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchDatasourceField()) - ->setLabel(pht('Owners')) - ->setKey('ownerPHIDs') - ->setAliases(array('owner', 'owners')) + ->setLabel(pht('Authority')) + ->setKey('authorityPHIDs') + ->setAliases(array('authority', 'authorities')) ->setDatasource(new PhabricatorProjectOrUserDatasource()), id(new PhabricatorSearchDatasourceField()) ->setLabel(pht('Repositories')) ->setKey('repositoryPHIDs') ->setAliases(array('repository', 'repositories')) ->setDatasource(new DiffusionRepositoryDatasource()), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Paths')) + ->setKey('paths') + ->setAliases(array('path')), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); - if ($map['ownerPHIDs']) { - $query->withOwnerPHIDs($map['ownerPHIDs']); + if ($map['authorityPHIDs']) { + $query->withAuthorityPHIDs($map['authorityPHIDs']); } if ($map['repositoryPHIDs']) { $query->withRepositoryPHIDs($map['repositoryPHIDs']); } + if ($map['paths']) { + $query->withPaths($map['paths']); + } + return $query; } @@ -52,7 +60,7 @@ final class PhabricatorOwnersPackageSearchEngine $names = array(); if ($this->requireViewer()->isLoggedIn()) { - $names['owned'] = pht('Owned'); + $names['authority'] = pht('Owned'); } $names += array( @@ -69,9 +77,9 @@ final class PhabricatorOwnersPackageSearchEngine switch ($query_key) { case 'all': return $query; - case 'owned': + case 'authority': return $query->setParameter( - 'ownerPHIDs', + 'authorityPHIDs', array($this->requireViewer()->getPHID())); } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index bfba5856ca..7d0d8e65a4 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -14,10 +14,13 @@ final class PhabricatorOwnersPackage protected $mailKey; private $paths = self::ATTACHABLE; + private $owners = self::ATTACHABLE; public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) - ->setAuditingEnabled(0); + ->setAuditingEnabled(0) + ->attachPaths(array()) + ->attachOwners(array()); } public function getCapabilities() { @@ -249,6 +252,16 @@ final class PhabricatorOwnersPackage return $this->assertAttached($this->paths); } + public function attachOwners(array $owners) { + assert_instances_of($owners, 'PhabricatorOwnersOwner'); + $this->owners = $owners; + return $this; + } + + public function getOwners() { + return $this->assertAttached($this->owners); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From 6010e80e5c5a79f81132dcb4fe1cdd5ee118662e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Aug 2015 10:14:22 -0700 Subject: [PATCH 21/41] Show packages in table of contents views in Diffusion and Differential Summary: Fixes T8004. - For paths which are part of a package, show the package. - Highlight paths which are part of a package you (the viewer) have authority over. Test Plan: {F725418} - Viewed owned and unowned chagnes in Diffusion and Differential. Reviewers: chad Reviewed By: chad Maniphest Tasks: T8004 Differential Revision: https://secure.phabricator.com/D13923 --- .../controller/DifferentialController.php | 42 +++++++++++++++++++ .../differential/storage/DifferentialDiff.php | 8 +++- .../controller/DiffusionCommitController.php | 38 ++++++++++++++++- .../view/PHUIDiffTableOfContentsListView.php | 38 ++++++++++++++++- 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 0ae33f4e59..eed059e265 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -30,6 +30,39 @@ abstract class DifferentialController extends PhabricatorController { $toc_view = id(new PHUIDiffTableOfContentsListView()) ->setUser($viewer); + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + if ($have_owners) { + $repository_phid = null; + if ($changesets) { + $changeset = head($changesets); + $diff = $changeset->getDiff(); + $repository_phid = $diff->getRepositoryPHID(); + } + + if (!$repository_phid) { + $have_owners = false; + } else { + if ($viewer->getPHID()) { + $packages = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withAuthorityPHIDs(array($viewer->getPHID())) + ->execute(); + $toc_view->setAuthorityPackages($packages); + } + + // TODO: For Subversion, we should adjust these paths to be relative to + // the repository root where possible. + $paths = mpull($changesets, 'getFilename'); + + $control_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withControl($repository_phid, $paths); + $control_query->execute(); + } + } + foreach ($changesets as $changeset_id => $changeset) { $is_visible = isset($visible_changesets[$changeset_id]); $anchor = $changeset->getAnchorName(); @@ -44,6 +77,15 @@ abstract class DifferentialController extends PhabricatorController { ->setCoverage(idx($coverage, $filename)) ->setCoverageID($coverage_id); + if ($have_owners) { + $package = $control_query->getControllingPackageForPath( + $repository_phid, + $changeset->getFilename()); + if ($package) { + $item->setPackage($package); + } + } + $toc_view->addItem($item); } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index e48ed5630d..7a5bc24203 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -101,9 +101,15 @@ final class DifferentialDiff if (!$this->getID()) { return array(); } - return id(new DifferentialChangeset())->loadAllWhere( + $changesets = id(new DifferentialChangeset())->loadAllWhere( 'diffID = %d', $this->getID()); + + foreach ($changesets as $changeset) { + $changeset->attachDiff($this); + } + + return $changesets; } public function save() { diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 927ad7f453..b91a7dc804 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1066,6 +1066,7 @@ final class DiffusionCommitController extends DiffusionController { } private function buildTableOfContents(array $changesets) { + $drequest = $this->getDiffusionRequest(); $viewer = $this->getViewer(); $toc_view = id(new PHUIDiffTableOfContentsListView()) @@ -1074,9 +1075,33 @@ final class DiffusionCommitController extends DiffusionController { // TODO: This is hacky, we just want access to the linkX() methods on // DiffusionView. $diffusion_view = id(new DiffusionEmptyResultView()) - ->setDiffusionRequest($this->getDiffusionRequest()); + ->setDiffusionRequest($drequest); - // TODO: Restore package stuff here. + $have_owners = PhabricatorApplication::isClassInstalledForViewer( + 'PhabricatorOwnersApplication', + $viewer); + + if (!$changesets) { + $have_owners = false; + } + + if ($have_owners) { + if ($viewer->getPHID()) { + $packages = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withAuthorityPHIDs(array($viewer->getPHID())) + ->execute(); + $toc_view->setAuthorityPackages($packages); + } + + $repository = $drequest->getRepository(); + $repository_phid = $repository->getPHID(); + + $control_query = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withControl($repository_phid, mpull($changesets, 'getFilename')); + $control_query->execute(); + } foreach ($changesets as $changeset_id => $changeset) { $path = $changeset->getFilename(); @@ -1095,6 +1120,15 @@ final class DiffusionCommitController extends DiffusionController { $browse_link, )); + if ($have_owners) { + $package = $control_query->getControllingPackageForPath( + $repository_phid, + $changeset->getFilename()); + if ($package) { + $item->setPackage($package); + } + } + $toc_view->addItem($item); } diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index 66677ee598..a7e3270c6e 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -3,12 +3,23 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { private $items = array(); + private $authorityPackages; public function addItem(PHUIDiffTableOfContentsItemView $item) { $this->items[] = $item; return $this; } + public function setAuthorityPackages(array $authority_packages) { + assert_instances_of($authority_packages, 'PhabricatorOwnersPackage'); + $this->authorityPackages = $authority_packages; + return $this; + } + + public function getAuthorityPackages() { + return $this->authorityPackages; + } + public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); @@ -16,12 +27,34 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { Javelin::initBehavior('phabricator-tooltips'); + if ($this->getAuthorityPackages()) { + $authority = mpull($this->getAuthorityPackages(), null, 'getPHID'); + } else { + $authority = array(); + } + $items = $this->items; $rows = array(); + $rowc = array(); foreach ($items as $item) { $item->setUser($this->getUser()); $rows[] = $item->render(); + + $have_authority = false; + + $package = $item->getPackage(); + if ($package) { + if (isset($authority[$package->getPHID()])) { + $have_authority = true; + } + } + + if ($have_authority) { + $rowc[] = 'highlighted'; + } else { + $rowc[] = null; + } } // Check if any item has content in these columns. If no item does, we'll @@ -60,6 +93,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { $reveal_link); $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) ->setHeaders( array( null, @@ -69,7 +103,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { pht('Path'), pht('Coverage (All)'), pht('Coverage (Touched)'), - null, + pht('Package'), )) ->setColumnClasses( array( @@ -80,7 +114,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { 'differential-toc-file wide', 'differential-toc-cov', 'differential-toc-cov', - 'center', + null, )) ->setColumnVisibility( array( From ee8e4363900edfc10c205695e3357bb96bd33db4 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Aug 2015 10:22:54 -0700 Subject: [PATCH 22/41] Add ability to reply to Ponder Answer emails Summary: Ref T9068, Ref T3846. Maybe fixes both, but I'm having issues testing email replies in a sandbox. Moves answer feed/mail generation to the AnswerEditor, hides it in QuestionEditor. Test Plan: Write an answer, see feed story, check /mail/ for mail generation. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3846, T9068 Differential Revision: https://secure.phabricator.com/D13905 --- src/__phutil_library_map__.php | 6 ++- .../ponder/editor/PonderQuestionEditor.php | 47 ++++++++----------- .../feed/PonderTransactionFeedStory.php | 14 ------ .../ponder/mail/PonderAnswerMailReceiver.php | 27 +++++++++++ .../ponder/mail/PonderAnswerReplyHandler.php | 16 +++++++ .../storage/PonderQuestionTransaction.php | 19 -------- 6 files changed, 67 insertions(+), 62 deletions(-) delete mode 100644 src/applications/ponder/feed/PonderTransactionFeedStory.php create mode 100644 src/applications/ponder/mail/PonderAnswerMailReceiver.php create mode 100644 src/applications/ponder/mail/PonderAnswerReplyHandler.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e589155500..3023eceaed 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3406,8 +3406,10 @@ phutil_register_library_map(array( 'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php', 'PonderAnswerHasVotingUserEdgeType' => 'applications/ponder/edge/PonderAnswerHasVotingUserEdgeType.php', 'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php', + 'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php', 'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php', 'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php', + 'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php', 'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php', 'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php', 'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php', @@ -3442,7 +3444,6 @@ phutil_register_library_map(array( 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', 'PonderSearchIndexer' => 'applications/ponder/search/PonderSearchIndexer.php', - 'PonderTransactionFeedStory' => 'applications/ponder/feed/PonderTransactionFeedStory.php', 'PonderVotableInterface' => 'applications/ponder/storage/PonderVotableInterface.php', 'PonderVote' => 'applications/ponder/constants/PonderVote.php', 'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php', @@ -7607,8 +7608,10 @@ phutil_register_library_map(array( 'PonderAnswerEditor' => 'PonderEditor', 'PonderAnswerHasVotingUserEdgeType' => 'PhabricatorEdgeType', 'PonderAnswerHistoryController' => 'PonderController', + 'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderAnswerPHIDType' => 'PhabricatorPHIDType', 'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderAnswerSaveController' => 'PonderController', 'PonderAnswerStatus' => 'PonderConstants', 'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction', @@ -7654,7 +7657,6 @@ phutil_register_library_map(array( 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PonderSearchIndexer' => 'PhabricatorSearchDocumentIndexer', - 'PonderTransactionFeedStory' => 'PhabricatorApplicationTransactionFeedStory', 'PonderVote' => 'PonderConstants', 'PonderVoteEditor' => 'PhabricatorEditor', 'PonderVotingUserHasAnswerEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index eeac4b3414..2fbe629002 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -177,30 +177,13 @@ final class PonderQuestionEditor return true; } - protected function getFeedStoryType() { - return 'PonderTransactionFeedStory'; - } - - protected function getFeedStoryData( - PhabricatorLiskDAO $object, - array $xactions) { - - $data = parent::getFeedStoryData($object, $xactions); - $answer = $this->getAnswer(); - if ($answer) { - $data['answerPHID'] = $answer->getPHID(); - } - - return $data; - } - protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PonderQuestionTransaction::TYPE_ANSWERS: - return true; + return false; } return parent::shouldImplyCC($object, $xaction); @@ -209,7 +192,25 @@ final class PonderQuestionEditor protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { - return true; + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + return false; + } + } + return true; + } + + protected function shouldPublishFeedStory( + PhabricatorLiskDAO $object, + array $xactions) { + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PonderQuestionTransaction::TYPE_ANSWERS: + return false; + } + } + return true; } public function getMailTagsMap() { @@ -248,14 +249,6 @@ final class PonderQuestionEditor $body->addRawSection($new); } } - // If the user gave an answer, add the answer text. Also update - // the header and uri to be more answer-specific. - if ($type == PonderQuestionTransaction::TYPE_ANSWERS) { - $answer = $this->getAnswer(); - $body->addRawSection($answer->getContent()); - $header = pht('ANSWER DETAIL'); - $uri = $answer->getURI(); - } } $body->addLinkSection( diff --git a/src/applications/ponder/feed/PonderTransactionFeedStory.php b/src/applications/ponder/feed/PonderTransactionFeedStory.php deleted file mode 100644 index 354170af51..0000000000 --- a/src/applications/ponder/feed/PonderTransactionFeedStory.php +++ /dev/null @@ -1,14 +0,0 @@ -getValue('answerPHID'); - if ($answer_phid) { - $phids[] = $answer_phid; - } - return $phids; - } -} diff --git a/src/applications/ponder/mail/PonderAnswerMailReceiver.php b/src/applications/ponder/mail/PonderAnswerMailReceiver.php new file mode 100644 index 0000000000..d7269ac861 --- /dev/null +++ b/src/applications/ponder/mail/PonderAnswerMailReceiver.php @@ -0,0 +1,27 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PonderAnswerReplyHandler(); + } + +} diff --git a/src/applications/ponder/mail/PonderAnswerReplyHandler.php b/src/applications/ponder/mail/PonderAnswerReplyHandler.php new file mode 100644 index 0000000000..bfc077a7ad --- /dev/null +++ b/src/applications/ponder/mail/PonderAnswerReplyHandler.php @@ -0,0 +1,16 @@ +getNewAnswerPHID(); - if ($answer_phid) { - return $story->getObject($answer_phid); - } - } - return null; - } - } From bd0bbc713a36ab132ed2e15525ccfb3eb08b4614 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Mon, 17 Aug 2015 11:48:29 -0700 Subject: [PATCH 23/41] Sort Ponder Answers by voteCount Summary: Fixes T9207. I think this is correct? Sorts based on vote count and reverses the order of the array so higher is first. Test Plan: Test vote ordering on a number of sample tasks. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9207 Differential Revision: https://secure.phabricator.com/D13924 --- .../ponder/controller/PonderQuestionViewController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 94830f176d..785ad2e709 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -225,9 +225,10 @@ final class PonderQuestionViewController extends PonderController { $xaction_groups = mgroup($xactions, 'getObjectPHID'); $author_phids = mpull($answers, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); + $answers_sort = array_reverse(msort($answers, 'getVoteCount')); $view = array(); - foreach ($answers as $answer) { + foreach ($answers_sort as $answer) { $xactions = idx($xaction_groups, $answer->getPHID(), array()); $id = $answer->getID(); $handle = $handles[$answer->getAuthorPHID()]; From b21270d498d3278b8d99c0ccca10759d72b3250f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Aug 2015 10:46:41 -0700 Subject: [PATCH 24/41] Probably fix bad loadArtifactLease() call Summary: Ref T9205. This is a likely fix. Test Plan: This isn't straightforward to test in the upstream unless you have custom code on top of it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9205 Differential Revision: https://secure.phabricator.com/D13928 --- .../step/HarbormasterCommandBuildStepImplementation.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php index 2a6b633a72..a99c56d0a3 100644 --- a/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php @@ -39,13 +39,14 @@ final class HarbormasterCommandBuildStepImplementation public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); $artifact = $build_target->loadArtifact($settings['hostartifact']); $impl = $artifact->getArtifactImplementation(); - $lease = $impl->loadArtifactLease(); + $lease = $impl->loadArtifactLease($viewer); $this->platform = $lease->getAttribute('platform'); From 13346ea9043052ed08e75260eeaa8586be74256f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 18 Aug 2015 10:51:17 -0700 Subject: [PATCH 25/41] Unbeta Ponder Summary: Pending other diffs, this actually removes Ponder as a prototype. Fixes T3578 Test Plan: No longer listed as a prototype Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3578 Differential Revision: https://secure.phabricator.com/D13920 --- .../ponder/application/PhabricatorPonderApplication.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index c91ba6803c..38546a8a7d 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -42,10 +42,6 @@ final class PhabricatorPonderApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/Q(?P[1-9]\d*)' From 0a509ea7eceb93f84712aa5ec32ef18c7408492f Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Aug 2015 12:12:51 -0700 Subject: [PATCH 26/41] Fix Owners package specificity ordering Summary: This algorithm isn't quite right with respect to ordering more-specific packages correctly. Test Plan: {F729864} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D13929 --- .../owners/storage/PhabricatorOwnersPackage.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 7d0d8e65a4..20aff7bd88 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -230,16 +230,19 @@ final class PhabricatorOwnersPackage } public static function splitPath($path) { - $result = array('/'); $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $path = trim($path, '/'); $parts = explode('/', $path); + + $result = array(); while (count($parts)) { $result[] = '/'.implode('/', $parts).$trailing_slash; $trailing_slash = '/'; array_pop($parts); } - return $result; + $result[] = '/'; + + return array_reverse($result); } public function attachPaths(array $paths) { From e0faa66772ed1afb8b8ea9964992b6c0ddd2dced Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 18 Aug 2015 13:36:05 -0700 Subject: [PATCH 27/41] Allow Owners Packages to be archived Summary: Fixes T8428. Adds status to packages, allows setting and application search. I presume though these need checked elsewhere? Test Plan: New package, edit package, archive package, run search queries. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8428 Differential Revision: https://secure.phabricator.com/D13925 --- .../autopatches/20150815.owners.status.1.sql | 2 ++ .../autopatches/20150815.owners.status.2.sql | 2 ++ .../controller/DifferentialController.php | 2 ++ .../controller/DiffusionBrowseController.php | 1 + .../controller/DiffusionCommitController.php | 2 ++ .../PhabricatorOwnersDetailController.php | 11 +++++++++ .../PhabricatorOwnersEditController.php | 23 +++++++++++++++++-- ...bricatorOwnersPackageTransactionEditor.php | 8 +++++++ .../query/PhabricatorOwnersPackageQuery.php | 13 +++++++++++ .../PhabricatorOwnersPackageSearchEngine.php | 21 +++++++++++++++++ .../storage/PhabricatorOwnersPackage.php | 17 ++++++++++++++ .../PhabricatorOwnersPackageTransaction.php | 11 +++++++++ 12 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 resources/sql/autopatches/20150815.owners.status.1.sql create mode 100644 resources/sql/autopatches/20150815.owners.status.2.sql diff --git a/resources/sql/autopatches/20150815.owners.status.1.sql b/resources/sql/autopatches/20150815.owners.status.1.sql new file mode 100644 index 0000000000..591e9efeef --- /dev/null +++ b/resources/sql/autopatches/20150815.owners.status.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20150815.owners.status.2.sql b/resources/sql/autopatches/20150815.owners.status.2.sql new file mode 100644 index 0000000000..8f8c05af06 --- /dev/null +++ b/resources/sql/autopatches/20150815.owners.status.2.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_owners.owners_package + SET status = 'active' WHERE status = ''; diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index eed059e265..31cb51eaab 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -47,6 +47,7 @@ abstract class DifferentialController extends PhabricatorController { if ($viewer->getPHID()) { $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withAuthorityPHIDs(array($viewer->getPHID())) ->execute(); $toc_view->setAuthorityPackages($packages); @@ -58,6 +59,7 @@ abstract class DifferentialController extends PhabricatorController { $control_query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withControl($repository_phid, $paths); $control_query->execute(); } diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 7450c3921a..e8bd46b735 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -167,6 +167,7 @@ abstract class DiffusionBrowseController extends DiffusionController { if (PhabricatorApplication::isClassInstalled($owners)) { $package_query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withControl( $repository->getPHID(), array( diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index b91a7dc804..627150b682 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1089,6 +1089,7 @@ final class DiffusionCommitController extends DiffusionController { if ($viewer->getPHID()) { $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withAuthorityPHIDs(array($viewer->getPHID())) ->execute(); $toc_view->setAuthorityPackages($packages); @@ -1099,6 +1100,7 @@ final class DiffusionCommitController extends DiffusionController { $control_query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) + ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) ->withControl($repository_phid, mpull($changesets, 'getFilename')); $control_query->execute(); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 008cb715e5..19f305dc09 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -41,9 +41,20 @@ final class PhabricatorOwnersDetailController $properties = $this->buildPackagePropertyView($package); $properties->setActionList($actions); + if ($package->isArchived()) { + $header_icon = 'fa-ban'; + $header_name = pht('Archived'); + $header_color = 'dark'; + } else { + $header_icon = 'fa-check'; + $header_name = pht('Active'); + $header_color = 'bluegrey'; + } + $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($package->getName()) + ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($package); $panel = id(new PHUIObjectBoxView()) diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index f601cd3115..af5ae29be8 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -34,6 +34,7 @@ final class PhabricatorOwnersEditController $v_owners = mpull($package->getOwners(), 'getUserPHID'); $v_auditing = $package->getAuditingEnabled(); $v_description = $package->getDescription(); + $v_status = $package->getStatus(); $errors = array(); @@ -44,11 +45,13 @@ final class PhabricatorOwnersEditController $v_owners = $request->getArr('owners'); $v_auditing = ($request->getStr('auditing') == 'enabled'); $v_description = $request->getStr('description'); + $v_status = $request->getStr('status'); $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; + $type_status = PhabricatorOwnersPackageTransaction::TYPE_STATUS; $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_name) @@ -66,6 +69,12 @@ final class PhabricatorOwnersEditController ->setTransactionType($type_description) ->setNewValue($v_description); + if (!$is_new) { + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_status) + ->setNewValue($v_status); + } + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) @@ -115,8 +124,18 @@ final class PhabricatorOwnersEditController ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setLabel(pht('Owners')) ->setName('owners') - ->setValue($v_owners)) - ->appendChild( + ->setValue($v_owners)); + + if (!$is_new) { + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Status')) + ->setName('status') + ->setValue($v_status) + ->setOptions($package->getStatusNameMap())); + } + + $form->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') ->setLabel(pht('Auditing')) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 79cce9e9af..8feeeb1408 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -19,6 +19,7 @@ final class PhabricatorOwnersPackageTransactionEditor $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; $types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS; + $types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS; return $types; } @@ -42,6 +43,8 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_PATHS: $paths = $object->getPaths(); return mpull($paths, 'getRef'); + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: + return $object->getStatus(); } } @@ -53,6 +56,7 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_NAME: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: return $xaction->getNewValue(); case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return (int)$xaction->getNewValue(); @@ -99,6 +103,9 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return; + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: + $object->setStatus($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -112,6 +119,7 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_NAME: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + case PhabricatorOwnersPackageTransaction::TYPE_STATUS: return; case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: $old = $xaction->getOldValue(); diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 74f77f79b7..c049e7406e 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -10,6 +10,7 @@ final class PhabricatorOwnersPackageQuery private $repositoryPHIDs; private $paths; private $namePrefix; + private $statuses; private $controlMap = array(); private $controlResults; @@ -57,6 +58,11 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function withControl($repository_phid, array $paths) { if (empty($this->controlMap[$repository_phid])) { $this->controlMap[$repository_phid] = array(); @@ -200,6 +206,13 @@ final class PhabricatorOwnersPackageQuery $this->getFragmentsForPaths($this->paths)); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'p.status IN (%Ls)', + $this->statuses); + } + if (strlen($this->namePrefix)) { // NOTE: This is a hacky mess, but this column is currently case // sensitive and unique. diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php index a26a9b3c56..d3131bba47 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -31,6 +31,12 @@ final class PhabricatorOwnersPackageSearchEngine ->setLabel(pht('Paths')) ->setKey('paths') ->setAliases(array('path')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Status')) + ->setOptions( + id(new PhabricatorOwnersPackage()) + ->getStatusNameMap()), ); } @@ -49,6 +55,10 @@ final class PhabricatorOwnersPackageSearchEngine $query->withPaths($map['paths']); } + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + return $query; } @@ -64,6 +74,7 @@ final class PhabricatorOwnersPackageSearchEngine } $names += array( + 'active' => pht('Active Packages'), 'all' => pht('All Packages'), ); @@ -77,6 +88,12 @@ final class PhabricatorOwnersPackageSearchEngine switch ($query_key) { case 'all': return $query; + case 'active': + return $query->setParameter( + 'statuses', + array( + PhabricatorOwnersPackage::STATUS_ACTIVE, + )); case 'authority': return $query->setParameter( 'authorityPHIDs', @@ -105,6 +122,10 @@ final class PhabricatorOwnersPackageSearchEngine ->setHeader($package->getName()) ->setHref('/owners/package/'.$id.'/'); + if ($package->isArchived()) { + $item->setDisabled(true); + } + $list->addItem($item); } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 20aff7bd88..d9a24b179a 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -12,14 +12,19 @@ final class PhabricatorOwnersPackage protected $description; protected $primaryOwnerPHID; protected $mailKey; + protected $status; private $paths = self::ATTACHABLE; private $owners = self::ATTACHABLE; + const STATUS_ACTIVE = 'active'; + const STATUS_ARCHIVED = 'archived'; + public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) ->setAuditingEnabled(0) ->attachPaths(array()) + ->setStatus(self::STATUS_ACTIVE) ->attachOwners(array()); } @@ -41,6 +46,13 @@ final class PhabricatorOwnersPackage return null; } + public static function getStatusNameMap() { + return array( + self::STATUS_ACTIVE => pht('Active'), + self::STATUS_ARCHIVED => pht('Archived'), + ); + } + protected function getConfiguration() { return array( // This information is better available from the history table. @@ -53,6 +65,7 @@ final class PhabricatorOwnersPackage 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', 'mailKey' => 'bytes20', + 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -81,6 +94,10 @@ final class PhabricatorOwnersPackage return parent::save(); } + public function isArchived() { + return ($this->getStatus() == self::STATUS_ARCHIVED); + } + public function setName($name) { $this->name = $name; if (!$this->getID()) { diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index bf676f941b..edb4f5db8f 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -9,6 +9,7 @@ final class PhabricatorOwnersPackageTransaction const TYPE_AUDITING = 'owners.auditing'; const TYPE_DESCRIPTION = 'owners.description'; const TYPE_PATHS = 'owners.paths'; + const TYPE_STATUS = 'owners.status'; public function getApplicationName() { return 'owners'; @@ -115,6 +116,16 @@ final class PhabricatorOwnersPackageTransaction return pht( '%s updated paths for this package.', $this->renderHandleLink($author_phid)); + case self::TYPE_STATUS: + if ($new == PhabricatorOwnersPackage::STATUS_ACTIVE) { + return pht( + '%s activated this package.', + $this->renderHandleLink($author_phid)); + } else if ($new == PhabricatorOwnersPackage::STATUS_ARCHIVED) { + return pht( + '%s archived this package.', + $this->renderHandleLink($author_phid)); + } } return parent::getTitle(); From a1d1cf77a7884a108b393bbf7a2c0e313139ce80 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 18 Aug 2015 17:52:55 -0700 Subject: [PATCH 28/41] Remove call to loadDrydockLease Summary: Fixes T9219... I think. Test Plan: swagging a guess for epriestley Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9219 Differential Revision: https://secure.phabricator.com/D13932 --- .../HarbormasterUploadArtifactBuildStepImplementation.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php index 88651826e7..f8bdb7fdfd 100644 --- a/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php @@ -25,6 +25,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation public function execute( HarbormasterBuild $build, HarbormasterBuildTarget $build_target) { + $viewer = PhabricatorUser::getOmnipotentUser(); $settings = $this->getSettings(); $variables = $build_target->getVariables(); @@ -35,7 +36,8 @@ final class HarbormasterUploadArtifactBuildStepImplementation $variables); $artifact = $build_target->loadArtifact($settings['hostartifact']); - $lease = $artifact->loadDrydockLease(); + $impl = $artifact->getArtifactImplementation(); + $lease = $impl->loadArtifactLease($viewer); $interface = $lease->getInterface('filesystem'); @@ -44,7 +46,7 @@ final class HarbormasterUploadArtifactBuildStepImplementation // Insert the artifact record. $artifact = $build_target->createArtifact( - PhabricatorUser::getOmnipotentUser(), + $viewer, $settings['name'], HarbormasterFileArtifact::ARTIFACTCONST, array( From c87d4630934e4d024d37166ede0d601f4431fa89 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 18 Aug 2015 19:55:22 -0700 Subject: [PATCH 29/41] Remove Calendar from Conpherence Summary: Ref T8834, cleanly removes "Calendar" widget from Conpherence. RIP. :( Test Plan: Bounce around Conpherence, no Calendar. grep for "calendar" in apps folder, css, js Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8834 Differential Revision: https://secure.phabricator.com/D13934 --- resources/celerity/map.php | 4 +- .../ConpherenceWidgetConfigConstants.php | 11 - .../controller/ConpherenceController.php | 8 - .../ConpherenceWidgetController.php | 211 ------------------ .../application/conpherence/widget-pane.css | 154 ------------- 5 files changed, 2 insertions(+), 386 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index af179cf770..6b7b0e4194 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -51,7 +51,7 @@ return array( 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', - 'rsrc/css/application/conpherence/widget-pane.css' => '419fd50c', + 'rsrc/css/application/conpherence/widget-pane.css' => 'b0793769', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => 'e7544472', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', @@ -514,7 +514,7 @@ return array( 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', - 'conpherence-widget-pane-css' => '419fd50c', + 'conpherence-widget-pane-css' => 'b0793769', 'differential-changeset-view-css' => 'b6b0d1bb', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'd4c87bf4', diff --git a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php index e7983a7504..9de4420e75 100644 --- a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php +++ b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php @@ -36,17 +36,6 @@ final class ConpherenceWidgetConfigConstants extends ConpherenceConstants { 'deviceOnly' => false, 'hasCreate' => false, ), - 'widgets-calendar' => array( - 'name' => pht('Calendar'), - 'icon' => 'fa-calendar', - 'deviceOnly' => false, - 'hasCreate' => true, - 'createData' => array( - 'refreshFromResponse' => false, - 'action' => ConpherenceUpdateActions::ADD_STATUS, - 'customHref' => '/calendar/event/create/', - ), - ), 'widgets-settings' => array( 'name' => pht('Notifications'), 'icon' => 'fa-wrench', diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 3317b0c092..8faae68546 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -27,14 +27,6 @@ abstract class ConpherenceController extends PhabricatorController { ->addSigil('conpherence-widget-adder') ->setMetadata(array('widget' => 'widgets-people'))); - $nav->addMenuItem( - id(new PHUIListItemView()) - ->setName(pht('New Calendar Item')) - ->setType(PHUIListItemView::TYPE_LINK) - ->setHref('/calendar/event/create/') - ->addSigil('conpherence-widget-adder') - ->setMetadata(array('widget' => 'widgets-calendar'))); - return $nav; } diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index 852ceb9e74..34fa20dee6 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -44,10 +44,6 @@ final class ConpherenceWidgetController extends ConpherenceController { case 'widgets-files': $content = $this->renderFileWidgetPaneContent(); break; - case 'widgets-calendar': - $widget = $this->renderCalendarWidgetPaneContent(); - $content = phutil_implode_html('', $widget); - break; case 'widgets-settings': $content = $this->renderSettingsWidgetPaneContent(); break; @@ -103,14 +99,6 @@ final class ConpherenceWidgetController extends ConpherenceController { 'style' => 'display: none;', ), $this->renderFileWidgetPaneContent()); - $widgets[] = phutil_tag( - 'div', - array( - 'class' => 'widgets-body', - 'id' => 'widgets-calendar', - 'style' => 'display: none;', - ), - $this->renderCalendarWidgetPaneContent()); $widgets[] = phutil_tag( 'div', array( @@ -227,205 +215,6 @@ final class ConpherenceWidgetController extends ConpherenceController { $layout); } - private function renderCalendarWidgetPaneContent() { - $user = $this->getRequest()->getUser(); - - $conpherence = $this->getConpherence(); - $participants = $conpherence->getParticipants(); - $widget_data = $conpherence->getWidgetData(); - - // TODO: This panel is built around an outdated notion of events and isn't - // invitee-aware. - - $statuses = $widget_data['events']; - $handles = $conpherence->getHandles(); - $content = array(); - $layout = id(new AphrontMultiColumnView()) - ->setFluidLayout(true); - $timestamps = CalendarTimeUtil::getCalendarWidgetTimestamps($user); - $today = $timestamps['today']; - $epoch_stamps = $timestamps['epoch_stamps']; - $one_day = 24 * 60 * 60; - $is_today = false; - $calendar_columns = 0; - $list_days = 0; - foreach ($epoch_stamps as $day) { - // build a header for the new day - if ($day->format('Ymd') == $today->format('Ymd')) { - $active_class = 'today'; - $is_today = true; - } else { - $active_class = ''; - $is_today = false; - } - - $should_draw_list = $list_days < 7; - $list_days++; - - if ($should_draw_list) { - $content[] = phutil_tag( - 'div', - array( - 'class' => 'day-header '.$active_class, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'day-name', - ), - $day->format('l')), - phutil_tag( - 'div', - array( - 'class' => 'day-date', - ), - $day->format('m/d/y')), - )); - } - - $week_day_number = $day->format('w'); - - $epoch_start = $day->format('U'); - $next_day = clone $day; - $next_day->modify('+1 day'); - $epoch_end = $next_day->format('U'); - - $first_status_of_the_day = true; - $statuses_of_the_day = array(); - // keep looking through statuses where we last left off - foreach ($statuses as $status) { - if ($status->getDateFrom() >= $epoch_end) { - // This list is sorted, so we can stop looking. - break; - } - - if ($status->getDateFrom() < $epoch_end && - $status->getDateTo() > $epoch_start) { - $statuses_of_the_day[$status->getUserPHID()] = $status; - if ($should_draw_list) { - $top_border = ''; - if (!$first_status_of_the_day) { - $top_border = ' top-border'; - } - $timespan = $status->getDateTo() - $status->getDateFrom(); - if ($timespan > $one_day) { - $time_str = 'm/d'; - } else { - $time_str = 'h:i A'; - } - $epoch_range = - phabricator_format_local_time( - $status->getDateFrom(), - $user, - $time_str). - ' - '. - phabricator_format_local_time( - $status->getDateTo(), - $user, - $time_str); - - if (isset($handles[$status->getUserPHID()])) { - $secondary_info = pht( - '%s, %s', - $handles[$status->getUserPHID()]->getName(), - $epoch_range); - } else { - $secondary_info = $epoch_range; - } - - $content[] = phutil_tag( - 'div', - array( - 'class' => 'user-status '.$top_border, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'icon', - ), - ''), - phutil_tag( - 'div', - array( - 'class' => 'description', - ), - array( - $status->getName(), - phutil_tag( - 'div', - array( - 'class' => 'participant', - ), - $secondary_info), - )), - )); - } - $first_status_of_the_day = false; - } - } - - // we didn't get a status on this day so add a spacer - if ($first_status_of_the_day && $should_draw_list) { - $content[] = phutil_tag( - 'div', - array('class' => 'no-events pm'), - pht('No Events Scheduled.')); - } - if ($is_today || ($calendar_columns && $calendar_columns < 3)) { - $active_class = ''; - if ($is_today) { - $active_class = '-active'; - } - $inner_layout = array(); - foreach ($participants as $phid => $participant) { - $status = idx($statuses_of_the_day, $phid, false); - if ($status) { - $inner_layout[] = phutil_tag( - 'div', - array(), - ''); - } else { - $inner_layout[] = phutil_tag( - 'div', - array( - 'class' => 'present', - ), - ''); - } - } - $layout->addColumn( - phutil_tag( - 'div', - array( - 'class' => 'day-column'.$active_class, - ), - array( - phutil_tag( - 'div', - array( - 'class' => 'day-name', - ), - $day->format('D')), - phutil_tag( - 'div', - array( - 'class' => 'day-number', - ), - $day->format('j')), - $inner_layout, - ))); - $calendar_columns++; - } - } - - return array( - $layout, - $content, - ); - } - private function getWidgetURI() { $conpherence = $this->getConpherence(); return $this->getApplicationURI('update/'.$conpherence->getID().'/'); diff --git a/webroot/rsrc/css/application/conpherence/widget-pane.css b/webroot/rsrc/css/application/conpherence/widget-pane.css index 0038ead357..f4f3449dc3 100644 --- a/webroot/rsrc/css/application/conpherence/widget-pane.css +++ b/webroot/rsrc/css/application/conpherence/widget-pane.css @@ -158,160 +158,6 @@ margin: 8px 0px 0px 10%; } -/* calendar widget */ - -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view { - width: 240px; -} -.device-phone .conpherence-widget-pane #widgets-calendar -.aphront-multi-column-view { - display: none; -} -.device-tablet .conpherence-widget-pane #widgets-calendar -.aphront-multi-column-view { - width: 100%; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column { - background: white; - border-right: 1px solid {$thinblueborder}; - text-align: center; -} -.device-phone .conpherence-widget-pane #widgets-calendar -.aphront-multi-column-view .aphront-multi-column-column { - border-right: 0; -} - -.device-phone .conpherence-widget-pane #widgets-calendar -.aphront-multi-column-fluid .aphront-multi-column-5-up -.aphront-multi-column-column-outer { - width: 20%; - margin-bottom: 0px; - float: left; - clear: none; -} - -.conpherence-widget-pane .no-events { - color: {$lightgreytext}; -} - -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column-last { - border-right: 0; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .day-column, -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .day-column-active { - color: #bfbfbf; - background-color: white; - font-weight: bold; - padding: 0px 0px 10px 0px; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .day-column-active { - color: black; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .present , -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .sporadic , -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .away { - height: 10px; - margin: 5px 0px 5px 0px; - width: 100%; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .present { - background-color: white; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .sporadic { - background-color: rgb(222, 226, 232); -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view -.aphront-multi-column-column .away { - background-color: rgb(102, 204, 255); -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view - .day-name { - padding: 5px 0px 0px 0px; - font-size: {$smallerfontsize}; -} -.conpherence-widget-pane #widgets-calendar .aphront-multi-column-view - .day-number { - font-size: {$biggestfontsize}; - padding: 0 0 5px 0; -} - -.conpherence-widget-pane #widgets-calendar .day-header { - overflow: hidden; - border-top: 1px solid {$thinblueborder}; - padding: 8px 8px 0px 8px; -} - -.conpherence-widget-pane #widgets-calendar .day-header.today .day-name, -.conpherence-widget-pane #widgets-calendar .day-header.today .day-date { - color: {$sky}; -} - -.conpherence-widget-pane #widgets-calendar .day-header .day-name { - float: left; - color: {$bluetext}; - font-weight: bold; - text-transform: uppercase; - font-size: {$smallestfontsize}; -} - -.conpherence-widget-pane #widgets-calendar .day-header .day-date { - float: right; - color: {$lightbluetext}; - font-size: {$smallestfontsize}; -} - -.conpherence-widget-pane #widgets-calendar .top-border { - border-top: 1px solid {$thinblueborder}; -} - -.conpherence-widget-pane #widgets-calendar .user-status { - padding: 10px 0px 10px 0px; - margin: 0px 0px 0px 10px; -} - -.conpherence-widget-pane #widgets-calendar .user-status .icon { - border-radius: 8px; - height: 8px; - width: 8px; - margin-top: 4px; - float: left; -} - -.conpherence-widget-pane #widgets-calendar .sporadic .icon { - background-color: {$orange}; -} - -.conpherence-widget-pane #widgets-calendar .away .icon { - background-color: {$red}; -} - -.conpherence-widget-pane #widgets-calendar .user-status .description { - width: 195px; - text-overflow: ellipsis; - margin: 0 0 0 16px; -} - -.conpherence-widget-pane #widgets-calendar .user-status .participant { - font-size: {$smallestfontsize}; - color: {$lightgreytext}; - padding-top: 2px; -} - -.device .conpherence-widget-pane #widgets-calendar .user-status .description, -.device .conpherence-widget-pane #widgets-calendar .user-status .participant { - /* we keep these short so no need to change the width */ -} - .conpherence-widget-pane .widget-icon { display: block; height: 14px; From 9790f93a835e58dcb99993c35811946cb46b0ba3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 20 Aug 2015 04:46:17 -0700 Subject: [PATCH 30/41] Show all packages with a claim on a file in the table of contents view Summary: Ref T9218. See discussion there for rationale; I think this is the right behavior to pursue. The screenshot below is pretty ugly. I think it's a lot worse than most real-world cases will be, since you have to sort of opt-in to having crazy levels of overlapping packages, and it's perfectly normal/reasonable for files owned by one package. Owners is powerful enough to let you specify sub-packages with exclusive ownership. That said, this may be more typical than I hope. I don't think we can reduce the complexity here much for free, but it would might be reasonable to add some view options (e.g.: group by package?, show only packages I own?, show packages as icons with a tooltip?) if it's an issue. Test Plan: {F734956} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9218 Differential Revision: https://secure.phabricator.com/D13940 --- .../controller/DifferentialController.php | 6 ++--- .../controller/DiffusionCommitController.php | 6 ++--- .../query/PhabricatorOwnersPackageQuery.php | 20 +++------------ .../view/PHUIDiffTableOfContentsItemView.php | 25 +++++++++++-------- .../view/PHUIDiffTableOfContentsListView.php | 16 ++++++------ 5 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 31cb51eaab..4f38015ece 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -80,12 +80,10 @@ abstract class DifferentialController extends PhabricatorController { ->setCoverageID($coverage_id); if ($have_owners) { - $package = $control_query->getControllingPackageForPath( + $packages = $control_query->getControllingPackagesForPath( $repository_phid, $changeset->getFilename()); - if ($package) { - $item->setPackage($package); - } + $item->setPackages($packages); } $toc_view->addItem($item); diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index 627150b682..6117bcdaca 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -1123,12 +1123,10 @@ final class DiffusionCommitController extends DiffusionController { )); if ($have_owners) { - $package = $control_query->getControllingPackageForPath( + $packages = $control_query->getControllingPackagesForPath( $repository_phid, $changeset->getFilename()); - if ($package) { - $item->setPackage($package); - } + $item->setPackages($packages); } $toc_view->addItem($item); diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index c049e7406e..f57ddf5a58 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -342,28 +342,14 @@ final class PhabricatorOwnersPackageQuery /* -( Path Control )------------------------------------------------------- */ - /** - * Get the package which controls a path, if one exists. - * - * @return PhabricatorOwnersPackage|null Package, if one exists. - */ - public function getControllingPackageForPath($repository_phid, $path) { - $packages = $this->getControllingPackagesForPath($repository_phid, $path); - - if (!$packages) { - return null; - } - - return head($packages); - } - - /** * Get a list of all packages which control a path or its parent directories, * ordered from weakest to strongest. * * The first package has the most specific claim on the path; the last - * package has the most general claim. + * package has the most general claim. Multiple packages may have claims of + * equal strength, so this ordering is primarily one of usability and + * convenience. * * @return list List of controlling packages. */ diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php index 13ab61de64..8f6e4e48d7 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php @@ -8,7 +8,7 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { private $coverage; private $coverageID; private $context; - private $package; + private $packages; public function setChangeset(DifferentialChangeset $changeset) { $this->changeset = $changeset; @@ -64,13 +64,14 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { return $this->context; } - public function setPackage(PhabricatorOwnersPackage $package) { - $this->package = $package; + public function setPackages(array $packages) { + assert_instances_of($packages, 'PhabricatorOwnersPackage'); + $this->packages = mpull($packages, null, 'getPHID'); return $this; } - public function getPackage() { - return $this->package; + public function getPackages() { + return $this->packages; } public function render() { @@ -97,7 +98,7 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { $cells[] = $this->renderCoverage(); $cells[] = $this->renderModifiedCoverage(); - $cells[] = $this->renderPackage(); + $cells[] = $this->renderPackages(); return $cells; } @@ -284,14 +285,16 @@ final class PHUIDiffTableOfContentsItemView extends AphrontView { $meta); } - private function renderPackage() { - $package = $this->getPackage(); - - if (!$package) { + private function renderPackages() { + $packages = $this->getPackages(); + if (!$packages) { return null; } - return $this->getUser()->renderHandle($package->getPHID()); + $viewer = $this->getUser(); + $package_phids = mpull($packages, 'getPHID'); + + return $viewer->renderHandleList($package_phids); } private function renderRename($self, $other, $arrow) { diff --git a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php index a7e3270c6e..c24acf7bb0 100644 --- a/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php +++ b/src/infrastructure/diff/view/PHUIDiffTableOfContentsListView.php @@ -43,9 +43,9 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { $have_authority = false; - $package = $item->getPackage(); - if ($package) { - if (isset($authority[$package->getPHID()])) { + $packages = $item->getPackages(); + if ($packages) { + if (array_intersect_key($packages, $authority)) { $have_authority = true; } } @@ -61,7 +61,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { // just hide them. $any_coverage = false; $any_context = false; - $any_package = false; + $any_packages = false; foreach ($items as $item) { if ($item->getContext() !== null) { $any_context = true; @@ -71,8 +71,8 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { $any_coverage = true; } - if ($item->getPackage() !== null) { - $any_package = true; + if ($item->getPackages() !== null) { + $any_packages = true; } } @@ -103,7 +103,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { pht('Path'), pht('Coverage (All)'), pht('Coverage (Touched)'), - pht('Package'), + pht('Packages'), )) ->setColumnClasses( array( @@ -125,7 +125,7 @@ final class PHUIDiffTableOfContentsListView extends AphrontView { true, $any_coverage, $any_coverage, - $any_package, + $any_packages, )) ->setDeviceVisibility( array( From 167eb9a25692d0fa4d02c966621aa1f9c23da639 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Aug 2015 08:24:42 -0700 Subject: [PATCH 31/41] Remove Files Widget from Conpherence Summary: Fixes T8834. Removes everywhere I could find references to Files. Test Plan: Use Conpherence, send a message, attach a file, try durable column, send message, send file. Seems snappy. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T8834 Differential Revision: https://secure.phabricator.com/D13936 --- resources/celerity/map.php | 36 ++++----- src/__phutil_library_map__.php | 2 - .../ConpherenceWidgetConfigConstants.php | 6 -- .../ConpherenceUpdateController.php | 7 -- .../ConpherenceWidgetController.php | 18 ----- .../view/ConpherenceFileWidgetView.php | 76 ------------------- .../application/conpherence/widget-pane.css | 63 --------------- .../application/conpherence/behavior-menu.js | 12 --- 8 files changed, 18 insertions(+), 202 deletions(-) delete mode 100644 src/applications/conpherence/view/ConpherenceFileWidgetView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6b7b0e4194..115c6ff699 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -51,7 +51,7 @@ return array( 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', - 'rsrc/css/application/conpherence/widget-pane.css' => 'b0793769', + 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => 'e7544472', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', @@ -346,7 +346,7 @@ return array( 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091', - 'rsrc/js/application/conpherence/behavior-menu.js' => 'd3782c93', + 'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'a8458711', @@ -514,7 +514,7 @@ return array( 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', - 'conpherence-widget-pane-css' => 'b0793769', + 'conpherence-widget-pane-css' => '775eaaba', 'differential-changeset-view-css' => 'b6b0d1bb', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => 'd4c87bf4', @@ -552,7 +552,7 @@ return array( 'javelin-behavior-choose-control' => '6153c708', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', - 'javelin-behavior-conpherence-menu' => 'd3782c93', + 'javelin-behavior-conpherence-menu' => '1d45c74d', 'javelin-behavior-conpherence-pontificate' => '21ba5861', 'javelin-behavior-conpherence-widget-pane' => 'a8458711', 'javelin-behavior-countdown-timer' => 'e4cc26b3', @@ -955,6 +955,20 @@ return array( 'javelin-dom', 'javelin-vector', ), + '1d45c74d' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-behavior-device', + 'javelin-history', + 'javelin-vector', + 'javelin-scrollbar', + 'phabricator-title', + 'phabricator-shaped-request', + 'conpherence-thread-manager', + ), '1def2711' => array( 'javelin-install', 'javelin-dom', @@ -1798,20 +1812,6 @@ return array( 'd254d646' => array( 'javelin-util', ), - 'd3782c93' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'javelin-behavior-device', - 'javelin-history', - 'javelin-vector', - 'javelin-scrollbar', - 'phabricator-title', - 'phabricator-shaped-request', - 'conpherence-thread-manager', - ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3023eceaed..d7f747ca13 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -232,7 +232,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', - 'ConpherenceFileWidgetView' => 'applications/conpherence/view/ConpherenceFileWidgetView.php', 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceHovercardEventListener' => 'applications/conpherence/events/ConpherenceHovercardEventListener.php', @@ -3853,7 +3852,6 @@ phutil_register_library_map(array( 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', - 'ConpherenceFileWidgetView' => 'ConpherenceWidgetView', 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceHovercardEventListener' => 'PhabricatorEventListener', diff --git a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php index 9de4420e75..36d4f7d617 100644 --- a/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php +++ b/src/applications/conpherence/constants/ConpherenceWidgetConfigConstants.php @@ -30,12 +30,6 @@ final class ConpherenceWidgetConfigConstants extends ConpherenceConstants { 'customHref' => null, ), ), - 'widgets-files' => array( - 'name' => pht('Files'), - 'icon' => 'fa-files-o', - 'deviceOnly' => false, - 'hasCreate' => false, - ), 'widgets-settings' => array( 'name' => pht('Notifications'), 'icon' => 'fa-wrench', diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php index 0739c4054c..6012c81437 100644 --- a/src/applications/conpherence/controller/ConpherenceUpdateController.php +++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php @@ -524,13 +524,6 @@ final class ConpherenceUpdateController ->renderSingleThread($conpherence, $policy_objects); $nav_item = hsprintf('%s', $nav_item); break; - case ConpherenceUpdateActions::MESSAGE: - $file_widget = id(new ConpherenceFileWidgetView()) - ->setUser($this->getRequest()->getUser()) - ->setConpherence($conpherence) - ->setUpdateURI($widget_uri); - $file_widget = hsprintf('%s', $file_widget->render()); - break; case ConpherenceUpdateActions::ADD_PERSON: $people_widget = id(new ConpherencePeopleWidgetView()) ->setUser($user) diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index 34fa20dee6..0408896a16 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -41,9 +41,6 @@ final class ConpherenceWidgetController extends ConpherenceController { case 'widgets-people': $content = $this->renderPeopleWidgetPaneContent(); break; - case 'widgets-files': - $content = $this->renderFileWidgetPaneContent(); - break; case 'widgets-settings': $content = $this->renderSettingsWidgetPaneContent(); break; @@ -90,15 +87,6 @@ final class ConpherenceWidgetController extends ConpherenceController { 'sigil' => 'widgets-people', ), $this->renderPeopleWidgetPaneContent()); - $widgets[] = javelin_tag( - 'div', - array( - 'class' => 'widgets-body', - 'id' => 'widgets-files', - 'sigil' => 'widgets-files', - 'style' => 'display: none;', - ), - $this->renderFileWidgetPaneContent()); $widgets[] = phutil_tag( 'div', array( @@ -127,12 +115,6 @@ final class ConpherenceWidgetController extends ConpherenceController { ->setUpdateURI($this->getWidgetURI()); } - private function renderFileWidgetPaneContent() { - return id(new ConpherenceFileWidgetView()) - ->setUser($this->getViewer()) - ->setConpherence($this->getConpherence()) - ->setUpdateURI($this->getWidgetURI()); - } private function renderSettingsWidgetPaneContent() { $viewer = $this->getViewer(); diff --git a/src/applications/conpherence/view/ConpherenceFileWidgetView.php b/src/applications/conpherence/view/ConpherenceFileWidgetView.php deleted file mode 100644 index 3cfb8072e3..0000000000 --- a/src/applications/conpherence/view/ConpherenceFileWidgetView.php +++ /dev/null @@ -1,76 +0,0 @@ -getConpherence(); - $widget_data = $conpherence->getWidgetData(); - $files = $widget_data['files']; - $files_authors = $widget_data['files_authors']; - $files_html = array(); - - foreach ($files as $file) { - $icon_class = $file->getDisplayIconForMimeType(); - $icon_view = phutil_tag( - 'div', - array( - 'class' => 'file-icon phui-font-fa phui-icon-view '.$icon_class, - ), - ''); - $file_view = id(new PhabricatorFileLinkView()) - ->setFilePHID($file->getPHID()) - ->setFileName(id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(28) - ->truncateString($file->getName())) - ->setFileViewable($file->isViewableImage()) - ->setFileViewURI($file->getBestURI()) - ->setCustomClass('file-title'); - - $who_done_it_text = ''; - // system generated files don't have authors - if ($file->getAuthorPHID()) { - $who_done_it_text = pht( - 'By %s ', - $files_authors[$file->getPHID()]->renderLink()); - } - $date_text = phabricator_relative_date( - $file->getDateCreated(), - $this->getUser()); - - $who_done_it = phutil_tag( - 'div', - array( - 'class' => 'file-uploaded-by', - ), - pht('%s%s.', $who_done_it_text, $date_text)); - - $files_html[] = phutil_tag( - 'div', - array( - 'class' => 'file-entry', - ), - array( - $icon_view, - $file_view, - $who_done_it, - )); - } - - if (empty($files)) { - $files_html[] = javelin_tag( - 'div', - array( - 'class' => 'no-files', - 'sigil' => 'no-files', - ), - pht('No files.')); - } - - return phutil_tag( - 'div', - array('class' => 'file-list'), - $files_html); - - } - -} diff --git a/webroot/rsrc/css/application/conpherence/widget-pane.css b/webroot/rsrc/css/application/conpherence/widget-pane.css index f4f3449dc3..6b5401b2a9 100644 --- a/webroot/rsrc/css/application/conpherence/widget-pane.css +++ b/webroot/rsrc/css/application/conpherence/widget-pane.css @@ -95,69 +95,6 @@ top: 142px; } -/* files widget */ - -.conpherence-widget-pane #widgets-files .no-files { - width: 200px; - padding: 20px; - text-align: center; - color: {$greytext}; -} - -.device .conpherence-widget-pane #widgets-files .no-files { - width: 60px; - margin: 0 auto 0 auto; -} - -.conpherence-widget-pane #widgets-files .file-entry { - padding: 8px 0; - margin: 0 4px 0 8px; - border-bottom: 1px solid {$thinblueborder}; -} - -.conpherence-widget-pane #widgets-files .file-entry a { - color: {$darkbluetext}; - font-weight: bold; -} - -.conpherence-widget-pane #widgets-files .file-icon { - width: 32px; - height: 32px; - float: left; - font-size: 24px; - color: {$blue}; - margin: 2px 0 2px 4px; -} - -.conpherence-widget-pane #widgets-files .file-title { - display: block; - position: relative; - top: -4px; - left: 0; - overflow-x: hidden; - width: 180px; - font-weight: bold; - text-overflow: ellipsis; - white-space: nowrap; -} -.conpherence-widget-pane #widgets-files .file-uploaded-by { - color: {$lightgreytext}; - position: relative; - top: 0; - left: 0; - width: 180px; - font-size: {$smallestfontsize}; -} - -.device .conpherence-widget-pane #widgets-files .file-title, -.device .conpherence-widget-pane #widgets-files .file-uploaded-by { - width: 82%; -} -.device .conpherence-widget-pane #widgets-files .divider { - width: 80%; - margin: 8px 0px 0px 10%; -} - .conpherence-widget-pane .widget-icon { display: block; height: 14px; diff --git a/webroot/rsrc/js/application/conpherence/behavior-menu.js b/webroot/rsrc/js/application/conpherence/behavior-menu.js index 4d18c44b51..5b93815716 100644 --- a/webroot/rsrc/js/application/conpherence/behavior-menu.js +++ b/webroot/rsrc/js/application/conpherence/behavior-menu.js @@ -66,18 +66,6 @@ JX.behavior('conpherence-menu', function(config) { var form_root = JX.DOM.find(root, 'div', 'conpherence-form'); var textarea = JX.DOM.find(form_root, 'textarea'); if (!non_update) { - var fileWidget = null; - try { - fileWidget = JX.DOM.find(root, 'div', 'widgets-files'); - } catch (ex) { - // Ignore; maybe no files widget - } - if (fileWidget) { - JX.DOM.setContent( - fileWidget, - JX.$H(r.file_widget)); - } - _scrollMessageWindow(); textarea.value = ''; } From b35f538bc4cf46d18e4eac83e00020c71b05d58b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Aug 2015 08:24:57 -0700 Subject: [PATCH 32/41] Use dateModified for Answer headers in Ponder Summary: Fixes T9226, shows the update time, not the creation time. Test Plan: Update an answer, see new date. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9226 Differential Revision: https://secure.phabricator.com/D13945 --- src/applications/ponder/view/PonderAnswerView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/ponder/view/PonderAnswerView.php b/src/applications/ponder/view/PonderAnswerView.php index 5f2b1f5a70..e97471945f 100644 --- a/src/applications/ponder/view/PonderAnswerView.php +++ b/src/applications/ponder/view/PonderAnswerView.php @@ -79,7 +79,7 @@ final class PonderAnswerView extends AphrontTagView { $header = id(new PHUIHeaderView()) ->setUser($viewer) - ->setEpoch($answer->getDateCreated()) + ->setEpoch($answer->getDateModified()) ->setHeader($handle->getName()) ->addActionLink($action_button) ->setImage($handle->getImageURI()) From 233b2d5a6ea2a8111f940fc56ac5b927638d7ab6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Aug 2015 11:00:30 -0700 Subject: [PATCH 33/41] Reduce tt padding by 1px Summary: If we have lots ot `text in monospace` everywhere, the padding bleeds. Reduce 1px. Test Plan: eyeball it Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13947 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/core/remarkup.css | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 115c6ff699..6546a005bf 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '12dcd407', + 'core.pkg.css' => 'aced76a5', 'core.pkg.js' => 'a590b451', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -104,7 +104,7 @@ return array( 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', 'rsrc/css/core/core.css' => 'a76cefc9', - 'rsrc/css/core/remarkup.css' => '66f5facd', + 'rsrc/css/core/remarkup.css' => '73fc4395', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => '5a337049', @@ -737,7 +737,7 @@ return array( 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '6920d200', - 'phabricator-remarkup-css' => '66f5facd', + 'phabricator-remarkup-css' => '73fc4395', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index 5821b3f482..cba6ba0e98 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -54,7 +54,7 @@ .phabricator-remarkup tt.remarkup-monospaced { color: #000; background: rgba(71,87,120,0.1); - padding: 2px 4px; + padding: 1px 4px; border-radius: 3px; white-space: pre-wrap; } From 5317f77f30eb588082e401c506f304fc5430124e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Aug 2015 11:23:30 -0700 Subject: [PATCH 34/41] Fix some quirkiness with Answer comments in Ponder Summary: There is still some general buginess with answer comments, trying to work them out. This replaces timeline rendering into one offs (less performant) but resolves many bugs. Or if there is a more performant way, let me know? Also when leaving an answer comment, you currently get redirected back to the page, but both the comment form is still populated and you dont see your answer without a reload. I feel like I'm missing some magical parameter to pass, so just redirecting back to the question itself. Test Plan: Leave lots of answer comments. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D13946 --- resources/celerity/map.php | 4 +-- .../PonderAnswerCommentController.php | 8 ++--- .../PonderQuestionViewController.php | 30 ++++++------------- .../ponder/view/PonderAnswerView.php | 14 +++------ .../css/application/ponder/ponder-view.css | 4 +++ 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 6546a005bf..446ce87a89 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -93,7 +93,7 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/ponder-view.css' => '870153f4', + 'rsrc/css/application/ponder/ponder-view.css' => 'bef48f86', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', @@ -811,7 +811,7 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-view-css' => '870153f4', + 'ponder-view-css' => 'bef48f86', 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', diff --git a/src/applications/ponder/controller/PonderAnswerCommentController.php b/src/applications/ponder/controller/PonderAnswerCommentController.php index 5531177586..4b60fb939a 100644 --- a/src/applications/ponder/controller/PonderAnswerCommentController.php +++ b/src/applications/ponder/controller/PonderAnswerCommentController.php @@ -19,11 +19,12 @@ final class PonderAnswerCommentController extends PonderController { } $is_preview = $request->isPreviewRequest(); -// $draft = PhabricatorDraft::buildFromRequest($request); $qid = $answer->getQuestion()->getID(); $aid = $answer->getID(); - $view_uri = "/Q{$qid}#A{$aid}"; + + // TODO, this behaves badly when redirecting to the answer + $view_uri = "/Q{$qid}"; $xactions = array(); $xactions[] = id(new PonderAnswerTransaction()) @@ -46,9 +47,6 @@ final class PonderAnswerCommentController extends PonderController { ->setException($ex); } -// if ($draft) { -// $draft->replaceOrDelete(); -// } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 785ad2e709..555ef51711 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -205,40 +205,28 @@ final class PonderQuestionViewController extends PonderController { private function buildAnswers(array $answers) { $viewer = $this->getViewer(); - $xactions = id(new PonderAnswerTransactionQuery()) - ->setViewer($viewer) - ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT)) - ->withObjectPHIDs(mpull($answers, 'getPHID')) - ->execute(); - - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($viewer); - foreach ($xactions as $xaction) { - if ($xaction->getComment()) { - $engine->addObject( - $xaction->getComment(), - PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); - } - } - $engine->process(); - - $xaction_groups = mgroup($xactions, 'getObjectPHID'); $author_phids = mpull($answers, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); $answers_sort = array_reverse(msort($answers, 'getVoteCount')); $view = array(); foreach ($answers_sort as $answer) { - $xactions = idx($xaction_groups, $answer->getPHID(), array()); $id = $answer->getID(); $handle = $handles[$answer->getAuthorPHID()]; + $timeline = $this->buildTransactionTimeline( + $answer, + id(new PonderAnswerTransactionQuery()) + ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); + $xactions = $timeline->getTransactions(); + + $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) ->setTransactions($xactions) - ->setHandle($handle) - ->setMarkupEngine($engine); + ->setTimeline($timeline) + ->setHandle($handle); } diff --git a/src/applications/ponder/view/PonderAnswerView.php b/src/applications/ponder/view/PonderAnswerView.php index e97471945f..6054e4f538 100644 --- a/src/applications/ponder/view/PonderAnswerView.php +++ b/src/applications/ponder/view/PonderAnswerView.php @@ -4,7 +4,7 @@ final class PonderAnswerView extends AphrontTagView { private $answer; private $transactions; - private $engine; + private $timeline; private $handle; public function setAnswer($answer) { @@ -17,8 +17,8 @@ final class PonderAnswerView extends AphrontTagView { return $this; } - public function setMarkupEngine(PhabricatorMarkupEngine $engine) { - $this->engine = $engine; + public function setTimeline($timeline) { + $this->timeline = $timeline; return $this; } @@ -124,12 +124,6 @@ final class PonderAnswerView extends AphrontTagView { ->appendChild($content) ->appendChild($footer); - $transaction_view = id(new PhabricatorApplicationTransactionView()) - ->setUser($viewer) - ->setObjectPHID($answer->getPHID()) - ->setTransactions($this->transactions) - ->setMarkupEngine($this->engine); - $comment_view = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($answer->getPHID()) @@ -145,7 +139,7 @@ final class PonderAnswerView extends AphrontTagView { 'style' => 'display: none;', ), array( - $transaction_view, + $this->timeline, $comment_view, )); diff --git a/webroot/rsrc/css/application/ponder/ponder-view.css b/webroot/rsrc/css/application/ponder/ponder-view.css index d1b599d529..39d5c2a8f2 100644 --- a/webroot/rsrc/css/application/ponder/ponder-view.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -26,6 +26,10 @@ margin-top: 16px; } +.device-desktop .ponder-answer-view .phui-timeline-view { + margin-left: 32px; +} + .ponder-answer-view .phui-header-subheader { display: inline; margin-left: 12px; From 89336197e38053c7b6706b54c83d976d6530b6db Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 20 Aug 2015 14:49:57 -0700 Subject: [PATCH 35/41] Correct link in Badges email Summary: Fixes T9231, adds a correct link Test Plan: read Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9231 Differential Revision: https://secure.phabricator.com/D13950 --- src/applications/badges/editor/PhabricatorBadgesEditor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/badges/editor/PhabricatorBadgesEditor.php b/src/applications/badges/editor/PhabricatorBadgesEditor.php index a6cd90bb92..d7403601ec 100644 --- a/src/applications/badges/editor/PhabricatorBadgesEditor.php +++ b/src/applications/badges/editor/PhabricatorBadgesEditor.php @@ -201,7 +201,7 @@ final class PhabricatorBadgesEditor $body->addLinkSection( pht('BADGE DETAIL'), - PhabricatorEnv::getProductionURI('/badge/view/'.$object->getID().'/')); + PhabricatorEnv::getProductionURI('/badges/view/'.$object->getID().'/')); return $body; } From 22dc3c1be2e09c9c27b0d0b45391751a12e7e0bf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Aug 2015 06:49:49 -0700 Subject: [PATCH 36/41] Allow public on Ponder Questions, History pages Summary: Ref T9234, these should allow being publicly visible Test Plan: log out, see page Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9234 Differential Revision: https://secure.phabricator.com/D13952 --- .../ponder/controller/PonderAnswerHistoryController.php | 4 ++++ .../ponder/controller/PonderQuestionHistoryController.php | 4 ++++ .../ponder/controller/PonderQuestionViewController.php | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/src/applications/ponder/controller/PonderAnswerHistoryController.php b/src/applications/ponder/controller/PonderAnswerHistoryController.php index b2c42de8c8..136cd8532e 100644 --- a/src/applications/ponder/controller/PonderAnswerHistoryController.php +++ b/src/applications/ponder/controller/PonderAnswerHistoryController.php @@ -2,6 +2,10 @@ final class PonderAnswerHistoryController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); diff --git a/src/applications/ponder/controller/PonderQuestionHistoryController.php b/src/applications/ponder/controller/PonderQuestionHistoryController.php index 91ded8c094..e7801ab4b1 100644 --- a/src/applications/ponder/controller/PonderQuestionHistoryController.php +++ b/src/applications/ponder/controller/PonderQuestionHistoryController.php @@ -2,6 +2,10 @@ final class PonderQuestionHistoryController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 555ef51711..fde0f067a3 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -2,6 +2,10 @@ final class PonderQuestionViewController extends PonderController { + public function shouldAllowPublic() { + return true; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); From ad93af83891d8cf9eabbb4be9d7e6957304fe40d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Aug 2015 06:58:06 -0700 Subject: [PATCH 37/41] Fix Ponder Answer query joins, builtins Summary: Fixes T9234. The joins method was still the old method and the builtin was calling the wrong key. Test Plan: Test authored builtin, custom search Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9234 Differential Revision: https://secure.phabricator.com/D13953 --- src/applications/ponder/query/PonderQuestionQuery.php | 4 ++-- src/applications/ponder/query/PonderQuestionSearchEngine.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/applications/ponder/query/PonderQuestionQuery.php b/src/applications/ponder/query/PonderQuestionQuery.php index dd5db40a48..480f2c3296 100644 --- a/src/applications/ponder/query/PonderQuestionQuery.php +++ b/src/applications/ponder/query/PonderQuestionQuery.php @@ -129,7 +129,7 @@ final class PonderQuestionQuery return $questions; } - private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->answererPHIDs) { @@ -141,7 +141,7 @@ final class PonderQuestionQuery $this->answererPHIDs); } - return implode(' ', $joins); + return $joins; } protected function getPrimaryTableAlias() { diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index b4cb7aef21..6692d8282a 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -96,7 +96,7 @@ final class PonderQuestionSearchEngine array($this->requireViewer()->getPHID())); case 'answered': return $query->setParameter( - 'answererPHIDs', + 'answerers', array($this->requireViewer()->getPHID())); } From ed77b639f0cf287494f39e9a8629cbb8608641bd Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 21 Aug 2015 07:04:17 -0700 Subject: [PATCH 38/41] Fix Ponder Answer email reply handler Summary: Should fix all email reply issues, but no solid means of testing at home (how do you local reply test?) Test Plan: Check for answer mail in /mail/ and see proper headers. Make sure question mail works too. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T3846 Differential Revision: https://secure.phabricator.com/D13951 --- .../ponder/editor/PonderAnswerEditor.php | 13 +++++++------ src/applications/ponder/editor/PonderEditor.php | 17 ----------------- .../ponder/editor/PonderQuestionEditor.php | 10 ++++++++++ 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/applications/ponder/editor/PonderAnswerEditor.php b/src/applications/ponder/editor/PonderAnswerEditor.php index b9e3c42869..fdb5c48437 100644 --- a/src/applications/ponder/editor/PonderAnswerEditor.php +++ b/src/applications/ponder/editor/PonderAnswerEditor.php @@ -86,16 +86,17 @@ final class PonderAnswerEditor extends PonderEditor { } protected function buildReplyHandler(PhabricatorLiskDAO $object) { - $question = $object->getQuestion(); - return id(new PonderQuestionReplyHandler()) - ->setMailReceiver($question); + return id(new PonderAnswerReplyHandler()) + ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { - $question = $object->getQuestion(); - return parent::buildMailTemplate($question); - } + $id = $object->getID(); + return id(new PhabricatorMetaMTAMail()) + ->setSubject("ANSR{$id}") + ->addHeader('Thread-Topic', "ANSR{$id}"); + } protected function buildMailBody( PhabricatorLiskDAO $object, diff --git a/src/applications/ponder/editor/PonderEditor.php b/src/applications/ponder/editor/PonderEditor.php index 89e525ce7c..24c6f2d8d2 100644 --- a/src/applications/ponder/editor/PonderEditor.php +++ b/src/applications/ponder/editor/PonderEditor.php @@ -7,23 +7,6 @@ abstract class PonderEditor return 'PhabricatorPonderApplication'; } - protected function shouldPublishFeedStory( - PhabricatorLiskDAO $object, - array $xactions) { - return true; - } - - protected function buildMailTemplate(PhabricatorLiskDAO $object) { - $id = $object->getID(); - $title = $object->getTitle(); - $original_title = $object->getOriginalTitle(); - - return id(new PhabricatorMetaMTAMail()) - ->setSubject("Q{$id}: {$title}") - ->addHeader('Thread-Topic', "Q{$id}: {$original_title}"); - } - - protected function getMailTo(PhabricatorLiskDAO $object) { return array( $object->getAuthorPHID(), diff --git a/src/applications/ponder/editor/PonderQuestionEditor.php b/src/applications/ponder/editor/PonderQuestionEditor.php index 2fbe629002..c2d9ef231b 100644 --- a/src/applications/ponder/editor/PonderQuestionEditor.php +++ b/src/applications/ponder/editor/PonderQuestionEditor.php @@ -231,6 +231,16 @@ final class PonderQuestionEditor ->setMailReceiver($object); } + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $title = $object->getTitle(); + $original_title = $object->getOriginalTitle(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject("Q{$id}: {$title}") + ->addHeader('Thread-Topic', "Q{$id}: {$original_title}"); + } + protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { From d825aae87dd752c38abbae12fd1a2a702be07758 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 21 Aug 2015 08:04:37 -0700 Subject: [PATCH 39/41] Use pink for "Unbreak Now" priority instead of indigo Summary: Fixes T9237. A while ago, the old (more-pink) indigo got split into "pink" (more pink) and "indigo" (more purple), but we didn't change this color config in Maniphest. This generally made the color more purple, and it's now pretty simliar to the "needs triage" color (violet). Make it "pink" instead. Test Plan: {F742617} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9237 Differential Revision: https://secure.phabricator.com/D13954 --- .../maniphest/config/PhabricatorManiphestConfigOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index c669e2f858..d92f562911 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -25,7 +25,7 @@ final class PhabricatorManiphestConfigOptions 100 => array( 'name' => pht('Unbreak Now!'), 'short' => pht('Unbreak!'), - 'color' => 'indigo', + 'color' => 'pink', 'keywords' => array('unbreak'), ), 90 => array( From 4b1815d6cca25f39b9fcb97946f8a3e83fcbc1e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 21 Aug 2015 14:53:29 -0700 Subject: [PATCH 40/41] Add a "Startup" to DarkConsole Summary: Ref T8588. It looks like something slow is happening //before// we start DarkConsole. Add some crude reporting to try to narrow it down. Test Plan: {F743050} Reviewers: chad Reviewed By: chad Maniphest Tasks: T8588 Differential Revision: https://secure.phabricator.com/D13956 --- src/__phutil_library_map__.php | 2 + .../AphrontApplicationConfiguration.php | 8 ++ .../plugin/DarkConsoleStartupPlugin.php | 84 +++++++++++++++++++ .../xhprof/DarkConsoleXHProfPluginAPI.php | 20 ++++- src/docs/user/field/darkconsole.diviner | 19 +++++ support/PhabricatorStartup.php | 64 +++++++++++++- webroot/index.php | 37 +++++--- 7 files changed, 219 insertions(+), 15 deletions(-) create mode 100644 src/applications/console/plugin/DarkConsoleStartupPlugin.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d7f747ca13..0942ad73d6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -286,6 +286,7 @@ phutil_register_library_map(array( 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', + 'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php', 'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php', 'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', 'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php', @@ -3912,6 +3913,7 @@ phutil_register_library_map(array( 'DarkConsolePlugin' => 'Phobject', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', + 'DarkConsoleStartupPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPluginAPI' => 'Phobject', 'DefaultDatabaseConfigurationProvider' => array( diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php index 0a33f6f77c..7943b8046d 100644 --- a/src/aphront/configuration/AphrontApplicationConfiguration.php +++ b/src/aphront/configuration/AphrontApplicationConfiguration.php @@ -58,6 +58,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { * @phutil-external-symbol class PhabricatorStartup */ public static function runHTTPRequest(AphrontHTTPSink $sink) { + PhabricatorStartup::beginStartupPhase('multimeter'); $multimeter = MultimeterControl::newInstance(); $multimeter->setEventContext(''); $multimeter->setEventViewer(''); @@ -67,6 +68,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { // request object first. $write_guard = new AphrontWriteGuard('id'); + PhabricatorStartup::beginStartupPhase('env.init'); PhabricatorEnv::initializeWebEnvironment(); $multimeter->setSampleRate( @@ -78,6 +80,7 @@ abstract class AphrontApplicationConfiguration extends Phobject { } // This is the earliest we can get away with this, we need env config first. + PhabricatorStartup::beginStartupPhase('log.access'); PhabricatorAccessLog::init(); $access_log = PhabricatorAccessLog::getLog(); PhabricatorStartup::setAccessLog($access_log); @@ -89,6 +92,11 @@ abstract class AphrontApplicationConfiguration extends Phobject { )); DarkConsoleXHProfPluginAPI::hookProfiler(); + + // We just activated the profiler, so we don't need to keep track of + // startup phases anymore: it can take over from here. + PhabricatorStartup::beginStartupPhase('startup.done'); + DarkConsoleErrorLogPluginAPI::registerErrorHandler(); $response = PhabricatorSetupCheck::willProcessRequest(); diff --git a/src/applications/console/plugin/DarkConsoleStartupPlugin.php b/src/applications/console/plugin/DarkConsoleStartupPlugin.php new file mode 100644 index 0000000000..4cc166725f --- /dev/null +++ b/src/applications/console/plugin/DarkConsoleStartupPlugin.php @@ -0,0 +1,84 @@ +getData(); + + // Compute the time offset and duration of each startup phase. + $prev_key = null; + $init = null; + $phases = array(); + foreach ($data as $key => $value) { + if ($init === null) { + $init = $value; + } + + $offset = (int)floor(1000 * ($value - $init)); + + $phases[$key] = array( + 'time' => $value, + 'offset' => $value - $init, + ); + + + if ($prev_key !== null) { + $phases[$prev_key]['duration'] = $value - $phases[$prev_key]['time']; + } + $prev_key = $key; + } + + // Render the phases. + $rows = array(); + foreach ($phases as $key => $phase) { + $offset_ms = (int)floor(1000 * $phase['offset']); + + if (isset($phase['duration'])) { + $duration_us = (int)floor(1000000 * $phase['duration']); + } else { + $duration_us = null; + } + + $rows[] = array( + $key, + pht('+%s ms', new PhutilNumber($offset_ms)), + ($duration_us === null) + ? pht('-') + : pht('%s us', new PhutilNumber($duration_us)), + null, + ); + } + + return id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Phase'), + pht('Offset'), + pht('Duration'), + null, + )) + ->setColumnClasses( + array( + '', + 'n right', + 'n right', + 'wide', + )); + } + +} diff --git a/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php b/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php index ea2f1bbadb..747ddd2267 100644 --- a/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php +++ b/src/applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php @@ -124,7 +124,13 @@ final class DarkConsoleXHProfPluginAPI extends Phobject { self::startProfiler(); } + + /** + * @phutil-external-symbol class PhabricatorStartup + */ private static function startProfiler() { + PhabricatorStartup::beginStartupPhase('profiler.init'); + self::includeXHProfLib(); xhprof_enable(); @@ -132,15 +138,23 @@ final class DarkConsoleXHProfPluginAPI extends Phobject { self::$profilerRunning = true; } + + /** + * @phutil-external-symbol class PhabricatorStartup + */ public static function getProfileFilePHID() { + if (!self::isProfilerRunning()) { + return; + } + + PhabricatorStartup::beginStartupPhase('profiler.stop'); self::stopProfiler(); + PhabricatorStartup::beginStartupPhase('profiler.done'); + return self::$profileFilePHID; } private static function stopProfiler() { - if (!self::isProfilerRunning()) { - return; - } $data = xhprof_disable(); $data = @json_encode($data); diff --git a/src/docs/user/field/darkconsole.diviner b/src/docs/user/field/darkconsole.diviner index d235f6c1e4..a31f0492ae 100644 --- a/src/docs/user/field/darkconsole.diviner +++ b/src/docs/user/field/darkconsole.diviner @@ -138,6 +138,25 @@ If the services tab looks fine, and particularly if a page is slow but the tool to understand problems in PHP is XHProf. +Plugin: Startup +=============== + +The "Startup" plugin shows information about startup phases. This information +can provide insight about performance problems which occur before the profiler +can start. + +Normally, the profiler is the best tool for understanding runtime performance, +but some work is performed before the profiler starts (for example, loading +libraries and configuration). If there is a substantial difference between the +wall time reported by the profiler and the "Entire Page" cost reported by the +Services tab, the Startup tab can help account for that time. + +It is normal for starting the profiler to increase the cost of the page +somewhat: the profiler itself adds overhead while it is running, and the page +must do some work after the profiler is stopped to save the profile and +complete other shutdown operations. + + Plugin: XHProf ============== diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php index fd612f27b0..198cc1b244 100644 --- a/support/PhabricatorStartup.php +++ b/support/PhabricatorStartup.php @@ -34,6 +34,7 @@ * @task apocalypse In Case Of Apocalypse * @task validation Validation * @task ratelimit Rate Limiting + * @task phases Startup Phase Timers */ final class PhabricatorStartup { @@ -43,6 +44,7 @@ final class PhabricatorStartup { private static $capturingOutput; private static $rawInput; private static $oldMemoryLimit; + private static $phases; // TODO: For now, disable rate limiting entirely by default. We need to // iterate on it a bit for Conduit, some of the specific score levels, and @@ -89,10 +91,14 @@ final class PhabricatorStartup { /** + * @param float Request start time, from `microtime(true)`. * @task hook */ - public static function didStartup() { - self::$startTime = microtime(true); + public static function didStartup($start_time) { + self::$startTime = $start_time; + + self::$phases = array(); + self::$accessLog = null; static $registered; @@ -854,4 +860,58 @@ final class PhabricatorStartup { exit(1); } + +/* -( Startup Timers )----------------------------------------------------- */ + + + /** + * Record the beginning of a new startup phase. + * + * For phases which occur before @{class:PhabricatorStartup} loads, save the + * time and record it with @{method:recordStartupPhase} after the class is + * available. + * + * @param string Phase name. + * @task phases + */ + public static function beginStartupPhase($phase) { + self::recordStartupPhase($phase, microtime(true)); + } + + + /** + * Record the start time of a previously executed startup phase. + * + * For startup phases which occur after @{class:PhabricatorStartup} loads, + * use @{method:beginStartupPhase} instead. This method can be used to + * record a time before the class loads, then hand it over once the class + * becomes available. + * + * @param string Phase name. + * @param float Phase start time, from `microtime(true)`. + * @task phases + */ + public static function recordStartupPhase($phase, $time) { + self::$phases[$phase] = $time; + } + + + /** + * Get information about startup phase timings. + * + * Sometimes, performance problems can occur before we start the profiler. + * Since the profiler can't examine these phases, it isn't useful in + * understanding their performance costs. + * + * Instead, the startup process marks when it enters various phases using + * @{method:beginStartupPhase}. A later call to this method can retrieve this + * information, which can be examined to gain greater insight into where + * time was spent. The output is still crude, but better than nothing. + * + * @task phases + */ + public static function getPhases() { + return self::$phases; + } + } diff --git a/webroot/index.php b/webroot/index.php index 3e749be0a9..59e5b71075 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -1,23 +1,19 @@ Date: Fri, 21 Aug 2015 15:00:57 -0700 Subject: [PATCH 41/41] Hide answer box if you asked the question in Ponder Summary: Fixes T9241. Users have a tendancy to assume Ponder is a "forum", make replying to your own question take an additional click. Test Plan: View my own question, see notice, click open answer box, reply. Visit not my question, see box as normal. {F743412} Reviewers: epriestley Reviewed By: epriestley Subscribers: cspeckmim, Korvin Maniphest Tasks: T9241 Differential Revision: https://secure.phabricator.com/D13957 --- .../ponder/view/PonderAddAnswerView.php | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 5ffe99edc7..5d3be0460d 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -34,13 +34,42 @@ final class PonderAddAnswerView extends AphrontView { $info_panel = null; if ($question->getStatus() != PonderQuestionStatus::STATUS_OPEN) { $info_panel = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->appendChild( pht( 'This question has been marked as closed, but you can still leave a new answer.')); } + $box_style = null; + $own_question = null; + $hide_action_id = celerity_generate_unique_node_id(); + $show_action_id = celerity_generate_unique_node_id(); + if ($question->getAuthorPHID() == $viewer->getPHID()) { + $box_style = 'display: none;'; + $open_link = javelin_tag( + 'a', + array( + 'sigil' => 'reveal-content', + 'class' => 'mml', + 'id' => $hide_action_id, + 'href' => '#', + 'meta' => array( + 'showIDs' => array($show_action_id), + 'hideIDs' => array($hide_action_id), + ), + ), + pht('Add an answer.')); + $own_question = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setID($hide_action_id) + ->appendChild( + pht( + 'This is your own question. You are welcome to provide + an answer if you have found a resolution.')) + ->appendChild($open_link); + } + $header = id(new PHUIHeaderView()) ->setHeader(pht('Add Answer')); @@ -69,6 +98,15 @@ final class PonderAddAnswerView extends AphrontView { $box->setInfoView($info_panel); } - return $box; + $box = phutil_tag( + 'div', + array( + 'style' => $box_style, + 'class' => 'mlt', + 'id' => $show_action_id, + ), + $box); + + return array($own_question, $box); } }