Page MenuHomePhabricator

D9809.id23540.diff
No OneTemporary

D9809.id23540.diff

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

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)

Event Timeline