Merge branch 'master' into 2187-enhance-user-setup
authorradhika chippada <radhika@radhika.curoverse>
Mon, 24 Mar 2014 18:19:48 +0000 (14:19 -0400)
committerradhika chippada <radhika@radhika.curoverse>
Mon, 24 Mar 2014 18:19:48 +0000 (14:19 -0400)
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/helpers/application_helper.rb
apps/workbench/app/models/arvados_base.rb
apps/workbench/app/models/arvados_resource_list.rb
apps/workbench/app/views/users/_show_activity.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/activity.html.erb [new file with mode: 0644]
apps/workbench/config/routes.rb
services/api/test/functional/arvados/v1/collections_controller_test.rb

index d31ecd30cd7a0b7b35b4c2637341253a5b7e9f5f..c169be27044ec5db6e0e8da8b67a0e75f8a1cf8d 100644 (file)
@@ -161,7 +161,8 @@ class ApplicationController < ActionController::Base
 
   def breadcrumb_page_name
     (@breadcrumb_page_name ||
-     (@object.friendly_link_name if @object.respond_to? :friendly_link_name))
+     (@object.friendly_link_name if @object.respond_to? :friendly_link_name) ||
+     action_name)
   end
 
   def index_pane_list
index c33de2d034a1630b56a3ca0000aa87440ad82c7a..35c9660d6ccdf55a29b165efecd3cb402ceb9e96 100644 (file)
@@ -1,5 +1,5 @@
 class UsersController < ApplicationController
-  skip_before_filter :find_object_by_uuid, :only => :welcome
+  skip_before_filter :find_object_by_uuid, :only => [:welcome, :activity]
   skip_around_filter :thread_with_mandatory_api_token, :only => :welcome
   before_filter :ensure_current_user_is_admin, only: :sudo
 
@@ -10,6 +10,59 @@ class UsersController < ApplicationController
     end
   end
 
+  def activity
+    @breadcrumb_page_name = nil
+    @users = User.all
+    @user_activity = {}
+    @activity = {
+      logins: {},
+      jobs: {},
+      pipeline_instances: {}
+    }
+    @total_activity = {}
+    @spans = [['This week', Time.now.beginning_of_week, Time.now],
+              ['Last week',
+               Time.now.beginning_of_week.advance(weeks:-1),
+               Time.now.beginning_of_week],
+              ['This month', Time.now.beginning_of_month, Time.now],
+              ['Last month',
+               1.month.ago.beginning_of_month,
+               Time.now.beginning_of_month]]
+    @spans.each do |span, threshold_start, threshold_end|
+      @activity[:logins][span] = Log.
+        filter([[:event_type, '=', 'login'],
+                [:object_kind, '=', 'arvados#user'],
+                [:created_at, '>=', threshold_start],
+                [:created_at, '<', threshold_end]])
+      @activity[:jobs][span] = Job.
+        filter([[:created_at, '>=', threshold_start],
+                [:created_at, '<', threshold_end]])
+      @activity[:pipeline_instances][span] = PipelineInstance.
+        filter([[:created_at, '>=', threshold_start],
+                [:created_at, '<', threshold_end]])
+      @activity.each do |type, act|
+        records = act[span]
+        @users.each do |u|
+          @user_activity[u.uuid] ||= {}
+          @user_activity[u.uuid][span + ' ' + type.to_s] ||= 0
+        end
+        records.each do |record|
+          @user_activity[record.modified_by_user_uuid] ||= {}
+          @user_activity[record.modified_by_user_uuid][span + ' ' + type.to_s] ||= 0
+          @user_activity[record.modified_by_user_uuid][span + ' ' + type.to_s] += 1
+          @total_activity[span + ' ' + type.to_s] ||= 0
+          @total_activity[span + ' ' + type.to_s] += 1
+        end
+      end
+    end
+    @users = @users.sort_by do |a|
+      [-@user_activity[a.uuid].values.inject(:+), a.full_name]
+    end
+    # Prepend a "Total" pseudo-user to the sorted list
+    @user_activity[nil] = @total_activity
+    @users = [OpenStruct.new(uuid: nil)] + @users
+  end
+
   def show_pane_list
     if current_user.andand.is_admin
       super | %w(Admin)
@@ -18,6 +71,14 @@ class UsersController < ApplicationController
     end
   end
 
+  def index_pane_list
+    if current_user.andand.is_admin
+      super | %w(Activity)
+    else
+      super
+    end
+  end
+
   def sudo
     resp = $arvados_api_client.api(ApiClientAuthorization, '', {
                                      api_client_authorization: {
index e608572f05f23495e4f15f886582f7adc637b8c8..9066224b7e80e473328bf7fcda8da6ceece8209f 100644 (file)
@@ -59,11 +59,15 @@ module ApplicationHelper
         link_name = link_uuid
 
         if opts[:friendly_name]
-          begin
-            link_name = resource_class.find(link_uuid).friendly_link_name
-          rescue RuntimeError
-            # If that lookup failed, the link will too. So don't make one.
-            return attrvalue
+          if attrvalue.respond_to? :friendly_link_name
+            link_name = attrvalue.friendly_link_name
+          else
+            begin
+              link_name = resource_class.find(link_uuid).friendly_link_name
+            rescue RuntimeError
+              # If that lookup failed, the link will too. So don't make one.
+              return attrvalue
+            end
           end
         end
         if opts[:with_class_name]
index 43f40d9f960f3498bab54282a2365629be3e89da..3c224aaba0560f492f923e138da7b421c3a17d59 100644 (file)
@@ -97,6 +97,10 @@ class ArvadosBase < ActiveRecord::Base
     ArvadosResourceList.new(self).order(*args)
   end
 
+  def self.filter(*args)
+    ArvadosResourceList.new(self).filter(*args)
+  end
+
   def self.where(*args)
     ArvadosResourceList.new(self).where(*args)
   end
index 6b17ececab17c8248c3e7a60e7a7cecf41109da9..16a59b173e680ed39fd6e9f58b7f2adf6fb857d6 100644 (file)
@@ -25,6 +25,12 @@ class ArvadosResourceList
     self
   end
 
+  def filter _filters
+    @filters ||= []
+    @filters += _filters
+    self
+  end
+
   def where(cond)
     cond = cond.dup
     cond.keys.each do |uuid_key|
@@ -58,6 +64,7 @@ class ArvadosResourceList
     api_params[:limit] = @limit if @limit
     api_params[:offset] = @offset if @offset
     api_params[:order] = @orderby_spec if @orderby_spec
+    api_params[:filters] = @filters if @filters
     res = $arvados_api_client.api @resource_class, '', api_params
     @results = $arvados_api_client.unpack_api_response res
     self
diff --git a/apps/workbench/app/views/users/_show_activity.html.erb b/apps/workbench/app/views/users/_show_activity.html.erb
new file mode 100644 (file)
index 0000000..ea53307
--- /dev/null
@@ -0,0 +1,4 @@
+<p>
+  As an admin user, you can <%= link_to "view recent user activity", activity_users_url %>.
+</p>
+
diff --git a/apps/workbench/app/views/users/activity.html.erb b/apps/workbench/app/views/users/activity.html.erb
new file mode 100644 (file)
index 0000000..eaf296a
--- /dev/null
@@ -0,0 +1,72 @@
+<% content_for :css do %>
+table#users-activity-table th {
+    overflow-x: hidden;
+}
+table#users-activity-table .cell-for-span-This-month,
+table#users-activity-table .cell-for-span-Last-month {
+    background: #eee;
+}
+<% end %>
+<table class="table table-condensed arv-index" id="users-activity-table">
+  <colgroup>
+    <col width="28%" />
+  </colgroup>
+  <% @spans.each do |_| %>
+  <colgroup>
+    <% 3.times do %>
+    <col width="<%= (72 / @spans.count / 3).floor %>%" />
+    <% end %>
+  </colgroup>
+  <% end %>
+
+  <tr>
+    <th rowspan="2">User</th>
+    <% @spans.each do |span, start_at, end_at| %>
+    <th colspan="3" class="cell-for-span-<%= span.gsub ' ','-' %>">
+      <%= span %>
+      <br />
+      <%= start_at.strftime('%b %-d') %>
+      -
+      <%= (end_at-1.second).strftime('%b %-d') %>
+    </th>
+    <% end %>
+  </tr>
+  <tr>
+    <% @spans.each do |span, _| %>
+    <th class="cell-for-span-<%= span.gsub ' ','-' %>">Logins</th>
+    <th class="cell-for-span-<%= span.gsub ' ','-' %>">Jobs</th>
+    <th class="cell-for-span-<%= span.gsub ' ','-' %>">Pipelines</th>
+    <% end %>
+  </tr>
+
+  <% @users.each do |user| %>
+  <tr>
+    <td>
+      <small>
+       <% if user.uuid %>
+       <%= link_to_if_arvados_object user, friendly_name: true %>
+       <% else %>
+       <b>Total</b>
+       <% end %>
+      </small>
+    </td>
+
+    <% @spans.each do |span, _| %>
+    <% ['logins', 'jobs', 'pipeline_instances'].each do |type| %>
+    <td class="cell-for-span-<%= span.gsub ' ','-' %>">
+      <small>
+       <%= @user_activity[user.uuid][span + " " + type].to_s %>
+      </small>
+    </td>
+    <% end %>
+    <% end %>
+  </tr>
+  <% end %>
+</table>
+
+<% content_for :footer_js do %>
+$('#users-activity-table td small').each(function(){
+    if ($(this).html().trim() == '0')
+       $(this).css('opacity', '0.3');
+});
+<% end %>
index 523771c0b172cf4000e0da80d4f656f328918597..0981d2ea37689e7b99635ed8bd1747e4fb409f93 100644 (file)
@@ -21,6 +21,7 @@ ArvadosWorkbench::Application.routes.draw do
   resources :users do
     get 'home', :on => :member
     get 'welcome', :on => :collection
+    get 'activity', :on => :collection
     post 'sudo', :on => :member
   end
   resources :logs
index 78a627ad1a25ef27da3af63802938d6edb3d8567..575eda3d929204beb23f54b0aef0710e51be44a2 100644 (file)
@@ -9,6 +9,28 @@ class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
     assert_not_nil assigns(:objects)
   end
 
+  [0,1,2].each do |limit|
+    test "get index with limit=#{limit}" do
+      authorize_with :active
+      get :index, limit: limit
+      assert_response :success
+      assert_equal limit, assigns(:objects).count
+      resp = JSON.parse(@response.body)
+      assert_equal limit, resp['limit']
+    end
+  end
+
+  test "get index with limit=2 offset=99999" do
+    # Assume there are not that many test fixtures.
+    authorize_with :active
+    get :index, limit: 2, offset: 99999
+    assert_response :success
+    assert_equal 0, assigns(:objects).count
+    resp = JSON.parse(@response.body)
+    assert_equal 2, resp['limit']
+    assert_equal 99999, resp['offset']
+  end
+
   test "should create" do
     authorize_with :active
     test_collection = {