[PATCH] RM3509 re-establish providers/ index page, with useful statistics and graphs; generic-ize chart code for ease of reuse

Tzu-Mainn Chen tzumainn at redhat.com
Fri Jul 20 19:14:33 UTC 2012


---
 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(",
+      ") }
+    ];
+
+    $.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                         |
-- 
1.7.6.5




More information about the aeolus-devel mailing list