Merge lp:~edwin-grubbs/launchpad/bug-399554-timeline-improvements into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/bug-399554-timeline-improvements
Merge into: lp:launchpad
Diff against target: 758 lines
8 files modified
lib/canonical/launchpad/javascript/lp/dragscroll.js (+49/-8)
lib/canonical/launchpad/javascript/registry/tests/timeline-iframe.html (+1/-1)
lib/canonical/launchpad/javascript/registry/tests/timeline.js (+27/-8)
lib/canonical/launchpad/javascript/registry/timeline.js (+210/-103)
lib/lp/registry/model/product.py (+6/-3)
lib/lp/registry/model/productseries.py (+1/-0)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+3/-0)
lib/lp/registry/templates/timeline-macros.pt (+2/-1)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-399554-timeline-improvements
Reviewer Review Type Date Requested Status
Martin Albisetti (community) ui Approve
Michael Nelson (community) ui* Approve
Gavin Panella (community) Approve
Review via email: mp+12952@code.launchpad.net

Commit message

Improve timeline graph by showing more information in the portlet on the project index page.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (3.4 KiB)

Summary
-------

Improves the timeline graph portlet on the project index page by allowing
more information to be seen without scrolling.

Implementation details
----------------------

Hid dates for milestones/releases when showing the graph on the project index
page. Moved horizontal series lines closer together. Added a date at the end of
the series line, if at least one of the milestones or releases has a date.
Removed the arrow from obsolete series and made its horizontal line gray.

Tests
-----

file:///./lib/canonical/launchpad/javascript/registry/tests/timeline.html
./bin/lp-windmill test=./lib/canonical/launchpad/windmill/tests/test_registry/test_timeline_graph.py firefox http://launchpad.dev:8085

Demo and Q/A
------------

* Open http://launchpad.dev/firefox
  * The timeline graph portlet should be bigger and only show dates at the
    end of the line.

Lint
----

I fixed all the lint errors except these rediculous ones regarding for-loops.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/javascript/registry/tests/timeline.js
  lib/canonical/launchpad/javascript/registry/timeline.js
  lib/lp/registry/model/productseries.py
  lib/lp/registry/templates/timeline-macros.pt

== JSLint notices ==
jslint: Lint found in '/home/egrubbs/canonical/lp-branches/checkout/lib/canonical/launchpad/javascript/registry/timeline.js':
Line 110 character 5: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
    for (var i in this.series.landmarks) {

Line 133 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (i in this.series.landmarks) {

Line 174 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var i in this.series.landmarks) {

Line 238 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var i in this.series.landmarks) {

Line 296 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var i in reversed_timeline) {

Line 310 character 13: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
            for (i in this.series_lines) {

Line 325 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var i in this.series_lines) {

Line 357 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var key in last_series.labels) {

Line 374 character 9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
        for (var i in this.series_lines) {

Line 499 character 13: The body of a for in should be wrapped in an if statement to filter unwanted properties f...

Read more...

Revision history for this message
Gavin Panella (allenap) wrote :

> Line 133 character 9: The body of a for in should be wrapped in an if
> statement to filter unwanted properties from the prototype.
> for (i in this.series.landmarks) {

The error above is not very helpful, but the MDC does recommend against using for...in loops on Arrays:

  "Although it may be tempting to use this as a way to iterate over an
   Array, this is a bad idea. The for...in statement iterates over
   user-defined properties in addition to the array elements"

    -- https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Statements/for...in#Description

I don't think you've added any properties to these arrays, but I think it's better to code defensively here. It'll also shut jslint up ;)

What do you think?

Revision history for this message
Gavin Panella (allenap) wrote :
Download full text (18.8 KiB)

Hi Edwin,

This is a really useful change, and the code looks good :)

I have several questions and comments, so I'll mark it as Needs
Information, though some things might also need fixing, depending on
those questions. In any case, these are only minor things.

Gavin.

> === modified file 'lib/canonical/launchpad/javascript/registry/tests/timeline.js'
> --- lib/canonical/launchpad/javascript/registry/tests/timeline.js 2009-07-16 16:55:46 +0000
> +++ lib/canonical/launchpad/javascript/registry/tests/timeline.js 2009-10-07 11:07:13 +0000
> @@ -55,7 +55,17 @@
> {name: '6', landmarks: [], is_development_focus: false},
> {name: '7', landmarks: [], is_development_focus: false},
> {name: '8', landmarks: [], is_development_focus: false},
> - {name: '9', landmarks: [], is_development_focus: false}
> + {name: '9', is_development_focus: false,
> + landmarks: [
> + {
> + 'code_name': 'zamboni',
> + 'date': '2200-05-26',
> + 'name': 'ski',
> + 'type': 'milestone',
> + 'uri': 'file:///firefox/+milestone/alpha'
> + }
> + ]
> + }
> ],
> resize_frame: 'timeline-iframe'
> };
> @@ -152,6 +162,15 @@
> this.timeline_graph.destroy();
> },
>
> + test_milestone_label_second_line: function() {
> + var label = this.content_box.query('div#ski');
> + var second_line = label.query('div');
> + Assert.areEqual(
> + '2200-05-26',
> + second_line.get('innerHTML'),
> + "Unexpected milestone date.");
> + },
> +
> test_resize_frame: function() {
> var frame = parent.document.getElementById(
> this.timeline_graph.resize_frame);
> @@ -166,7 +185,7 @@
> Assert.areEqual(1, this.timeline_graph.graph_scale);
> Assert.areEqual(
> canvas.get('offsetHeight'), frame.height,
> - 'The frame was not resized to match the canvas.');
> + '(1st) The frame was not resized to match the canvas.');
>
> simulate(
> this.timeline_graph, '.yui-timelinegraph-zoom-in', 'click');
> @@ -177,7 +196,7 @@
> Assert.areEqual(1.1, this.timeline_graph.graph_scale);
> Assert.areEqual(
> canvas.get('offsetHeight'), frame.height,
> - 'The frame was not resized to match the canvas.');
> + '(2nd) The frame was not resized to match the canvas.');
> Assert.isTrue(
> canvas.get('offsetHeight') > first_canvas_height,
> 'The canvas did not get scaled.');
> @@ -189,7 +208,7 @@
> Assert.areEqual(1, this.timeline_graph.graph_scale);
> Assert.areEqual(
> canvas.get('offsetHeight'), frame.height,
> - 'The frame was not resized to match the canvas.');
> + '(3rd) The frame was not resized to match the canvas.');
> }
> }));
>
> @@ -235,10 +254,10 @@
> "Unexpected milestone link href.");
>
> var second_line = label.query('div');
> - Assert.areEqual(
> - '2056-10-16...

review: Needs Information (code)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (27.8 KiB)

Hi Gavin,

Thanks for the review. An incremental diff is included at the bottom.

-Edwin

On Wed, Oct 7, 2009 at 8:20 AM, Gavin Panella
<email address hidden> wrote:
> Review: Needs Information code
> Hi Edwin,
>
> This is a really useful change, and the code looks good :)
>
> I have several questions and comments, so I'll mark it as Needs
> Information, though some things might also need fixing, depending on
> those questions. In any case, these are only minor things.
>
> Gavin.
>
>
>> === modified file 'lib/canonical/launchpad/javascript/registry/timeline.js'
>> --- lib/canonical/launchpad/javascript/registry/timeline.js   2009-07-27 14:27:53 +0000
>> +++ lib/canonical/launchpad/javascript/registry/timeline.js   2009-10-07 11:07:13 +0000
>> @@ -10,34 +10,33 @@
>>
>>  var module = Y.namespace('registry.timeline');
>>
>> -TIMELINE_GRAPH = 'timelinegraph';
>> +var TIMELINE_GRAPH = 'timelinegraph';
>> +var OBSOLETE_SERIES_STATUS = 'Obsolete';
>>  var getCN = Y.ClassNameManager.getClassName;
>>  var C_ZOOM_BOX = getCN(TIMELINE_GRAPH, 'zoom-box');
>>  var C_ZOOM_IN = getCN(TIMELINE_GRAPH, 'zoom-in');
>>  var C_ZOOM_OUT = getCN(TIMELINE_GRAPH, 'zoom-out');
>>  var SECOND_MOUSE_BUTTON = 2;
>>  // px spacing and sizes.
>> -var MARGIN_LEFT = 80;
>> +var MARGIN_LEFT = 20;
>>  var MARGIN_TOP = 25;
>>  var MARGIN_BOTTOM = 10;
>> -var Y_SPACING = 60;
>> -var LEGEND_INDENT= 90;
>> -var LEGEND_SPACING = 20;
>> -var LEGEND_BOX_PADDING = 7;
>>  var MILESTONE_RADIUS = 5;
>>  var RELEASE_RADIUS = 5;
>>  var ARROW_HEIGHT = 10;
>>  var ARROW_WIDTH = 15;
>>  // Defines angle of vertical timeline.
>> -var ANGLE_X_SPACING = 0.3 * Y_SPACING;
>> +var ANGLE_DEGREES = 84;
>> +var ANGLE_RADIANS = ANGLE_DEGREES / 180 * Math.PI;
>> +var ANGLE_TANGENT = Math.tan(ANGLE_RADIANS);
>>  // Font size in em's.
>>  var FONT_SIZE = 1;
>>  // Colors.
>>  var LINE_COLOR = 'darkgreen';
>> +var OBSOLETE_SERIES_COLOR = '#777777';
>>  var MILESTONE_LINE_COLOR = 'darkgray';
>>  var MILESTONE_FILL_COLOR = 'white';
>>  var RELEASE_COLOR = 'black';
>> -var LEGEND_BOX_COLOR = 'black';
>>  var ARROW_COLOR = LINE_COLOR;
>>  // Zoom level (increase/decrease 10%)
>>  var ZOOM_JUMPS = 1.1;
>> @@ -96,7 +95,7 @@
>>      this.timeline_graph = timeline_graph;
>>      this.series = series;
>>      this.start = start;
>> -    var tooltip = 'Series';
>> +    var tooltip = this.series.status + ' Series';
>>      if (this.series.is_development_focus) {
>>          tooltip = 'Development Focus Series';
>>      }
>> @@ -105,18 +104,41 @@
>>      this.series_label = this.timeline_graph.make_label(
>>          label_text, tooltip, this.series.uri,
>>          {id: series.name});
>> +
>>      this.labels = {};
>>      for (var i in this.series.landmarks) {
>> -        i = parseInt(i, 10);
>>          var landmark = this.series.landmarks[i];
>>          var landmark_tooltip =
>>              landmark.type.charAt(0).toUpperCase() + landmark.type.substr(1);
>>          if (Y.Lang.isString(landmark.code_name)) {
>>              landmark_tooltip += ': ' + landmark.code_name;
>>          }
>> +
>> +        var cfg = {id: landmark.name};
>> +        if (Y.Lang.isString(this.timeline_graph.resize_frame)) {
>> + ...

Revision history for this message
Gavin Panella (allenap) wrote :

On Thu, 08 Oct 2009 05:39:10 -0000
Edwin Grubbs <email address hidden> wrote:

> Hi Gavin,
>
> Thanks for the review. An incremental diff is included at the bottom.

The diff looks really good, and thanks for all your explanations :)

 review approve
 merge approve

> It took me quite a while to understand how evil prototype
> is. Ahhhhhh. I added an if-statement, although I think that there is
> a good chance that editing the prototype of built-in objects would
> actually cause the downfall of society, in which case, no bug would
> be reported.

:)

On one hand I quite like those libraries that monkeypatch
Array.prototype, String.prototype and others to add familiar and/or
useful methods. But it does make iterating through stuff so much more
difficult. Javascript would benefit from a better iterator protocol
(and, iirc, is getting one or has one in newer specs).

review: Approve
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Wow - that is amazing Edwin! ui=me* with two suggestions/thoughts below.

In addition to the things you've mentioned, it also looks like you've also reduced the angle of the series line? Which means that it's a much better use of whitespace.

I see that you've ensured that the timeline always displays the current development focus tip by default? That's great - I wonder whether it would make sense to have the series name right-aligned along the milestone lines rather than centred? (ie. so it's always visible in the default project-index display such as shown at:
https://chinstrap.canonical.com/~egrubbs/project_index_page_timeline.png
) Was there a reason for it being centred?

This is probably just related to the state of data on my local lp.dev, but after I ran the script to add the series and milestones, I expected all the 3-6 series to be above the 1.0 series - but they're not:

http://people.canonical.com/~michaeln/tmp/timeline_ordering.png

perhaps it's because currently trunk (0.x) is the development focus? Ah yep, that did it - setting myseries-6 as the development focus (although 1.0 is still the next in the graph?).

Finally, regarding the 'grabability' mentioned in the bug - I wonder whether having separate icons for mouseover and mousedown would help? When I first played with it here, I wondered about the hand icon, clicked and nothing happened. If we had an open-hand-cursor during mouseover, and then when I click (more specifically, while the mouse button is down) the cursor changed to a fist - it might give people more visual feedback? (try clicking when viewing a photo in the default eye-of-gnome app).

Thanks for the excellent work - looking forward to seeing it on edge!

review: Approve (ui*)
Revision history for this message
Martin Albisetti (beuno) wrote :

Wonderful branch, and wonderful review.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/javascript/lp/dragscroll.js'
--- lib/canonical/launchpad/javascript/lp/dragscroll.js 2009-06-10 04:06:59 +0000
+++ lib/canonical/launchpad/javascript/lp/dragscroll.js 2009-10-09 03:26:12 +0000
@@ -12,6 +12,7 @@
12DragScrollEventHandler = function() {12DragScrollEventHandler = function() {
13 this.dragging = false;13 this.dragging = false;
14 this.last_position = null;14 this.last_position = null;
15 this.event_listeners = [];
15}16}
1617
17DragScrollEventHandler.prototype = {18DragScrollEventHandler.prototype = {
@@ -22,11 +23,11 @@
22 * @method activate23 * @method activate
23 */24 */
24 activate: function() {25 activate: function() {
25 document.addEventListener("mousedown", this._startDragScroll, false);26 this._addEventListener("mousedown", this._startDragScroll);
26 document.addEventListener("mouseup", this._stopDragScroll, false);27 this._addEventListener("mouseup", this._stopDragScroll);
27 document.addEventListener("mouseout", this._stopDragScroll, false);28 this._addEventListener("mouseout", this._stopDragScroll);
28 document.addEventListener("mousemove", this._dragScroll, false);29 this._addEventListener("mousemove", this._dragScroll);
29 document.body.style.cursor = 'move';30 this._setGrabCursor();
30 },31 },
3132
32 /**33 /**
@@ -38,12 +39,50 @@
38 deactivate: function() {39 deactivate: function() {
39 document.removeEventListener(40 document.removeEventListener(
40 "mousedown", this._startDragScroll, false);41 "mousedown", this._startDragScroll, false);
41 document.removeEventListener("mouseup", this._stopDragScroll, false);42 this._removeEventListeners();
42 document.removeEventListener("mouseout", this._stopDragScroll, false);43 this._unsetCursor();
43 document.removeEventListener("mousemove", this._dragScroll, false);44 },
45
46 _addEventListener: function(event_type, action) {
47 // Wrap the method in a different function that forces
48 // `this` to be the `DragScrollEventHandler` object.
49 var self = this;
50 var event_listener = function(e) {
51 action.call(self, e)
52 };
53 var event_args = [event_type, event_listener, false];
54 this.event_listeners.push(event_args);
55 document.addEventListener.apply(document, event_args);
56 },
57
58 _removeEventListeners: function() {
59 for (var i=0; i<this.event_listeners.length; i++) {
60 var event_args = this.event_listeners[i];
61 document.removeEventListener.apply(document, event_args);
62 }
63 },
64
65 _unsetCursor: function() {
44 document.body.style.cursor = '';66 document.body.style.cursor = '';
45 },67 },
4668
69 _setGrabCursor: function() {
70 // Styles for W3C, IE, Mozilla, Webkit.
71 // Unknown styles will fail to change the value.
72 document.body.style.cursor = 'move';
73 document.body.style.cursor = 'grab';
74 document.body.style.cursor = '-moz-grab';
75 document.body.style.cursor = '-webkit-grab';
76 },
77
78 _setGrabbingCursor: function() {
79 // Styles for IE, Mozilla, and Webkit.
80 // Unknown styles will fail to change the value.
81 document.body.style.cursor = 'grabbing';
82 document.body.style.cursor = '-moz-grabbing';
83 document.body.style.cursor = '-webkit-grabbing';
84 },
85
47 /**86 /**
48 * MouseDown event handler that causes _dragScroll to87 * MouseDown event handler that causes _dragScroll to
49 * take action when it receives a MouseMove event.88 * take action when it receives a MouseMove event.
@@ -54,6 +93,7 @@
54 if (e.button == 0) {93 if (e.button == 0) {
55 this.dragging = true;94 this.dragging = true;
56 this.last_position = e;95 this.last_position = e;
96 this._setGrabbingCursor();
57 }97 }
58 e.preventDefault();98 e.preventDefault();
59 e.stopPropagation();99 e.stopPropagation();
@@ -69,6 +109,7 @@
69 */109 */
70 _stopDragScroll: function(e) {110 _stopDragScroll: function(e) {
71 this.dragging = false;111 this.dragging = false;
112 this._setGrabCursor();
72 e.preventDefault();113 e.preventDefault();
73 e.stopPropagation();114 e.stopPropagation();
74 },115 },
75116
=== modified file 'lib/canonical/launchpad/javascript/registry/tests/timeline-iframe.html'
--- lib/canonical/launchpad/javascript/registry/tests/timeline-iframe.html 2009-07-01 02:25:33 +0000
+++ lib/canonical/launchpad/javascript/registry/tests/timeline-iframe.html 2009-10-09 03:26:12 +0000
@@ -17,7 +17,7 @@
17 <style>17 <style>
18 /* Taken and customized from testlogger.css */18 /* Taken and customized from testlogger.css */
19 #log .yui-console-content { width:44em }19 #log .yui-console-content { width:44em }
20 #log .yui-console .yui-console-bd { height:30em }20 #log .yui-console .yui-console-bd { height:100%}
21 #log .yui-console .yui-console-controls { display:none; }21 #log .yui-console .yui-console-controls { display:none; }
22 #log .yui-console .yui-console-hd { display:none; }22 #log .yui-console .yui-console-hd { display:none; }
23 #log .yui-console .yui-console-ft { position:absolute;top:0em; }23 #log .yui-console .yui-console-ft { position:absolute;top:0em; }
2424
=== modified file 'lib/canonical/launchpad/javascript/registry/tests/timeline.js'
--- lib/canonical/launchpad/javascript/registry/tests/timeline.js 2009-07-16 16:55:46 +0000
+++ lib/canonical/launchpad/javascript/registry/tests/timeline.js 2009-10-09 03:26:12 +0000
@@ -55,7 +55,17 @@
55 {name: '6', landmarks: [], is_development_focus: false},55 {name: '6', landmarks: [], is_development_focus: false},
56 {name: '7', landmarks: [], is_development_focus: false},56 {name: '7', landmarks: [], is_development_focus: false},
57 {name: '8', landmarks: [], is_development_focus: false},57 {name: '8', landmarks: [], is_development_focus: false},
58 {name: '9', landmarks: [], is_development_focus: false}58 {name: '9', is_development_focus: false,
59 landmarks: [
60 {
61 'code_name': 'zamboni',
62 'date': '2200-05-26',
63 'name': 'ski',
64 'type': 'milestone',
65 'uri': 'file:///firefox/+milestone/alpha'
66 }
67 ]
68 }
59 ],69 ],
60 resize_frame: 'timeline-iframe'70 resize_frame: 'timeline-iframe'
61};71};
@@ -152,6 +162,15 @@
152 this.timeline_graph.destroy();162 this.timeline_graph.destroy();
153 },163 },
154164
165 test_milestone_label_second_line: function() {
166 var label = this.content_box.query('div#ski');
167 var second_line = label.query('div');
168 Assert.areEqual(
169 '2200-05-26',
170 second_line.get('innerHTML'),
171 "Unexpected milestone date.");
172 },
173
155 test_resize_frame: function() {174 test_resize_frame: function() {
156 var frame = parent.document.getElementById(175 var frame = parent.document.getElementById(
157 this.timeline_graph.resize_frame);176 this.timeline_graph.resize_frame);
@@ -166,7 +185,7 @@
166 Assert.areEqual(1, this.timeline_graph.graph_scale);185 Assert.areEqual(1, this.timeline_graph.graph_scale);
167 Assert.areEqual(186 Assert.areEqual(
168 canvas.get('offsetHeight'), frame.height,187 canvas.get('offsetHeight'), frame.height,
169 'The frame was not resized to match the canvas.');188 '(1st) The frame was not resized to match the canvas.');
170189
171 simulate(190 simulate(
172 this.timeline_graph, '.yui-timelinegraph-zoom-in', 'click');191 this.timeline_graph, '.yui-timelinegraph-zoom-in', 'click');
@@ -177,7 +196,7 @@
177 Assert.areEqual(1.1, this.timeline_graph.graph_scale);196 Assert.areEqual(1.1, this.timeline_graph.graph_scale);
178 Assert.areEqual(197 Assert.areEqual(
179 canvas.get('offsetHeight'), frame.height,198 canvas.get('offsetHeight'), frame.height,
180 'The frame was not resized to match the canvas.');199 '(2nd) The frame was not resized to match the canvas.');
181 Assert.isTrue(200 Assert.isTrue(
182 canvas.get('offsetHeight') > first_canvas_height,201 canvas.get('offsetHeight') > first_canvas_height,
183 'The canvas did not get scaled.');202 'The canvas did not get scaled.');
@@ -189,7 +208,7 @@
189 Assert.areEqual(1, this.timeline_graph.graph_scale);208 Assert.areEqual(1, this.timeline_graph.graph_scale);
190 Assert.areEqual(209 Assert.areEqual(
191 canvas.get('offsetHeight'), frame.height,210 canvas.get('offsetHeight'), frame.height,
192 'The frame was not resized to match the canvas.');211 '(3rd) The frame was not resized to match the canvas.');
193 }212 }
194}));213}));
195214
@@ -235,10 +254,10 @@
235 "Unexpected milestone link href.");254 "Unexpected milestone link href.");
236255
237 var second_line = label.query('div');256 var second_line = label.query('div');
238 Assert.areEqual(257 Assert.isNull(
239 '2056-10-16',258 second_line,
240 second_line.get('innerHTML'),259 "There should be no second line for landmarks when " +
241 "Unexpected milestone date.");260 "resize_frame is false.");
242 }261 }
243}));262}));
244263
245264
=== modified file 'lib/canonical/launchpad/javascript/registry/timeline.js'
--- lib/canonical/launchpad/javascript/registry/timeline.js 2009-07-27 14:27:53 +0000
+++ lib/canonical/launchpad/javascript/registry/timeline.js 2009-10-09 03:26:12 +0000
@@ -10,34 +10,33 @@
1010
11var module = Y.namespace('registry.timeline');11var module = Y.namespace('registry.timeline');
1212
13TIMELINE_GRAPH = 'timelinegraph';13var TIMELINE_GRAPH = 'timelinegraph';
14var OBSOLETE_SERIES_STATUS = 'Obsolete';
14var getCN = Y.ClassNameManager.getClassName;15var getCN = Y.ClassNameManager.getClassName;
15var C_ZOOM_BOX = getCN(TIMELINE_GRAPH, 'zoom-box');16var C_ZOOM_BOX = getCN(TIMELINE_GRAPH, 'zoom-box');
16var C_ZOOM_IN = getCN(TIMELINE_GRAPH, 'zoom-in');17var C_ZOOM_IN = getCN(TIMELINE_GRAPH, 'zoom-in');
17var C_ZOOM_OUT = getCN(TIMELINE_GRAPH, 'zoom-out');18var C_ZOOM_OUT = getCN(TIMELINE_GRAPH, 'zoom-out');
18var SECOND_MOUSE_BUTTON = 2;19var SECOND_MOUSE_BUTTON = 2;
19// px spacing and sizes.20// px spacing and sizes.
20var MARGIN_LEFT = 80;21var MARGIN_LEFT = 20;
21var MARGIN_TOP = 25;22var MARGIN_TOP = 25;
22var MARGIN_BOTTOM = 10;23var MARGIN_BOTTOM = 10;
23var Y_SPACING = 60;
24var LEGEND_INDENT= 90;
25var LEGEND_SPACING = 20;
26var LEGEND_BOX_PADDING = 7;
27var MILESTONE_RADIUS = 5;24var MILESTONE_RADIUS = 5;
28var RELEASE_RADIUS = 5;25var RELEASE_RADIUS = 5;
29var ARROW_HEIGHT = 10;26var ARROW_HEIGHT = 10;
30var ARROW_WIDTH = 15;27var ARROW_WIDTH = 15;
31// Defines angle of vertical timeline.28// Defines angle of vertical timeline.
32var ANGLE_X_SPACING = 0.3 * Y_SPACING;29var ANGLE_DEGREES = 84;
30var ANGLE_RADIANS = ANGLE_DEGREES / 180 * Math.PI;
31var ANGLE_TANGENT = Math.tan(ANGLE_RADIANS);
33// Font size in em's.32// Font size in em's.
34var FONT_SIZE = 1;33var FONT_SIZE = 1;
35// Colors.34// Colors.
36var LINE_COLOR = 'darkgreen';35var LINE_COLOR = 'darkgreen';
36var OBSOLETE_SERIES_COLOR = '#777777';
37var MILESTONE_LINE_COLOR = 'darkgray';37var MILESTONE_LINE_COLOR = 'darkgray';
38var MILESTONE_FILL_COLOR = 'white';38var MILESTONE_FILL_COLOR = 'white';
39var RELEASE_COLOR = 'black';39var RELEASE_COLOR = 'black';
40var LEGEND_BOX_COLOR = 'black';
41var ARROW_COLOR = LINE_COLOR;40var ARROW_COLOR = LINE_COLOR;
42// Zoom level (increase/decrease 10%)41// Zoom level (increase/decrease 10%)
43var ZOOM_JUMPS = 1.1;42var ZOOM_JUMPS = 1.1;
@@ -50,15 +49,10 @@
50 */49 */
51var draw_line = function(canvas_context, points, fill) {50var draw_line = function(canvas_context, points, fill) {
52 canvas_context.beginPath();51 canvas_context.beginPath();
53 for (i in points) {52 canvas_context.moveTo(points[0].x, points[0].y);
54 if (i === 0) {53 Y.each(points.slice(1), function(point, i) {
55 // Starting the line.54 canvas_context.lineTo(point.x, point.y);
56 canvas_context.moveTo(points[i].x, points[i].y);55 });
57 } else {
58 // Specify points.
59 canvas_context.lineTo(points[i].x, points[i].y);
60 }
61 }
62 // Draw!56 // Draw!
63 if (fill === true) {57 if (fill === true) {
64 canvas_context.fill();58 canvas_context.fill();
@@ -96,28 +90,59 @@
96 this.timeline_graph = timeline_graph;90 this.timeline_graph = timeline_graph;
97 this.series = series;91 this.series = series;
98 this.start = start;92 this.start = start;
99 var tooltip = 'Series';93 var tooltip = this.series.status + ' Series';
100 if (this.series.is_development_focus) {94 if (this.series.is_development_focus) {
101 tooltip = 'Development Focus Series';95 tooltip = 'Development Focus Series';
102 }96 }
103 var label_text = Y.Node.create('<strong/>');97
104 label_text.appendChild(document.createTextNode(series.name));
105 this.series_label = this.timeline_graph.make_label(
106 label_text, tooltip, this.series.uri,
107 {id: series.name});
108 this.labels = {};98 this.labels = {};
109 for (var i in this.series.landmarks) {99 Y.each(this.series.landmarks, function(landmark, i) {
110 i = parseInt(i, 10);
111 var landmark = this.series.landmarks[i];
112 var landmark_tooltip =100 var landmark_tooltip =
113 landmark.type.charAt(0).toUpperCase() + landmark.type.substr(1);101 landmark.type.charAt(0).toUpperCase() + landmark.type.substr(1);
114 if (Y.Lang.isString(landmark.code_name)) {102 if (Y.Lang.isString(landmark.code_name)) {
115 landmark_tooltip += ': ' + landmark.code_name;103 landmark_tooltip += ': ' + landmark.code_name;
116 }104 }
105
106 var cfg = {id: landmark.name};
107 if (Y.Lang.isString(this.timeline_graph.resize_frame)) {
108 cfg.second_line = landmark.date;
109 }
117 this.labels[landmark.name] = this.timeline_graph.make_label(110 this.labels[landmark.name] = this.timeline_graph.make_label(
118 landmark.name, landmark_tooltip, landmark.uri,111 landmark.name, landmark_tooltip, landmark.uri, cfg);
119 {second_line: landmark.date, id: landmark.name});112 }, this);
113
114 // If the frame is not going to be resized, the dates are
115 // not displayed under the landmarks, so a single date
116 // is displayed at the end of the series line where it
117 // will not increase the vertical spacing.
118 this.series_date_label = null;
119 if (!Y.Lang.isString(this.timeline_graph.resize_frame)) {
120 for (var i=0; i < this.series.landmarks.length; i++) {
121 var landmark = this.series.landmarks[i];
122 if (landmark.date !== null) {
123 this.series_date_label = this.timeline_graph.make_label(
124 '', 'Last date in series', this.series.uri,
125 {second_line: landmark.date,
126 id: series.name + '-' + landmark.date});
127 break;
128 }
129 }
120 }130 }
131
132 // Center series label.
133 var label_text = Y.Node.create('<strong/>');
134 label_text.appendChild(document.createTextNode(series.name));
135 this.center_series_label = this.timeline_graph.make_label(
136 label_text, tooltip, this.series.uri,
137 {id: series.name});
138 // Left label.
139 this.left_series_label = this.timeline_graph.make_label(
140 '', tooltip, this.series.uri,
141 {second_line: series.name, id: series.name});
142 // Right label.
143 this.right_series_label = this.timeline_graph.make_label(
144 '', tooltip, this.series.uri,
145 {second_line: series.name, id: series.name});
121};146};
122147
123SeriesLine.prototype = {148SeriesLine.prototype = {
@@ -128,10 +153,36 @@
128 * @method get_length153 * @method get_length
129 */154 */
130 get_length: function() {155 get_length: function() {
156 // No arrow at the end of obsolete series lines.
157 var length = 0;
158 if (this.series.status == OBSOLETE_SERIES_STATUS) {
159 length = this.series.landmarks.length *
160 this.timeline_graph.landmark_spacing;
161 } else {
162 length = (this.series.landmarks.length + 1) *
163 this.timeline_graph.landmark_spacing;
164 }
131 // Display a line stub for series without any landmarks.165 // Display a line stub for series without any landmarks.
132 return (this.series.landmarks.length + 1) *166 return Math.max(length, this.timeline_graph.min_series_line_length);
133 this.timeline_graph.landmark_spacing;167 },
134 },168
169 /**
170 * Calculate the vertical spacing of the horizontal lines based on twice
171 * the height of the series name, plus the height of the landmark text,
172 * which may or may not have a second line for the date.
173 *
174 * @method get_y_spacing()
175 */
176 get_y_spacing: function() {
177 var max_y = 0;
178 Y.each(this.series.landmarks, function(landmark, i) {
179 var label = this.labels[landmark.name];
180 max_y = Math.max(label.get('offsetHeight'));
181 }, this);
182 return max_y + (2 * RELEASE_RADIUS) +
183 this.center_series_label.get('offsetHeight');
184 },
185
135186
136 /**187 /**
137 * The main method which is called by the ProjectLine.draw()188 * The main method which is called by the ProjectLine.draw()
@@ -146,38 +197,74 @@
146 this.start.x + this.get_length(),197 this.start.x + this.get_length(),
147 this.start.y);198 this.start.y);
148199
149 if (this.series.is_development_focus === true) {200 var thickness, offset;
150 // Draw a thick line as a rectangle.201 // Draw a line of various thicknesses as a rectangle.
151 var thickness = 4;202 if (this.series.status == OBSOLETE_SERIES_STATUS) {
152 var offset = -2;203 thickness = 2;
153 context.fillStyle = LINE_COLOR;204 offset = -1;
154 context.fillRect(205 context.fillStyle = OBSOLETE_SERIES_COLOR;
155 this.start.x,206 } else if (this.series.is_development_focus) {
156 this.start.y + offset,207 thickness = 4;
157 stop.x - this.start.x,208 offset = -2;
158 stop.y - this.start.y + thickness);209 context.fillStyle = LINE_COLOR;
159 }210 } else {
160 else {211 thickness = 1;
161 // We can't draw a 1 pixel wide rectangle reliably, so212 offset = 0;
162 // we have to use the line drawing method.213 context.fillStyle = LINE_COLOR;
163 context.strokeStyle = LINE_COLOR;214 }
164 draw_line(context, [this.start, stop]);215 context.fillRect(
165 }216 this.start.x,
217 this.start.y + offset,
218 stop.x - this.start.x,
219 stop.y - this.start.y + thickness);
166220
167 // Arrow at end of series line.221 // Arrow at end of series line.
168 this.timeline_graph.make_landmark(stop, 'arrow');222 if (this.series.status != OBSOLETE_SERIES_STATUS) {
223 this.timeline_graph.make_landmark(stop, 'arrow');
224 }
169225
170 // Series label.226 // Center series label.
171 var label_position = new Position(227 var center_position = new Position(
172 this.start.x + (this.get_length() / 2),228 this.start.x + (this.get_length() / 2),
173 this.start.y - RELEASE_RADIUS);229 this.start.y - RELEASE_RADIUS);
174 this.timeline_graph.place_label(230 this.timeline_graph.place_label(
175 label_position, this.series_label, 'center', 'above');231 center_position, this.center_series_label, 'center', 'above');
232
233
234 // Only show the left and right series labels if the
235 // series line is wider than the viewport (iframe).
236 var line_width = this.get_length() * this.timeline_graph.graph_scale;
237 if (line_width < Y.DOM.winWidth()) {
238 this.left_series_label.setStyle('display', 'none');
239 this.right_series_label.setStyle('display', 'none');
240 } else {
241 this.left_series_label.setStyle('display', 'block');
242 this.right_series_label.setStyle('display', 'block');
243
244 // Left series label.
245 var left_position = new Position(
246 this.start.x + 10,
247 this.start.y - RELEASE_RADIUS);
248 this.timeline_graph.place_label(
249 left_position, this.left_series_label, 'right', 'above');
250
251 // Right series label.
252 var right_position = new Position(
253 this.start.x + this.get_length(),
254 this.start.y - RELEASE_RADIUS);
255 this.timeline_graph.place_label(
256 right_position, this.right_series_label, 'left', 'above');
257 }
258
259 if (this.series_date_label !== null) {
260 var label_position = new Position(
261 stop.x + (ARROW_WIDTH / 2), this.start.y);
262 this.timeline_graph.place_label(
263 label_position, this.series_date_label, 'right', 'middle');
264 }
176265
177 // Landmark labels.266 // Landmark labels.
178 for (var i in this.series.landmarks) {267 Y.each(this.series.landmarks, function(landmark, i) {
179 i = parseInt(i, 10);
180 var landmark = this.series.landmarks[i];
181 // The newest milestones are at the beginning, and268 // The newest milestones are at the beginning, and
182 // they need to be placed at the end of the horizontal269 // they need to be placed at the end of the horizontal
183 // line.270 // line.
@@ -196,7 +283,7 @@
196 this.timeline_graph.place_label(283 this.timeline_graph.place_label(
197 landmark_label_position, this.labels[landmark.name],284 landmark_label_position, this.labels[landmark.name],
198 'center', 'below');285 'center', 'below');
199 }286 }, this);
200 }287 }
201};288};
202289
@@ -209,19 +296,18 @@
209 * @constructor296 * @constructor
210 */297 */
211ProjectLine = function(timeline_graph, timeline) {298ProjectLine = function(timeline_graph, timeline) {
299 if (timeline.length === 0) {
300 throw new Error("The timeline array is empty.");
301 }
212 this.timeline_graph = timeline_graph;302 this.timeline_graph = timeline_graph;
213 this.timeline = timeline;303 this.timeline = timeline;
214304
215 var width = (this.timeline.length - 1) * ANGLE_X_SPACING;
216 this.start = new Position(
217 MARGIN_LEFT,
218 (this.timeline.length-1) * Y_SPACING + MARGIN_TOP);
219 this.stop = new Position(
220 this.start.x + width,
221 MARGIN_TOP);
222
223 this.series_lines = [];305 this.series_lines = [];
224 this.initSeries();306 this.initSeries();
307
308 this.start = this.series_lines[0].start.copy();
309 var last_series = this.series_lines[this.series_lines.length-1];
310 this.stop = last_series.start.copy();
225};311};
226312
227ProjectLine.prototype = {313ProjectLine.prototype = {
@@ -235,19 +321,23 @@
235 * @method initSeries321 * @method initSeries
236 */322 */
237 initSeries: function() {323 initSeries: function() {
238 for (var i in this.timeline) {324 var current = new Position(0, MARGIN_TOP);
239 // Convert index to an actual integer so it can325 var reversed_timeline = this.timeline.slice().reverse();
240 // used in calculations.326 Y.each(reversed_timeline, function(series, i) {
241 i = parseInt(i, 10);
242 var series = this.timeline[i];
243
244 var series_position = new Position(
245 this.start.x + (i * ANGLE_X_SPACING),
246 this.start.y - (i * Y_SPACING));
247
248 var series_line = new SeriesLine(327 var series_line = new SeriesLine(
249 this.timeline_graph, series, series_position);328 this.timeline_graph, series, current.copy());
250 this.series_lines.push(series_line);329 this.series_lines.push(series_line);
330
331 var height = series_line.get_y_spacing();
332 current.x -= height / ANGLE_TANGENT;
333 current.y += height;
334 }, this);
335
336 if (current.x < MARGIN_LEFT) {
337 var shift_x = -current.x + MARGIN_LEFT;
338 Y.each(this.series_lines, function(series_line, i) {
339 series_line.start.x += shift_x;
340 }, this);
251 }341 }
252 },342 },
253343
@@ -260,8 +350,8 @@
260 */350 */
261 get_width: function() {351 get_width: function() {
262 var max_x = 0;352 var max_x = 0;
263 for (var i in this.series_lines) {353 Y.each(this.series_lines, function(series_line, i) {
264 var landmarks = this.series_lines[i].series.landmarks;354 var landmarks = series_line.series.landmarks;
265 var text_beyond_last_landmark;355 var text_beyond_last_landmark;
266 if (landmarks.length === 0) {356 if (landmarks.length === 0) {
267 // Even a project with zero landmarks needs to have357 // Even a project with zero landmarks needs to have
@@ -277,12 +367,12 @@
277 this.series_lines[i].get_length() +367 this.series_lines[i].get_length() +
278 text_beyond_last_landmark;368 text_beyond_last_landmark;
279 max_x = Math.max(max_x, series_width);369 max_x = Math.max(max_x, series_width);
280 }370 }, this);
281 return max_x;371 return max_x;
282 },372 },
283373
284 /**374 /**
285 * Calculate the height based on the start.y value, which375 * Calculate the height based on the stop.y value, which
286 * is based on the number of series. It also adds the376 * is based on the number of series. It also adds the
287 * distance for the labels below the bottom series line.377 * distance for the labels below the bottom series line.
288 *378 *
@@ -293,11 +383,13 @@
293 var bottom_label_height = 0;383 var bottom_label_height = 0;
294 var last_series = this.series_lines[this.series_lines.length-1];384 var last_series = this.series_lines[this.series_lines.length-1];
295 for (var key in last_series.labels) {385 for (var key in last_series.labels) {
296 var label = last_series.labels[key];386 if (last_series.labels.hasOwnProperty(key)) {
297 bottom_label_height = Math.max(387 var label = last_series.labels[key];
298 bottom_label_height, label.get('offsetHeight'));388 bottom_label_height = Math.max(
389 bottom_label_height, label.get('offsetHeight'));
390 }
299 }391 }
300 return this.start.y + bottom_label_height + RELEASE_RADIUS;392 return this.stop.y + bottom_label_height + RELEASE_RADIUS;
301 },393 },
302394
303 /**395 /**
@@ -309,9 +401,9 @@
309 var context = this.timeline_graph.canvas_context;401 var context = this.timeline_graph.canvas_context;
310 context.strokeStyle = LINE_COLOR;402 context.strokeStyle = LINE_COLOR;
311 draw_line(context, [this.start, this.stop]);403 draw_line(context, [this.start, this.stop]);
312 for (var i in this.series_lines) {404 Y.each(this.series_lines, function(series_line, i) {
313 this.series_lines[i].draw();405 series_line.draw();
314 }406 }, this);
315 }407 }
316};408};
317409
@@ -391,10 +483,17 @@
391 * @method recreate_canvas483 * @method recreate_canvas
392 */484 */
393 recreate_canvas: function() {485 recreate_canvas: function() {
394 var width = this.graph_scale *486 var width = Math.ceil(
395 (this.project_line.get_width() + MARGIN_LEFT);487 this.graph_scale * (this.project_line.get_width() + MARGIN_LEFT));
396 var height = this.graph_scale * this.project_line.get_height() +488
397 MARGIN_BOTTOM;489 // The get_height() method already includes the MARGIN_TOP, so that
490 // gets multiplied by the graph_scale. Alternatively, we could have
491 // made changes elsewhere so that MARGIN_LEFT and MARGIN_TOP are
492 // not scaled at all.
493 var height = Math.ceil(
494 (this.graph_scale * this.project_line.get_height()) +
495 MARGIN_BOTTOM);
496
398 if (this.canvas) {497 if (this.canvas) {
399 this.get('contentBox').removeChild(this.canvas);498 this.get('contentBox').removeChild(this.canvas);
400 }499 }
@@ -425,20 +524,21 @@
425 */524 */
426 calculate_landmark_spacing: function() {525 calculate_landmark_spacing: function() {
427 var max_label_width = 0;526 var max_label_width = 0;
428 for (var i in this.project_line.series_lines) {527 var series_max_label_width = 0;
429 var series_line = this.project_line.series_lines[i];528 Y.each(this.project_line.series_lines, function(series_line, i) {
430 max_label_width = Math.max(529 series_max_label_width = Math.max(
431 max_label_width, series_line.series_label.get('offsetWidth'));530 series_max_label_width,
432 for (var j in series_line.labels) {531 series_line.center_series_label.get('offsetWidth'));
433 var label = series_line.labels[j];532 Y.each(series_line.labels, function(label, j) {
434 // We have to set the font size here so that533 // We have to set the font size here so that
435 // offsetWidth will be correct.534 // offsetWidth will be correct.
436 this.set_font_size(label);535 this.set_font_size(label);
437 max_label_width = Math.max(536 max_label_width = Math.max(
438 max_label_width, label.get('offsetWidth'));537 max_label_width, label.get('offsetWidth'));
439 }538 }, this);
440 }539 }, this);
441 this.landmark_spacing = max_label_width + 5;540 this.landmark_spacing = max_label_width + 5;
541 this.min_series_line_length = series_max_label_width + 5;
442 },542 },
443543
444 /**544 /**
@@ -459,14 +559,19 @@
459 * @method scroll_to_last_development_focus_landmark559 * @method scroll_to_last_development_focus_landmark
460 */560 */
461 scroll_to_last_development_focus_landmark: function(label) {561 scroll_to_last_development_focus_landmark: function(label) {
462 var series_lines = this.project_line.series_lines;562 var series_line = this.project_line.series_lines[0];
463 var series_line = series_lines[series_lines.length-1];
464 var landmark = series_line.series.landmarks[0];563 var landmark = series_line.series.landmarks[0];
465 if (landmark) {564 if (landmark) {
466 var landmark_label = series_line.labels[landmark.name];565 var landmark_label = series_line.labels[landmark.name];
467 var scroll_x = landmark_label.get('offsetLeft') +566 var date_label_width = 0;
468 landmark_label.get('offsetWidth') -567 if (series_line.series_date_label !== null) {
469 window.getViewportDimensions().w;568 date_label_width =
569 series_line.series_date_label.get('offsetWidth');
570 }
571 var scroll_x =
572 series_line.start.x + series_line.get_length() +
573 ARROW_WIDTH + date_label_width -
574 Y.DOM.winWidth();
470 // scrollBy is relative, so adjust it by575 // scrollBy is relative, so adjust it by
471 // the current scroll position.576 // the current scroll position.
472 scroll_x -= window.scrollX;577 scroll_x -= window.scrollX;
@@ -619,6 +724,8 @@
619 y_align_offset = -label_height;724 y_align_offset = -label_height;
620 } else if (y_align == 'below') {725 } else if (y_align == 'below') {
621 y_align_offset = 0;726 y_align_offset = 0;
727 } else if (y_align == 'middle') {
728 y_align_offset = -(label_height / 2);
622 } else {729 } else {
623 throw "Invalid y_align argument: " + y_align;730 throw "Invalid y_align argument: " + y_align;
624 }731 }
625732
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2009-10-01 16:19:10 +0000
+++ lib/lp/registry/model/product.py 2009-10-09 03:26:12 +0000
@@ -996,9 +996,12 @@
996 series_list.remove(self.development_focus)996 series_list.remove(self.development_focus)
997 series_list.insert(0, self.development_focus)997 series_list.insert(0, self.development_focus)
998 series_list.reverse()998 series_list.reverse()
999 return [series.getTimeline(include_inactive=include_inactive)999 return [
1000 for series in series_list1000 series.getTimeline(include_inactive=include_inactive)
1001 if include_inactive or series.active]1001 for series in series_list
1002 if include_inactive or series.active or
1003 series == self.development_focus
1004 ]
10021005
10031006
1004class ProductSet:1007class ProductSet:
10051008
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2009-09-16 21:22:12 +0000
+++ lib/lp/registry/model/productseries.py 2009-10-09 03:26:12 +0000
@@ -569,6 +569,7 @@
569 return dict(569 return dict(
570 name=self.name,570 name=self.name,
571 is_development_focus=self.is_development_focus,571 is_development_focus=self.is_development_focus,
572 status=self.status.title,
572 uri=canonical_url(self, path_only_if_possible=True),573 uri=canonical_url(self, path_only_if_possible=True),
573 landmarks=landmarks)574 landmarks=landmarks)
574575
575576
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2009-10-01 12:30:32 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2009-10-09 03:26:12 +0000
@@ -430,6 +430,7 @@
430 u'type': u'release',430 u'type': u'release',
431 u'uri': u'/firefox/1.0/1.0.0'}],431 u'uri': u'/firefox/1.0/1.0.0'}],
432 u'name': u'1.0',432 u'name': u'1.0',
433 u'status': u'Active Development',
433 u'uri': u'/firefox/1.0'},434 u'uri': u'/firefox/1.0'},
434 {u'is_development_focus': True,435 {u'is_development_focus': True,
435 u'landmarks': [{u'code_name': None,436 u'landmarks': [{u'code_name': None,
@@ -453,6 +454,7 @@
453 u'type': u'release',454 u'type': u'release',
454 u'uri': u'/firefox/trunk/0.9'}],455 u'uri': u'/firefox/trunk/0.9'}],
455 u'name': u'trunk',456 u'name': u'trunk',
457 u'status': u'Obsolete',
456 u'uri': u'/firefox/trunk'}]458 u'uri': u'/firefox/trunk'}]
457459
458460
@@ -820,6 +822,7 @@
820 {u'is_development_focus': False,822 {u'is_development_focus': False,
821 u'landmarks': [],823 u'landmarks': [],
822 u'name': u'foobadoo',824 u'name': u'foobadoo',
825 u'status': u'Active Development',
823 u'uri': u'/babadoo/foobadoo'}826 u'uri': u'/babadoo/foobadoo'}
824827
825828
826829
=== modified file 'lib/lp/registry/templates/timeline-macros.pt'
--- lib/lp/registry/templates/timeline-macros.pt 2009-07-17 18:46:25 +0000
+++ lib/lp/registry/templates/timeline-macros.pt 2009-10-09 03:26:12 +0000
@@ -21,9 +21,10 @@
2121
22 <!-- Opera ignores overflow:hidden for iframe, so use scrolling=no. -->22 <!-- Opera ignores overflow:hidden for iframe, so use scrolling=no. -->
23 <iframe id="timeline-iframe" name='timeline-iframe'23 <iframe id="timeline-iframe" name='timeline-iframe'
24 class="timeline-iframe"
24 style="display: none; border: 0"25 style="display: none; border: 0"
25 scrolling="no"26 scrolling="no"
26 width="100%" height="116px"></iframe>27 width="100%" height="216px"></iframe>
27 <script>28 <script>
28 function timeline_iframe(auto_resize, include_inactive) {29 function timeline_iframe(auto_resize, include_inactive) {
29 var timeline_url = "+timeline-graph?";30 var timeline_url = "+timeline-graph?";