Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13968780
D9809.id23540.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D9809.id23540.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -68,7 +68,7 @@
'rsrc/css/application/feed/feed.css' => '4e544db4',
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
'rsrc/css/application/flag/flag.css' => '5337623f',
- 'rsrc/css/application/harbormaster/harbormaster.css' => 'cec833b7',
+ 'rsrc/css/application/harbormaster/harbormaster.css' => '278e7358',
'rsrc/css/application/herald/herald-test.css' => '778b008e',
'rsrc/css/application/herald/herald.css' => 'c544dd1c',
'rsrc/css/application/maniphest/batch-editor.css' => '8f380ebc',
@@ -385,7 +385,7 @@
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781',
'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
- 'rsrc/js/application/harbormaster/behavior-reorder-steps.js' => 'b716477f',
+ 'rsrc/js/application/harbormaster/behavior-reorder-steps.js' => '2f80c720',
'rsrc/js/application/herald/HeraldRuleEditor.js' => '58e048fc',
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
@@ -535,7 +535,7 @@
'font-fontawesome' => '73d075c3',
'font-source-sans-pro' => '91d53463',
'global-drag-and-drop-css' => '697324ad',
- 'harbormaster-css' => 'cec833b7',
+ 'harbormaster-css' => '278e7358',
'herald-css' => 'c544dd1c',
'herald-rule-editor' => '58e048fc',
'herald-test-css' => '778b008e',
@@ -587,7 +587,7 @@
'javelin-behavior-error-log' => 'a5d7cf86',
'javelin-behavior-fancy-datepicker' => 'a5573bcd',
'javelin-behavior-global-drag-and-drop' => '3672899b',
- 'javelin-behavior-harbormaster-reorder-steps' => 'b716477f',
+ 'javelin-behavior-harbormaster-reorder-steps' => '2f80c720',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => '8fc1c918',
'javelin-behavior-history-install' => '7ee2b591',
@@ -1056,6 +1056,14 @@
4 => 'javelin-vector',
5 => 'javelin-magical-init',
),
+ '2f80c720' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-stratcom',
+ 2 => 'javelin-workflow',
+ 3 => 'javelin-dom',
+ 4 => 'phabricator-draggable-list',
+ ),
'2fa810fc' =>
array(
0 => 'javelin-behavior',
@@ -1774,14 +1782,6 @@
4 => 'javelin-install',
5 => 'javelin-util',
),
- 'b716477f' =>
- array(
- 0 => 'javelin-behavior',
- 1 => 'javelin-stratcom',
- 2 => 'javelin-workflow',
- 3 => 'javelin-dom',
- 4 => 'phabricator-draggable-list',
- ),
'b9f26029' =>
array(
0 => 'javelin-install',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -765,6 +765,7 @@
'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php',
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
+ 'HarbormasterGraphView' => 'applications/harbormaster/view/HarbormasterGraphView.php',
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php',
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
@@ -3519,6 +3520,7 @@
'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterController' => 'PhabricatorController',
'HarbormasterDAO' => 'PhabricatorLiskDAO',
+ 'HarbormasterGraphView' => 'AphrontTagView',
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanOrderController.php b/src/applications/harbormaster/controller/HarbormasterPlanOrderController.php
--- a/src/applications/harbormaster/controller/HarbormasterPlanOrderController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanOrderController.php
@@ -47,7 +47,10 @@
// will show that there are ordering issues.
// Force the page to re-render.
- return id(new AphrontRedirectResponse());
+ return id(new AphrontRedirectResponse())
+ ->setURI(
+ '/harbormaster/plan/'.$this->id.
+ '/?graph='.$request->getStr('graph'));
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -50,7 +50,7 @@
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Plan %d', $id));
- list($step_list, $has_any_conflicts) = $this->buildStepList($plan);
+ list($canvas, $step_list, $has_any_conflicts) = $this->buildStepList($plan);
if ($has_any_conflicts) {
$box->setFormErrors(
@@ -61,11 +61,16 @@
));
}
+ $container = phutil_tag(
+ 'div',
+ array('class' => 'harbormaster-step-list'),
+ array($canvas, $step_list));
+
return $this->buildApplicationPage(
array(
$crumbs,
$box,
- $step_list,
+ $container,
$xaction_view,
),
array(
@@ -78,6 +83,7 @@
$viewer = $request->getUser();
$list_id = celerity_generate_unique_node_id();
+ $canvas_id = celerity_generate_unique_node_id();
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($viewer)
@@ -97,7 +103,9 @@
'harbormaster-reorder-steps',
array(
'listID' => $list_id,
+ 'canvasID' => $canvas_id,
'orderURI' => '/harbormaster/plan/order/'.$plan->getID().'/',
+ 'initiallyVisible' => $request->getBool('graph'),
));
$has_any_conflicts = false;
@@ -201,7 +209,10 @@
$step_list->addItem($item);
}
- return array($step_list, $has_any_conflicts);
+ $canvas = id(new HarbormasterGraphView())
+ ->setID($canvas_id);
+
+ return array($canvas, $step_list, $has_any_conflicts);
}
private function buildActionList(HarbormasterBuildPlan $plan) {
@@ -259,6 +270,14 @@
->setDisabled(!$can_edit)
->setIcon('fa-play-circle'));
+ $list->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Visualise Build Order'))
+ ->setHref('#')
+ ->addSigil('reveal-graph')
+ ->setWorkflow(true)
+ ->setIcon('fa-eye'));
+
return $list;
}
@@ -292,7 +311,6 @@
return array(null, $has_conflicts);
}
-
$this->requireResource('harbormaster-css');
$header = phutil_tag(
@@ -370,8 +388,16 @@
$note = $bound;
}
+ $kind_sigil = 'output';
+ if ($is_input) {
+ $kind_sigil = 'input';
+ }
+
$list->addItem(
id(new PHUIStatusItemView())
+ ->addSigil(
+ 'harbormaster-artifact-'.
+ $kind_sigil.'-'.$artifact['type'])
->setIcon($icon, $color, $icon_label)
->setTarget($artifact['name'])
->setNote($note));
diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
@@ -129,7 +129,7 @@
*/
public static function loadAvailableArtifacts(
HarbormasterBuildPlan $build_plan,
- HarbormasterBuildStep $current_build_step,
+ $current_build_step,
$artifact_type) {
$build_steps = $build_plan->loadOrderedBuildSteps();
diff --git a/src/applications/harbormaster/view/HarbormasterGraphView.php b/src/applications/harbormaster/view/HarbormasterGraphView.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/view/HarbormasterGraphView.php
@@ -0,0 +1,16 @@
+<?php
+
+final class HarbormasterGraphView extends AphrontTagView {
+
+ protected function getTagAttributes() {
+ return array();
+ }
+
+ public function getTagName() {
+ return 'canvas';
+ }
+
+ public function getTagContent() {
+ return $this->renderChildren();
+ }
+}
diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css
--- a/webroot/rsrc/css/application/harbormaster/harbormaster.css
+++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css
@@ -17,3 +17,14 @@
margin-bottom: 2px;
color: {$darkbluetext};
}
+
+.harbormaster-step-list canvas {
+ display: none;
+ width: 0px;
+ margin-bottom: 12px;
+}
+
+.harbormaster-step-list ul.phui-object-item-list-view {
+ display: inline-block;
+ float:right;
+}
diff --git a/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js b/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
--- a/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
+++ b/webroot/rsrc/js/application/harbormaster/behavior-reorder-steps.js
@@ -16,6 +16,33 @@
return JX.DOM.scry(root, 'li', 'build-step');
});
+ var updateGraph = null;
+
+ var canvas = JX.$(config.canvasID);
+
+ if (config.initiallyVisible) {
+ canvas.style.display = 'inline-block';
+ }
+
+ var isVisible = function() {
+ return canvas.style.display === 'inline-block';
+ };
+
+ JX.Stratcom.listen(
+ 'click',
+ 'reveal-graph',
+ function(e) {
+ e.kill();
+
+ if (isVisible()) {
+ canvas.style.display = 'none';
+ } else {
+ canvas.style.display = 'inline-block';
+ }
+
+ updateGraph();
+ });
+
list.listen('didDrop', function(node) {
var nodes = list.findItems();
var order = [];
@@ -30,12 +57,226 @@
list.lock();
JX.DOM.alterClass(node, 'drag-sending', true);
- new JX.Workflow(config.orderURI, {order: order.join()})
- .setHandler(function() {
- JX.DOM.alterClass(node, 'drag-sending', false);
- list.unlock();
- })
- .start();
+ new JX.Workflow(
+ config.orderURI + '?graph=' + isVisible(),
+ {order: order.join()})
+ .setHandler(function() {
+ JX.DOM.alterClass(node, 'drag-sending', false);
+ updateGraph();
+ list.unlock();
+ })
+ .start();
});
+ var itemWidth = 48;
+
+ updateGraph = function() {
+ if (!isVisible()) {
+ root.style.width = '';
+ root.style.float = 'none';
+ return;
+ }
+
+ var width = list.findItems().length * itemWidth + 32;
+ var height = JX.Vector.getDim(root).y + 16;
+ var v = JX.Vector.getViewport();
+
+ height -= 32;
+
+ canvas.width = width;
+ canvas.height = height;
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ root.style.width = (v.x - width - 47) + 'px';
+ root.style.float = 'right';
+
+ var context = canvas.getContext('2d');
+
+ // Clear the canvas.
+ context.clearRect(0, 0, width, height);
+ context.translate(16.5, 16.5);
+ context.lineWidth = 0.5;
+
+ var dependencyGraph = {};
+ var lookup = {};
+
+ var colors = [
+ '#c0392b',
+ '#e67e22',
+ '#f1c40f',
+ '#139543',
+ '#2980b9',
+ '#3498db',
+ '#c6539d',
+ '#8e44ad'
+ ];
+
+ var nodes = list.findItems();
+ for (var ii = 0; ii < nodes.length; ii++) {
+ var itemi = nodes[ii];
+
+ var inputnodes = JX.DOM.scry(
+ itemi,
+ 'tr',
+ 'harbormaster-artifact-input-buildstate');
+ var outputnodes = JX.DOM.scry(
+ itemi,
+ 'tr',
+ 'harbormaster-artifact-output-buildstate');
+
+ // Find the output build state (each artifact has exactly one).
+ var outputArtifact = null;
+ for (var o = 0; o < outputnodes.length; o++) {
+ outputArtifact =
+ outputnodes[o].firstChild.nextSibling.nextSibling.innerText;
+ break;
+ }
+
+ // Find the input build states (each artifact has between 0-N).
+ var inputArtifacts = [];
+ for (var n = 0; n < inputnodes.length; n++) {
+ inputArtifacts.push(
+ inputnodes[n].firstChild.nextSibling.nextSibling.innerText);
+ }
+
+ if (outputArtifact === null) {
+ continue;
+ }
+
+ dependencyGraph[outputArtifact] = {
+ 'inputs': inputArtifacts,
+ 'color': colors[ii % colors.length],
+ 'index': ii,
+ 'node': itemi
+ };
+
+ lookup[ii] = outputArtifact;
+ }
+
+ for (var i = 0; i < nodes.length; i++) {
+ var item = nodes[i];
+ var x = i * itemWidth;
+ var y = (new JX.Vector(item).y - new JX.Vector(root).y) - 16;
+ var size = JX.Vector.getDim(item);
+
+ context.lineWidth = 0.5;
+ context.fillStyle = '#F0F0F0';
+ context.fillRect(x + 8, y, 32, size.y - 1);
+ context.strokeStyle = '#A1A6B0';
+ context.strokeRect(x + 8, y, 32, size.y - 1);
+
+ var inputs = dependencyGraph[lookup[i]]['inputs'];
+
+ for (var a = 0; a < inputs.length; a++) {
+ if (dependencyGraph[inputs[a]] === undefined) {
+ continue;
+ }
+
+ var refnode = dependencyGraph[inputs[a]]['node'];
+ var refcolor = dependencyGraph[inputs[a]]['color'];
+ var refindex = dependencyGraph[inputs[a]]['index'];
+
+ var refdim = new JX.Vector.getDim(refnode);
+
+ var xdestoffset = (refindex / (nodes.length - 2)) * (32 - 6) + 3 + 8;
+ var ydestoffset = (refindex / (nodes.length - 1)) * (refdim.y - 8) + 4;
+
+ var refx = refindex * itemWidth;
+ var refy = new JX.Vector(refnode).y - new JX.Vector(root).y;
+
+ context.strokeStyle = refcolor;
+ context.fillStyle = refcolor;
+ context.lineWidth = 2;
+
+ context.beginPath();
+ context.moveTo(refx + 40, refy - 16 + ydestoffset);
+ context.lineTo(x + xdestoffset, refy - 16 + ydestoffset);
+ context.lineTo(x + xdestoffset, y);
+ context.stroke();
+
+ context.beginPath();
+ context.moveTo(x + xdestoffset - 3, y - 8);
+ context.lineTo(x + xdestoffset, y - 2);
+ context.lineTo(x + xdestoffset + 3, y - 8);
+ context.fill();
+ context.stroke();
+
+ context.beginPath();
+ context.moveTo(refx + 40, refy - 16 + ydestoffset - 3);
+ context.lineTo(refx + 40 + 8, refy - 16 + ydestoffset);
+ context.lineTo(refx + 40, refy - 16 + ydestoffset + 3);
+ context.fill();
+ context.stroke();
+ }
+
+ if (inputs.length === 0) {
+ var xsrcoffset2 = 3 + 8;
+
+ // Render the input line.
+ context.strokeStyle = 'black';
+ context.fillStyle = 'black';
+ context.lineWidth = 2;
+
+ context.beginPath();
+ context.moveTo(x + xsrcoffset2, -16);
+ context.lineTo(x + xsrcoffset2, y);
+ context.fill();
+ context.stroke();
+
+ context.beginPath();
+ context.moveTo(x + xsrcoffset2 - 3, y - 8);
+ context.lineTo(x + xsrcoffset2, y - 2);
+ context.lineTo(x + xsrcoffset2 + 3, y - 8);
+ context.fill();
+ context.stroke();
+ }
+
+ var outputIsNotUsedAnywhere = true;
+ for (var c = 0; c < nodes.length; c++) {
+ var inputsTemp = dependencyGraph[lookup[c]]['inputs'];
+
+ for (var d = 0; d < inputsTemp.length; d++) {
+ if (inputsTemp[d] === lookup[i]) {
+ outputIsNotUsedAnywhere = false;
+ break;
+ }
+ }
+ }
+
+ if (outputIsNotUsedAnywhere) {
+ // Render the input line.
+ context.strokeStyle = 'black';
+ context.fillStyle = 'black';
+ context.lineWidth = 2;
+
+ context.beginPath();
+ context.moveTo(x + 40, y + (size.y - 16));
+ context.lineTo(width - 24, y + (size.y - 16));
+ context.fill();
+ context.stroke();
+
+ context.beginPath();
+ context.moveTo(x + 40, y + (size.y - 16) - 3);
+ context.lineTo(x + 40 + 8, y + (size.y - 16));
+ context.lineTo(x + 40, y + (size.y - 16) + 3);
+ context.fill();
+ context.stroke();
+
+ context.beginPath();
+ context.moveTo(width - 24, y + (size.y - 16));
+ context.lineTo(width - 24, height - 24);
+ context.fill();
+ context.stroke();
+
+ context.beginPath();
+ context.arc(width - 24, height - 24, 4, 0, 2 * Math.PI, false);
+ context.fillStyle = 'black';
+ context.fill();
+ }
+ }
+ };
+
+ JX.Stratcom.listen('resize', null, updateGraph);
+
+ updateGraph();
});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Oct 17, 11:00 PM (3 w, 18 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6723822
Default Alt Text
D9809.id23540.diff (17 KB)
Attached To
Mode
D9809: Implement a mechanism for visualising the build step order
Attached
Detach File
Event Timeline
Log In to Comment