31 |
32 |
106 | <% end %>
107 |
108 |
--------------------------------------------------------------------------------
/assets/javascripts/saveSvgAsPng.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var out$ = typeof exports != 'undefined' && exports || this;
3 |
4 | var doctype = '';
5 |
6 | function inlineImages(callback) {
7 | var images = document.querySelectorAll('svg image');
8 | var left = images.length;
9 | if (left == 0) {
10 | callback();
11 | }
12 | for (var i = 0; i < images.length; i++) {
13 | (function(image) {
14 | if (image.getAttribute('xlink:href')) {
15 | var href = image.getAttribute('xlink:href').value;
16 | if (/^http/.test(href) && !(new RegExp('^' + window.location.host).test(href))) {
17 | throw new Error("Cannot render embedded images linking to external hosts.");
18 | }
19 | }
20 | var canvas = document.createElement('canvas');
21 | var ctx = canvas.getContext('2d');
22 | var img = new Image();
23 | img.src = image.getAttribute('xlink:href');
24 | img.onload = function() {
25 | canvas.width = img.width;
26 | canvas.height = img.height;
27 | ctx.drawImage(img, 0, 0);
28 | image.setAttribute('xlink:href', canvas.toDataURL('image/png'));
29 | left--;
30 | if (left == 0) {
31 | callback();
32 | }
33 | }
34 | })(images[i]);
35 | }
36 | }
37 |
38 | function styles(dom) {
39 | var used = "";
40 | var sheets = document.styleSheets;
41 | for (var i = 0; i < sheets.length; i++) {
42 | var rules = sheets[i].cssRules;
43 | for (var j = 0; j < rules.length; j++) {
44 | var rule = rules[j];
45 | if (typeof(rule.style) != "undefined") {
46 | var elems = dom.querySelectorAll(rule.selectorText);
47 | if (elems.length > 0) {
48 | used += rule.selectorText + " { " + rule.style.cssText + " }\n";
49 | }
50 | }
51 | }
52 | }
53 |
54 | var s = document.createElement('style');
55 | s.setAttribute('type', 'text/css');
56 | s.innerHTML = "";
57 |
58 | var defs = document.createElement('defs');
59 | defs.appendChild(s);
60 | return defs;
61 | }
62 |
63 | out$.svgAsDataUri = function(el, scaleFactor, cb) {
64 | scaleFactor = scaleFactor || 1;
65 |
66 | inlineImages(function() {
67 | var outer = document.createElement("div");
68 | var clone = el.cloneNode(true);
69 | var width = parseInt(clone.getAttribute("width"));
70 | var height = parseInt(clone.getAttribute("height"));
71 |
72 | var xmlns = "http://www.w3.org/2000/xmlns/";
73 |
74 | clone.setAttribute("version", "1.1");
75 | clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
76 | clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
77 | clone.setAttribute("width", width * scaleFactor);
78 | clone.setAttribute("height", height * scaleFactor);
79 | clone.setAttribute("viewBox", "0 0 " + width + " " + height);
80 | outer.appendChild(clone);
81 |
82 | clone.insertBefore(styles(clone), clone.firstChild);
83 |
84 | var svg = doctype + outer.innerHTML;
85 | var uri = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svg)));
86 | if (cb) {
87 | cb(uri);
88 | }
89 | });
90 | }
91 |
92 | out$.saveSvgAsPng = function(el, name, scaleFactor) {
93 | out$.svgAsDataUri(el, scaleFactor, function(uri) {
94 | var image = new Image();
95 | image.src = uri;
96 | image.onload = function() {
97 | var canvas = document.createElement('canvas');
98 | canvas.width = image.width;
99 | canvas.height = image.height;
100 | var context = canvas.getContext('2d');
101 | context.drawImage(image, 0, 0);
102 |
103 | var a = document.createElement('a');
104 | a.download = name;
105 | a.href = canvas.toDataURL('image/png');
106 | document.body.appendChild(a);
107 | a.click();
108 | }
109 | });
110 | }
111 | })();
112 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # This is copied from Redmine 2.5
2 | ---
3 | users_004:
4 | created_on: 2006-07-19 19:34:07 +02:00
5 | status: 1
6 | last_login_on:
7 | language: en
8 | # password = foo
9 | salt: 3126f764c3c5ac61cbfc103f25f934cf
10 | hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
11 | updated_on: 2006-07-19 19:34:07 +02:00
12 | admin: false
13 | lastname: Hill
14 | firstname: Robert
15 | id: 4
16 | auth_source_id:
17 | mail_notification: all
18 | login: rhill
19 | type: User
20 | users_001:
21 | created_on: 2006-07-19 19:12:21 +02:00
22 | status: 1
23 | last_login_on: 2006-07-19 22:57:52 +02:00
24 | language: en
25 | # password = admin
26 | salt: 82090c953c4a0000a7db253b0691a6b4
27 | hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
28 | updated_on: 2006-07-19 22:57:52 +02:00
29 | admin: true
30 | lastname: Admin
31 | firstname: Redmine
32 | id: 1
33 | auth_source_id:
34 | mail_notification: all
35 | login: admin
36 | type: User
37 | users_002:
38 | created_on: 2006-07-19 19:32:09 +02:00
39 | status: 1
40 | last_login_on: 2006-07-19 22:42:15 +02:00
41 | language: en
42 | # password = jsmith
43 | salt: 67eb4732624d5a7753dcea7ce0bb7d7d
44 | hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
45 | updated_on: 2006-07-19 22:42:15 +02:00
46 | admin: false
47 | lastname: Smith
48 | firstname: John
49 | id: 2
50 | auth_source_id:
51 | mail_notification: all
52 | login: jsmith
53 | type: User
54 | users_003:
55 | created_on: 2006-07-19 19:33:19 +02:00
56 | status: 1
57 | last_login_on:
58 | language: en
59 | # password = foo
60 | salt: 7599f9963ec07b5a3b55b354407120c0
61 | hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
62 | updated_on: 2006-07-19 19:33:19 +02:00
63 | admin: false
64 | lastname: Lopper
65 | firstname: Dave
66 | id: 3
67 | auth_source_id:
68 | mail_notification: all
69 | login: dlopper
70 | type: User
71 | users_005:
72 | id: 5
73 | created_on: 2006-07-19 19:33:19 +02:00
74 | # Locked
75 | status: 3
76 | last_login_on:
77 | language: en
78 | hashed_password: 1
79 | updated_on: 2006-07-19 19:33:19 +02:00
80 | admin: false
81 | lastname: Lopper2
82 | firstname: Dave2
83 | auth_source_id:
84 | mail_notification: all
85 | login: dlopper2
86 | type: User
87 | users_006:
88 | id: 6
89 | created_on: 2006-07-19 19:33:19 +02:00
90 | status: 0
91 | last_login_on:
92 | language: ''
93 | hashed_password: 1
94 | updated_on: 2006-07-19 19:33:19 +02:00
95 | admin: false
96 | lastname: Anonymous
97 | firstname: ''
98 | auth_source_id:
99 | mail_notification: only_my_events
100 | login: ''
101 | type: AnonymousUser
102 | users_007:
103 | # A user who does not belong to any project
104 | id: 7
105 | created_on: 2006-07-19 19:33:19 +02:00
106 | status: 1
107 | last_login_on:
108 | language: 'en'
109 | # password = foo
110 | salt: 7599f9963ec07b5a3b55b354407120c0
111 | hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
112 | updated_on: 2006-07-19 19:33:19 +02:00
113 | admin: false
114 | lastname: One
115 | firstname: Some
116 | auth_source_id:
117 | mail_notification: only_my_events
118 | login: someone
119 | type: User
120 | users_008:
121 | id: 8
122 | created_on: 2006-07-19 19:33:19 +02:00
123 | status: 1
124 | last_login_on:
125 | language: 'it'
126 | # password = foo
127 | salt: 7599f9963ec07b5a3b55b354407120c0
128 | hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
129 | updated_on: 2006-07-19 19:33:19 +02:00
130 | admin: false
131 | lastname: Misc
132 | firstname: User
133 | auth_source_id:
134 | mail_notification: only_my_events
135 | login: miscuser8
136 | type: User
137 | users_009:
138 | id: 9
139 | created_on: 2006-07-19 19:33:19 +02:00
140 | status: 1
141 | last_login_on:
142 | language: 'it'
143 | hashed_password: 1
144 | updated_on: 2006-07-19 19:33:19 +02:00
145 | admin: false
146 | lastname: Misc
147 | firstname: User
148 | auth_source_id:
149 | mail_notification: only_my_events
150 | login: miscuser9
151 | type: User
152 | groups_010:
153 | id: 10
154 | lastname: A Team
155 | type: Group
156 | groups_011:
157 | id: 11
158 | lastname: B Team
159 | type: Group
160 |
161 |
162 |
--------------------------------------------------------------------------------
/test/fixtures/roles.yml:
--------------------------------------------------------------------------------
1 | # This is copied from Redmine 2.5 and modified
2 | ---
3 | roles_001:
4 | name: Manager
5 | id: 1
6 | builtin: 0
7 | issues_visibility: all
8 | permissions: |
9 | ---
10 | - :add_project
11 | - :edit_project
12 | - :close_project
13 | - :select_project_modules
14 | - :manage_members
15 | - :manage_versions
16 | - :manage_categories
17 | - :view_issues
18 | - :add_issues
19 | - :edit_issues
20 | - :manage_issue_relations
21 | - :manage_subtasks
22 | - :add_issue_notes
23 | - :move_issues
24 | - :delete_issues
25 | - :view_issue_watchers
26 | - :add_issue_watchers
27 | - :set_issues_private
28 | - :set_notes_private
29 | - :view_private_notes
30 | - :delete_issue_watchers
31 | - :manage_public_queries
32 | - :save_queries
33 | - :view_gantt
34 | - :view_calendar
35 | - :log_time
36 | - :view_time_entries
37 | - :edit_time_entries
38 | - :delete_time_entries
39 | - :manage_news
40 | - :comment_news
41 | - :view_documents
42 | - :add_documents
43 | - :edit_documents
44 | - :delete_documents
45 | - :view_wiki_pages
46 | - :export_wiki_pages
47 | - :view_wiki_edits
48 | - :edit_wiki_pages
49 | - :delete_wiki_pages_attachments
50 | - :protect_wiki_pages
51 | - :delete_wiki_pages
52 | - :rename_wiki_pages
53 | - :add_messages
54 | - :edit_messages
55 | - :delete_messages
56 | - :manage_boards
57 | - :view_files
58 | - :manage_files
59 | - :browse_repository
60 | - :manage_repository
61 | - :view_changesets
62 | - :manage_related_issues
63 | - :manage_project_activities
64 |
65 | position: 1
66 | roles_002:
67 | name: Developer
68 | id: 2
69 | builtin: 0
70 | issues_visibility: default
71 | permissions: |
72 | ---
73 | - :edit_project
74 | - :manage_members
75 | - :manage_versions
76 | - :manage_categories
77 | - :view_issues
78 | - :add_issues
79 | - :edit_issues
80 | - :manage_issue_relations
81 | - :manage_subtasks
82 | - :add_issue_notes
83 | - :move_issues
84 | - :delete_issues
85 | - :view_issue_watchers
86 | - :save_queries
87 | - :view_gantt
88 | - :view_calendar
89 | - :log_time
90 | - :view_time_entries
91 | - :edit_own_time_entries
92 | - :manage_news
93 | - :comment_news
94 | - :view_documents
95 | - :add_documents
96 | - :edit_documents
97 | - :delete_documents
98 | - :view_wiki_pages
99 | - :view_wiki_edits
100 | - :edit_wiki_pages
101 | - :protect_wiki_pages
102 | - :delete_wiki_pages
103 | - :add_messages
104 | - :edit_own_messages
105 | - :delete_own_messages
106 | - :manage_boards
107 | - :view_files
108 | - :manage_files
109 | - :browse_repository
110 | - :view_changesets
111 |
112 | position: 2
113 | roles_003:
114 | name: Reporter
115 | id: 3
116 | builtin: 0
117 | issues_visibility: default
118 | permissions: |
119 | ---
120 | - :edit_project
121 | - :manage_members
122 | - :manage_versions
123 | - :manage_categories
124 | - :view_issues
125 | - :add_issues
126 | - :edit_issues
127 | - :manage_issue_relations
128 | - :add_issue_notes
129 | - :move_issues
130 | - :view_issue_watchers
131 | - :save_queries
132 | - :view_gantt
133 | - :view_calendar
134 | - :log_time
135 | - :view_time_entries
136 | - :manage_news
137 | - :comment_news
138 | - :view_documents
139 | - :add_documents
140 | - :edit_documents
141 | - :delete_documents
142 | - :view_wiki_pages
143 | - :view_wiki_edits
144 | - :edit_wiki_pages
145 | - :delete_wiki_pages
146 | - :add_messages
147 | - :manage_boards
148 | - :view_files
149 | - :manage_files
150 | - :browse_repository
151 | - :view_changesets
152 |
153 | position: 3
154 | roles_004:
155 | name: Non member
156 | id: 4
157 | builtin: 1
158 | issues_visibility: default
159 | permissions: |
160 | ---
161 | - :view_issues
162 | - :add_issues
163 | - :edit_issues
164 | - :manage_issue_relations
165 | - :add_issue_notes
166 | - :save_queries
167 | - :view_gantt
168 | - :view_calendar
169 | - :log_time
170 | - :view_time_entries
171 | - :comment_news
172 | - :view_documents
173 | - :view_wiki_pages
174 | - :view_wiki_edits
175 | - :edit_wiki_pages
176 | - :add_messages
177 | - :view_files
178 | - :manage_files
179 | - :browse_repository
180 | - :view_changesets
181 |
182 | position: 4
183 | roles_005:
184 | name: Anonymous
185 | id: 5
186 | builtin: 2
187 | issues_visibility: default
188 | permissions: |
189 | ---
190 | - :view_issues
191 | - :add_issue_notes
192 | - :view_gantt
193 | - :view_calendar
194 | - :view_time_entries
195 | - :view_documents
196 | - :view_wiki_pages
197 | - :view_wiki_edits
198 | - :view_files
199 | - :browse_repository
200 | - :view_changesets
201 |
202 | position: 5
203 |
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redmine Workflow Enhancements
2 |
3 | Add various enhancements to workflow editing. Currently this consists of these:
4 |
5 | * Visualization with dagre-d3:
6 |
7 | * Workflow edit
8 | * Tracker edit
9 | * Issue edit (behind the little question mark next to "Issue Status")
10 |
11 | * Ability to pre-define association between tracker and issue statuses for
12 | better overview.
13 |
14 |
15 | Requirements:
16 |
17 | * Redmine 2.2 or higher
18 | * Ruby 1.9.3 or higher
19 |
20 | ## Installation
21 |
22 | Installing the plugin requires these steps. From within the Redmine root
23 | directory:
24 |
25 | 1. **Get the plugin**
26 |
27 | ```
28 | cd plugins
29 | git clone git://github.com/dr-itz/redmine_workflow_enhancements.git
30 | ```
31 | 2. **Run bundler**
32 |
33 | Run excatly the same as during Redmine installation. This might be:
34 |
35 | ```
36 | bundle install --without development test
37 | ```
38 |
39 | 3. **Run plugin migrations**
40 |
41 | ```
42 | bundle exec rake redmine:plugins NAME=redmine_workflow_enhancements RAILS_ENV=production
43 | ```
44 |
45 | 4. **Restart Redmine**
46 |
47 | The second step is to restart Redmine. How this is done depends on how Redmine is
48 | setup. After the restart, configuration of the plugin can begin.
49 |
50 | ## Uninstalling
51 |
52 | Uninstalling the plugin is easy as well:
53 |
54 | 1. **Reverse plugin migrations**
55 |
56 | ```
57 | bundle exec rake redmine:plugins NAME=redmine_workflow_enhancements RAILS_ENV=production VERSION=0
58 | ```
59 |
60 | 2. **Removing the plugin directory**
61 |
62 | ```
63 | rm -r plugins/redmine_workflow_enhancements
64 | ```
65 |
66 | 3. **Restart Redmine**
67 |
68 | The second step is to restart Redmine. Once restarted, the plugin will be gone.
69 |
70 | ## Usage
71 |
72 | * Go to Administration -> Workflow, select a single workflow and click 'Edit'
73 | * Go to Administration -> Tracker, select a tracker
74 | * For each role that should be able to display the workflow graph in the issue form:
75 |
76 | * Go to Administration -> Roles and permissions, select the desired role
77 | * Check "View workflow graph" in the category "Issue Tracking"
78 |
79 | * In the issue edit form, click on the "?" next to the issue status
80 |
81 | ### What is displayed
82 |
83 | The generated graph always includes the full workflow of a tracker. To
84 | differentiate things:
85 |
86 | **Transitions:**
87 |
88 | * Black
89 |
90 | The transition is possible with the current roles
91 |
92 | * Grey
93 |
94 | The transition is not possible by the current role
95 |
96 | * Dashed line
97 |
98 | The transition is only possible when the user is the author of the issue
99 |
100 | * Red line
101 |
102 | The transition is only possible when the user is the assignee of the issue
103 |
104 |
105 | **Statuses:**
106 |
107 | * Green background
108 |
109 | The issue is closed in this state
110 |
111 | * Red background
112 |
113 | This is a status a new issues is can have. The default status for a new
114 | issues is displayed with bold text.
115 |
116 | Notes:
117 |
118 | * The default issue status for a tracker is configurable since Redmine
119 | 3.0. Older versions of Redmine always use "New".
120 |
121 | * The possible statuses for a new issue is configurable since Redmine 3.2.
122 | Older versions of Redmine allow the default status and all directly
123 | reachable statuses. The highlighting only shows the new Redmine 3.2
124 | configuration.
125 |
126 | * Grey border
127 |
128 | In issue edit form, this is the current selected status
129 |
130 | * Blue border
131 |
132 | In issue edit form, these are the next possible statuses in the workflow
133 |
134 |
135 | ### How the association between Tracker and Statuses works
136 |
137 | The idea is to control what's showed in "Administration" -> "Workflow" ->
138 | "Status transitions" -> "Edit". With a lot of different statuses and creating a
139 | new tracker, things get messy. The default is to only show statuses that are
140 | included in the workflow ("Only display statuses that are used by this tracker"
141 | checked). With a new tracker there are none. Unchecking said checkbox displays
142 | all statuses which makes it hard to find the relevant ones. It also involves a
143 | lot of scrolling on small screens.
144 |
145 | The plugin solves this with the pre-defined association in the tracker. It
146 | changes the behavior when "Only display statuses that are used by this tracker"
147 | is checked. It will display all statuses involved in the actual workflow (normal
148 | behavior) but also includes all the pre-defined ones. The behavior with that
149 | checkbox unchecked is unchanged, it will display all the statuses.
150 |
151 |
152 | ## Development and test
153 |
154 | To run the tests, an additional Gem is required for code coverage: simplecov. If
155 | bundler was initially run with `--without developmen test`, run again without
156 | these arguments to install *with* the development and test gems.
157 |
158 | To run the tests:
159 |
160 | ````
161 | bundle exec rake redmine:plugins:test NAME=redmine_workflow_enhancements
162 | ````
163 |
164 |
165 | ## License and thanks
166 |
167 | Since this is a Redmine plugin, Redmine is licensed under the GPLv2 and the
168 | GPLv2 is very clear about derived work and such, this plugin is licensed under
169 | the same license.
170 |
171 | This plugin borrows an extension to ActiveView that provides a `render :super`
172 | from ActiveScaffold, https://github.com/activescaffold/active_scaffold
173 |
--------------------------------------------------------------------------------
/test/unit/graph_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../../test_helper', __FILE__)
2 |
3 | class GraphTest < ActionView::TestCase
4 | fixtures :projects,
5 | :users,
6 | :roles,
7 | :members,
8 | :member_roles,
9 | :issues,
10 | :issue_statuses,
11 | :trackers,
12 | :projects_trackers,
13 | :enabled_modules,
14 | :workflows
15 |
16 | include ActionView::TestCase::Behavior
17 |
18 | def setup
19 | @tracker_bug = Tracker.find(1)
20 | @role_manager = Role.find(1)
21 | @role_reporter = Role.find(3)
22 | @role_nonmember = Role.find(4)
23 | @issue = Issue.find(1)
24 |
25 | @bug_states = [
26 | { :id => 1, :value => { :label => "New", :nodeclass => "state-new" }},
27 | { :id => 2, :value => { :label => "Assigned", :nodeclass => "" }},
28 | { :id => 3, :value => { :label => "Resolved", :nodeclass => "" }},
29 | { :id => 4, :value => { :label => "Feedback", :nodeclass => "" }},
30 | { :id => 5, :value => { :label => "Closed", :nodeclass => "state-closed" }},
31 | { :id => 6, :value => { :label => "Rejected", :nodeclass => "state-closed" }}]
32 |
33 | @bug_states_issue = [
34 | { :id => 1, :value => { :label => "New", :nodeclass => "state-new state-current" }},
35 | { :id => 2, :value => { :label => "Assigned", :nodeclass => " state-possible" }},
36 | { :id => 3, :value => { :label => "Resolved", :nodeclass => "" }},
37 | { :id => 4, :value => { :label => "Feedback", :nodeclass => "" }},
38 | { :id => 5, :value => { :label => "Closed", :nodeclass => "state-closed" }},
39 | { :id => 6, :value => { :label => "Rejected", :nodeclass => "state-closed" }}]
40 |
41 | @bug_transition_all = [
42 | { :u => 1, :v => 2, :value => { :edgeclass => "" }},
43 | { :u => 2, :v => 1, :value => { :edgeclass => "" }},
44 | { :u => 2, :v => 3, :value => { :edgeclass => "" }},
45 | { :u => 3, :v => 2, :value => { :edgeclass => "" }},
46 | { :u => 3, :v => 5, :value => { :edgeclass => "" }},
47 | { :u => 3, :v => 4, :value => { :edgeclass => "" }},
48 | { :u => 4, :v => 2, :value => { :edgeclass => "" }},
49 | { :u => 4, :v => 5, :value => { :edgeclass => "" }},
50 | { :u => 2, :v => 6, :value => { :edgeclass => "" }}]
51 |
52 | @bug_transition_manager = Marshal.load(Marshal.dump(@bug_transition_all))
53 | @bug_transition_manager.each {|x| x[:value][:edgeclass] = 'transOwn' }
54 |
55 | @bug_transition_reporter = [
56 | { :u => 1, :v => 2, :value => { :edgeclass => "transOther" }},
57 | { :u => 2, :v => 1, :value => { :edgeclass => "transOther" }},
58 | { :u => 2, :v => 3, :value => { :edgeclass => "transOwn transOwn-assignee" }},
59 | { :u => 3, :v => 2, :value => { :edgeclass => "transOther" }},
60 | { :u => 3, :v => 5, :value => { :edgeclass => "transOther" }},
61 | { :u => 3, :v => 4, :value => { :edgeclass => "transOther" }},
62 | { :u => 4, :v => 2, :value => { :edgeclass => "transOwn transOwn-author" }},
63 | { :u => 4, :v => 5, :value => { :edgeclass => "transOwn transOwn-author" }},
64 | { :u => 2, :v => 6, :value => { :edgeclass => "transOther" }}]
65 |
66 | @bug_transition_nonmember = Marshal.load(Marshal.dump(@bug_transition_all))
67 | @bug_transition_nonmember.each {|x| x[:value][:edgeclass] = 'transOther' }
68 | end
69 |
70 | def test_empty_data
71 | result = WorkflowEnhancements::Graph.load_data(nil, nil)
72 | assert_equal({ :nodes => [], :edges => [] }, result)
73 | end
74 |
75 | def test_empty_data_array
76 | result = WorkflowEnhancements::Graph.load_data([], [])
77 | assert_equal({ :nodes => [], :edges => [] }, result)
78 | end
79 |
80 | def test_multiple_trackers
81 | #trackers = Tracker.all
82 | #result = WorkflowEnhancements::Graph.load_data(nil, trackers)
83 | #assert_equal({ :nodes => [], :edges => [] }, result)
84 | end
85 |
86 | def test_with_tracker_bug_all
87 | result = WorkflowEnhancements::Graph.load_data(nil, @tracker_bug)
88 | assert result.has_key? :nodes
89 | assert result.has_key? :edges
90 | assert_equal @bug_states, result[:nodes]
91 | assert_equal @bug_transition_all, result[:edges]
92 | end
93 |
94 | def test_with_tracker_bug_all_empty_array
95 | result = WorkflowEnhancements::Graph.load_data([], @tracker_bug)
96 | assert_equal @bug_states, result[:nodes]
97 | assert_equal @bug_transition_all, result[:edges]
98 | end
99 |
100 | def test_with_tracker_bug_manager
101 | result = WorkflowEnhancements::Graph.load_data(@role_manager, @tracker_bug)
102 | assert_equal @bug_states, result[:nodes]
103 | assert_equal @bug_transition_manager, result[:edges]
104 | end
105 |
106 | def test_with_tracker_bug_manager_issue
107 | User.current = User.find(2) # manager
108 |
109 | result = WorkflowEnhancements::Graph.load_data(@role_manager, @tracker_bug, @issue)
110 | Rails.logger.warn result[:nodes].inspect
111 | assert_equal @bug_states_issue, result[:nodes]
112 | assert_equal @bug_transition_manager, result[:edges]
113 | end
114 |
115 | def test_with_tracker_bug_reporter
116 | result = WorkflowEnhancements::Graph.load_data(@role_reporter, @tracker_bug)
117 | assert_equal @bug_states, result[:nodes]
118 | assert_equal @bug_transition_reporter, result[:edges]
119 | end
120 |
121 | def test_with_tracker_bug_nonmember
122 | result = WorkflowEnhancements::Graph.load_data(@role_nonmember, @tracker_bug)
123 | assert_equal @bug_states, result[:nodes]
124 | assert_equal @bug_transition_nonmember, result[:edges]
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/test/fixtures/issues.yml:
--------------------------------------------------------------------------------
1 | ---
2 | issues_001:
3 | created_on: <%= 3.days.ago.to_s(:db) %>
4 | project_id: 1
5 | updated_on: <%= 1.day.ago.to_s(:db) %>
6 | priority_id: 4
7 | subject: Can't print recipes
8 | id: 1
9 | fixed_version_id:
10 | category_id: 1
11 | description: Unable to print recipes
12 | tracker_id: 1
13 | assigned_to_id:
14 | author_id: 2
15 | status_id: 1
16 | start_date: <%= 1.day.ago.to_date.to_s(:db) %>
17 | due_date: <%= 10.day.from_now.to_date.to_s(:db) %>
18 | root_id: 1
19 | lft: 1
20 | rgt: 2
21 | lock_version: 3
22 | issues_002:
23 | created_on: 2006-07-19 21:04:21 +02:00
24 | project_id: 1
25 | updated_on: 2006-07-19 21:09:50 +02:00
26 | priority_id: 5
27 | subject: Add ingredients categories
28 | id: 2
29 | fixed_version_id: 2
30 | category_id:
31 | description: Ingredients of the recipe should be classified by categories
32 | tracker_id: 2
33 | assigned_to_id: 3
34 | author_id: 2
35 | status_id: 2
36 | start_date: <%= 2.day.ago.to_date.to_s(:db) %>
37 | due_date:
38 | root_id: 2
39 | lft: 1
40 | rgt: 2
41 | lock_version: 3
42 | done_ratio: 30
43 | issues_003:
44 | created_on: 2006-07-19 21:07:27 +02:00
45 | project_id: 1
46 | updated_on: 2006-07-19 21:07:27 +02:00
47 | priority_id: 4
48 | subject: Error 281 when updating a recipe
49 | id: 3
50 | fixed_version_id:
51 | category_id:
52 | description: Error 281 is encountered when saving a recipe
53 | tracker_id: 1
54 | assigned_to_id: 3
55 | author_id: 2
56 | status_id: 1
57 | start_date: <%= 15.day.ago.to_date.to_s(:db) %>
58 | due_date: <%= 5.day.ago.to_date.to_s(:db) %>
59 | root_id: 3
60 | lft: 1
61 | rgt: 2
62 | issues_004:
63 | created_on: <%= 5.days.ago.to_s(:db) %>
64 | project_id: 2
65 | updated_on: <%= 2.days.ago.to_s(:db) %>
66 | priority_id: 4
67 | subject: Issue on project 2
68 | id: 4
69 | fixed_version_id:
70 | category_id:
71 | description: Issue on project 2
72 | tracker_id: 1
73 | assigned_to_id: 2
74 | author_id: 2
75 | status_id: 1
76 | root_id: 4
77 | lft: 1
78 | rgt: 2
79 | issues_005:
80 | created_on: <%= 5.days.ago.to_s(:db) %>
81 | project_id: 3
82 | updated_on: <%= 2.days.ago.to_s(:db) %>
83 | priority_id: 4
84 | subject: Subproject issue
85 | id: 5
86 | fixed_version_id:
87 | category_id:
88 | description: This is an issue on a cookbook subproject
89 | tracker_id: 1
90 | assigned_to_id:
91 | author_id: 2
92 | status_id: 1
93 | root_id: 5
94 | lft: 1
95 | rgt: 2
96 | issues_006:
97 | created_on: <%= 1.minute.ago.to_s(:db) %>
98 | project_id: 5
99 | updated_on: <%= 1.minute.ago.to_s(:db) %>
100 | priority_id: 4
101 | subject: Issue of a private subproject
102 | id: 6
103 | fixed_version_id:
104 | category_id:
105 | description: This is an issue of a private subproject of cookbook
106 | tracker_id: 1
107 | assigned_to_id:
108 | author_id: 2
109 | status_id: 1
110 | start_date: <%= Date.today.to_s(:db) %>
111 | due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
112 | root_id: 6
113 | lft: 1
114 | rgt: 2
115 | issues_007:
116 | created_on: <%= 10.days.ago.to_s(:db) %>
117 | project_id: 1
118 | updated_on: <%= 10.days.ago.to_s(:db) %>
119 | priority_id: 5
120 | subject: Issue due today
121 | id: 7
122 | fixed_version_id:
123 | category_id:
124 | description: This is an issue that is due today
125 | tracker_id: 1
126 | assigned_to_id:
127 | author_id: 2
128 | status_id: 1
129 | start_date: <%= 10.days.ago.to_s(:db) %>
130 | due_date: <%= Date.today.to_s(:db) %>
131 | lock_version: 0
132 | root_id: 7
133 | lft: 1
134 | rgt: 2
135 | issues_008:
136 | created_on: <%= 10.days.ago.to_s(:db) %>
137 | project_id: 1
138 | updated_on: <%= 10.days.ago.to_s(:db) %>
139 | priority_id: 5
140 | subject: Closed issue
141 | id: 8
142 | fixed_version_id:
143 | category_id:
144 | description: This is a closed issue.
145 | tracker_id: 1
146 | assigned_to_id:
147 | author_id: 2
148 | status_id: 5
149 | start_date:
150 | due_date:
151 | lock_version: 0
152 | root_id: 8
153 | lft: 1
154 | rgt: 2
155 | closed_on: <%= 3.days.ago.to_s(:db) %>
156 | issues_009:
157 | created_on: <%= 1.minute.ago.to_s(:db) %>
158 | project_id: 5
159 | updated_on: <%= 1.minute.ago.to_s(:db) %>
160 | priority_id: 5
161 | subject: Blocked Issue
162 | id: 9
163 | fixed_version_id:
164 | category_id:
165 | description: This is an issue that is blocked by issue #10
166 | tracker_id: 1
167 | assigned_to_id:
168 | author_id: 2
169 | status_id: 1
170 | start_date: <%= Date.today.to_s(:db) %>
171 | due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
172 | root_id: 9
173 | lft: 1
174 | rgt: 2
175 | issues_010:
176 | created_on: <%= 1.minute.ago.to_s(:db) %>
177 | project_id: 5
178 | updated_on: <%= 1.minute.ago.to_s(:db) %>
179 | priority_id: 5
180 | subject: Issue Doing the Blocking
181 | id: 10
182 | fixed_version_id:
183 | category_id:
184 | description: This is an issue that blocks issue #9
185 | tracker_id: 1
186 | assigned_to_id:
187 | author_id: 2
188 | status_id: 1
189 | start_date: <%= Date.today.to_s(:db) %>
190 | due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
191 | root_id: 10
192 | lft: 1
193 | rgt: 2
194 | issues_011:
195 | created_on: <%= 3.days.ago.to_s(:db) %>
196 | project_id: 1
197 | updated_on: <%= 1.day.ago.to_s(:db) %>
198 | priority_id: 5
199 | subject: Closed issue on a closed version
200 | id: 11
201 | fixed_version_id: 1
202 | category_id: 1
203 | description:
204 | tracker_id: 1
205 | assigned_to_id:
206 | author_id: 2
207 | status_id: 5
208 | start_date: <%= 1.day.ago.to_date.to_s(:db) %>
209 | due_date:
210 | root_id: 11
211 | lft: 1
212 | rgt: 2
213 | closed_on: <%= 1.day.ago.to_s(:db) %>
214 | issues_012:
215 | created_on: <%= 3.days.ago.to_s(:db) %>
216 | project_id: 1
217 | updated_on: <%= 1.day.ago.to_s(:db) %>
218 | priority_id: 5
219 | subject: Closed issue on a locked version
220 | id: 12
221 | fixed_version_id: 2
222 | category_id: 1
223 | description:
224 | tracker_id: 1
225 | assigned_to_id:
226 | author_id: 3
227 | status_id: 5
228 | start_date: <%= 1.day.ago.to_date.to_s(:db) %>
229 | due_date:
230 | root_id: 12
231 | lft: 1
232 | rgt: 2
233 | closed_on: <%= 1.day.ago.to_s(:db) %>
234 | issues_013:
235 | created_on: <%= 5.days.ago.to_s(:db) %>
236 | project_id: 3
237 | updated_on: <%= 2.days.ago.to_s(:db) %>
238 | priority_id: 4
239 | subject: Subproject issue two
240 | id: 13
241 | fixed_version_id:
242 | category_id:
243 | description: This is a second issue on a cookbook subproject
244 | tracker_id: 1
245 | assigned_to_id:
246 | author_id: 2
247 | status_id: 1
248 | root_id: 13
249 | lft: 1
250 | rgt: 2
251 | issues_014:
252 | id: 14
253 | created_on: <%= 15.days.ago.to_s(:db) %>
254 | project_id: 3
255 | updated_on: <%= 15.days.ago.to_s(:db) %>
256 | priority_id: 5
257 | subject: Private issue on public project
258 | fixed_version_id:
259 | category_id:
260 | description: This is a private issue
261 | tracker_id: 1
262 | assigned_to_id:
263 | author_id: 2
264 | status_id: 1
265 | is_private: true
266 | root_id: 14
267 | lft: 1
268 | rgt: 2
269 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 |
2 | GNU GENERAL PUBLIC LICENSE
3 | Version 2, June 1991
4 |
5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
7 | Everyone is permitted to copy and distribute verbatim copies
8 | of this license document, but changing it is not allowed.
9 |
10 | Preamble
11 |
12 | The licenses for most software are designed to take away your
13 | freedom to share and change it. By contrast, the GNU General Public
14 | License is intended to guarantee your freedom to share and change free
15 | software--to make sure the software is free for all its users. This
16 | General Public License applies to most of the Free Software
17 | Foundation's software and to any other program whose authors commit to
18 | using it. (Some other Free Software Foundation software is covered by
19 | the GNU Lesser General Public License instead.) You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | this service if you wish), that you receive source code or can get it
26 | if you want it, that you can change the software or use pieces of it
27 | in new free programs; and that you know you can do these things.
28 |
29 | To protect your rights, we need to make restrictions that forbid
30 | anyone to deny you these rights or to ask you to surrender the rights.
31 | These restrictions translate to certain responsibilities for you if you
32 | distribute copies of the software, or if you modify it.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must give the recipients all the rights that
36 | you have. You must make sure that they, too, receive or can get the
37 | source code. And you must show them these terms so they know their
38 | rights.
39 |
40 | We protect your rights with two steps: (1) copyright the software, and
41 | (2) offer you this license which gives you legal permission to copy,
42 | distribute and/or modify the software.
43 |
44 | Also, for each author's protection and ours, we want to make certain
45 | that everyone understands that there is no warranty for this free
46 | software. If the software is modified by someone else and passed on, we
47 | want its recipients to know that what they have is not the original, so
48 | that any problems introduced by others will not reflect on the original
49 | authors' reputations.
50 |
51 | Finally, any free program is threatened constantly by software
52 | patents. We wish to avoid the danger that redistributors of a free
53 | program will individually obtain patent licenses, in effect making the
54 | program proprietary. To prevent this, we have made it clear that any
55 | patent must be licensed for everyone's free use or not licensed at all.
56 |
57 | The precise terms and conditions for copying, distribution and
58 | modification follow.
59 |
60 | GNU GENERAL PUBLIC LICENSE
61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
62 |
63 | 0. This License applies to any program or other work which contains
64 | a notice placed by the copyright holder saying it may be distributed
65 | under the terms of this General Public License. The "Program", below,
66 | refers to any such program or work, and a "work based on the Program"
67 | means either the Program or any derivative work under copyright law:
68 | that is to say, a work containing the Program or a portion of it,
69 | either verbatim or with modifications and/or translated into another
70 | language. (Hereinafter, translation is included without limitation in
71 | the term "modification".) Each licensee is addressed as "you".
72 |
73 | Activities other than copying, distribution and modification are not
74 | covered by this License; they are outside its scope. The act of
75 | running the Program is not restricted, and the output from the Program
76 | is covered only if its contents constitute a work based on the
77 | Program (independent of having been made by running the Program).
78 | Whether that is true depends on what the Program does.
79 |
80 | 1. You may copy and distribute verbatim copies of the Program's
81 | source code as you receive it, in any medium, provided that you
82 | conspicuously and appropriately publish on each copy an appropriate
83 | copyright notice and disclaimer of warranty; keep intact all the
84 | notices that refer to this License and to the absence of any warranty;
85 | and give any other recipients of the Program a copy of this License
86 | along with the Program.
87 |
88 | You may charge a fee for the physical act of transferring a copy, and
89 | you may at your option offer warranty protection in exchange for a fee.
90 |
91 | 2. You may modify your copy or copies of the Program or any portion
92 | of it, thus forming a work based on the Program, and copy and
93 | distribute such modifications or work under the terms of Section 1
94 | above, provided that you also meet all of these conditions:
95 |
96 | a) You must cause the modified files to carry prominent notices
97 | stating that you changed the files and the date of any change.
98 |
99 | b) You must cause any work that you distribute or publish, that in
100 | whole or in part contains or is derived from the Program or any
101 | part thereof, to be licensed as a whole at no charge to all third
102 | parties under the terms of this License.
103 |
104 | c) If the modified program normally reads commands interactively
105 | when run, you must cause it, when started running for such
106 | interactive use in the most ordinary way, to print or display an
107 | announcement including an appropriate copyright notice and a
108 | notice that there is no warranty (or else, saying that you provide
109 | a warranty) and that users may redistribute the program under
110 | these conditions, and telling the user how to view a copy of this
111 | License. (Exception: if the Program itself is interactive but
112 | does not normally print such an announcement, your work based on
113 | the Program is not required to print an announcement.)
114 |
115 | These requirements apply to the modified work as a whole. If
116 | identifiable sections of that work are not derived from the Program,
117 | and can be reasonably considered independent and separate works in
118 | themselves, then this License, and its terms, do not apply to those
119 | sections when you distribute them as separate works. But when you
120 | distribute the same sections as part of a whole which is a work based
121 | on the Program, the distribution of the whole must be on the terms of
122 | this License, whose permissions for other licensees extend to the
123 | entire whole, and thus to each and every part regardless of who wrote it.
124 |
125 | Thus, it is not the intent of this section to claim rights or contest
126 | your rights to work written entirely by you; rather, the intent is to
127 | exercise the right to control the distribution of derivative or
128 | collective works based on the Program.
129 |
130 | In addition, mere aggregation of another work not based on the Program
131 | with the Program (or with a work based on the Program) on a volume of
132 | a storage or distribution medium does not bring the other work under
133 | the scope of this License.
134 |
135 | 3. You may copy and distribute the Program (or a work based on it,
136 | under Section 2) in object code or executable form under the terms of
137 | Sections 1 and 2 above provided that you also do one of the following:
138 |
139 | a) Accompany it with the complete corresponding machine-readable
140 | source code, which must be distributed under the terms of Sections
141 | 1 and 2 above on a medium customarily used for software interchange; or,
142 |
143 | b) Accompany it with a written offer, valid for at least three
144 | years, to give any third party, for a charge no more than your
145 | cost of physically performing source distribution, a complete
146 | machine-readable copy of the corresponding source code, to be
147 | distributed under the terms of Sections 1 and 2 above on a medium
148 | customarily used for software interchange; or,
149 |
150 | c) Accompany it with the information you received as to the offer
151 | to distribute corresponding source code. (This alternative is
152 | allowed only for noncommercial distribution and only if you
153 | received the program in object code or executable form with such
154 | an offer, in accord with Subsection b above.)
155 |
156 | The source code for a work means the preferred form of the work for
157 | making modifications to it. For an executable work, complete source
158 | code means all the source code for all modules it contains, plus any
159 | associated interface definition files, plus the scripts used to
160 | control compilation and installation of the executable. However, as a
161 | special exception, the source code distributed need not include
162 | anything that is normally distributed (in either source or binary
163 | form) with the major components (compiler, kernel, and so on) of the
164 | operating system on which the executable runs, unless that component
165 | itself accompanies the executable.
166 |
167 | If distribution of executable or object code is made by offering
168 | access to copy from a designated place, then offering equivalent
169 | access to copy the source code from the same place counts as
170 | distribution of the source code, even though third parties are not
171 | compelled to copy the source along with the object code.
172 |
173 | 4. You may not copy, modify, sublicense, or distribute the Program
174 | except as expressly provided under this License. Any attempt
175 | otherwise to copy, modify, sublicense or distribute the Program is
176 | void, and will automatically terminate your rights under this License.
177 | However, parties who have received copies, or rights, from you under
178 | this License will not have their licenses terminated so long as such
179 | parties remain in full compliance.
180 |
181 | 5. You are not required to accept this License, since you have not
182 | signed it. However, nothing else grants you permission to modify or
183 | distribute the Program or its derivative works. These actions are
184 | prohibited by law if you do not accept this License. Therefore, by
185 | modifying or distributing the Program (or any work based on the
186 | Program), you indicate your acceptance of this License to do so, and
187 | all its terms and conditions for copying, distributing or modifying
188 | the Program or works based on it.
189 |
190 | 6. Each time you redistribute the Program (or any work based on the
191 | Program), the recipient automatically receives a license from the
192 | original licensor to copy, distribute or modify the Program subject to
193 | these terms and conditions. You may not impose any further
194 | restrictions on the recipients' exercise of the rights granted herein.
195 | You are not responsible for enforcing compliance by third parties to
196 | this License.
197 |
198 | 7. If, as a consequence of a court judgment or allegation of patent
199 | infringement or for any other reason (not limited to patent issues),
200 | conditions are imposed on you (whether by court order, agreement or
201 | otherwise) that contradict the conditions of this License, they do not
202 | excuse you from the conditions of this License. If you cannot
203 | distribute so as to satisfy simultaneously your obligations under this
204 | License and any other pertinent obligations, then as a consequence you
205 | may not distribute the Program at all. For example, if a patent
206 | license would not permit royalty-free redistribution of the Program by
207 | all those who receive copies directly or indirectly through you, then
208 | the only way you could satisfy both it and this License would be to
209 | refrain entirely from distribution of the Program.
210 |
211 | If any portion of this section is held invalid or unenforceable under
212 | any particular circumstance, the balance of the section is intended to
213 | apply and the section as a whole is intended to apply in other
214 | circumstances.
215 |
216 | It is not the purpose of this section to induce you to infringe any
217 | patents or other property right claims or to contest validity of any
218 | such claims; this section has the sole purpose of protecting the
219 | integrity of the free software distribution system, which is
220 | implemented by public license practices. Many people have made
221 | generous contributions to the wide range of software distributed
222 | through that system in reliance on consistent application of that
223 | system; it is up to the author/donor to decide if he or she is willing
224 | to distribute software through any other system and a licensee cannot
225 | impose that choice.
226 |
227 | This section is intended to make thoroughly clear what is believed to
228 | be a consequence of the rest of this License.
229 |
230 | 8. If the distribution and/or use of the Program is restricted in
231 | certain countries either by patents or by copyrighted interfaces, the
232 | original copyright holder who places the Program under this License
233 | may add an explicit geographical distribution limitation excluding
234 | those countries, so that distribution is permitted only in or among
235 | countries not thus excluded. In such case, this License incorporates
236 | the limitation as if written in the body of this License.
237 |
238 | 9. The Free Software Foundation may publish revised and/or new versions
239 | of the General Public License from time to time. Such new versions will
240 | be similar in spirit to the present version, but may differ in detail to
241 | address new problems or concerns.
242 |
243 | Each version is given a distinguishing version number. If the Program
244 | specifies a version number of this License which applies to it and "any
245 | later version", you have the option of following the terms and conditions
246 | either of that version or of any later version published by the Free
247 | Software Foundation. If the Program does not specify a version number of
248 | this License, you may choose any version ever published by the Free Software
249 | Foundation.
250 |
251 | 10. If you wish to incorporate parts of the Program into other free
252 | programs whose distribution conditions are different, write to the author
253 | to ask for permission. For software which is copyrighted by the Free
254 | Software Foundation, write to the Free Software Foundation; we sometimes
255 | make exceptions for this. Our decision will be guided by the two goals
256 | of preserving the free status of all derivatives of our free software and
257 | of promoting the sharing and reuse of software generally.
258 |
259 | NO WARRANTY
260 |
261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
269 | REPAIR OR CORRECTION.
270 |
271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
279 | POSSIBILITY OF SUCH DAMAGES.
280 |
281 | END OF TERMS AND CONDITIONS
282 |
283 | How to Apply These Terms to Your New Programs
284 |
285 | If you develop a new program, and you want it to be of the greatest
286 | possible use to the public, the best way to achieve this is to make it
287 | free software which everyone can redistribute and change under these terms.
288 |
289 | To do so, attach the following notices to the program. It is safest
290 | to attach them to the start of each source file to most effectively
291 | convey the exclusion of warranty; and each file should have at least
292 | the "copyright" line and a pointer to where the full notice is found.
293 |
294 |
295 | Copyright (C)
296 |
297 | This program is free software; you can redistribute it and/or modify
298 | it under the terms of the GNU General Public License as published by
299 | the Free Software Foundation; either version 2 of the License, or
300 | (at your option) any later version.
301 |
302 | This program is distributed in the hope that it will be useful,
303 | but WITHOUT ANY WARRANTY; without even the implied warranty of
304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
305 | GNU General Public License for more details.
306 |
307 | You should have received a copy of the GNU General Public License along
308 | with this program; if not, write to the Free Software Foundation, Inc.,
309 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
310 |
311 | Also add information on how to contact you by electronic and paper mail.
312 |
313 | If the program is interactive, make it output a short notice like this
314 | when it starts in an interactive mode:
315 |
316 | Gnomovision version 69, Copyright (C) year name of author
317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 | This is free software, and you are welcome to redistribute it
319 | under certain conditions; type `show c' for details.
320 |
321 | The hypothetical commands `show w' and `show c' should show the appropriate
322 | parts of the General Public License. Of course, the commands you use may
323 | be called something other than `show w' and `show c'; they could even be
324 | mouse-clicks or menu items--whatever suits your program.
325 |
326 | You should also get your employer (if you work as a programmer) or your
327 | school, if any, to sign a "copyright disclaimer" for the program, if
328 | necessary. Here is a sample; alter the names:
329 |
330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
332 |
333 | , 1 April 1989
334 | Ty Coon, President of Vice
335 |
336 | This General Public License does not permit incorporating your program into
337 | proprietary programs. If your program is a subroutine library, you may
338 | consider it more useful to permit linking proprietary applications with the
339 | library. If this is what you want to do, use the GNU Lesser General
340 | Public License instead of this License.
341 |
--------------------------------------------------------------------------------
/assets/javascripts/dagre-d3.min.js:
--------------------------------------------------------------------------------
1 | (function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var j=typeof require=="function"&&require;if(!h&&j)return j(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}var f=typeof require=="function"&&require;for(var g=0;gMath.abs(e)*h?(f<0&&(h=-h),i=f===0?0:h*e/f,j=h):(e<0&&(g=-g),i=g,j=e===0?0:g*f/e),{x:c+i,y:d+j}}function B(a,b){return"children"in a&&a.children(b).length}function C(a,b){return a.bind?a.bind(b):function(){return a.apply(b,arguments)}}var d=a("dagre").layout,e;try{e=a("d3")}catch(f){e=window.d3}b.exports=g,g.prototype.layout=function(a){return arguments.length?(this._layout=a,this):this._layout},g.prototype.drawNodes=function(a){return arguments.length?(this._drawNodes=C(a,this),this):this._drawNodes},g.prototype.drawEdgeLabels=function(a){return arguments.length?(this._drawEdgeLabels=C(a,this),this):this._drawEdgeLabels},g.prototype.drawEdgePaths=function(a){return arguments.length?(this._drawEdgePaths=C(a,this),this):this._drawEdgePaths},g.prototype.positionNodes=function(a){return arguments.length?(this._positionNodes=C(a,this),this):this._positionNodes},g.prototype.positionEdgeLabels=function(a){return arguments.length?(this._positionEdgeLabels=C(a,this),this):this._positionEdgeLabels},g.prototype.positionEdgePaths=function(a){return arguments.length?(this._positionEdgePaths=C(a,this),this):this._positionEdgePaths},g.prototype.transition=function(a){return arguments.length?(this._transition=C(a,this),this):this._transition},g.prototype.zoomSetup=function(a){return arguments.length?(this._zoomSetup=C(a,this),this):this._zoomSetup},g.prototype.zoom=function(a){return arguments.length?(a?this._zoom=C(a,this):delete this._zoom,this):this._zoom},g.prototype.postLayout=function(a){return arguments.length?(this._postLayout=C(a,this),this):this._postLayout},g.prototype.postRender=function(a){return arguments.length?(this._postRender=C(a,this),this):this._postRender},g.prototype.edgeInterpolate=function(a){return arguments.length?(this._edgeInterpolate=a,this):this._edgeInterpolate},g.prototype.edgeTension=function(a){return arguments.length?(this._edgeTension=a,this):this._edgeTension},g.prototype.run=function(a,b){a=h(a),b=this._zoomSetup(a,b),b.selectAll("g.edgePaths, g.edgeLabels, g.nodes").data(["edgePaths","edgeLabels","nodes"]).enter().append("g").attr("class",function(a){return a});var c=this._drawNodes(a,b.select("g.nodes")),d=this._drawEdgeLabels(a,b.select("g.edgeLabels"));c.each(function(b){i(this,a.node(b))}),d.each(function(b){i(this,a.edge(b))});var e=j(a,this._layout);this._postLayout(e,b);var f=this._drawEdgePaths(a,b.select("g.edgePaths"));return this._positionNodes(e,c),this._positionEdgeLabels(e,d),this._positionEdgePaths(e,f),this._postRender(e,b),e};var m=function(a,b){var c=b.selectAll("g.edgePath").classed("enter",!1).data(a.edges(),function(a){return a});return c.enter().append("g").attr("class","edgePath enter").append("path").style("opacity",0).attr("marker-end","url(#arrowhead)"),this._transition(c.exit()).style("opacity",0).remove(),c}},{d3:10,dagre:11}],4:[function(a,b,c){b.exports="0.2.3"},{}],5:[function(a,b,c){c.Set=a("./lib/Set"),c.PriorityQueue=a("./lib/PriorityQueue"),c.version=a("./lib/version")},{"./lib/PriorityQueue":6,"./lib/Set":7,"./lib/version":9}],6:[function(a,b,c){function d(){this._arr=[],this._keyIndices={}}b.exports=d,d.prototype.size=function(){return this._arr.length},d.prototype.keys=function(){return this._arr.map(function(a){return a.key})},d.prototype.has=function(a){return a in this._keyIndices},d.prototype.priority=function(a){var b=this._keyIndices[a];if(b!==undefined)return this._arr[b].priority},d.prototype.min=function(){if(this.size()===0)throw new Error("Queue underflow");return this._arr[0].key},d.prototype.add=function(a,b){var c=this._keyIndices;if(a in c)return!1;var d=this._arr,e=d.length;return c[a]=e,d.push({key:a,priority:b}),this._decrease(e),!0},d.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var a=this._arr.pop();return delete this._keyIndices[a.key],this._heapify(0),a.key},d.prototype.decrease=function(a,b){var c=this._keyIndices[a];if(b>this._arr[c].priority)throw new Error("New priority is greater than current priority. Key: "+a+" Old: "+this._arr[c].priority+" New: "+b);this._arr[c].priority=b,this._decrease(c)},d.prototype._heapify=function(a){var b=this._arr,c=2*a,d=c+1,e=a;c>1;if(b[d].priority>>0,g=!1;1d;++d)a.hasOwnProperty(d)&&(g?e=b(e,a[d],d,a):(e=a[d],g=!0));if(!g)throw new TypeError("Reduce of empty array with no initial value");return e}:c.reduce=function(a,b,c){return a.reduce(b,c)}},{}],9:[function(a,b,c){b.exports="1.1.3"},{}],10:[function(a,b,c){a("./d3"),b.exports=d3,function(){delete this.d3}()},{}],11:[function(a,b,c){c.Digraph=a("graphlib").Digraph,c.Graph=a("graphlib").Graph,c.layout=a("./lib/layout"),c.version=a("./lib/version")},{"./lib/layout":12,"./lib/version":27,graphlib:28}],12:[function(a,b,c){var d=a("./util"),e=a("./rank"),f=a("./order"),g=a("graphlib").CGraph,h=a("graphlib").CDigraph;b.exports=function(){function j(a){var c=new h;a.eachNode(function(a,b){b===undefined&&(b={}),c.addNode(a,{width:b.width,height:b.height}),b.hasOwnProperty("rank")&&(c.node(a).prefRank=b.rank)}),a.parent&&a.nodes().forEach(function(b){c.parent(b,a.parent(b))}),a.eachEdge(function(a,b,d,e){e===undefined&&(e={});var f={e:a,minLen:e.minLen||1,width:e.width||0,height:e.height||0,points:[]};c.addEdge(null,b,d,f)});var d=a.graph()||{};return c.graph({rankDir:d.rankDir||b.rankDir,orderRestarts:d.orderRestarts}),c}function k(a){var g=i.rankSep(),h;try{return h=d.time("initLayoutGraph",j)(a),h.order()===0?h:(h.eachEdge(function(a,b,c,d){d.minLen*=2}),i.rankSep(g/2),d.time("rank.run",e.run)(h,b.rankSimplex),d.time("normalize",l)(h),d.time("order",f)(h,b.orderMaxSweeps),d.time("position",c.run)(h),d.time("undoNormalize",m)(h),d.time("fixupEdgePoints",n)(h),d.time("rank.restoreEdges",e.restoreEdges)(h),d.time("createFinalGraph",o)(h,a.isDirected()))}finally{i.rankSep(g)}}function l(a){var b=0;a.eachEdge(function(c,d,e,f){var g=a.node(d).rank,h=a.node(e).rank;if(g+10),d.log(2,"Order phase start cross count: "+a.graph().orderInitCC);var q,r,s;for(q=0,r=0;r<4&&q0;++q,++r,++i)n(a,h,q),s=e(a),s=0;--i)h(b[i],c,m(a,b[i].nodes()))}var d=a("./util"),e=a("./order/crossCount"),f=a("./order/initLayerGraphs"),g=a("./order/initOrder"),h=a("./order/sortLayer");b.exports=k;var j=24;k.DEFAULT_MAX_SWEEPS=j},{"./order/crossCount":14,"./order/initLayerGraphs":15,"./order/initOrder":16,"./order/sortLayer":17,"./util":26}],14:[function(a,b,c){function e(a){var b=0,c=d.ordering(a);for(var e=1;e0)b%2&&(i+=g[b+1]),b=b-1>>1,++g[b]}),i}var d=a("../util");b.exports=e},{"../util":26}],15:[function(a,b,c){function f(a){function c(d){if(d===null){a.children(d).forEach(function(a){c(a)});return}var f=a.node(d);f.minRank="rank"in f?f.rank:Number.MAX_VALUE,f.maxRank="rank"in f?f.rank:Number.MIN_VALUE;var h=new e;return a.children(d).forEach(function(b){var d=c(b);h=e.union([h,d]),f.minRank=Math.min(f.minRank,a.node(b).minRank),f.maxRank=Math.max(f.maxRank,a.node(b).maxRank)}),"rank"in f&&h.add(f.rank),h.keys().forEach(function(a){a in b||(b[a]=[]),b[a].push(d)}),h}var b=[];c(null);var f=[];return b.forEach(function(b,c){f[c]=a.filterNodes(d(b))}),f}var d=a("graphlib").filter.nodesFromList,e=a("cp-data").Set;b.exports=f},{"cp-data":5,graphlib:28}],16:[function(a,b,c){function f(a,b){var c=[];a.eachNode(function(b,d){var e=c[d.rank];if(a.children&&a.children(b).length>0)return;e||(e=c[d.rank]=[]),e.push(b)}),c.forEach(function(c){b&&e.shuffle(c),c.forEach(function(b,c){a.node(b).order=c})});var f=d(a);a.graph().orderInitCC=f,a.graph().orderCC=Number.MAX_VALUE}var d=a("./crossCount"),e=a("../util");b.exports=f},{"../util":26,"./crossCount":14}],17:[function(a,b,c){function e(a,b,c){var e=[],f={};a.eachNode(function(a,b){e[b.order]=a;var g=c[a];g.length&&(f[a]=d.sum(g)/g.length)});var g=a.nodes().filter(function(a){return f[a]!==undefined});g.sort(function(b,c){return f[b]-f[c]||a.node(b).order-a.node(c).order});for(var h=0,i=0,j=g.length;i=3&&t(d+f,b,c,i[j]),f==="r"&&l(i[j]),f==="r"&&m(c)}),d==="d"&&c.reverse()}),k(b,c,i),b.eachNode(function(a){var c=[];for(var d in i){var e=i[d][a];r(d,b,a,e),c.push(e)}c.sort(function(a,b){return a-b}),q(b,a,(c[1]+c[2])/2)});var j=0,p=b.graph().rankDir==="BT"||b.graph().rankDir==="RL";c.forEach(function(c){var e=d.max(c.map(function(a){return o(b,a)}));j+=e/2,c.forEach(function(a){s(b,a,p?-j:j)}),j+=e/2+a.rankSep});var u=d.min(b.nodes().map(function(a){return q(b,a)-n(b,a)/2})),v=d.min(b.nodes().map(function(a){return s(b,a)-o(b,a)/2}));b.eachNode(function(a){q(b,a,q(b,a)-u),s(b,a,s(b,a)-v)})}function e(a,b){return aj)c[e(g[i],a)]=!0}var c={},d={},f,g,h,i,j;if(b.length<=2)return c;b[1].forEach(function(a,b){d[a]=b});for(var l=1;l0&&(j.sort(function(a,b){return f[a]-f[b]}),k=(j.length-1)/2,j.slice(Math.floor(k),Math.ceil(k)+1).forEach(function(a){h[b]===b&&!c[e(a,b)]&&i0){var h=e[j[d]];m(h),g[b]===b&&(g[b]=g[h]);var i=p(a,j[d])+p(a,d);g[b]!==g[h]?l(g[h],g[b],k[b]-k[h]-i):k[b]=Math.max(k[b],k[h]+i)}d=f[d]}while(d!==b)}}var g={},h={},i={},j={},k={};return b.forEach(function(a){a.forEach(function(b,c){g[b]=b,h[b]={},c>0&&(j[b]=a[c-1])})}),d.values(e).forEach(function(a){m(a)}),b.forEach(function(a){a.forEach(function(a){k[a]=k[e[a]];if(a===e[a]&&a===g[a]){var b=0;a in h&&Object.keys(h[a]).length>0&&(b=d.min(Object.keys(h[a]).map(function(b){return h[a][b]+(b in i?i[b]:0)}))),i[a]=b}})}),b.forEach(function(a){a.forEach(function(a){k[a]+=i[g[e[a]]]||0})}),k}function i(a,b,c){return d.min(b.map(function(a){var b=a[0];return c[b]}))}function j(a,b,c){return d.max(b.map(function(a){var b=a[a.length-1];return c[b]}))}function k(a,b,c){function h(a){c[l][a]+=g[l]}var d={},e={},f,g={},k=Number.POSITIVE_INFINITY;for(var l in c){var m=c[l];d[l]=i(a,b,m),e[l]=j(a,b,m);var n=e[l]-d[l];na.node(d).rank&&(a.delEdge(b),e.reversed=!0,a.addEdge(b,d,c,e))})}function r(a,b){var c=g(a);b&&(d.log(1,"Using network simplex for ranking"),i(a,c)),s(a)}function s(a){var b=d.min(a.nodes().map(function(b){return a.node(b).rank}));a.eachNode(function(a,c){c.rank-=b})}var d=a("./util"),e=a("./rank/acyclic"),f=a("./rank/initRank"),g=a("./rank/feasibleTree"),h=a("./rank/constraints"),i=a("./rank/simplex"),j=a("graphlib").alg.components,k=a("graphlib").filter;c.run=l,c.restoreEdges=m},{"./rank/acyclic":20,"./rank/constraints":21,"./rank/feasibleTree":22,"./rank/initRank":23,"./rank/simplex":25,"./util":26,graphlib:28}],20:[function(a,b,c){function e(a){function f(d){if(d in c)return;c[d]=b[d]=!0,a.outEdges(d).forEach(function(c){var h=a.target(c),i;d===h?console.error('Warning: found self loop "'+c+'" for node "'+d+'"'):h in b?(i=a.edge(c),a.delEdge(c),i.reversed=!0,++e,a.addEdge(c,h,d,i)):f(h)}),delete b[d]}var b={},c={},e=0;return a.eachNode(function(a){f(a)}),d.log(2,"Acyclic Phase: reversed "+e+" edge(s)"),e}function f(a){a.eachEdge(function(b,c,d,e){e.reversed&&(delete e.reversed,a.delEdge(b),a.addEdge(b,d,c,e))})}var d=a("../util");b.exports=e,b.exports.undo=f},{"../util":26}],21:[function(a,b,c){function d(a){return a!=="min"&&a!=="max"&&a.indexOf("same_")!==0?(console.error("Unsupported rank type: "+a),!1):!0}function e(a,b,c,d){a.inEdges(b).forEach(function(b){var e=a.edge(b),f;e.originalEdge?f=e:f={originalEdge:{e:b,u:a.source(b),v:a.target(b),value:e},minLen:a.edge(b).minLen},e.selfLoop&&(d=!1),d?(a.addEdge(null,c,a.source(b),f),f.reversed=!0):a.addEdge(null,a.source(b),c,f)})}function f(a,b,c,d){a.outEdges(b).forEach(function(b){var e=a.edge(b),f;e.originalEdge?f=e:f={originalEdge:{e:b,u:a.source(b),v:a.target(b),value:e},minLen:a.edge(b).minLen},e.selfLoop&&(d=!1),d?(a.addEdge(null,a.target(b),c,f),f.reversed=!0):a.addEdge(null,c,a.target(b),f)})}function g(a,b,c){c!==undefined&&a.children(b).forEach(function(b){b!==c&&!a.outEdges(c,b).length&&!a.node(b).dummy&&a.addEdge(null,c,b,{minLen:0})})}function h(a,b,c){c!==undefined&&a.children(b).forEach(function(b){b!==c&&!a.outEdges(b,c).length&&!a.node(b).dummy&&a.addEdge(null,b,c,{minLen:0})})}c.apply=function(a){function b(c){var i={};a.children(c).forEach(function(g){if(a.children(g).length){b(g);return}var h=a.node(g),j=h.prefRank;if(j!==undefined){if(!d(j))return;j in i?i.prefRank.push(g):i.prefRank=[g];var k=i[j];k===undefined&&(k=i[j]=a.addNode(null,{originalNodes:[]}),a.parent(k,c)),e(a,g,k,j==="min"),f(a,g,k,j==="max"),a.node(k).originalNodes.push({u:g,value:h,parent:c}),a.delNode(g)}}),g(a,c,i.min),h(a,c,i.max)}b(null)},c.relax=function(a){var b=[];a.eachEdge(function(a,c,d,e){var f=e.originalEdge;f&&b.push(f)}),a.eachNode(function(b,c){var d=c.originalNodes;d&&(d.forEach(function(b){b.value.rank=c.rank,a.addNode(b.u,b.value),a.parent(b.u,b.parent)}),a.delNode(b))}),b.forEach(function(b){a.addEdge(b.e,b.u,b.v,b.value)})}},{}],22:[function(a,b,c){function g(a){function g(d){var e=!0;return a.predecessors(d).forEach(function(f){b.has(f)&&!h(a,f,d)&&(b.has(d)&&(c.addNode(d,{}),b.remove(d),c.graph({root:d})),c.addNode(f,{}),c.addEdge(null,f,d,{reversed:!0}),b.remove(f),g(f),e=!1)}),a.successors(d).forEach(function(f){b.has(f)&&!h(a,d,f)&&(b.has(d)&&(c.addNode(d,{}),b.remove(d),c.graph({root:d})),c.addNode(f,{}),c.addEdge(null,d,f,{}),b.remove(f),g(f),e=!1)}),e}function i(){var d=Number.MAX_VALUE;b.keys().forEach(function(c){a.predecessors(c).forEach(function(e){if(!b.has(e)){var f=h(a,e,c);Math.abs(f)0)i=b.source(j[0]),j=b.inEdges(i);b.graph().root=i,b.addEdge(null,e,f,{cutValue:0}),g(a,b),n(a,b)}function n(a,b){function c(d){var e=b.successors(d);e.forEach(function(b){var e=o(a,d,b);a.node(b).rank=a.node(d).rank+e,c(b)})}c(b.graph().root)}function o(a,b,c){var e=a.outEdges(b,c);if(e.length>0)return d.max(e.map(function(b){return a.edge(b).minLen}));var f=a.inEdges(b,c);if(f.length>0)return-d.max(f.map(function(b){return a.edge(b).minLen}))}var d=a("../util"),e=a("./rankUtil");b.exports=f},{"../util":26,"./rankUtil":24}],26:[function(a,b,c){function d(a,b){return function(){var c=(new Date).getTime();try{return b.apply(null,arguments)}finally{e(1,a+" time: "+((new Date).getTime()-c)+"ms")}}}function e(a){e.level>=a&&console.log.apply(console,Array.prototype.slice.call(arguments,1))}c.min=function(a){return Math.min.apply(Math,a)},c.max=function(a){return Math.max.apply(Math,a)},c.all=function(a,b){for(var c=0;c0;--i){var b=Math.floor(Math.random()*(i+1)),c=a[b];a[b]=a[i],a[i]=c}},c.propertyAccessor=function(a,b,c,d){return function(e){return arguments.length?(b[c]=e,d&&d(e),a):b[c]}},c.ordering=function(a){var b=[];return a.eachNode(function(a,c){var d=b[c.rank]||(b[c.rank]=[]);d[c.order]=a}),b},c.filterNonSubgraphs=function(a){return function(b){return a.children(b).length===0}},d.enabled=!1,c.time=d,e.level=0,c.log=e},{}],27:[function(a,b,c){b.exports="0.4.5"},{}],28:[function(a,b,c){c.Graph=a("./lib/Graph"),c.Digraph=a("./lib/Digraph"
2 | ),c.CGraph=a("./lib/CGraph"),c.CDigraph=a("./lib/CDigraph"),a("./lib/graph-converters"),c.alg={isAcyclic:a("./lib/alg/isAcyclic"),components:a("./lib/alg/components"),dijkstra:a("./lib/alg/dijkstra"),dijkstraAll:a("./lib/alg/dijkstraAll"),findCycles:a("./lib/alg/findCycles"),floydWarshall:a("./lib/alg/floydWarshall"),postorder:a("./lib/alg/postorder"),preorder:a("./lib/alg/preorder"),prim:a("./lib/alg/prim"),tarjan:a("./lib/alg/tarjan"),topsort:a("./lib/alg/topsort")},c.converter={json:a("./lib/converter/json.js")};var d=a("./lib/filter");c.filter={all:d.all,nodesFromList:d.nodesFromList},c.version=a("./lib/version")},{"./lib/CDigraph":30,"./lib/CGraph":31,"./lib/Digraph":32,"./lib/Graph":33,"./lib/alg/components":34,"./lib/alg/dijkstra":35,"./lib/alg/dijkstraAll":36,"./lib/alg/findCycles":37,"./lib/alg/floydWarshall":38,"./lib/alg/isAcyclic":39,"./lib/alg/postorder":40,"./lib/alg/preorder":41,"./lib/alg/prim":42,"./lib/alg/tarjan":43,"./lib/alg/topsort":44,"./lib/converter/json.js":46,"./lib/filter":47,"./lib/graph-converters":48,"./lib/version":50}],29:[function(a,b,c){function e(){this._value=undefined,this._nodes={},this._edges={},this._nextId=0}function f(a,b,c){(a[b]||(a[b]=new d)).add(c)}function g(a,b,c){var d=a[b];d.remove(c),d.size()===0&&delete a[b]}var d=a("cp-data").Set;b.exports=e,e.prototype.order=function(){return Object.keys(this._nodes).length},e.prototype.size=function(){return Object.keys(this._edges).length},e.prototype.graph=function(a){if(arguments.length===0)return this._value;this._value=a},e.prototype.hasNode=function(a){return a in this._nodes},e.prototype.node=function(a,b){var c=this._strictGetNode(a);if(arguments.length===1)return c.value;c.value=b},e.prototype.nodes=function(){var a=[];return this.eachNode(function(b){a.push(b)}),a},e.prototype.eachNode=function(a){for(var b in this._nodes){var c=this._nodes[b];a(c.id,c.value)}},e.prototype.hasEdge=function(a){return a in this._edges},e.prototype.edge=function(a,b){var c=this._strictGetEdge(a);if(arguments.length===1)return c.value;c.value=b},e.prototype.edges=function(){var a=[];return this.eachEdge(function(b){a.push(b)}),a},e.prototype.eachEdge=function(a){for(var b in this._edges){var c=this._edges[b];a(c.id,c.u,c.v,c.value)}},e.prototype.incidentNodes=function(a){var b=this._strictGetEdge(a);return[b.u,b.v]},e.prototype.addNode=function(a,b){if(a===undefined||a===null){do a="_"+ ++this._nextId;while(this.hasNode(a))}else if(this.hasNode(a))throw new Error("Graph already has node '"+a+"'");return this._nodes[a]={id:a,value:b},a},e.prototype.delNode=function(a){this._strictGetNode(a),this.incidentEdges(a).forEach(function(a){this.delEdge(a)},this),delete this._nodes[a]},e.prototype._addEdge=function(a,b,c,d,e,g){this._strictGetNode(b),this._strictGetNode(c);if(a===undefined||a===null){do a="_"+ ++this._nextId;while(this.hasEdge(a))}else if(this.hasEdge(a))throw new Error("Graph already has edge '"+a+"'");return this._edges[a]={id:a,u:b,v:c,value:d},f(e[c],b,a),f(g[b],c,a),a},e.prototype._delEdge=function(a,b,c){var d=this._strictGetEdge(a);g(b[d.v],d.u,a),g(c[d.u],d.v,a),delete this._edges[a]},e.prototype.copy=function(){var a=new this.constructor;return a.graph(this.graph()),this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(b,c,d,e)}),a._nextId=this._nextId,a},e.prototype.filterNodes=function(a){var b=new this.constructor;return b.graph(this.graph()),this.eachNode(function(c,d){a(c)&&b.addNode(c,d)}),this.eachEdge(function(a,c,d,e){b.hasNode(c)&&b.hasNode(d)&&b.addEdge(a,c,d,e)}),b},e.prototype._strictGetNode=function(a){var b=this._nodes[a];if(b===undefined)throw new Error("Node '"+a+"' is not in graph");return b},e.prototype._strictGetEdge=function(a){var b=this._edges[a];if(b===undefined)throw new Error("Edge '"+a+"' is not in graph");return b}},{"cp-data":5}],30:[function(a,b,c){var d=a("./Digraph"),e=a("./compoundify"),f=e(d);b.exports=f,f.fromDigraph=function(a){var b=new f,c=a.graph();return c!==undefined&&b.graph(c),a.eachNode(function(a,c){c===undefined?b.addNode(a):b.addNode(a,c)}),a.eachEdge(function(a,c,d,e){e===undefined?b.addEdge(null,c,d):b.addEdge(null,c,d,e)}),b},f.prototype.toString=function(){return"CDigraph "+JSON.stringify(this,null,2)}},{"./Digraph":32,"./compoundify":45}],31:[function(a,b,c){var d=a("./Graph"),e=a("./compoundify"),f=e(d);b.exports=f,f.fromGraph=function(a){var b=new f,c=a.graph();return c!==undefined&&b.graph(c),a.eachNode(function(a,c){c===undefined?b.addNode(a):b.addNode(a,c)}),a.eachEdge(function(a,c,d,e){e===undefined?b.addEdge(null,c,d):b.addEdge(null,c,d,e)}),b},f.prototype.toString=function(){return"CGraph "+JSON.stringify(this,null,2)}},{"./Graph":33,"./compoundify":45}],32:[function(a,b,c){function g(){e.call(this),this._inEdges={},this._outEdges={}}var d=a("./util"),e=a("./BaseGraph"),f=a("cp-data").Set;b.exports=g,g.prototype=new e,g.prototype.constructor=g,g.prototype.isDirected=function(){return!0},g.prototype.successors=function(a){return this._strictGetNode(a),Object.keys(this._outEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.predecessors=function(a){return this._strictGetNode(a),Object.keys(this._inEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.neighbors=function(a){return f.union([this.successors(a),this.predecessors(a)]).keys()},g.prototype.sources=function(){var a=this;return this._filterNodes(function(b){return a.inEdges(b).length===0})},g.prototype.sinks=function(){var a=this;return this._filterNodes(function(b){return a.outEdges(b).length===0})},g.prototype.source=function(a){return this._strictGetEdge(a).u},g.prototype.target=function(a){return this._strictGetEdge(a).v},g.prototype.inEdges=function(a,b){this._strictGetNode(a);var c=f.union(d.values(this._inEdges[a])).keys();return arguments.length>1&&(this._strictGetNode(b),c=c.filter(function(a){return this.source(a)===b},this)),c},g.prototype.outEdges=function(a,b){this._strictGetNode(a);var c=f.union(d.values(this._outEdges[a])).keys();return arguments.length>1&&(this._strictGetNode(b),c=c.filter(function(a){return this.target(a)===b},this)),c},g.prototype.incidentEdges=function(a,b){return arguments.length>1?f.union([this.outEdges(a,b),this.outEdges(b,a)]).keys():f.union([this.inEdges(a),this.outEdges(a)]).keys()},g.prototype.toString=function(){return"Digraph "+JSON.stringify(this,null,2)},g.prototype.addNode=function(a,b){return a=e.prototype.addNode.call(this,a,b),this._inEdges[a]={},this._outEdges[a]={},a},g.prototype.delNode=function(a){e.prototype.delNode.call(this,a),delete this._inEdges[a],delete this._outEdges[a]},g.prototype.addEdge=function(a,b,c,d){return e.prototype._addEdge.call(this,a,b,c,d,this._inEdges,this._outEdges)},g.prototype.delEdge=function(a){e.prototype._delEdge.call(this,a,this._inEdges,this._outEdges)},g.prototype._filterNodes=function(a){var b=[];return this.eachNode(function(c){a(c)&&b.push(c)}),b}},{"./BaseGraph":29,"./util":49,"cp-data":5}],33:[function(a,b,c){function g(){e.call(this),this._incidentEdges={}}var d=a("./util"),e=a("./BaseGraph"),f=a("cp-data").Set;b.exports=g,g.prototype=new e,g.prototype.constructor=g,g.prototype.isDirected=function(){return!1},g.prototype.neighbors=function(a){return this._strictGetNode(a),Object.keys(this._incidentEdges[a]).map(function(a){return this._nodes[a].id},this)},g.prototype.incidentEdges=function(a,b){return this._strictGetNode(a),arguments.length>1?(this._strictGetNode(b),b in this._incidentEdges[a]?this._incidentEdges[a][b].keys():[]):f.union(d.values(this._incidentEdges[a])).keys()},g.prototype.toString=function(){return"Graph "+JSON.stringify(this,null,2)},g.prototype.addNode=function(a,b){return a=e.prototype.addNode.call(this,a,b),this._incidentEdges[a]={},a},g.prototype.delNode=function(a){e.prototype.delNode.call(this,a),delete this._incidentEdges[a]},g.prototype.addEdge=function(a,b,c,d){return e.prototype._addEdge.call(this,a,b,c,d,this._incidentEdges,this._incidentEdges)},g.prototype.delEdge=function(a){e.prototype._delEdge.call(this,a,this._incidentEdges,this._incidentEdges)}},{"./BaseGraph":29,"./util":49,"cp-data":5}],34:[function(a,b,c){function e(a){function e(b,d){c.has(b)||(c.add(b),d.push(b),a.neighbors(b).forEach(function(a){e(a,d)}))}var b=[],c=new d;return a.nodes().forEach(function(a){var c=[];e(a,c),c.length>0&&b.push(c)}),b}var d=a("cp-data").Set;b.exports=e},{"cp-data":5}],35:[function(a,b,c){function e(a,b,c,e){function h(b){var d=a.incidentNodes(b),e=d[0]!==i?d[0]:d[1],h=f[e],k=c(b),l=j.distance+k;if(k<0)throw new Error("dijkstra does not allow negative edge weights. Bad edge: "+b+" Weight: "+k);l0){i=g.removeMin(),j=f[i];if(j.distance===Number.POSITIVE_INFINITY)break;e(i).forEach(h)}return f}var d=a("cp-data").PriorityQueue;b.exports=e},{"cp-data":5}],36:[function(a,b,c){function e(a,b,c){var e={};return a.eachNode(function(f){e[f]=d(a,f,b,c)}),e}var d=a("./dijkstra");b.exports=e},{"./dijkstra":35}],37:[function(a,b,c){function e(a){return d(a).filter(function(a){return a.length>1})}var d=a("./tarjan");b.exports=e},{"./tarjan":43}],38:[function(a,b,c){function d(a,b,c){var d={},e=a.nodes();return b=b||function(){return 1},c=c||(a.isDirected()?function(b){return a.outEdges(b)}:function(b){return a.incidentEdges(b)}),e.forEach(function(f){d[f]={},d[f][f]={distance:0},e.forEach(function(a){f!==a&&(d[f][a]={distance:Number.POSITIVE_INFINITY})}),c(f).forEach(function(c){var e=a.incidentNodes(c),h=e[0]!==f?e[0]:e[1],i=b(c);i0){h=g.removeMin();if(h in f)c.addEdge(null,h,f[h]);else{if(j)throw new Error("Input graph is not connected: "+a);j=!0}a.incidentEdges(h).forEach(i)}return c}var d=a("../Graph"),e=a("cp-data").PriorityQueue;b.exports=f},{"../Graph":33,"cp-data":5}],43:[function(a,b,c){function d(a){function f(h){var i=d[h]={onStack:!0,lowlink:b,index:b++};c.push(h),a.successors(h).forEach(function(a){a in d?d[a].onStack&&(i.lowlink=Math.min(i.lowlink,d[a].index)):(f(a),i.lowlink=Math.min(i.lowlink,d[a].lowlink))});if(i.lowlink===i.index){var j=[],k;do k=c.pop(),d[k].onStack=!1,j.push(k);while(h!==k);e.push(j)}}if(!a.isDirected())throw new Error("tarjan can only be applied to a directed graph. Bad input: "+a);var b=0,c=[],d={},e=[];return a.nodes().forEach(function(a){a in d||f(a)}),e}b.exports=d},{}],44:[function(a,b,c){function d(a){function f(g){if(g in c)throw new e;g in b||(c[g]=!0,b[g]=!0,a.predecessors(g).forEach(function(a){f(a)}),delete c[g],d.push(g))}if(!a.isDirected())throw new Error("topsort can only be applied to a directed graph. Bad input: "+a);var b={},c={},d=[],g=a.sinks();if(a.order()!==0&&g.length===0)throw new e;return a.sinks().forEach(function(a){f(a)}),d}function e(){}b.exports=d,d.CycleException=e,e.prototype.toString=function(){return"Graph has at least one cycle"}},{}],45:[function(a,b,c){function e(a){function b(){a.call(this),this._parents={},this._children={},this._children[null]=new d}return b.prototype=new a,b.prototype.constructor=b,b.prototype.parent=function(a,b){this._strictGetNode(a);if(arguments.length<2)return this._parents[a];if(a===b)throw new Error("Cannot make "+a+" a parent of itself");b!==null&&this._strictGetNode(b),this._children[this._parents[a]].remove(a),this._parents[a]=b,this._children[b].add(a)},b.prototype.children=function(a){return a!==null&&this._strictGetNode(a),this._children[a].keys()},b.prototype.addNode=function(b,c){return b=a.prototype.addNode.call(this,b,c),this._parents[b]=null,this._children[b]=new d,this._children[null].add(b),b},b.prototype.delNode=function(b){var c=this.parent(b);return this._children[b].keys().forEach(function(a){this.parent(a,c)},this),this._children[c].remove(b),delete this._parents[b],delete this._children[b],a.prototype.delNode.call(this,b)},b.prototype.copy=function(){var b=a.prototype.copy.call(this);return this.nodes().forEach(function(a){b.parent(a,this.parent(a))},this),b},b.prototype.filterNodes=function(b){function f(a){var b=c.parent(a);return b===null||d.hasNode(b)?(e[a]=b,b):b in e?e[b]:f(b)}var c=this,d=a.prototype.filterNodes.call(this,b),e={};return d.eachNode(function(a){d.parent(a,f(a))}),d},b}var d=a("cp-data").Set;b.exports=e},{"cp-data":5}],46:[function(a,b,c){function h(a){return Object.prototype.toString.call(a).slice(8,-1)}var d=a("../Graph"),e=a("../Digraph"),f=a("../CGraph"),g=a("../CDigraph");c.decode=function(a,b,c){c=c||e;if(h(a)!=="Array")throw new Error("nodes is not an Array");if(h(b)!=="Array")throw new Error("edges is not an Array");if(typeof c=="string")switch(c){case"graph":c=d;break;case"digraph":c=e;break;case"cgraph":c=f;break;case"cdigraph":c=g;break;default:throw new Error("Unrecognized graph type: "+c)}var i=new c;return a.forEach(function(a){i.addNode(a.id,a.value)}),i.parent&&a.forEach(function(a){a.children&&a.children.forEach(function(b){i.parent(b,a.id)})}),b.forEach(function(a){i.addEdge(a.id,a.u,a.v,a.value)}),i},c.encode=function(a){var b=[],c=[];a.eachNode(function(c,d){var e={id:c,value:d};if(a.children){var f=a.children(c);f.length&&(e.children=f)}b.push(e)}),a.eachEdge(function(a,b,d,e){c.push({id:a,u:b,v:d,value:e})});var h;if(a instanceof g)h="cdigraph";else if(a instanceof f)h="cgraph";else if(a instanceof e)h="digraph";else if(a instanceof d)h="graph";else throw new Error("Couldn't determine type of graph: "+a);return{nodes:b,edges:c,type:h}}},{"../CDigraph":30,"../CGraph":31,"../Digraph":32,"../Graph":33}],47:[function(a,b,c){var d=a("cp-data").Set;c.all=function(){return function(){return!0}},c.nodesFromList=function(a){var b=new d(a);return function(a){return b.has(a)}}},{"cp-data":5}],48:[function(a,b,c){var d=a("./Graph"),e=a("./Digraph");d.prototype.toDigraph=d.prototype.asDirected=function(){var a=new e;return this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(null,c,d,e),a.addEdge(null,d,c,e)}),a},e.prototype.toGraph=e.prototype.asUndirected=function(){var a=new d;return this.eachNode(function(b,c){a.addNode(b,c)}),this.eachEdge(function(b,c,d,e){a.addEdge(b,c,d,e)}),a}},{"./Digraph":32,"./Graph":33}],49:[function(a,b,c){c.values=function(a){var b=Object.keys(a),c=b.length,d=new Array(c),e;for(e=0;e