[PATCH] RM3509 re-establish providers/ index page, with useful statistics and graphs; generic-ize chart code for ease of reuse
Jan Provaznik
jprovazn at redhat.com
Tue Jul 24 09:25:48 UTC 2012
On 07/20/2012 09:14 PM, Tzu-Mainn Chen wrote:
> ---
> src/app/controllers/logs_controller.rb | 41 +----
> src/app/controllers/providers_controller.rb | 194 +++++++++++++++++++++---
> src/app/stylesheets/layout.scss | 15 ++
> src/app/util/chart_datasets.rb | 59 +++++++
> src/app/views/charts/_time_chart.html.haml | 25 +++
> src/app/views/logs/_pretty_view.html.haml | 25 +---
> src/app/views/providers/_page_header.html.haml | 4 +
> src/app/views/providers/index.html.haml | 53 ++++++-
> src/config/locales/en.yml | 7 +
> src/config/routes.rb | 1 +
> src/features/provider.feature | 11 --
> 11 files changed, 345 insertions(+), 90 deletions(-)
> create mode 100644 src/app/util/chart_datasets.rb
> create mode 100644 src/app/views/charts/_time_chart.html.haml
>
> diff --git a/src/app/controllers/logs_controller.rb b/src/app/controllers/logs_controller.rb
> index 316b375..06ff76a 100644
> --- a/src/app/controllers/logs_controller.rb
> +++ b/src/app/controllers/logs_controller.rb
> @@ -99,7 +99,7 @@ class LogsController < ApplicationController
> Date.civil(params[:from_date][:year].to_i,
> params[:from_date][:month].to_i,
> params[:from_date][:day].to_i)
> - @to_date = params[:to_date].nil? ? Date.today + 1.days :
> + @to_date = params[:to_date].nil? ? Date.today :
> Date.civil(params[:to_date][:year].to_i,
> params[:to_date][:month].to_i,
> params[:to_date][:day].to_i)
> @@ -283,27 +283,17 @@ class LogsController < ApplicationController
> find(:all, :conditions => initial_conditions)
> end
>
> - @datasets = Hash.new
> - counts = Hash.new
> -
> - counts["All"] = @initial_sources.count
> + @datasets = ChartDatasets.new(@from_date, @to_date)
> + @datasets.increment_count("All", at initial_sources.count)
>
> if @group_options.include?(@group)
> @initial_sources.each do |source|
> label = get_source_label(source, @group)
> -
> - if counts.has_key?(label)
> - counts[label] = counts[label] + 1
> - else
> - counts[label] = 1
> - end
> + @datasets.increment_count(label,1)
> end
> end
>
> - counts.each.map{ |label,count|
> - @datasets[label] = [[@from_date.to_datetime.beginning_of_day.to_i * 1000,
> - count]]
> - }
> + @datasets.initialize_datasets
>
> @events.each do |event|
> event_timestamp = event.event_time.to_i * 1000
> @@ -311,23 +301,21 @@ class LogsController < ApplicationController
> if event.status_code == start_code || event.status_code == end_code
> increment = (event.status_code == end_code) ? -1 : 1
>
> - add_dataset_point("All", event_timestamp, increment, counts)
> + @datasets.add_dataset_point("All", event_timestamp, increment)
>
> if @group_options.include?(@group)
> label = get_source_label(event.source, @group)
> - add_dataset_point(label, event_timestamp, increment, counts)
> + @datasets.add_dataset_point(label, event_timestamp, increment)
> end
> end
> end
>
> - counts.each.map{ |label,count|
> - @datasets[label] << [@to_date.to_datetime.end_of_day.to_i * 1000, count]
> - }
> + @datasets.finalize_datasets
> end
>
> def get_source_label(source, label_type)
> label = "Unknown"
> - case @group
> + case label_type
> when t('logs.index.pool')
> label = source.pool.name unless source.pool.nil?
> when t('logs.index.provider')
> @@ -338,15 +326,4 @@ class LogsController < ApplicationController
>
> label
> end
> -
> - def add_dataset_point(label, timestamp, increment, counts)
> - if !@datasets.has_key?(label)
> - counts[label] = 0
> - @datasets[label] = [ [timestamp - 1, 0] ]
> - end
> -
> - @datasets[label] << [timestamp - 1, counts[label]]
> - counts[label] = counts[label] + increment
> - @datasets[label] << [timestamp, counts[label]]
> - end
> end
> diff --git a/src/app/controllers/providers_controller.rb b/src/app/controllers/providers_controller.rb
> index 81ac69f..2f78e9f 100644
> --- a/src/app/controllers/providers_controller.rb
> +++ b/src/app/controllers/providers_controller.rb
> @@ -21,27 +21,28 @@ class ProvidersController < ApplicationController
> before_filter :parse_provider_type, :only => [:create, :update]
>
> def index
> - @params = params
> - begin
> - @provider = Provider.find(session[:current_provider_id]) if session[:current_provider_id]
> - rescue ActiveRecord::RecordNotFound => exc
> - logger.error(exc.message)
> - ensure
> - @provider ||= @providers.first
> - end
> + @from_date = params[:from_date].nil? ? Date.today - 7.days :
> + Date.civil(params[:from_date][:year].to_i,
> + params[:from_date][:month].to_i,
> + params[:from_date][:day].to_i)
> + @to_date = params[:to_date].nil? ? Date.today :
> + Date.civil(params[:to_date][:year].to_i,
> + params[:to_date][:month].to_i,
> + params[:to_date][:day].to_i)
> +
> + load_headers
> + statistics
>
> respond_to do |format|
> - format.html do
> - if @providers.present?
> - flash.keep
> - redirect_to edit_provider_path(@provider)
> - else
> - render :action => :index
> - end
> - end
> + format.html
> + format.js { render :partial => 'list' }
> format.xml { render :partial => 'list.xml' }
> end
> + end
>
> + def filter
> + redirect_to_original({ "from_date" => params[:from_date],
> + "to_date" => params[:to_date] })
> end
>
> def new
> @@ -221,8 +222,8 @@ class ProvidersController < ApplicationController
> end
>
> def load_providers
> - @providers = Provider.list_for_user(current_session, current_user,
> - Privilege::VIEW)
> + @providers = Provider.includes(:provider_type).list_for_user(current_session, current_user,
> + Privilege::VIEW).order("name")
> end
>
> def disable_provider
> @@ -322,4 +323,161 @@ class ProvidersController < ApplicationController
>
> @view = @details_tab[:view]
> end
> +
> + def load_headers
> + @header = [
> + { :name => t('providers.index.provider_name'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.provider_type'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.running_instances'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.pending_instances'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.error_instances'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.historical_running_instances'), :class => 'center',
> + :sortable => false },
> + { :name => t('providers.index.historical_error_instances'), :class => 'center',
> + :sortable => false },
> + ]
> + end
> +
> + def statistics
> + @statistics = Hash.new
> + @providers.each do |provider|
> + @statistics[provider.id] = {
> + "running_instances" => 0,
> + "pending_instances" => 0,
> + "error_instances" => 0,
> + "historical_running_instances" => 0,
> + "historical_error_instances" => 0,
> + }
> + end
> +
> + # Queries are NOT permissioned by instance, as info is
> + # used purely for statistical purposes.
> +
> + # current instances
> + provider_counts = ProviderAccount.joins(:instances).
> + merge(Instance.scoped).
> + select("provider_id, state, count(*) as count").
> + where(:provider_id => @providers.map{|provider| provider.id}).
> + group("provider_id, state")
> +
> + provider_counts.each do |provider_count|
> + provider_id = provider_count["provider_id"]
> + state = provider_count["state"]
> + count = provider_count["count"]
> +
> + if Instance::FAILED_STATES.include?(state)
> + @statistics[provider_id]["error_instances"] += count.to_i
> + elsif [Instance::STATE_RUNNING, Instance::STATE_SHUTTING_DOWN].
> + include?(state)
> + @statistics[provider_id]["running_instances"] += count.to_i
> + elsif [Instance::STATE_NEW, Instance::STATE_PENDING].
> + include?(state)
> + @statistics[provider_id]["pending_instances"] += count.to_i
> + end
> + end
> +
> + # instances that were running between historical date range
> + historical_running_provider_counts = ProviderAccount.joins(:instances).
> + merge(Instance.unscoped).
> + select("provider_id, state, count(*) as count").
> + where(:provider_id => @providers.map{|provider| provider.id}).
> + where("time_last_running <= :to_date and
> + (time_last_stopped is null
> + or time_last_stopped >= :from_date)",
> + :to_date => @to_date.to_datetime.end_of_day,
> + :from_date => @from_date.to_datetime.beginning_of_day
> + ).
> + group("provider_id, state")
> +
> + historical_running_provider_counts.each do |provider_count|
> + provider_id = provider_count["provider_id"]
> + count = provider_count["count"]
> +
> + @statistics[provider_id]["historical_running_instances"] += count.to_i
> + end
> +
> + # instances that threw an error between historical date range
> + historical_error_provider_counts = ProviderAccount.joins(:instances).
> + merge(Instance.unscoped).
> + select("provider_id, count(*) as count").
> + where(:provider_id => @providers.map{|provider| provider.id}).
> + where("instances.state" => Instance::FAILED_STATES).
> + where("instances.updated_at between :from_date and :to_date",
> + :states => Instance::FAILED_STATES,
> + :to_date => @to_date.to_datetime.end_of_day,
> + :from_date => @from_date.to_datetime.beginning_of_day
> + ).
> + group("provider_id")
> +
> + historical_error_provider_counts.each do |provider_count|
> + provider_id = provider_count["provider_id"]
> + count = provider_count["count"]
> +
> + @statistics[provider_id]["historical_error_instances"] += count.to_i
> + end
> +
> + # all running instances during historical date range
> + historical_instances = Instance.unscoped.
> + find(:all,
> + :conditions => ["time_last_running <= ? and
> + (time_last_stopped is null
> + or time_last_stopped >= ?)",
> + @to_date.to_datetime.end_of_day,
> + @from_date.to_datetime.beginning_of_day],
> + :include => {:provider_account => [:provider]}
> + )
> +
> + @datasets = ChartDatasets.new(@from_date, @to_date)
> + events = Array.new
> +
> + historical_instances.each do |instance|
> + provider_account = instance.provider_account
> +
> + if check_privilege(Privilege::VIEW, provider_account)
> + label = provider_account.nil? ?
> + 'Unknown' :
> + provider_account.provider.name +
> + " (" + provider_account.name + ")"
> +
> + # see if instance started before from_date
> + if instance.time_last_running <= @from_date.to_datetime.beginning_of_day
> + @datasets.increment_count(label,1)
> + @datasets.increment_count("All",1)
> + else
> + events << {
> + "time" => instance.time_last_running,
> + "label" => label,
> + "increment" => 1
> + }
> + end
> +
> + if !instance.time_last_stopped.nil? &&
> + instance.time_last_stopped <= @to_date.to_datetime.end_of_day
> + events << {
> + "time" => instance.time_last_stopped,
> + "label" => label,
> + "increment" => -1
> + }
> + end
> + end
> + end
> +
> + @datasets.initialize_datasets
> +
> + events.sort_by {|event| event["time"]}.each do |event|
> + timestamp = event["time"].to_i * 1000
> + increment = event["increment"]
> +
> + [ event["label"], "All" ].each { |label|
> + @datasets.add_dataset_point(label,timestamp,increment)
> + }
> + end
> +
> + @datasets.finalize_datasets
> + end
> end
> diff --git a/src/app/stylesheets/layout.scss b/src/app/stylesheets/layout.scss
> index e161f65..45127fd 100644
> --- a/src/app/stylesheets/layout.scss
> +++ b/src/app/stylesheets/layout.scss
> @@ -1962,6 +1962,10 @@ table.flat, form.filterable-data table{
> &.checkbox{ width: 21px; text-align: center; padding: 0px 3px; }
> &.alert{ width: 16px; text-align: center; padding: 0px 4px;}
> &.center{ text-align: center;}
> +
> + &.running_instances{ color: green; text-align: center; }
> + &.pending_instances{ color: brown; text-align: center; }
> + &.error_instances{ color: red; text-align: center; }
> }
>
> tr.stripe{
> @@ -2290,6 +2294,17 @@ ul.listing, ol.listing{
> }
> }
>
> +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +Charts
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
> +
> +div.provider-history-graph {
> + width: 100%; height: 250px;
> +}
> +
> +div.log-history-graph {
> + width: 100%; height: 600px;
> +}
>
> /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> Page Footer -- v.0.0.1 [footer] (footer.scss)
> diff --git a/src/app/util/chart_datasets.rb b/src/app/util/chart_datasets.rb
> new file mode 100644
> index 0000000..3e9d3e6
> --- /dev/null
> +++ b/src/app/util/chart_datasets.rb
> @@ -0,0 +1,59 @@
> +#
> +# Copyright 2011 Red Hat, Inc.
> +#
> +# Licensed under the Apache License, Version 2.0 (the "License");
> +# you may not use this file except in compliance with the License.
> +# You may obtain a copy of the License at
> +#
> +# http://www.apache.org/licenses/LICENSE-2.0
> +#
> +# Unless required by applicable law or agreed to in writing, software
> +# distributed under the License is distributed on an "AS IS" BASIS,
> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> +# See the License for the specific language governing permissions and
> +# limitations under the License.
> +#
> +
> +class ChartDatasets
> + attr_accessor :data
> +
> + def initialize(from_date, to_date)
> + @data = Hash.new
> + @counts = Hash.new
> + @from_date = from_date
> + @to_date = to_date
> + end
> +
> + def initialize_datasets()
> + @counts.each.map{ |label,count|
> + @data[label] = [[@from_date.to_datetime.beginning_of_day.to_i * 1000,
> + count]]
> + }
> + end
> +
> + def finalize_datasets()
> + @counts.each.map{ |label,count|
> + @data[label] << [@to_date.to_datetime.end_of_day.to_i * 1000, count]
> + }
> + end
> +
> + def increment_count(label, increment)
> + if !@counts.has_key?(label)
> + @counts[label] = 0
> + end
> +
> + @counts[label] = @counts[label] + increment
> + end
> +
> + def add_dataset_point(label, timestamp, increment)
> + if !@data.has_key?(label)
> + @data[label] = [[timestamp - 1, 0]]
> + else
> + @data[label] << [timestamp - 1, @counts[label]]
> + end
> +
> + increment_count(label,increment)
> + @data[label] << [timestamp, @counts[label]]
> + end
> +
> +end
> diff --git a/src/app/views/charts/_time_chart.html.haml b/src/app/views/charts/_time_chart.html.haml
> new file mode 100644
> index 0000000..c1db5fb
> --- /dev/null
> +++ b/src/app/views/charts/_time_chart.html.haml
> @@ -0,0 +1,25 @@
> +%div{:class => "#{name}", :id => "#{name}"}
> +
> +:javascript
> + $(function () {
> + var datasets = [
> + #{ datasets.reject{|label,data| label != "All"}.each.map{ |label,data| "{
> + label: \"" + label + "\",
> + data: [#{ data.each.map{ |p| "[#{p[0]}, #{p[1]}]"}.join(", ") }]
> + },
> + "} }
> + #{ datasets.reject{|label,data| label == "All"}.each.map{ |label,data| "{
> + label: \"" + label + "\",
> + data: [#{ data.each.map{ |p| "[#{p[0]}, #{p[1]}]"}.join(", ") }]
> + }" }.join(",
> + ") }
> + ];
I'm getting this JS error when accessing providers page:
providers (line 266, col 22): missing ] after element list
geenrated JS looks like this:
var datasets = [
["{\n label: \"All\",\n data: [[1342483200000, 9], [1343174399000, 9]]\n
},\n "]
{
label: "mock (mock)",
data: [[1342483200000, 9], [1343174399000, 9]]
}
];
Maybe this code would deserve some DRYing. The 'All' key might be
computed automatically inside ChartDatasets class (instead of doing this
in controllers), whenever increment_count or add_dataset_point is
called, data for 'All' are updated too, then there would be some 'to_a'
method, which would return data as an aray sorted by labels.
> +
> + $.plot($("##{name}"), datasets, {
> + xaxis: {
> + mode: "time",
> + min: #{from_date.to_datetime.beginning_of_day.to_i * 1000},
> + max: #{to_date.to_datetime.end_of_day.to_i * 1000}
> + }
> + });
> + });
> diff --git a/src/app/views/logs/_pretty_view.html.haml b/src/app/views/logs/_pretty_view.html.haml
> index d559a4f..ac8d223 100644
> --- a/src/app/views/logs/_pretty_view.html.haml
> +++ b/src/app/views/logs/_pretty_view.html.haml
> @@ -14,27 +14,4 @@
> = restful_submit_tag t("filter_table.apply_filters"), "index", filter_logs_path, 'POST', :class => 'button', :id => 'apply_logs_filter'
> %br
>
> -%div{:id => 'placeholder', :style => 'width:925px;height:600px;'}
> -
> -:javascript
> - $(function () {
> - var datasets = [
> - {
> - label: "All",
> - data: [#{ @datasets["All"].each.map{ |p| "[#{p[0]}, #{p[1]}]"}.join(", ") }]
> - },
> - #{ @datasets.reject{|label,data| label == "All"}.each.map{ |label,data| "{
> - label: \"" + label + "\",
> - data: [#{ data.each.map{ |p| "[#{p[0]}, #{p[1]}]"}.join(", ") }]
> - }" }.join(",
> - ") }
> - ];
> -
> - $.plot($("#placeholder"), datasets, {
> - xaxis: {
> - mode: "time",
> - min: #{@from_date.to_datetime.beginning_of_day.to_i * 1000},
> - max: #{@to_date.to_datetime.end_of_day.to_i * 1000}
> - }
> - });
> - });
> += render :partial => 'charts/time_chart', :locals => { :datasets => @datasets.data, :from_date => @from_date, :to_date => @to_date, :name => 'log-history-graph' }
> diff --git a/src/app/views/providers/_page_header.html.haml b/src/app/views/providers/_page_header.html.haml
> index f48f2ac..7752c6f 100644
> --- a/src/app/views/providers/_page_header.html.haml
> +++ b/src/app/views/providers/_page_header.html.haml
> @@ -12,4 +12,8 @@
> = form_for @provider, :url => provider_path(@provider), :html => { :method => :put, :class => 'generic horizontal' } do |f|
> = f.hidden_field :enabled, :value => !@provider.enabled
> = f.submit "", :confirm => "#{@provider.enabled ? t("providers.disable_provider") : t("providers.enable_provider")}", :class => "provider_toggle #{@provider.enabled ? "on" : "off"}"
> + %br
> + .return_to
> + =t'return_to'
> + = link_to "#{t'providers.providers'}", providers_path
> %h1.providers{:class => @provider.enabled ? 'enabled' : 'disabled'}= @provider.name.blank? ? t("providers.editing_provider") : @provider.name
> diff --git a/src/app/views/providers/index.html.haml b/src/app/views/providers/index.html.haml
> index c45d064..7ea0da6 100644
> --- a/src/app/views/providers/index.html.haml
> +++ b/src/app/views/providers/index.html.haml
> @@ -1,6 +1,49 @@
> = render :partial => 'layouts/admin_nav'
> -%section.content-section.provider
> - = t("providers.index.no_providers_available")
> - - if check_privilege(Privilege::CREATE, Provider)
> - = t("providers.index.create_one")
> - = link_to t("providers.index.create_new_provider"), new_provider_path, :class => 'button', :id => "create_new_provider"
> +%section.content-section
> + %header
> + %h2.providers #{t'providers.providers'}
> + .content
> + - content_for :form_header do
> + - if check_privilege(Privilege::CREATE, Provider)
> + %li= link_to t("providers.index.create_new_provider"), new_provider_path, :class => 'button', :id => "create_new_provider"
> +
> + - content_for :filter_controls do
> + %li
> + = hidden_field_tag :current_path, request.fullpath
> + = t('filter_table.from')
> + = select_date @from_date, :prefix => :from_date
> + = t('filter_table.to')
> + = select_date @to_date, :prefix => :to_date
> + = restful_submit_tag t("filter_table.apply_filters"), "index", filter_providers_path, 'POST', :class => 'button', :id => 'apply_provider_index_filter'
> + %span.label.badge.dark= @providers.count
> + = filter_table(@header, @providers) do |provider|
> + - statistics = @statistics[provider.id]
> + %tr{:class => cycle('nostripe','stripe')}
> + %td= link_to provider.name, edit_provider_path(provider)
> + %td= provider.provider_type.name
> + - if statistics["running_instances"] > 0
> + %td{:class => 'running_instances'}= statistics["running_instances"]
> + - else
> + %td.center= statistics["running_instances"]
> + - if statistics["pending_instances"] > 0
> + %td{:class => 'pending_instances'}= statistics["pending_instances"]
> + - else
> + %td.center= statistics["pending_instances"]
> + - if statistics["error_instances"] > 0
> + %td{:class => 'error_instances'}= statistics["error_instances"]
> + - else
> + %td.center= statistics["error_instances"]
> + - if statistics["historical_running_instances"] > 0
> + %td{:class => 'running_instances'}= statistics["historical_running_instances"]
> + - else
> + %td.center= statistics["historical_running_instances"]
> + - if statistics["historical_error_instances"] > 0
> + %td{:class => 'error_instances'}= statistics["historical_error_instances"]
> + - else
> + %td.center= statistics["historical_error_instances"]
> +
> +%section.content-section
> + %header
> + %h2.activity #{t'activity.activity'}
> + .content
> + = render :partial => 'charts/time_chart', :locals => { :datasets => @datasets.data, :from_date => @from_date, :to_date => @to_date, :name => 'provider-history-graph' }
> diff --git a/src/config/locales/en.yml b/src/config/locales/en.yml
> index 42b15f5..1fcd708 100644
> --- a/src/config/locales/en.yml
> +++ b/src/config/locales/en.yml
> @@ -10,6 +10,8 @@ en:
> other: "%{count} hours"
> search:
> no_results: "No matching results."
> + activity:
> + activity: Activity
> logs:
> options:
> default_users: All Users
> @@ -1111,6 +1113,11 @@ en:
> provider_name: Provider Name
> provider_url: Provider URL
> provider_type: Provider Type
> + running_instances: Running Instances (Current)
> + pending_instances: Pending (Current)
> + error_instances: Errors (Current)
> + historical_running_instances: Running (Historical)
> + historical_error_instances: Errors (Historical)
> x_deltacloud_driver: X-Deltacloud-Driver
> x_deltacloud_provider: X-Deltacloud-Provider
> no_providers_available: No providers available.
> diff --git a/src/config/routes.rb b/src/config/routes.rb
> index e7f26c5..3e81885 100644
> --- a/src/config/routes.rb
> +++ b/src/config/routes.rb
> @@ -139,6 +139,7 @@ Conductor::Application.routes.draw do
>
> resources :providers do
> delete 'multi_destroy', :on => :collection
> + post :filter, :on => :collection
>
> resources :provider_accounts do
> collection do
> diff --git a/src/features/provider.feature b/src/features/provider.feature
> index 2ba7081..e5ca8dc 100644
> --- a/src/features/provider.feature
> +++ b/src/features/provider.feature
> @@ -92,17 +92,6 @@ Feature: Manage Providers
> And I should see "Provider is disabled."
> And provider "provider1" should have all instances stopped
>
> - Scenario: Persist selected provider
> - Given there is a provider named "provider1"
> - And there is a provider named "provider2"
> - When I am on the provider1's edit provider page
> - And I click on the Providers icon in the menu
> - Then I should be on the provider1's edit provider page
> - When I am on the provider2's edit provider page
> - And I click on the Providers icon in the menu
> - Then I should be on the provider2's edit provider page
> -
> -
> # Scenario: Search for hardware profiles
> # Given there are these providers:
> # | name | url |
>
More information about the aeolus-devel
mailing list