@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Fix negative chart values, add storage for charts

Summary:
Ref T13279. I think I'm going to fling some stuff at the wall for a bit here and hope most of it sticks, so this series of changes may not be terribly cohesive or focused. Here:

The range of the chart is locked to "[0, 105% of max]". This is trying to make a pleasing extra margin above the maximum value, but currently just breaks charts with negative values. Later:

- I'll probably let users customize this.
- We should likely select 0 as the automatic minimum for charts with no negative values.
- For charts with positive values, it would be nice to automatically pick a pleasantly round number (25, 100, 1000) as a maximum by default.

We don't have any storage for charts yet. Add some. This works like queries, where every possible configuration gets a short URL slug. Nothing writes or reads this yet.

Rename `fn()` to `css_function()`. This builds CSS functions for D3. The JS is likely to get substantial structural rewrites later on, `fn()` was just particularly offensive.

Test Plan: Viewed a fact series with negative values. Ran `bin/storage upgrade`.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim, PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20438

+98 -17
+8 -8
resources/celerity/map.php
··· 397 397 'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3', 398 398 'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d', 399 399 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688', 400 - 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'c8147a20', 400 + 'rsrc/js/application/maniphest/behavior-line-chart.js' => '11167911', 401 401 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867', 402 402 'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9', 403 403 'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a', ··· 625 625 'javelin-behavior-icon-composer' => '38a6cedb', 626 626 'javelin-behavior-launch-icon-composer' => 'a17b84f1', 627 627 'javelin-behavior-lightbox-attachments' => 'c7e748bf', 628 - 'javelin-behavior-line-chart' => 'c8147a20', 628 + 'javelin-behavior-line-chart' => '11167911', 629 629 'javelin-behavior-linked-container' => '74446546', 630 630 'javelin-behavior-maniphest-batch-selector' => '139ef688', 631 631 'javelin-behavior-maniphest-list-editor' => 'c687e867', ··· 1006 1006 'phuix-action-view', 1007 1007 'javelin-workflow', 1008 1008 'phuix-icon-view', 1009 + ), 1010 + 11167911 => array( 1011 + 'javelin-behavior', 1012 + 'javelin-dom', 1013 + 'javelin-vector', 1014 + 'phui-chart-css', 1009 1015 ), 1010 1016 '111bfd2d' => array( 1011 1017 'javelin-install', ··· 1996 2002 'javelin-util', 1997 2003 'phuix-icon-view', 1998 2004 'phabricator-busy', 1999 - ), 2000 - 'c8147a20' => array( 2001 - 'javelin-behavior', 2002 - 'javelin-dom', 2003 - 'javelin-vector', 2004 - 'phui-chart-css', 2005 2005 ), 2006 2006 'c9749dcd' => array( 2007 2007 'javelin-install',
+6
resources/sql/autopatches/20190416.chart.01.storage.sql
··· 1 + CREATE TABLE {$NAMESPACE}_fact.fact_chart ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + chartKey BINARY(12) NOT NULL, 4 + chartParameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 5 + UNIQUE KEY `key_chart` (chartKey) 6 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+5
src/__phutil_library_map__.php
··· 3201 3201 'PhabricatorFact' => 'applications/fact/fact/PhabricatorFact.php', 3202 3202 'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php', 3203 3203 'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php', 3204 + 'PhabricatorFactChart' => 'applications/fact/storage/PhabricatorFactChart.php', 3204 3205 'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php', 3205 3206 'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php', 3206 3207 'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php', ··· 9229 9230 'PhabricatorFact' => 'Phobject', 9230 9231 'PhabricatorFactAggregate' => 'PhabricatorFactDAO', 9231 9232 'PhabricatorFactApplication' => 'PhabricatorApplication', 9233 + 'PhabricatorFactChart' => array( 9234 + 'PhabricatorFactDAO', 9235 + 'PhabricatorPolicyInterface', 9236 + ), 9232 9237 'PhabricatorFactChartController' => 'PhabricatorFactController', 9233 9238 'PhabricatorFactController' => 'PhabricatorController', 9234 9239 'PhabricatorFactCursor' => 'PhabricatorFactDAO',
+5 -2
src/applications/fact/controller/PhabricatorFactChartController.php
··· 74 74 'hardpoint' => $id, 75 75 'x' => array($x), 76 76 'y' => array($y), 77 + 'yMax' => max(0, max($y)), 78 + 'yMin' => min(0, min($y)), 77 79 'xformat' => 'epoch', 78 80 'colors' => array('#0000ff'), 79 81 )); ··· 82 84 ->setHeaderText(pht('Count of %s', $fact->getName())) 83 85 ->appendChild($chart); 84 86 85 - $crumbs = $this->buildApplicationCrumbs(); 86 - $crumbs->addTextCrumb(pht('Chart')); 87 + $crumbs = $this->buildApplicationCrumbs() 88 + ->addTextCrumb(pht('Chart')) 89 + ->setBorder(true); 87 90 88 91 $title = pht('Chart'); 89 92
+69
src/applications/fact/storage/PhabricatorFactChart.php
··· 1 + <?php 2 + 3 + final class PhabricatorFactChart 4 + extends PhabricatorFactDAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $chartKey; 8 + protected $chartParameters = array(); 9 + 10 + protected function getConfiguration() { 11 + return array( 12 + self::CONFIG_SERIALIZATION => array( 13 + 'chartParameters' => self::SERIALIZATION_JSON, 14 + ), 15 + self::CONFIG_COLUMN_SCHEMA => array( 16 + 'chartKey' => 'bytes12', 17 + ), 18 + self::CONFIG_KEY_SCHEMA => array( 19 + 'key_chart' => array( 20 + 'columns' => array('chartKey'), 21 + 'unique' => true, 22 + ), 23 + ), 24 + ) + parent::getConfiguration(); 25 + } 26 + 27 + public function setChartParameter($key, $value) { 28 + $this->chartParameters[$key] = $value; 29 + return $this; 30 + } 31 + 32 + public function getChartParameter($key, $default = null) { 33 + return idx($this->chartParameters, $key, $default); 34 + } 35 + 36 + public function save() { 37 + if ($this->getID()) { 38 + throw new Exception( 39 + pht( 40 + 'Chart configurations are not mutable. You can not update or '. 41 + 'overwrite an existing chart configuration.')); 42 + } 43 + 44 + $digest = serialize($this->chartParameters); 45 + $digest = PhabricatorHash::digestForIndex($digest); 46 + 47 + $this->chartKey = $digest; 48 + 49 + return parent::save(); 50 + } 51 + 52 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 53 + 54 + public function getCapabilities() { 55 + return array( 56 + PhabricatorPolicyCapability::CAN_VIEW, 57 + ); 58 + } 59 + 60 + public function getPolicy($capability) { 61 + return PhabricatorPolicies::getMostOpenPolicy(); 62 + } 63 + 64 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 65 + return false; 66 + } 67 + 68 + 69 + }
+5 -7
webroot/rsrc/js/application/maniphest/behavior-line-chart.js
··· 8 8 9 9 JX.behavior('line-chart', function(config) { 10 10 11 - function fn(n) { 11 + function css_function(n) { 12 12 return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; 13 13 } 14 14 ··· 50 50 .attr('class', 'chart'); 51 51 52 52 var g = svg.append('g') 53 - .attr('transform', fn('translate', padding.left, padding.top)); 53 + .attr('transform', css_function('translate', padding.left, padding.top)); 54 54 55 55 g.append('rect') 56 56 .attr('class', 'inner') ··· 73 73 x.domain(d3.extent(data, function(d) { return d.date; })); 74 74 75 75 var yex = d3.extent(data, function(d) { return d.count; }); 76 - yex[0] = 0; 77 - yex[1] = yex[1] * 1.05; 78 - y.domain(yex); 76 + y.domain([config.yMin, config.yMax]); 79 77 80 78 g.append('path') 81 79 .datum(data) ··· 84 82 85 83 g.append('g') 86 84 .attr('class', 'x axis') 87 - .attr('transform', fn('translate', 0, size.height)) 85 + .attr('transform', css_function('translate', 0, size.height)) 88 86 .call(xAxis); 89 87 90 88 g.append('g') 91 89 .attr('class', 'y axis') 92 - .attr('transform', fn('translate', 0, 0)) 90 + .attr('transform', css_function('translate', 0, 0)) 93 91 .call(yAxis); 94 92 95 93 var div = d3.select('body')