Merge pull request #421 from knu/improve_diagram-2

Adding a badge to each Agent node in a diagram.

Andrew Cantino 10 years ago
parent
commit
58e9016255

+ 20 - 0
app/assets/javascripts/diagram.js.coffee

@@ -0,0 +1,20 @@
1
+$ ->
2
+  svg = document.querySelector('.agent-diagram svg.diagram')
3
+  overlay = document.querySelector('.agent-diagram .overlay')
4
+  getTopLeft = (node) ->
5
+    bbox = node.getBBox()
6
+    point = svg.createSVGPoint()
7
+    point.x = bbox.x + bbox.width
8
+    point.y = bbox.y
9
+    point.matrixTransform(node.getCTM())
10
+  $(svg).find('g.node[data-badge-id]').each ->
11
+    tl = getTopLeft(this)
12
+    $('#' + this.getAttribute('data-badge-id'), overlay).each ->
13
+      badge = $(this)
14
+      badge.css
15
+        left: tl.x - badge.outerWidth()  * (2/3)
16
+        top:  tl.y - badge.outerHeight() * (1/3)
17
+        'background-color': badge.find('.label').css('background-color')
18
+      .show()
19
+      return
20
+    return

+ 30 - 0
app/assets/stylesheets/diagram.css.scss

@@ -0,0 +1,30 @@
1
+.agent-diagram {
2
+  position: relative;
3
+  z-index: auto;
4
+
5
+  svg.diagram {
6
+    position: absolute;
7
+    z-index: 1;
8
+  }
9
+
10
+  .overlay-container {
11
+    position: absolute;
12
+    top: 0;
13
+    left: 0;
14
+    z-index: auto;
15
+
16
+    .overlay {
17
+      position: relative;
18
+      z-index: auto;
19
+      width: 100%;
20
+      height: 100%;
21
+
22
+      .badge {
23
+        position: absolute;
24
+        display: none;
25
+        color: white !important;
26
+        z-index: 2;
27
+      }
28
+    }
29
+  }
30
+}

+ 70 - 13
app/helpers/dot_helper.rb

@@ -6,7 +6,7 @@ module DotHelper
6 6
           dot.close_write
7 7
           dot.read
8 8
         } rescue false)
9
-      svg.html_safe
9
+      decorate_svg(svg, agents).html_safe
10 10
     else
11 11
       tag('img', src: URI('https://chart.googleapis.com/chart').tap { |uri|
12 12
             uri.query = URI.encode_www_form(cht: 'gv', chl: agents_dot(agents))
@@ -57,6 +57,13 @@ module DotHelper
57 57
       end
58 58
     end
59 59
 
60
+    def ids(values)
61
+      values.each_with_index { |id, i|
62
+        raw ' ' if i > 0
63
+        id id
64
+      }
65
+    end
66
+
60 67
     def attr_list(attrs = nil)
61 68
       return if attrs.nil?
62 69
       attrs = attrs.select { |key, value| value.present? }
@@ -86,16 +93,13 @@ module DotHelper
86 93
     end
87 94
 
88 95
     def statement(ids, attrs = nil)
89
-      Array(ids).each_with_index { |id, i|
90
-        raw ' ' if i > 0
91
-        id id
92
-      }
96
+      ids Array(ids)
93 97
       attr_list attrs
94 98
       raw ';'
95 99
     end
96 100
 
97
-    def block(title, &block)
98
-      raw title
101
+    def block(*ids, &block)
102
+      ids ids
99 103
       raw '{'
100 104
       block.call
101 105
       raw '}'
@@ -112,11 +116,7 @@ module DotHelper
112 116
     draw(agents: agents,
113 117
          agent_id: ->agent { 'a%d' % agent.id },
114 118
          agent_label: ->agent {
115
-           if agent.disabled?
116
-             '%s (Disabled)' % agent.name
117
-           else
118
-             agent.name
119
-           end.gsub(/(.{20}\S*)\s+/) {
119
+           agent.name.gsub(/(.{20}\S*)\s+/) {
120 120
              # Fold after every 20+ characters
121 121
              $1 + "\n"
122 122
            }
@@ -128,6 +128,7 @@ module DotHelper
128 128
       def agent_node(agent)
129 129
         node(agent_id[agent],
130 130
              label: agent_label[agent],
131
+             tooltip: (agent.short_type.titleize if rich),
131 132
              URL: (agent_url[agent] if rich),
132 133
              style: ('rounded,dashed' if agent.disabled?),
133 134
              color: (@disabled if agent.disabled?),
@@ -141,7 +142,7 @@ module DotHelper
141 142
              color: (@disabled if agent.disabled? || receiver.disabled?))
142 143
       end
143 144
 
144
-      block('digraph foo') {
145
+      block('digraph', 'Agent Event Flow') {
145 146
         # statement 'graph', rankdir: 'LR'
146 147
         statement 'node',
147 148
                   shape: 'box',
@@ -160,4 +161,60 @@ module DotHelper
160 161
       }
161 162
     }
162 163
   end
164
+
165
+  def decorate_svg(xml, agents)
166
+    svg = Nokogiri::XML(xml).at('svg')
167
+
168
+    Nokogiri::HTML::Document.new.tap { |doc|
169
+      doc << root = Nokogiri::XML::Node.new('div', doc) { |div|
170
+        div['class'] = 'agent-diagram'
171
+      }
172
+
173
+      svg['class'] = 'diagram'
174
+
175
+      root << svg
176
+      root << overlay_container = Nokogiri::XML::Node.new('div', doc) { |div|
177
+        div['class'] = 'overlay-container'
178
+        div['style'] = "width: #{svg['width']}; height: #{svg['height']}"
179
+      }
180
+      overlay_container << overlay = Nokogiri::XML::Node.new('div', doc) { |div|
181
+        div['class'] = 'overlay'
182
+      }
183
+
184
+      svg.xpath('//xmlns:g[@class="node"]', svg.namespaces).each { |node|
185
+        agent_id = (node.xpath('./xmlns:title/text()', svg.namespaces).to_s[/\d+/] or next).to_i
186
+        agent = agents.find { |a| a.id == agent_id }
187
+
188
+        count = agent.events_count
189
+        next unless count && count > 0
190
+
191
+        overlay << Nokogiri::XML::Node.new('a', doc) { |badge|
192
+          badge['id'] = id = 'b%d' % agent_id
193
+          badge['class'] = 'badge'
194
+          badge['href'] = events_path(agent: agent)
195
+          badge['target'] = '_blank'
196
+          badge['title'] = "#{count} events created"
197
+          badge.content = count.to_s
198
+
199
+          node['data-badge-id'] = id
200
+
201
+          badge << Nokogiri::XML::Node.new('span', doc) { |label|
202
+            # a dummy label only to obtain the background color
203
+            label['class'] = [
204
+              'label',
205
+              if agent.disabled?
206
+                'label-warning'
207
+              elsif agent.working?
208
+                'label-success'
209
+              else
210
+                'label-danger'
211
+              end
212
+            ].join(' ')
213
+            label['style'] = 'display: none';
214
+          }
215
+        }
216
+      }
217
+      # See also: app/assets/diagram.js.coffee
218
+    }.at('div.agent-diagram').to_s
219
+  end
163 220
 end

+ 4 - 0
app/views/diagrams/show.html.erb

@@ -1,3 +1,7 @@
1
+<% content_for :head do %>
2
+  <%= javascript_include_tag "diagram" %>
3
+<% end %>
4
+
1 5
 <div class='container'>
2 6
   <div class='row'>
3 7
     <div class='col-md-12'>

+ 1 - 1
config/environments/production.rb

@@ -61,7 +61,7 @@ Huginn::Application.configure do
61 61
   end
62 62
 
63 63
   # Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added)
64
-  config.assets.precompile += %w( graphing.js user_credentials.js )
64
+  config.assets.precompile += %w( diagram.js graphing.js user_credentials.js )
65 65
 
66 66
   # Ignore bad email addresses and do not raise email delivery errors.
67 67
   # Set this to true and configure the email server for immediate delivery to raise delivery errors.

+ 7 - 7
spec/helpers/dot_helper_spec.rb

@@ -56,13 +56,13 @@ describe DotHelper do
56 56
       it "generates a DOT script" do
57 57
         agents_dot(@agents).should =~ %r{
58 58
           \A
59
-          digraph \s foo \{
59
+          digraph \x20 "Agent \x20 Event \x20 Flow" \{
60 60
             node \[ [^\]]+ \];
61 61
             (?<foo>\w+) \[label=foo\];
62 62
             \k<foo> -> (?<bar1>\w+) \[style=dashed\];
63 63
             \k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
64 64
             \k<bar1> \[label=bar1\];
65
-            \k<bar2> \[label="bar2 \s \(Disabled\)",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
65
+            \k<bar2> \[label=bar2,style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
66 66
             \k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
67 67
             \k<bar3> \[label=bar3\];
68 68
           \}
@@ -73,15 +73,15 @@ describe DotHelper do
73 73
       it "generates a richer DOT script" do
74 74
         agents_dot(@agents, true).should =~ %r{
75 75
           \A
76
-          digraph \s foo \{
76
+          digraph \x20 "Agent \x20 Event \x20 Flow" \{
77 77
             node \[ [^\]]+ \];
78
-            (?<foo>\w+) \[label=foo,URL="#{Regexp.quote(agent_path(@foo))}"\];
78
+            (?<foo>\w+) \[label=foo,tooltip="Dot \x20 Foo",URL="#{Regexp.quote(agent_path(@foo))}"\];
79 79
             \k<foo> -> (?<bar1>\w+) \[style=dashed\];
80 80
             \k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
81
-            \k<bar1> \[label=bar1,URL="#{Regexp.quote(agent_path(@bar1))}"\];
82
-            \k<bar2> \[label="bar2 \s \(Disabled\)",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
81
+            \k<bar1> \[label=bar1,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar1))}"\];
82
+            \k<bar2> \[label=bar2,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
83 83
             \k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
84
-            \k<bar3> \[label=bar3,URL="#{Regexp.quote(agent_path(@bar3))}"\];
84
+            \k<bar3> \[label=bar3,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar3))}"\];
85 85
           \}
86 86
           \z
87 87
         }x