Commit cff951912734a5aeea0ebb3e4ca01f704004a466
1 parent
6d5c9698
Exists in
master
and in
4 other branches
Dashboard perfomance improved. Filter for projects page
Showing
16 changed files
with
210 additions
and
107 deletions
Show diff stats
README.md
| @@ -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
app/controllers/dashboard_controller.rb
| @@ -3,7 +3,7 @@ class DashboardController < ApplicationController | @@ -3,7 +3,7 @@ class DashboardController < 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 < ApplicationController | @@ -11,9 +11,10 @@ class ProjectsController < 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 < ActiveRecord::Base | @@ -35,9 +35,8 @@ class MergeRequest < 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 < ActiveRecord::Base | @@ -52,6 +52,9 @@ class Project < 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 < ActiveRecord::Base | @@ -195,6 +198,24 @@ class Project < 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 < ActiveRecord::Base | @@ -204,7 +225,7 @@ class Project < 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
app/views/merge_requests/_diffs.html.haml
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 |
| @@ -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