[Ovirt-devel] Re: [PATCH server] Add to summary pages a rudimentary flash chart written in flex framework

Jason Guiditta jguiditt at redhat.com
Fri Oct 10 20:56:55 UTC 2008


Assuming we are pushing this in with all the temporary caveats for
generating the swf, this does work for me, but a few questions/comments
inline.  ACK with suggestions added, I tested locally and they work fine
for me.

On Fri, 2008-10-10 at 15:00 -0500, Steve Linabery wrote:
> ---
>  src/app/controllers/graph_controller.rb            |   30 +-
>  src/app/views/graph/flexchart_data.rhtml           |    1 +
>  src/app/views/graph/history_graphs.rhtml           |   87 +---
>  src/config/routes.rb                               |    1 +
>  src/flexchart/README.txt                           |    8 +
>  src/flexchart/com/adobe/serialization/json/JSON.as |   85 +++
>  .../com/adobe/serialization/json/JSONDecoder.as    |  221 ++++++++
>  .../com/adobe/serialization/json/JSONEncoder.as    |  299 +++++++++++
>  .../com/adobe/serialization/json/JSONParseError.as |   87 +++
>  .../com/adobe/serialization/json/JSONToken.as      |  104 ++++
>  .../com/adobe/serialization/json/JSONTokenType.as  |   67 +++
>  .../com/adobe/serialization/json/JSONTokenizer.as  |  547 ++++++++++++++++++++
>  src/flexchart/flexchart.mxml                       |   20 +
>  src/flexchart/org/ovirt/ChartLoader.as             |   64 +++
>  src/flexchart/org/ovirt/DataSeries.as              |   42 ++
>  src/flexchart/org/ovirt/DataSource.as              |   57 ++
>  src/public/javascripts/jquery.flash.js             |  288 ++++++++++
>  17 files changed, 1930 insertions(+), 78 deletions(-)
>  create mode 100644 src/app/views/graph/flexchart_data.rhtml
>  create mode 100644 src/flexchart/README.txt
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSON.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONDecoder.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONEncoder.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONParseError.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONToken.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenType.as
>  create mode 100644 src/flexchart/com/adobe/serialization/json/JSONTokenizer.as
>  create mode 100644 src/flexchart/flexchart.mxml
>  create mode 100644 src/flexchart/org/ovirt/ChartLoader.as
>  create mode 100644 src/flexchart/org/ovirt/DataSeries.as
>  create mode 100644 src/flexchart/org/ovirt/DataSource.as
>  create mode 100644 src/public/javascripts/jquery.flash.js
> 
> diff --git a/src/app/controllers/graph_controller.rb b/src/app/controllers/graph_controller.rb
> index dbe2afc..6450935 100644
> --- a/src/app/controllers/graph_controller.rb
> +++ b/src/app/controllers/graph_controller.rb
> @@ -3,7 +3,24 @@ require 'util/stats/Stats'
>  class GraphController < ApplicationController
>    layout nil
>  
> -  # generate layout for avaialability bar graphs
> +  def flexchart_data
> +
> +    #FIXME: use the stats package aggregation (when it's available)
> +    #instead of the old method
> +    graph_obj = history_graph_data_object
> +
> +    #FIXME: for this release, the flexchart shows only peak values,
> +    #       and only shows a default of the last 40 data points in rrd.
> +    graph_data = { :labels => graph_obj[:timepoints].last(40),
> +                   :values => graph_obj[:dataset][2][:values].last(40) }
> +    my_data = graph_data[:labels].zip(graph_data[:values])
> +    @graph = { :vectors => my_data,
> +               :max_value => graph_obj[:total_peak]
> +             }
> +  end
> +
> +
> +  # generate layout for availability bar graphs
>    def availability_graph
>      @id = params[:id]
>      @target = params[:target]
> @@ -67,6 +84,10 @@ class GraphController < ApplicationController
>  
>    # retrieves data for history graphs
>    def history_graph_data
> +    render :json => history_graph_data_object
> +  end
> +
> +  def history_graph_data_object
>      history_graphs
>      myDays = params[:days]
>      target = params[:target]
> @@ -212,9 +233,10 @@ class GraphController < ApplicationController
>                  :stroke => @avg_history[:color],
>                  :strokeWidth => 1
>              }
> -       ]
> +       ],
> +       :total_peak => total_peak
>      }
> -    render :json => graph_object
> +
>    end
>  
> 
> @@ -261,7 +283,7 @@ class GraphController < ApplicationController
>              }
>         ]
>      }
> -    render :json => graph_object
> +
>  
>    end
>    
> diff --git a/src/app/views/graph/flexchart_data.rhtml b/src/app/views/graph/flexchart_data.rhtml
> new file mode 100644
> index 0000000..a79ce06
> --- /dev/null
> +++ b/src/app/views/graph/flexchart_data.rhtml
> @@ -0,0 +1 @@
Don't understand why you are doing this here, rather than doing a
render :json in the controller?
> +<%= @graph.to_json %>
> diff --git a/src/app/views/graph/history_graphs.rhtml b/src/app/views/graph/history_graphs.rhtml
> index 2b6874f..f372e4b 100644
> --- a/src/app/views/graph/history_graphs.rhtml
> +++ b/src/app/views/graph/history_graphs.rhtml
> @@ -1,76 +1,15 @@
> +<%= javascript_include_tag "jquery.flash.js" %>
> +<div id="the-div-name"></div>
>  <script type="text/javascript">
> -
> -var graph = "load_history";
> -var days  = "7";
> -
> -function swap_history_graph(newgraph, newdays){
> -    if(newgraph == null) newgraph = graph
> -    if(newdays == null) newdays = days
> -    $('.history_graph').hide(); 
> -    $('#' + newgraph + "_" + newdays).parent().show();
> -    eval("draw_" + newgraph + "_" + newdays + "_graph_get_data()");
> -}
> -function swap_history_graph_target(title, newgraph){
> -    swap_history_graph(newgraph, null);
> -    $('#history_graph_selection').html(title + '  <%= image_tag 'icon_menu_arrow.gif' %>');
> -    graph = newgraph
> -}
> -function swap_history_graph_time(title, newdays){
> -    swap_history_graph(null, newdays);
> -    $('#history_graph_time_selection').html(title + '  <%= image_tag 'icon_menu_arrow.gif' %>');
> -    days = newdays
> -}
> -
> -</script>
> -
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_cpu_history_1_graph',     :div_id => 'cpu_history_1',     :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20,  :scaleX => 173,   :ticksY => 10, :scaleY => 110,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu',    :poolType => @poolType, :days => 1   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_cpu_history_7_graph',     :div_id => 'cpu_history_7',     :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272,  :ticksY => 10, :scaleY => 110,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu',    :poolType => @poolType, :days => 7   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_cpu_history_30_graph',    :div_id => 'cpu_history_30',    :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120,  :scaleX => 1200,  :ticksY => 10, :scaleY => 110,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'cpu',    :poolType => @poolType, :days => 30  } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_memory_history_1_graph',  :div_id => 'memory_history_1',  :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20,  :scaleX => 173,   :ticksY => 50, :scaleY => 756,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 1   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_memory_history_7_graph',  :div_id => 'memory_history_7',  :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272,  :ticksY => 50, :scaleY => 756,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 7   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_memory_history_30_graph', :div_id => 'memory_history_30', :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120,  :scaleX => 1162,  :ticksY => 50, :scaleY => 756,  :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'memory', :poolType => @poolType, :days => 30  } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_load_history_1_graph',    :div_id => 'load_history_1',    :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 20,  :scaleX => 173,   :ticksY => 2,  :scaleY => 23,   :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load',   :poolType => @poolType, :days => 1   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_load_history_7_graph',    :div_id => 'load_history_7',    :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 39, :scaleX => 272,  :ticksY => 2,  :scaleY => 23,   :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load',   :poolType => @poolType, :days => 7   } ) } %>
> -<%= render :partial => '/layouts/graph', :locals => { :drawMe => false,  :includeDiv => false, :methodName=> 'draw_load_history_30_graph',   :div_id => 'load_history_30',   :chartType => 'line', :yGridLines => 'lightgrey', :xGridLines => 'lightgrey', :ticksX => 120,  :scaleX => 1200,  :ticksY => 2,  :scaleY => 23,   :url => (url_for :escape => false, :controller => 'graph', :action => 'history_graph_data', :id => @id, :params => { :target => 'load',   :poolType => @poolType, :days => 30  } ) } %>
> -
> -<div id="history_graphs">
> -  <div id="history_graphs_control">
> -   <div class="history_graphs_menu">
> -      <ul>
> -        <li><div id="history_graph_selection" class="history_graph_menu_header">Overall Load   <%= image_tag 'icon_menu_arrow.gif' %></div></li>
> -        <li class="history_graph_menu_item history_graph_menu_fitem"><a href="#" onclick="swap_history_graph_target('Overall Load', 'load_history')" >Overall Load</a></li>
> -        <li class="history_graph_menu_item"><a href="#" onclick="swap_history_graph_target('CPU History', 'cpu_history');" >CPU History</a></li>
> -        <li class="history_graph_menu_item history_graph_menu_litem"><a href="#" onclick="swap_history_graph_target('Memory History', 'memory_history');">Memory History</a></li>
> -      </ul>
> -   </div>
> -   <div class="history_graphs_menu">
> -      <ul>
> -        <li><div id="history_graph_time_selection" class="history_graph_menu_header">Last 7 Days   <%= image_tag 'icon_menu_arrow.gif' %></div></li>
> -        <li class="history_graph_menu_item history_graph_menu_fitem"<a href="#" onclick="swap_history_graph_time('Last 24 Hours', '1')" >Last 24 Hours</a></li>
> -        <li class="history_graph_menu_item"><a href="#" onclick="swap_history_graph_time('Last 7 days', '7')" >Last 7  Days</a></li>
> -        <li class="history_graph_menu_item history_graph_menu_litem"><a href="#" onclick="swap_history_graph_time('Last 30 Days', '30')" >Last 30 Days</a></li>
> -      </ul>
> -   </div>
> -   <div class="history_graphs_legend">
> -       <font color="<%= @peak_history[:color]   %>">Peak    </font>
> -       <font color="<%= @avg_history[:color] %>">Average    </font>
> -       <font color="<%= @roll_peak_history[:color]   %>">Rolling Peak    </font>
> -       <font color="<%= @roll_avg_history[:color] %>">Rolling Average    </font>
> -   </div>
> -  </div>
> -  <div id="history_graphs_graphs">
> -    <div class="history_graph"><div id="cpu_history_1">       </div></div>
> -    <div class="history_graph"><div id="cpu_history_7">       </div></div>
> -    <div class="history_graph"><div id="cpu_history_30">      </div></div>
> -    <div class="history_graph"><div id="memory_history_1">    </div></div>
> -    <div class="history_graph"><div id="memory_history_7">    </div></div>
> -    <div class="history_graph"><div id="memory_history_30">   </div></div>
> -    <div class="history_graph"><div id="load_history_1">      </div></div>
> -    <div class="history_graph"><div id="load_history_7">      </div></div>
> -    <div class="history_graph"><div id="load_history_30">     </div></div>
> -  </div>
> -</div>
> -
I like how much shorter this is!  However, not so keen on having the
path hardcoded like this.  I did a little poking, and think you might
want to add a helper to application_helper.rb like so:

def flash_path(source)
  compute_public_path(source, 'swfs', 'swf')
end

'swfs' is a directory under /public.  I think we should have all the
flash swfs (even if it is only this one) in a dir rather than top level,
doesn't have to be called swfs, could be something else.
> -<script type="text/javascript">
> -    swap_history_graph(null, null); // display 1st graph
> +$(document).ready(function(){
This div needs a more appropriate name that describes what it contains.
> +$('#the-div-name').flash(
> +        {
> +          src: '/ovirt/flexchart.swf',
This line can be changed to:
	     src: '<%=flash_path("flexchart")%>',
> +          width: 720,
> +          height: 300,
> +          flashvars: { flexchart_data: '/ovirt/graph/flexchart_data/<%= @id %>/memory/1' }
Similarly, please do this one as:
             flashvars: { flexchart_data: "<%= url_for :controller
=>'/graph', :action => 'flexchart_data' %>/<%= @id %>/memory/1" }
> +        },
> +        { version: 9 }
> +    );
> +});
>  </script>
> diff --git a/src/config/routes.rb b/src/config/routes.rb
> index 6f8e481..8d538cb 100644
> --- a/src/config/routes.rb
> +++ b/src/config/routes.rb
> @@ -41,6 +41,7 @@ ActionController::Routing::Routes.draw do |map|
>    map.connect ':controller/service.wsdl', :action => 'wsdl'
>  
>    # Install the default route as the lowest priority.
> +  map.connect 'graph/flexchart_data/:id/:target/:days', :controller => 'graph', :action => 'flexchart_data'
>    map.connect ':controller/:action/:id.:format'
>    map.connect ':controller/:action/:id'
>  
> diff --git a/src/flexchart/README.txt b/src/flexchart/README.txt
> new file mode 100644
> index 0000000..66eb183
> --- /dev/null
> +++ b/src/flexchart/README.txt
> @@ -0,0 +1,8 @@
> +Until mxmlc gets packaged and this becomes part of autobuild,
> +you must obtain the open flex SDK to build the swf movie.
> +
> +Once you have mxmlc on your system, run:
> +
> +mxmlc flexchart.mxml
> +
> +in this directory, and copy the resulting file flexchart.swf to /usr/share/ovirt-server/public on your appliance.
<snip adobe code>

Don't know AS well enough to comment on quality of code too much, mostly
seems reasonable as a first cut.
> diff --git a/src/flexchart/flexchart.mxml b/src/flexchart/flexchart.mxml
> new file mode 100644
> index 0000000..796329d
> --- /dev/null
> +++ b/src/flexchart/flexchart.mxml
> @@ -0,0 +1,20 @@
> +<?xml version="1.0"?>
> +<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" applicationComplete="populate(flexChart)">
> +  <mx:Script>
> +    <![CDATA[
> +
> +      import mx.containers.Box;
> +      import org.ovirt.*;
> +
> +      private function populate(chart:Box):void {
> +        var chartLoader:ChartLoader = new ChartLoader(chart, parameters['flexchart_data']);
> +        chartLoader.load();
> +      }
> +
> +    ]]>
> +  </mx:Script>
> +  <mx:Panel height="100%" width="100%" visible="true">
> +    <mx:HBox id="flexChart"  height="100%" width="100%" visible="true" verticalAlign="bottom" opaqueBackground="0xFFFFFF" borderThickness="0">
> +    </mx:HBox>
> +  </mx:Panel>
> +</mx:Application>
> diff --git a/src/flexchart/org/ovirt/ChartLoader.as b/src/flexchart/org/ovirt/ChartLoader.as
> new file mode 100644
> index 0000000..4e493a4
> --- /dev/null
> +++ b/src/flexchart/org/ovirt/ChartLoader.as
> @@ -0,0 +1,64 @@
> +/*
> + Copyright (C) 2008 Red Hat, Inc.
> + Written by Steve Linabery <slinabery at redhat.com>
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; version 2 of the License.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> + MA  02110-1301, USA.  A copy of the GNU General Public License is
> + also available at http://www.gnu.org/copyleft/gpl.html.
> +*/
> +
> +package org.ovirt {
> +
> +  import mx.containers.Box;
> +  import mx.containers.HBox;
> +  import mx.controls.Text;
> +
> +  public class ChartLoader {
> +
> +    private var element:Box;
> +    private var datasourceUrl:String;
> +
> +    public function ChartLoader(element:Box, datasourceUrl:String) {
> +      this.element = element;
> +      this.datasourceUrl = datasourceUrl;
> +    }
> +
> +    public function addData(dataSeries:DataSeries):void {
> +      var points:Array = dataSeries.getPoints();
> +      var maxValue:Number = dataSeries.getMaxValue();
> +      var scale:Number = maxValue;
> +      if (scale == 0) { scale = 1; }
> +      var size:int = points.length;
> +      element.removeAllChildren();
> +      element.setStyle("horizontalGap","2");
Maybe I am spoiled by ruby and jquery - no .each in AS?
> +      for (var i:int = 0; i < size; i++) {
> +        var value:Number = (points[i] as Array)[1];
> +        var bar:HBox = new HBox();
> +        bar.percentHeight = ((value / scale) * 90);
> +        bar.percentWidth = (100 / size);
> +        bar.setStyle("backgroundColor","0x0000FF");
> +        bar.setStyle("left","1");
> +        bar.setStyle("right","1");
> +        bar.visible = true;
> +        bar.setVisible(true);
> +        element.addChild(bar);
> +      }
> +    }
> +
> +    public function load():void {
> +      var dataSource:DataSource = new DataSource(this);
> +      dataSource.retrieveData(datasourceUrl);
> +    }
> +  }
> +}
> \ No newline at end of file
> diff --git a/src/flexchart/org/ovirt/DataSeries.as b/src/flexchart/org/ovirt/DataSeries.as
> new file mode 100644
> index 0000000..d63162a
> --- /dev/null
> +++ b/src/flexchart/org/ovirt/DataSeries.as
> @@ -0,0 +1,42 @@
> +/*
> + Copyright (C) 2008 Red Hat, Inc.
> + Written by Steve Linabery <slinabery at redhat.com>
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; version 2 of the License.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> + MA  02110-1301, USA.  A copy of the GNU General Public License is
> + also available at http://www.gnu.org/copyleft/gpl.html.
> +*/
> +
> +//class to encapsulate the json object representation of a data
> +//series returned from stats package
> +
> +package org.ovirt {
> +
> +  public class DataSeries {
> +
> +    private var object:Object;
> +
> +    public function DataSeries (object:Object) {
> +      this.object = object;
> +    }
> +
> +    public function getPoints():Array {
> +      return object["vectors"] as Array;
> +    }
> +
> +    public function getMaxValue():Number {
> +      return object["max_value"] as Number;
> +    }
> +  }
> +}
> \ No newline at end of file
> diff --git a/src/flexchart/org/ovirt/DataSource.as b/src/flexchart/org/ovirt/DataSource.as
> new file mode 100644
> index 0000000..1a64f03
> --- /dev/null
> +++ b/src/flexchart/org/ovirt/DataSource.as
> @@ -0,0 +1,57 @@
> +/*
> + Copyright (C) 2008 Red Hat, Inc.
> + Written by Steve Linabery <slinabery at redhat.com>
> +
> + This program is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; version 2 of the License.
> +
> + This program is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + GNU General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
> + MA  02110-1301, USA.  A copy of the GNU General Public License is
> + also available at http://www.gnu.org/copyleft/gpl.html.
> +*/
> +
> +package org.ovirt {
> +
> +  import flash.net.URLLoader;
> +  import flash.net.URLRequest;
> +  import com.adobe.serialization.json.JSON;
> +  import flash.events.Event;
> +  import flash.events.IOErrorEvent;
> +
> +  public class DataSource {
> +
> +    private var chartLoader:ChartLoader;
> +
> +    public function DataSource(chartLoader:ChartLoader) {
> +      this.chartLoader = chartLoader;
> +    }
> +
> +    public function retrieveData(url:String):void {
> +      var loader:URLLoader = new URLLoader();
> +      loader.addEventListener( IOErrorEvent.IO_ERROR, this.ioError );
> +      loader.addEventListener( Event.COMPLETE, dataLoaded );
> +      var request:URLRequest = new URLRequest(url);
> +      loader.load(request);
> +    }
> +
> +    private function dataLoaded(event:Event):void {
> +      var loader:URLLoader = URLLoader(event.target);
> +      var object:Object = JSON.decode(loader.data);
> +      var series:DataSeries = new DataSeries(object);
> +      chartLoader.addData(series);
> +    }
> +
> +    private function ioError( e:IOErrorEvent ):void {
> +      //FIXME:
> +      //do something useful with this error
> +    }
> +  }
> +}
<snip new jq plugin>




More information about the ovirt-devel mailing list