Commit cff951912734a5aeea0ebb3e4ca01f704004a466

Authored by Dmitriy Zaporozhets
1 parent 6d5c9698

Dashboard perfomance improved. Filter for projects page

README.md
... ... @@ -3,9 +3,6 @@
3 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 6 ## Application details
10 7  
11 8 rails 3.1
... ...
app/assets/javascripts/application.js
... ... @@ -16,7 +16,7 @@
16 16 //= require branch-graph
17 17 //= require_tree .
18 18  
19   -$(function(){
  19 +$(document).ready(function(){
20 20 $(".one_click_select").live("click", function(){
21 21 $(this).select();
22 22 });
... ... @@ -27,8 +27,50 @@ $(function(){
27 27 $(".account-box").mouseenter(showMenu);
28 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 74 function updatePage(data){
33 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 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 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 647 border-radius:3px;
648 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 3  
4 4 def index
5 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 8 respond_to do |format|
9 9 format.html
... ...
app/controllers/projects_controller.rb
... ... @@ -11,9 +11,10 @@ class ProjectsController &lt; ApplicationController
11 11 before_filter :require_non_empty_project, :only => [:blob, :tree, :graph]
12 12  
13 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 18 end
18 19  
19 20 def new
... ...
app/models/merge_request.rb
... ... @@ -35,9 +35,8 @@ class MergeRequest &lt; ActiveRecord::Base
35 35 end
36 36  
37 37 def diffs
38   - commit = project.commit(source_branch)
39 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 40 end
42 41  
43 42 def last_commit
... ...
app/models/project.rb
... ... @@ -52,6 +52,9 @@ class Project &lt; ActiveRecord::Base
52 52  
53 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 59 def self.access_options
57 60 {
... ... @@ -195,6 +198,24 @@ class Project &lt; ActiveRecord::Base
195 198 last_activity.try(:created_at)
196 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 219 # Get project updates from cache
199 220 # or calculate.
200 221 def cached_updates(limit, expire = 2.minutes)
... ... @@ -204,7 +225,7 @@ class Project &lt; ActiveRecord::Base
204 225 activities = cached_activities
205 226 else
206 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 229 end
209 230  
210 231 activities
... ...
app/views/dashboard/_sidebar.html.haml
... ... @@ -11,5 +11,5 @@
11 11 %span.project-name= project.name
12 12 %span.time
13 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 15 ago
16 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 20 %p
21 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 %input{ :value => project.url_to_repo, :class => ['git-url', 'one_click_select', 'text', 'project_list_url'], :readonly => 'readonly' }
11 11 %p.title.activity
12 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 18 .buttons
19 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 7 %h2.icon
8 8 %span
9 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 14 %div.clear
12 15 - unless @projects.empty?
13   - %div{:class => "tile", :style => view_mode_style("tile")}
  16 + %div{:class => "tile"}
14 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 27 - else
18 28 %center.prepend-top
19 29 %h2
... ...
app/views/projects/index.js.haml 0 → 100644
... ... @@ -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 1 #if defined?(Footnotes) && Rails.env.development?
2 2 #Footnotes.run! # first of all
3   -
4   - # ... other init code
5 3 #end
... ...