From b5a009337f8e048cb463cc13ddb3589f02f8993e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 22 Oct 2013 15:01:06 -0700 Subject: [PATCH] Harbormaster v(-2) Summary: Ref T1049. I don't really want to sink too much time into this right now, but a seemingly reasonable architecture came to me in a dream. Here's a high-level overview of how things fit together: - **"Build"**: In Harbormaster, "build" means any process we want to run against a working copy. It might actually be building an executable, but it might also be running lint, running unit tests, generating documentation, generating symbols, running a deploy, setting up a sandcastle, etc. - `HarbormasterBuildable`: A "buildable" is some piece of code which build operations can run on. Generally, this is either a Differential diff or a Diffusion commit. The Buildable class just wraps those objects and provides a layer of abstraction. Currently, you can manually create a buildable from a commit. In the future, this will be done automatically. - `HarbormasterBuildStep`: A "build step" is an individual build operation, like "run lint", "run unit", "build docs", etc. The step defines how to perform the operation (for example, "run unit tests by executing 'arc unit'"). In this diff, this barely exists. - `HarbormasterBuildPlan`: This glues together build steps into groups or sequences. For example, you might want to "run unit", and then "deploy" if the tests pass. You can create a build plan which says "run step "unit tests", then run step "deploy" on success" or whatever. In the future, these will also contain triggers/conditions ("Automatically run this build plan against every commit") and probably be able to define failure actions ("If this plan fails, send someone an email"). Because build plans will run commands, only administrators can manage them. - `HarbormasterBuild`: This is the concrete result of running a `BuildPlan` against a `Buildable`. It tracks the build status and collects results, so you can see if the build is running/successful/failed. A `Buildable` may have several `Build`s, because you can execute more than one `BuildPlan` against it. For example, you might have a "documentation" build plan which you run continuously against HEAD, but a "unit" build plan which you want to run against every commit. - `HarbormasterBuildTarget`: This is the concrete result of running a `BuildStep` against a `Buildable`. These are children of `Build`. A step might be able to produce multiple targets, but generally this is something like "Unit Tests" or "Lint" and has an overall status, so you can see at a glance that unit tests were fine but lint had some issues. - `HarbormasterBuildItem`: An optional subitem for a target. For lint, this might be an individual file. For unit tests, an individual test. For normal builds, an executable. For deploys, a server. For documentation generation, there might just not be subitems. - `HarbormasterBuildLog`: Provides extra information, like command/execution transcripts. This is where stdout/stderr will get dumped, and general details and other messages. - `HarbormasterBuildArtifact`: Stores side effects or results from build steps. For example, something which builds a binary might put the binary in "Files" and then put its PHID here. Unit tests might put coverage information here. Generally, any build step which produces some high-level output object can use this table to record its existence. This diff implements almost nothing and does nothing useful, but puts most of these object relationships in place. The two major things you can't easily do with these objects are: 1) Run arbitrary cron jobs. Jenkins does this, but it feels tacked on and I don't know of anyone using it for that. We could create fake Buildables to get a similar effect, but if we need to do this I'd rather do it elsewhere in general. Build and cron/service/monitoring feel like pretty different problems to me. 2) Run parameterized/matrix steps (maybe?). Bamboo has this plan/stage/task/job breakdown where a build step can generate a zillion actual jobs, like "build client on x86", "build server on x86", "build client on ARM", "build server on ARM", etc. We can sort of do this by having a Step map to multiple Targets, but I haven't really thought about it too much and it may end up being not-great. I'd guess we have like an 80% chance of getting a clean implementation if/when we get there. I suspect no one actually needs this, or when they do they'll just implement a custom Step and it can be parameterized at that level. I'm not too worried about this overall. The major difference between this and Jenkins/Bamboo/TravisCI is that all three of those are **plan-centric**: the primary object in the system is a build plan, and the dashboard shows you all your build plans and the current status. I don't think this is the right model. One disadvantage is that you basically end up with top-level messaging that says "Trunk is broken", not "Trunk was broken by commit af32f392f". Harbormaster is **buildable-centric**: the primary object in the system is stuff you can run build operations against (commits/branches/revisions), and actual build plans are secondary. The main view will be "recent commits on this branch, and whether they're good or not" -- which I think is what's most important in a larger/more complex product -- not the pass/fail status of all jobs. This also makes it easier and more natural to integrate with Differential and Diffusion, which both care about the overall status of the commit/revision, not the current status of jobs. Test Plan: Poked around, but this doesn't really do anything yet. Reviewers: btrahan Reviewed By: btrahan CC: zeeg, chad, aran, seporaitis Maniphest Tasks: T1049 Differential Revision: https://secure.phabricator.com/D7368 --- .../sql/patches/20131020.harbormaster.sql | 74 ++++++++ src/__phutil_library_map__.php | 103 +++++++++++ .../PhabricatorApplicationHarbormaster.php | 68 ++++++++ .../HarbormasterCapabilityManagePlans.php | 21 +++ .../HarbormasterBuildableEditController.php | 144 +++++++++++++++ .../HarbormasterBuildableListController.php | 78 +++++++++ .../HarbormasterBuildableViewController.php | 105 +++++++++++ .../controller/HarbormasterController.php | 17 ++ .../controller/HarbormasterPlanController.php | 16 ++ .../HarbormasterPlanEditController.php | 121 +++++++++++++ .../HarbormasterPlanExecuteController.php | 88 ++++++++++ .../HarbormasterPlanListController.php | 69 ++++++++ .../HarbormasterPlanViewController.php | 121 +++++++++++++ .../editor/HarbormasterBuildPlanEditor.php | 91 ++++++++++ .../phid/HarbormasterPHIDTypeBuild.php | 37 ++++ .../phid/HarbormasterPHIDTypeBuildItem.php | 37 ++++ .../phid/HarbormasterPHIDTypeBuildPlan.php | 37 ++++ .../phid/HarbormasterPHIDTypeBuildStep.php | 37 ++++ .../phid/HarbormasterPHIDTypeBuildTarget.php | 37 ++++ .../phid/HarbormasterPHIDTypeBuildable.php | 74 ++++++++ .../query/HarbormasterBuildItemQuery.php | 60 +++++++ .../query/HarbormasterBuildPlanQuery.php | 61 +++++++ .../HarbormasterBuildPlanSearchEngine.php | 49 ++++++ .../HarbormasterBuildPlanTransactionQuery.php | 10 ++ .../query/HarbormasterBuildQuery.php | 141 +++++++++++++++ .../query/HarbormasterBuildStepQuery.php | 60 +++++++ .../query/HarbormasterBuildTargetQuery.php | 60 +++++++ .../HarbormasterBuildableArtifactQuery.php | 116 ++++++++++++ .../query/HarbormasterBuildableQuery.php | 165 ++++++++++++++++++ .../HarbormasterBuildableSearchEngine.php | 49 ++++++ .../remarkup/HarbormasterRemarkupRule.php | 18 ++ .../storage/HarbormasterBuildable.php | 85 +++++++++ .../storage/build/HarbormasterBuild.php | 80 +++++++++ .../build/HarbormasterBuildArtifact.php | 60 +++++++ .../storage/build/HarbormasterBuildItem.php | 18 ++ .../storage/build/HarbormasterBuildLog.php | 7 + .../storage/build/HarbormasterBuildTarget.php | 16 ++ .../configuration/HarbormasterBuildPlan.php | 71 ++++++++ .../HarbormasterBuildPlanTransaction.php | 74 ++++++++ ...arbormasterBuildPlanTransactionComment.php | 10 ++ .../configuration/HarbormasterBuildStep.php | 29 +++ .../PhabricatorPolicyExplainController.php | 2 +- .../patch/PhabricatorBuiltinPatchList.php | 4 + 43 files changed, 2619 insertions(+), 1 deletion(-) create mode 100644 resources/sql/patches/20131020.harbormaster.sql create mode 100644 src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php create mode 100644 src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php create mode 100644 src/applications/harbormaster/controller/HarbormasterBuildableEditController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterBuildableListController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterBuildableViewController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterPlanController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterPlanEditController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterPlanListController.php create mode 100644 src/applications/harbormaster/controller/HarbormasterPlanViewController.php create mode 100644 src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php create mode 100644 src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildItemQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildStepQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildableQuery.php create mode 100644 src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php create mode 100644 src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php create mode 100644 src/applications/harbormaster/storage/HarbormasterBuildable.php create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuild.php create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildItem.php create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildLog.php create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php create mode 100644 src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php create mode 100644 src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php create mode 100644 src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php create mode 100644 src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php diff --git a/resources/sql/patches/20131020.harbormaster.sql b/resources/sql/patches/20131020.harbormaster.sql new file mode 100644 index 0000000000..eb7ffe4d2a --- /dev/null +++ b/resources/sql/patches/20131020.harbormaster.sql @@ -0,0 +1,74 @@ +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + containerPHID VARCHAR(64) COLLATE utf8_bin, + buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + buildableStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_buildable` (buildablePHID), + KEY `key_container` (containerPHID), + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + artifactType VARCHAR(32) NOT NULL COLLATE utf8_bin, + artifactIndex VARCHAR(12) NOT NULL COLLATE utf8_bin, + artifactKey VARCHAR(255) NOT NULL COLLATE utf8_bin, + artifactData LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_artifact` (buildablePHID, artifactType, artifactIndex), + UNIQUE KEY `key_artifact_type` (artifactType, artifactIndex), + KEY `key_garbagecollect` (artifactType, dateCreated) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + name VARCHAR(255) NOT NULL, + planStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_status` (planStatus) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplantransaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + commentPHID VARCHAR(64) COLLATE utf8_bin, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, + newValue LONGTEXT NOT NULL COLLATE utf8_bin, + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, + metadata LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) + +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_build ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + buildablePHID varchar(64) NOT NULL COLLATE utf8_bin, + buildPlanPHID varchar(64) NOT NULL COLLATE utf8_bin, + buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_buildable` (buildablePHID), + KEY `key_plan` (buildPlanPHID), + KEY `key_status` (buildStatus) +) ENGINE=InnoDB, COLLATE utf8_general_ci; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a57a4f0865..3836fdf57f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -619,8 +619,46 @@ phutil_register_library_map(array( 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', + 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', + 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', + 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php', + 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php', + 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', + 'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php', + 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', + 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', + 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php', + 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php', + 'HarbormasterBuildPlanTransactionComment' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php', + 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', + 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', + 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', + 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', + 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', + 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', + 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', + 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', + 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', + 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', + 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', + 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', + 'HarbormasterCapabilityManagePlans' => 'applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php', + 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', + 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php', + 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php', + 'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php', + 'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php', + 'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php', + 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', + 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', + 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', + 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', + 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', + 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', + 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', @@ -850,6 +888,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationFeed' => 'applications/feed/application/PhabricatorApplicationFeed.php', 'PhabricatorApplicationFiles' => 'applications/files/application/PhabricatorApplicationFiles.php', 'PhabricatorApplicationFlags' => 'applications/flag/application/PhabricatorApplicationFlags.php', + 'PhabricatorApplicationHarbormaster' => 'applications/harbormaster/application/PhabricatorApplicationHarbormaster.php', 'PhabricatorApplicationHerald' => 'applications/herald/application/PhabricatorApplicationHerald.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationLegalpad' => 'applications/legalpad/application/PhabricatorApplicationLegalpad.php', @@ -2752,8 +2791,71 @@ phutil_register_library_map(array( 'FileCreateMailReceiver' => 'PhabricatorMailReceiver', 'FileMailReceiver' => 'PhabricatorObjectMailReceiver', 'FileReplyHandler' => 'PhabricatorMailReplyHandler', + 'HarbormasterBuild' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildArtifact' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildItem' => 'HarbormasterDAO', + 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildLog' => 'HarbormasterDAO', + 'HarbormasterBuildPlan' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + 2 => 'PhabricatorSubscribableInterface', + ), + 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor', + 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction', + 'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment', + 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildStep' => 'HarbormasterDAO', + 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildTarget' => 'HarbormasterDAO', + 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildable' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildableEditController' => 'HarbormasterController', + 'HarbormasterBuildableListController' => + array( + 0 => 'HarbormasterController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), + 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildableViewController' => 'HarbormasterController', + 'HarbormasterCapabilityManagePlans' => 'PhabricatorPolicyCapability', + 'HarbormasterController' => 'PhabricatorController', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterObject' => 'HarbormasterDAO', + 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', + 'HarbormasterPlanController' => 'PhabricatorController', + 'HarbormasterPlanEditController' => 'HarbormasterPlanController', + 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', + 'HarbormasterPlanListController' => + array( + 0 => 'HarbormasterPlanController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), + 'HarbormasterPlanViewController' => 'HarbormasterPlanController', + 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HeraldAction' => 'HeraldDAO', @@ -3008,6 +3110,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationFeed' => 'PhabricatorApplication', 'PhabricatorApplicationFiles' => 'PhabricatorApplication', 'PhabricatorApplicationFlags' => 'PhabricatorApplication', + 'PhabricatorApplicationHarbormaster' => 'PhabricatorApplication', 'PhabricatorApplicationHerald' => 'PhabricatorApplication', 'PhabricatorApplicationLaunchView' => 'AphrontView', 'PhabricatorApplicationLegalpad' => 'PhabricatorApplication', diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php new file mode 100644 index 0000000000..4e41dc7a02 --- /dev/null +++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -0,0 +1,68 @@ +[1-9]\d*)' => 'HarbormasterBuildableViewController', + '/harbormaster/' => array( + '(?:query/(?P[^/]+)/)?' + => 'HarbormasterBuildableListController', + 'buildable/' => array( + 'edit/(?:(?P\d+)/)?' => 'HarbormasterBuildableEditController', + ), + 'plan/' => array( + '(?:query/(?P[^/]+)/)?' + => 'HarbormasterPlanListController', + 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', + '(?P\d+)/' => 'HarbormasterPlanViewController', + 'execute/(?P\d+)/' => 'HarbormasterPlanExecuteController', + ), + ), + ); + } + + public function getCustomCapabilities() { + return array( + HarbormasterCapabilityManagePlans::CAPABILITY => array( + 'caption' => pht('Can create and manage build plans.'), + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + +} diff --git a/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php b/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php new file mode 100644 index 0000000000..06f92edc41 --- /dev/null +++ b/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php @@ -0,0 +1,21 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + if ($this->id) { + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$buildable) { + return new Aphront404Response(); + } + } else { + $buildable = HarbormasterBuildable::initializeNewBuildable($viewer); + } + + $e_name = true; + $v_name = null; + + $errors = array(); + if ($request->isFormPost()) { + $v_name = $request->getStr('buildablePHID'); + + if ($v_name) { + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($v_name)) + ->executeOne(); + + if ($object instanceof DifferentialRevision) { + throw new Exception( + "TODO: We need to assign PHIDs to diffs before this will work."); + } else if ($object instanceof PhabricatorRepositoryCommit) { + $buildable + ->setBuildablePHID($object->getPHID()) + ->setContainerPHID($object->getRepository()->getPHID()); + } else { + $e_name = pht('Invalid'); + $errors[] = pht('Enter the name of a revision or commit.'); + } + } else { + $e_name = pht('Required'); + $errors[] = pht('You must choose a revision or commit to build.'); + } + + if (!$errors) { + $buildable->save(); + + $buildable_uri = '/B'.$buildable->getID(); + return id(new AphrontRedirectResponse())->setURI($buildable_uri); + } + } + + if ($errors) { + $errors = id(new AphrontErrorView())->setErrors($errors); + } + + $is_new = (!$buildable->getID()); + if ($is_new) { + $title = pht('New Buildable'); + $cancel_uri = $this->getApplicationURI(); + $save_button = pht('Create Buildable'); + } else { + $id = $buildable->getID(); + + $title = pht('Edit Buildable'); + $cancel_uri = "/B{$id}"; + $save_button = pht('Save Buildable'); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer); + + if ($is_new) { + $form + ->appendRemarkupInstructions( + pht( + 'Enter the name of a commit or revision, like `rX123456789` '. + 'or `D123`.')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Buildable Name') + ->setName('buildablePHID') + ->setError($e_name) + ->setValue($v_name)); + } else { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Buildable')) + ->setValue($buildable->getBuildableHandle()->renderLink())); + } + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($save_button) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormError($errors) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('New Buildable'))); + } else { + $id = $buildable->getID(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName("B{$id}") + ->setHref("/B{$id}")); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit'))); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php new file mode 100644 index 0000000000..4260410474 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -0,0 +1,78 @@ +queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new HarbormasterBuildableSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function renderResultsList( + array $buildables, + PhabricatorSavedQuery $query) { + assert_instances_of($buildables, 'HarbormasterBuildable'); + + $viewer = $this->getRequest()->getUser(); + + $list = new PHUIObjectItemListView(); + foreach ($buildables as $buildable) { + $id = $buildable->getID(); + + $item = id(new PHUIObjectItemView()) + ->setHeader(pht('Build %d', $buildable->getID())); + + if ($id) { + $item->setHref("/B{$id}"); + } + + $list->addItem($item); + } + + return $list; + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new HarbormasterBuildableSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + if ($for_app) { + $nav->addFilter('new/', pht('New Build Plan')); + } + + $nav->addLabel('Utilities'); + $nav->addFilter('buildable/edit/', pht('New Manual Build')); + $nav->addFilter('plan/', pht('Manage Build Plans')); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php new file mode 100644 index 0000000000..7ee92981c9 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -0,0 +1,105 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needBuildableHandles(true) + ->needContainerObjects(true) + ->executeOne(); + if (!$buildable) { + return new Aphront404Response(); + } + + $builds = id(new HarbormasterBuildQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(array($buildable->getPHID())) + ->needBuildPlans(true) + ->execute(); + + $build_list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($builds as $build) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Build %d', $build->getID())) + ->setHeader($build->getName()); + $build_list->addItem($item); + } + + $title = pht("Buildable %d", $id); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($buildable); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + $actions = $this->buildActionList($buildable); + $this->buildPropertyLists($box, $buildable, $actions); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName("B{$id}")); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $build_list, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + + private function buildActionList(HarbormasterBuildable $buildable) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $id = $buildable->getID(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($buildable) + ->setObjectURI("/B{$id}"); + + return $list; + } + + private function buildPropertyLists( + PHUIObjectBoxView $box, + HarbormasterBuildable $buildable, + PhabricatorActionListView $actions) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($buildable) + ->setActionList($actions); + $box->addPropertyList($properties); + + $properties->addProperty( + pht('Buildable'), + $buildable->getBuildableHandle()->renderLink()); + + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterController.php b/src/applications/harbormaster/controller/HarbormasterController.php new file mode 100644 index 0000000000..2aba01b881 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterController.php @@ -0,0 +1,17 @@ +addAction( + id(new PHUIListItemView()) + ->setName(pht('New Build Plan')) + ->setHref($this->getApplicationURI('plan/edit/')) + ->setIcon('create')); + + return $crumbs; + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanController.php b/src/applications/harbormaster/controller/HarbormasterPlanController.php new file mode 100644 index 0000000000..6ba3d8c0bd --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterPlanController.php @@ -0,0 +1,16 @@ +addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Build Plans')) + ->setHref($this->getApplicationURI('plan/'))); + + return $crumbs; + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php new file mode 100644 index 0000000000..03fd378147 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php @@ -0,0 +1,121 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + if ($this->id) { + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + } else { + $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer); + } + + $e_name = true; + $v_name = $plan->getName(); + $validation_exception = null; + if ($request->isFormPost()) { + $xactions = array(); + + $v_name = $request->getStr('name'); + $e_name = null; + $type_name = HarbormasterBuildPlanTransaction::TYPE_NAME; + + $xactions[] = id(new HarbormasterBuildPlanTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $editor = id(new HarbormasterBuildPlanEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request); + + try { + $editor->applyTransactions($plan, $xactions); + return id(new AphrontRedirectResponse()) + ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/')); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $validation_exception->getShortMessage( + HarbormasterBuildPlanTransaction::TYPE_NAME); + } + + } + + $is_new = (!$plan->getID()); + if ($is_new) { + $title = pht('New Build Plan'); + $cancel_uri = $this->getApplicationURI(); + $save_button = pht('Create Build Plan'); + } else { + $id = $plan->getID(); + + $title = pht('Edit Build Plan'); + $cancel_uri = "/B{$id}"; + $save_button = pht('Save Build Plan'); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Plan Name') + ->setName('name') + ->setError($e_name) + ->setValue($v_name)); + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($save_button) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setValidationException($validation_exception) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('New Build Plan'))); + } else { + $id = $plan->getID(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht("Plan %d", $id)) + ->setHref($this->getApplicationURI("plan/{$id}/"))); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit'))); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php new file mode 100644 index 0000000000..c720e0b797 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php @@ -0,0 +1,88 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + $id = $this->id; + + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + + $cancel_uri = $this->getApplicationURI("plan/{$id}/"); + + $v_buildable = null; + $e_buildable = null; + + $errors = array(); + if ($request->isFormPost()) { + $v_buildable = $request->getStr('buildable'); + + if ($v_buildable) { + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array(trim($v_buildable, 'B'))) + ->executeOne(); + if (!$buildable) { + $e_buildable = pht('Invalid'); + } + } else { + $e_buildable = pht('Required'); + $errors[] = pht('You must provide a buildable.'); + } + + if (!$errors) { + $build_plan = HarbormasterBuild::initializeNewBuild($viewer) + ->setBuildablePHID($buildable->getPHID()) + ->setBuildPlanPHID($plan->getPHID()) + ->save(); + + $buildable_id = $buildable->getID(); + + return id(new AphrontRedirectResponse()) + ->setURI("/B{$buildable_id}"); + } + } + + if ($errors) { + $errors = id(new AphrontErrorView())->setErrors($errors); + } + + $form = id(new PHUIFormLayoutView()) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Buildable')) + ->setName('buildable') + ->setValue($v_buildable) + ->setError($e_buildable)); + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Execute Build Plan')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($errors) + ->appendChild($form) + ->addSubmitButton(pht('Execute Build Plan')) + ->addCancelButton($cancel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanListController.php b/src/applications/harbormaster/controller/HarbormasterPlanListController.php new file mode 100644 index 0000000000..749de67707 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterPlanListController.php @@ -0,0 +1,69 @@ +queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function renderResultsList( + array $plans, + PhabricatorSavedQuery $query) { + assert_instances_of($plans, 'HarbormasterBuildPlan'); + + $viewer = $this->getRequest()->getUser(); + + $list = new PHUIObjectItemListView(); + foreach ($plans as $plan) { + $id = $plan->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Plan %d', $plan->getID())) + ->setHeader($plan->getName()); + + $item->setHref($this->getApplicationURI("plan/{$id}/")); + + $list->addItem($item); + } + + return $list; + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new HarbormasterBuildPlanSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php new file mode 100644 index 0000000000..cc604e4e9a --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -0,0 +1,121 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + + $xactions = id(new HarbormasterBuildPlanTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($plan->getPHID())) + ->execute(); + + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($viewer); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($plan->getPHID()) + ->setTransactions($xactions) + ->setMarkupEngine($engine); + + $title = pht("Plan %d", $id); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($plan); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + $actions = $this->buildActionList($plan); + $this->buildPropertyLists($box, $plan, $actions); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht("Plan %d", $id))); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $xaction_view, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + + private function buildActionList(HarbormasterBuildPlan $plan) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $id = $plan->getID(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($plan) + ->setObjectURI($this->getApplicationURI("plan/{$id}/")); + + $can_edit = $this->hasApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Plan')) + ->setHref($this->getApplicationURI("plan/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setIcon('edit')); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manually Execute Plan')) + ->setHref($this->getApplicationURI("plan/execute/{$id}/")) + ->setWorkflow(true) + ->setDisabled(!$can_edit) + ->setIcon('arrow_right')); + + return $list; + } + + private function buildPropertyLists( + PHUIObjectBoxView $box, + HarbormasterBuildPlan $plan, + PhabricatorActionListView $actions) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($plan) + ->setActionList($actions); + $box->addPropertyList($properties); + + $properties->addProperty( + pht('Created'), + phabricator_datetime($plan->getDateCreated(), $viewer)); + + } + +} diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php new file mode 100644 index 0000000000..f1a045e35f --- /dev/null +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php @@ -0,0 +1,91 @@ +getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + if ($this->getIsNewObject()) { + return null; + } + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + } + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + return; + } + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + $missing_name = true; + if (strlen($object->getName()) && empty($xactions)) { + $missing_name = false; + } else if (strlen(last($xactions)->getNewValue())) { + $missing_name = false; + } + + if ($missing_name) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Plan name is required.'), + last($xactions)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php new file mode 100644 index 0000000000..1cfec404fc --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_plan = $objects[$phid]; + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php new file mode 100644 index 0000000000..4d6fdb1982 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_item = $objects[$phid]; + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php new file mode 100644 index 0000000000..c378902f5d --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_plan = $objects[$phid]; + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php new file mode 100644 index 0000000000..3081f32e89 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_step = $objects[$phid]; + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php new file mode 100644 index 0000000000..7f893b77b7 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_target = $objects[$phid]; + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php new file mode 100644 index 0000000000..6b05ec6d50 --- /dev/null +++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php @@ -0,0 +1,74 @@ +withPHIDs($phids) + ->needBuildableHandles(true); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $buildable = $objects[$phid]; + + $id = $buildable->getID(); + $target = $buildable->getBuildableHandle()->getFullName(); + + $handle->setURI("/B{$id}"); + $handle->setName("B{$id}"); + $handle->setFullName("B{$id}: ".$target); + } + } + + public function canLoadNamedObject($name) { + return preg_match('/^B\d*[1-9]\d*$/i', $name); + } + + public function loadNamedObjects( + PhabricatorObjectQuery $query, + array $names) { + + $id_map = array(); + foreach ($names as $name) { + $id = (int)substr($name, 1); + $id_map[$id][] = $name; + } + + $objects = id(new HarbormasterBuildableQuery()) + ->setViewer($query->getViewer()) + ->withIDs(array_keys($id_map)) + ->execute(); + + $results = array(); + foreach ($objects as $id => $object) { + foreach (idx($id_map, $id, array()) as $name) { + $results[$name] = $object; + } + } + + return $results; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php new file mode 100644 index 0000000000..d04fa251a5 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildItem(); + $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); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php new file mode 100644 index 0000000000..a888b876b1 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php @@ -0,0 +1,61 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildPlan(); + $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); + } + + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php new file mode 100644 index 0000000000..41cd854d77 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -0,0 +1,49 @@ + pht('All Plans'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php new file mode 100644 index 0000000000..a69365e01f --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php @@ -0,0 +1,10 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withBuildablePHIDs(array $buildable_phids) { + $this->buildablePHIDs = $buildable_phids; + return $this; + } + + public function withBuildPlanPHIDs(array $build_plan_phids) { + $this->buildPlanPHIDs = $build_plan_phids; + return $this; + } + + public function needBuildPlans($need_plans) { + $this->needBuildPlans = $need_plans; + return $this; + } + + 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); + } + + protected function willFilterPage(array $page) { + $buildables = array(); + + $buildable_phids = array_filter(mpull($page, 'getBuildablePHID')); + if ($buildable_phids) { + $buildables = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($buildable_phids) + ->setParentQuery($this) + ->execute(); + $buildables = mpull($buildables, null, 'getPHID'); + } + + foreach ($page as $key => $build) { + $buildable_phid = $build->getBuildablePHID(); + if (empty($buildables[$buildable_phid])) { + unset($page[$key]); + continue; + } + $build->attachBuildable($buildables[$buildable_phid]); + } + + return $page; + } + + protected function didFilterPage(array $page) { + if ($this->needBuildPlans) { + $plans = array(); + + $plan_phids = array_filter(mpull($page, 'getBuildPlanPHID')); + if ($plan_phids) { + $plans = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($plan_phids) + ->setParentQuery($this) + ->execute(); + $plans = mpull($plans, null, 'getPHID'); + } + + foreach ($page as $key => $build) { + $plan_phid = $build->getBuildPlanPHID(); + $build->attachBuildPlan(idx($plans, $plan_phid)); + } + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->buildPlanPHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildPlanPHID IN (%Ls)', + $this->buildPlanPHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php new file mode 100644 index 0000000000..9a94be6e2b --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildStep(); + $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); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php new file mode 100644 index 0000000000..2d7e1fa7d9 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + 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); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php new file mode 100644 index 0000000000..901335a0ca --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php @@ -0,0 +1,116 @@ +ids = $ids; + return $this; + } + + public function withBuildablePHIDs(array $buildable_phids) { + $this->buildablePHIDs = $buildable_phids; + return $this; + } + + public function withArtifactTypes(array $artifact_types) { + $this->artifactTypes = $artifact_types; + return $this; + } + + public function withArtifactKeys(array $artifact_keys) { + $this->artifactKeys = $artifact_keys; + return $this; + } + + 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); + } + + protected function willFilterPage(array $page) { + $buildables = array(); + + $buildable_phids = array_filter(mpull($page, 'getBuildablePHID')); + if ($buildable_phids) { + $buildables = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($buildable_phids) + ->setParentQuery($this) + ->execute(); + $buildables = mpull($buildables, null, 'getPHID'); + } + + foreach ($page as $key => $artifact) { + $buildable_phid = $artifact->getBuildablePHID(); + if (empty($buildables[$buildable_phid])) { + unset($page[$key]); + continue; + } + $artifact->attachBuildable($buildables[$buildable_phid]); + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->artifactTypes) { + $where[] = qsprintf( + $conn_r, + 'artifactType in (%Ls)', + $this->artifactTypes); + } + + if ($this->artifactKeys) { + $indexes = array(); + foreach ($this->artifactKeys as $key) { + $indexes[] = PhabricatorHash::digestForIndex($key); + } + + $where[] = qsprintf( + $conn_r, + 'artifactIndex IN (%Ls)', + $indexes); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php new file mode 100644 index 0000000000..6666e78955 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -0,0 +1,165 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withBuildablePHIDs(array $buildable_phids) { + $this->buildablePHIDs = $buildable_phids; + return $this; + } + + public function withContainerPHIDs(array $container_phids) { + $this->containerPHIDs = $container_phids; + return $this; + } + + public function needContainerObjects($need) { + $this->needContainerObjects = $need; + return $this; + } + + public function needBuildableHandles($need) { + $this->needBuildableHandles = $need; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildable(); + $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); + } + + protected function willFilterPage(array $page) { + $buildables = array(); + + $buildable_phids = array_filter(mpull($page, 'getBuildablePHID')); + if ($buildable_phids) { + $buildables = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($buildable_phids) + ->setParentQuery($this) + ->execute(); + $buildables = mpull($buildables, null, 'getPHID'); + } + + foreach ($page as $key => $buildable) { + $buildable_phid = $buildable->getBuildablePHID(); + if (empty($buildables[$buildable_phid])) { + unset($page[$key]); + continue; + } + $buildable->attachBuildableObject($buildables[$buildable_phid]); + } + + return $page; + } + + protected function didFilterPage(array $page) { + if ($this->needContainerObjects) { + $containers = array(); + + $container_phids = array_filter(mpull($page, 'getContainerPHID')); + if ($container_phids) { + $containers = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($container_phids) + ->setParentQuery($this) + ->execute(); + $containers = mpull($containers, null, 'getPHID'); + } + + foreach ($page as $key => $buildable) { + $container_phid = $buildable->getContainerPHID(); + $buildable->attachContainerObject(idx($containers, $container_phid)); + } + } + + if ($this->needBuildableHandles) { + $handles = array(); + + $handle_phids = array_filter(mpull($page, 'getBuildablePHID')); + if ($handle_phids) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($handle_phids) + ->setParentQuery($this) + ->execute(); + } + + foreach ($page as $key => $buildable) { + $handle_phid = $buildable->getBuildablePHID(); + $buildable->attachBuildableHandle(idx($handles, $handle_phid)); + } + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->containerPHIDs) { + $where[] = qsprintf( + $conn_r, + 'containerPHID in (%Ls)', + $this->containerPHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php new file mode 100644 index 0000000000..8c549878e5 --- /dev/null +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -0,0 +1,49 @@ + pht('All Buildables'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} diff --git a/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php b/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php new file mode 100644 index 0000000000..5c3e18208c --- /dev/null +++ b/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php @@ -0,0 +1,18 @@ +getEngine()->getConfig('viewer'); + return id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php new file mode 100644 index 0000000000..d3100584bd --- /dev/null +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -0,0 +1,85 @@ +setBuildStatus('new') // TODO: Define these. + ->setBuildableStatus('active'); // TODO: Define these, too. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildable::TYPECONST); + } + + public function attachBuildableObject($buildable_object) { + $this->buildableObject = $buildable_object; + return $this; + } + + public function getBuildableObject() { + return $this->assertAttached($this->buildableObject); + } + + public function attachContainerObject($container_object) { + $this->containerObject = $container_object; + return $this; + } + + public function getContainerObject() { + return $this->assertAttached($this->containerObject); + } + + public function attachBuildableHandle($buildable_handle) { + $this->buildableHandle = $buildable_handle; + return $this; + } + + public function getBuildableHandle() { + return $this->assertAttached($this->buildableHandle); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getBuildableObject()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getBuildableObject()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + 'Users must be able to see the revision or repository to see a '. + 'buildable.'); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php new file mode 100644 index 0000000000..325f30931d --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -0,0 +1,80 @@ +setBuildStatus('building'); // TODO: Sort this. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildPlan::TYPECONST); + } + + public function attachBuildable(HarbormasterBuildable $buildable) { + $this->buildable = $buildable; + return $this; + } + + public function getBuildable() { + return $this->assertAttached($this->buildable); + } + + public function getName() { + if ($this->getBuildPlan()) { + return $this->getBuildPlan()->getName(); + } + return pht('Build'); + } + + public function attachBuildPlan( + HarbormasterBuildPlan $build_plan = null) { + $this->buildPlan = $build_plan; + return $this; + } + + public function getBuildPlan() { + return $this->assertAttached($this->buildPlan); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getBuildable()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getBuildable()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + 'Users must be able to see a buildable to view its build plans.'); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php new file mode 100644 index 0000000000..f262d3810c --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -0,0 +1,60 @@ + array( + 'artifactData' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function attachBuildable(HarbormasterBuildable $buildable) { + $this->buildable = $buildable; + return $this; + } + + public function getBuildable() { + return $this->assertAttached($this->buildable); + } + + public function setArtifactKey($key) { + $this->artifactIndex = PhabricatorHash::digestForIndex($key); + $this->artifactKey = $key; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getBuildable()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getBuildable()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + 'Users must be able to see a buildable to see its artifacts.'); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php new file mode 100644 index 0000000000..db12278053 --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php @@ -0,0 +1,18 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildItem::TYPECONST); + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php new file mode 100644 index 0000000000..2e5d05d7eb --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -0,0 +1,7 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildTarget::TYPECONST); + } + +} diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php new file mode 100644 index 0000000000..8f36057636 --- /dev/null +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -0,0 +1,71 @@ +setPlanStatus('active'); // TODO: Figure this out. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildPlan::TYPECONST); + } + + public function attachBuildSteps(array $steps) { + assert_instances_of($steps, 'HarbormasterBuildStep'); + $this->buildSteps = $steps; + return $this; + } + + public function getBuildSteps() { + return $this->assertAttached($this->buildSteps); + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } +} diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php new file mode 100644 index 0000000000..a48ce27073 --- /dev/null +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php @@ -0,0 +1,74 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return 'create'; + } + break; + } + + return parent::getIcon(); + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return 'green'; + } + break; + } + + return parent::getIcon(); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $author_handle = $this->renderHandleLink($this->getAuthorPHID()); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this build plan.', + $author_handle); + } else { + return pht( + '%s renamed this build plan from "%s" to "%s".', + $author_handle, + $old, + $new); + } + break; + } + + return parent::getTitle(); + } + +} diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php new file mode 100644 index 0000000000..c1ad15d52b --- /dev/null +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php @@ -0,0 +1,10 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildStep::TYPECONST); + } + + public function attachBuildPlan(HarbormasterBuildPlan $plan) { + $this->buildPlan = $plan; + return $this; + } + + public function getBuildPlan() { + return $this->assertAttached($this->buildPlan); + } + +} diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php index 2679a0519a..09fd69242a 100644 --- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -43,7 +43,7 @@ final class PhabricatorPolicyExplainController ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); - $object_uri = $handle->getURI(); + $object_uri = nonempty($handle->getURI(), '/'); $explanation = PhabricatorPolicy::getPolicyExplanation( $viewer, diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 179b10cc0d..88ded8d29f 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1700,6 +1700,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'php', 'name' => $this->getPatchPath('20131020.pxactionmig.php'), ), + '20131020.harbormaster.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131020.harbormaster.sql'), + ), ); } }