Commit cff951912734a5aeea0ebb3e4ca01f704004a466

Authored by Dmitriy Zaporozhets
1 parent 6d5c9698

Dashboard perfomance improved. Filter for projects page

@@ -3,9 +3,6 @@ @@ -3,9 +3,6 @@
3 GitLab is a free Project/Repository management application 3 GitLab is a free Project/Repository management application
4 4
5 5
6 -<img src="http://gitlabhq.com/front.png" width="900" height="471">  
7 -  
8 -  
9 ## Application details 6 ## Application details
10 7
11 rails 3.1 8 rails 3.1
app/assets/javascripts/application.js
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 //= require branch-graph 16 //= require branch-graph
17 //= require_tree . 17 //= require_tree .
18 18
19 -$(function(){ 19 +$(document).ready(function(){
20 $(".one_click_select").live("click", function(){ 20 $(".one_click_select").live("click", function(){
21 $(this).select(); 21 $(this).select();
22 }); 22 });
@@ -27,8 +27,50 @@ $(function(){ @@ -27,8 +27,50 @@ $(function(){
27 $(".account-box").mouseenter(showMenu); 27 $(".account-box").mouseenter(showMenu);
28 $(".account-box").mouseleave(resetMenu); 28 $(".account-box").mouseleave(resetMenu);
29 29
  30 + $("#projects-list .project").live('click', function(e){
  31 + if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
  32 + location.href = $(this).attr("url");
  33 + e.stopPropagation();
  34 + return false;
  35 + }
  36 + });
  37 +
  38 + $("#issues-table .issue").live('click', function(e){
  39 + if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {
  40 + location.href = $(this).attr("url");
  41 + e.stopPropagation();
  42 + return false;
  43 + }
  44 + });
  45 +
  46 + $(document).keypress(function(e) {
  47 + if( $(e.target).is(":input") ) return;
  48 + switch(e.which) {
  49 + case 115: focusSearch();
  50 + e.preventDefault();
  51 + }
  52 + });
  53 +
30 }); 54 });
31 55
  56 +function focusSearch() {
  57 + $("#search").focus();
  58 +}
  59 +
  60 +function taggifyForm(){
  61 + var tag_field = $('#tag_field').tagify();
  62 +
  63 + tag_field.tagify('inputField').autocomplete({
  64 + source: '/tags.json'
  65 + });
  66 +
  67 + $('form').submit( function() {
  68 + var tag_field = $('#tag_field')
  69 + tag_field.val( tag_field.tagify('serialize') );
  70 + return true;
  71 + });
  72 +}
  73 +
32 function updatePage(data){ 74 function updatePage(data){
33 $.ajax({type: "GET", url: location.href, data: data, dataType: "script"}); 75 $.ajax({type: "GET", url: location.href, data: data, dataType: "script"});
34 } 76 }
app/assets/javascripts/commits.js
1 -$(document).ready(function(){  
2 - $(".day-commits-table li.commit").live('click', function(e){  
3 - if(e.target.nodeName != "A") {  
4 - location.href = $(this).attr("url");  
5 - e.stopPropagation();  
6 - return false;  
7 - }  
8 - });  
9 -});  
10 -  
11 var CommitsList = { 1 var CommitsList = {
  2 + ref:null,
  3 + limit:0,
  4 + offset:0,
12 5
13 -ref:null,  
14 -limit:0,  
15 -offset:0,  
16 -  
17 -init:  
18 - function(ref, limit) {  
19 - this.ref=ref;  
20 - this.limit=limit;  
21 - this.offset=limit;  
22 - this.initLoadMore();  
23 - $('.loading').show();  
24 - },  
25 -  
26 -getOld:  
27 - function() {  
28 - $('.loading').show();  
29 - $.ajax({  
30 - type: "GET",  
31 - url: location.href,  
32 - data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,  
33 - complete: function(){ $('.loading').hide()},  
34 - dataType: "script"});  
35 - }, 6 + init:
  7 + function(ref, limit) {
  8 + $(".day-commits-table li.commit").live('click', function(e){
  9 + if(e.target.nodeName != "A") {
  10 + location.href = $(this).attr("url");
  11 + e.stopPropagation();
  12 + return false;
  13 + }
  14 + });
36 15
37 -append:  
38 - function(count, html) {  
39 - $("#commits_list").append(html);  
40 - if(count > 0) {  
41 - this.offset += count; 16 + this.ref=ref;
  17 + this.limit=limit;
  18 + this.offset=limit;
42 this.initLoadMore(); 19 this.initLoadMore();
43 - }  
44 - }, 20 + $('.loading').show();
  21 + },
  22 +
  23 + getOld:
  24 + function() {
  25 + $('.loading').show();
  26 + $.ajax({
  27 + type: "GET",
  28 + url: location.href,
  29 + data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
  30 + complete: function(){ $('.loading').hide()},
  31 + dataType: "script"});
  32 + },
45 33
46 -initLoadMore:  
47 - function() {  
48 - $(window).bind('scroll', function(){  
49 - if($(window).scrollTop() == $(document).height() - $(window).height()){  
50 - $(window).unbind('scroll');  
51 - CommitsList.getOld(); 34 + append:
  35 + function(count, html) {
  36 + $("#commits_list").append(html);
  37 + if(count > 0) {
  38 + this.offset += count;
  39 + this.initLoadMore();
52 } 40 }
53 - });  
54 - } 41 + },
  42 +
  43 + initLoadMore:
  44 + function() {
  45 + $(window).bind('scroll', function(){
  46 + if($(window).scrollTop() == $(document).height() - $(window).height()){
  47 + $(window).unbind('scroll');
  48 + CommitsList.getOld();
  49 + }
  50 + });
  51 + }
55 } 52 }
app/assets/javascripts/projects.js
1 -$(document).ready(function(){  
2 - $("#projects-list .project").live('click', function(e){  
3 - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {  
4 - location.href = $(this).attr("url");  
5 - e.stopPropagation();  
6 - return false;  
7 - }  
8 - }); 1 +var ProjectsList = {
  2 + limit:0,
  3 + offset:0,
9 4
10 - $("#issues-table .issue").live('click', function(e){  
11 - if(e.target.nodeName != "A" && e.target.nodeName != "INPUT") {  
12 - location.href = $(this).attr("url");  
13 - e.stopPropagation();  
14 - return false;  
15 - }  
16 - }); 5 + init:
  6 + function(limit) {
  7 + this.limit=limit;
  8 + this.offset=limit;
  9 + this.initLoadMore();
17 10
18 - $(document).keypress(function(e) {  
19 - if( $(e.target).is(":input") ) return;  
20 - switch(e.which) {  
21 - case 115: focusSearch();  
22 - e.preventDefault();  
23 - }  
24 - }); 11 + $('.project_search').keyup(function() {
  12 + var terms = $(this).val();
  13 + if (terms.length >= 2 || terms.length == 0) {
  14 + url = $('.project_search').parent().attr('action');
  15 + $.ajax({
  16 + type: "GET",
  17 + url: location.href,
  18 + data: { 'terms': terms, 'replace': true },
  19 + dataType: "script"
  20 + });
  21 + }
  22 + });
  23 + },
25 24
26 -}); 25 + getOld:
  26 + function() {
  27 + $('.loading').show();
  28 + $.ajax({
  29 + type: "GET",
  30 + url: location.href,
  31 + data: "limit=" + this.limit + "&offset=" + this.offset,
  32 + complete: function(){ $('.loading').hide()},
  33 + dataType: "script"});
  34 + },
27 35
28 -function focusSearch() {  
29 - $("#search").focus();  
30 -} 36 + replace:
  37 + function(count, html) {
  38 + $(".tile").html(html);
  39 + if(count == ProjectsList.limit) {
  40 + this.offset = count;
  41 + this.initLoadMore();
  42 + } else {
  43 + this.offset = 0;
  44 + }
  45 + },
31 46
32 -function taggifyForm(){  
33 - var tag_field = $('#tag_field').tagify(); 47 + append:
  48 + function(count, html) {
  49 + $(".tile").append(html);
  50 + if(count > 0) {
  51 + this.offset += count;
  52 + this.initLoadMore();
  53 + }
  54 + },
34 55
35 - tag_field.tagify('inputField').autocomplete({  
36 - source: '/tags.json'  
37 - });  
38 -  
39 - $('form').submit( function() {  
40 - var tag_field = $('#tag_field')  
41 - tag_field.val( tag_field.tagify('serialize') );  
42 - return true;  
43 - }); 56 + initLoadMore:
  57 + function() {
  58 + $(window).bind('scroll', function(){
  59 + if($(window).scrollTop() == $(document).height() - $(window).height()){
  60 + $(window).unbind('scroll');
  61 + $('.loading').show();
  62 + ProjectsList.getOld();
  63 + }
  64 + });
  65 + }
44 } 66 }
45 -  
app/assets/stylesheets/projects.css.scss
@@ -647,3 +647,9 @@ h4.middle-panel { @@ -647,3 +647,9 @@ h4.middle-panel {
647 border-radius:3px; 647 border-radius:3px;
648 float:left; 648 float:left;
649 } 649 }
  650 +
  651 +.project_search {
  652 + margin: 1.5em 0;
  653 + padding: 8px !important;
  654 + width: 300px;
  655 +}
app/controllers/dashboard_controller.rb
@@ -3,7 +3,7 @@ class DashboardController &lt; ApplicationController @@ -3,7 +3,7 @@ class DashboardController &lt; ApplicationController
3 3
4 def index 4 def index
5 @projects = current_user.projects.all 5 @projects = current_user.projects.all
6 - @active_projects = @projects.select(&:last_activity_date).sort_by(&:last_activity_date).reverse 6 + @active_projects = @projects.select(&:last_activity_date_cached).sort_by(&:last_activity_date_cached).reverse
7 7
8 respond_to do |format| 8 respond_to do |format|
9 format.html 9 format.html
app/controllers/projects_controller.rb
@@ -11,9 +11,10 @@ class ProjectsController &lt; ApplicationController @@ -11,9 +11,10 @@ class ProjectsController &lt; ApplicationController
11 before_filter :require_non_empty_project, :only => [:blob, :tree, :graph] 11 before_filter :require_non_empty_project, :only => [:blob, :tree, :graph]
12 12
13 def index 13 def index
14 - source = current_user.projects  
15 - source = source.tagged_with(params[:tag]) unless params[:tag].blank?  
16 - @projects = source.all 14 + @limit, @offset = (params[:limit] || 16), (params[:offset] || 0)
  15 + @projects = current_user.projects
  16 + @projects = @projects.where("name LIKE ?", "%#{params[:terms]}%") unless params[:terms].blank?
  17 + @projects = @projects.limit(@limit).offset(@offset)
17 end 18 end
18 19
19 def new 20 def new
app/models/merge_request.rb
@@ -35,9 +35,8 @@ class MergeRequest &lt; ActiveRecord::Base @@ -35,9 +35,8 @@ class MergeRequest &lt; ActiveRecord::Base
35 end 35 end
36 36
37 def diffs 37 def diffs
38 - commit = project.commit(source_branch)  
39 commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)} 38 commits = project.repo.commits_between(target_branch, source_branch).map {|c| Commit.new(c)}
40 - diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) 39 + diffs = project.repo.diff(commits.first.prev_commit.id, commits.last.id) rescue []
41 end 40 end
42 41
43 def last_commit 42 def last_commit
app/models/project.rb
@@ -52,6 +52,9 @@ class Project &lt; ActiveRecord::Base @@ -52,6 +52,9 @@ class Project &lt; ActiveRecord::Base
52 52
53 scope :public_only, where(:private_flag => false) 53 scope :public_only, where(:private_flag => false)
54 54
  55 + def self.active
  56 + joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC")
  57 + end
55 58
56 def self.access_options 59 def self.access_options
57 { 60 {
@@ -195,6 +198,24 @@ class Project &lt; ActiveRecord::Base @@ -195,6 +198,24 @@ class Project &lt; ActiveRecord::Base
195 last_activity.try(:created_at) 198 last_activity.try(:created_at)
196 end 199 end
197 200
  201 + def last_activity_date_cached(expire = 1.hour)
  202 + activity_date_key = "project_#{id}_activity_date"
  203 +
  204 + cached_activities = Rails.cache.read(activity_date_key)
  205 + if cached_activities
  206 + activity_date = if cached_activities == "Never"
  207 + nil
  208 + else
  209 + cached_activities
  210 + end
  211 + else
  212 + activity_date = last_activity_date
  213 + Rails.cache.write(activity_date_key, activity_date || "Never", :expires_in => expire)
  214 + end
  215 +
  216 + activity_date
  217 + end
  218 +
198 # Get project updates from cache 219 # Get project updates from cache
199 # or calculate. 220 # or calculate.
200 def cached_updates(limit, expire = 2.minutes) 221 def cached_updates(limit, expire = 2.minutes)
@@ -204,7 +225,7 @@ class Project &lt; ActiveRecord::Base @@ -204,7 +225,7 @@ class Project &lt; ActiveRecord::Base
204 activities = cached_activities 225 activities = cached_activities
205 else 226 else
206 activities = updates(limit) 227 activities = updates(limit)
207 - Rails.cache.write(activities_key, activities, :expires_in => 60.seconds) 228 + Rails.cache.write(activities_key, activities, :expires_in => expire)
208 end 229 end
209 230
210 activities 231 activities
app/views/dashboard/_sidebar.html.haml
@@ -11,5 +11,5 @@ @@ -11,5 +11,5 @@
11 %span.project-name= project.name 11 %span.project-name= project.name
12 %span.time 12 %span.time
13 %strong Last activity: 13 %strong Last activity:
14 - = project.last_activity_date ? time_ago_in_words(project.last_activity_date) + " ago" : "Never" 14 + = project.last_activity_date_cached ? time_ago_in_words(project.last_activity_date_cached) + " ago" : "Never"
15 15
app/views/merge_requests/_commits.html.haml
@@ -15,3 +15,5 @@ @@ -15,3 +15,5 @@
15 ago 15 ago
16 .clear 16 .clear
17 17
  18 +- if @commits.empty?
  19 + %p.cgray Nothing to merge
app/views/merge_requests/_diffs.html.haml
@@ -20,3 +20,5 @@ @@ -20,3 +20,5 @@
20 %p 20 %p
21 %center No preview for this file type 21 %center No preview for this file type
22 22
  23 +- if @diffs.empty?
  24 + %p.cgray Nothing to merge
app/views/projects/_tile.html.haml
@@ -10,10 +10,10 @@ @@ -10,10 +10,10 @@
10 %input{ :value => project.url_to_repo, :class => ['git-url', 'one_click_select', 'text', 'project_list_url'], :readonly => 'readonly' } 10 %input{ :value => project.url_to_repo, :class => ['git-url', 'one_click_select', 'text', 'project_list_url'], :readonly => 'readonly' }
11 %p.title.activity 11 %p.title.activity
12 %span Last Activity: 12 %span Last Activity:
13 - - last_note = project.notes.last  
14 - = last_note ? last_note.created_at.stamp("24 Aug, 2011") : "Never"  
15 -  
16 - %p.small-tags= tag_list project 13 + - if project.last_activity_date_cached
  14 + = project.last_activity_date_cached.stamp("24 Aug, 2011")
  15 + - else
  16 + Never
17 17
18 .buttons 18 .buttons
19 %a.browse-code.button.yellow{:href => tree_project_ref_path(project, project.root_ref)} Browse code 19 %a.browse-code.button.yellow{:href => tree_project_ref_path(project, project.root_ref)} Browse code
app/views/projects/index.html.haml
@@ -7,13 +7,23 @@ @@ -7,13 +7,23 @@
7 %h2.icon 7 %h2.icon
8 %span 8 %span
9 Projects 9 Projects
  10 + %center
  11 + = form_tag projects_path, :method => :get, :remote => true, :id => "projects_search_form" do
  12 + = search_field_tag :project_search, nil, { :placeholder => 'Filter projects by name', :class => 'project_search text' }
10 13
11 %div.clear 14 %div.clear
12 - unless @projects.empty? 15 - unless @projects.empty?
13 - %div{:class => "tile", :style => view_mode_style("tile")} 16 + %div{:class => "tile"}
14 = render "tile" 17 = render "tile"
15 - %div{:class => "list", :style => view_mode_style("list")}  
16 - = render "list" 18 + .clear
  19 + .loading{ :style => "display:none;"}
  20 + %center= image_tag "ajax-loader.gif"
  21 +
  22 + - if @projects.count == @limit
  23 + :javascript
  24 + $(function(){
  25 + ProjectsList.init(16);
  26 + });
17 - else 27 - else
18 %center.prepend-top 28 %center.prepend-top
19 %h2 29 %h2
app/views/projects/index.js.haml 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +- if params[:replace]
  2 + :plain
  3 + ProjectsList.replace(#{@projects.count}, "#{escape_javascript(render(:partial => 'projects/tile'))}");
  4 +- else
  5 + :plain
  6 + ProjectsList.append(#{@projects.count}, "#{escape_javascript(render(:partial => 'projects/tile'))}");
  7 +
config/initializers/rails_footnotes.rb
1 #if defined?(Footnotes) && Rails.env.development? 1 #if defined?(Footnotes) && Rails.env.development?
2 #Footnotes.run! # first of all 2 #Footnotes.run! # first of all
3 -  
4 - # ... other init code  
5 #end 3 #end