Commit 9e80d2d4f79edf81185461a34a50adcd251073f1
Exists in
master
and in
4 other branches
Merge branch 'feature/groups' of dev.gitlabhq.com:gitlabhq
Showing
39 changed files
with
747 additions
and
37 deletions
Show diff stats
app/assets/stylesheets/sections/projects.scss
... | ... | @@ -0,0 +1,75 @@ |
1 | +class Admin::GroupsController < AdminController | |
2 | + before_filter :group, only: [:edit, :show, :update, :destroy, :project_update] | |
3 | + | |
4 | + def index | |
5 | + @groups = Group.scoped | |
6 | + @groups = @groups.search(params[:name]) if params[:name].present? | |
7 | + @groups = @groups.page(params[:page]).per(20) | |
8 | + end | |
9 | + | |
10 | + def show | |
11 | + @projects = Project.scoped | |
12 | + @projects = @projects.not_in_group(@group) if @group.projects.present? | |
13 | + @projects = @projects.all | |
14 | + end | |
15 | + | |
16 | + def new | |
17 | + @group = Group.new | |
18 | + end | |
19 | + | |
20 | + def edit | |
21 | + end | |
22 | + | |
23 | + def create | |
24 | + @group = Group.new(params[:group]) | |
25 | + @group.owner = current_user | |
26 | + | |
27 | + if @group.save | |
28 | + redirect_to [:admin, @group], notice: 'Group was successfully created.' | |
29 | + else | |
30 | + render action: "new" | |
31 | + end | |
32 | + end | |
33 | + | |
34 | + def update | |
35 | + group_params = params[:group].dup | |
36 | + owner_id =group_params.delete(:owner_id) | |
37 | + | |
38 | + if owner_id | |
39 | + @group.owner = User.find(owner_id) | |
40 | + end | |
41 | + | |
42 | + if @group.update_attributes(group_params) | |
43 | + redirect_to [:admin, @group], notice: 'Group was successfully updated.' | |
44 | + else | |
45 | + render action: "edit" | |
46 | + end | |
47 | + end | |
48 | + | |
49 | + def project_update | |
50 | + project_ids = params[:project_ids] | |
51 | + Project.where(id: project_ids).update_all(group_id: @group.id) | |
52 | + | |
53 | + redirect_to :back, notice: 'Group was successfully updated.' | |
54 | + end | |
55 | + | |
56 | + def remove_project | |
57 | + @project = Project.find(params[:project_id]) | |
58 | + @project.group_id = nil | |
59 | + @project.save | |
60 | + | |
61 | + redirect_to :back, notice: 'Group was successfully updated.' | |
62 | + end | |
63 | + | |
64 | + def destroy | |
65 | + @group.destroy | |
66 | + | |
67 | + redirect_to groups_url, notice: 'Group was successfully deleted.' | |
68 | + end | |
69 | + | |
70 | + private | |
71 | + | |
72 | + def group | |
73 | + @group = Group.find_by_code(params[:id]) | |
74 | + end | |
75 | +end | ... | ... |
app/controllers/dashboard_controller.rb
... | ... | @@ -2,7 +2,10 @@ class DashboardController < ApplicationController |
2 | 2 | respond_to :html |
3 | 3 | |
4 | 4 | def index |
5 | - @projects = current_user.projects_with_events.page(params[:page]).per(40) | |
5 | + @groups = Group.where(id: current_user.projects.pluck(:group_id)) | |
6 | + @projects = current_user.projects_with_events | |
7 | + @projects = @projects.page(params[:page]).per(40) | |
8 | + | |
6 | 9 | @events = Event.recent_for_user(current_user).limit(20).offset(params[:offset] || 0) |
7 | 10 | @last_push = current_user.recent_push |
8 | 11 | ... | ... |
... | ... | @@ -0,0 +1,69 @@ |
1 | +class GroupsController < ApplicationController | |
2 | + respond_to :html | |
3 | + layout 'group' | |
4 | + | |
5 | + before_filter :group | |
6 | + before_filter :projects | |
7 | + | |
8 | + def show | |
9 | + @events = Event.where(project_id: project_ids). | |
10 | + order('id DESC'). | |
11 | + limit(20).offset(params[:offset] || 0) | |
12 | + | |
13 | + @last_push = current_user.recent_push | |
14 | + | |
15 | + respond_to do |format| | |
16 | + format.html | |
17 | + format.js | |
18 | + format.atom { render layout: false } | |
19 | + end | |
20 | + end | |
21 | + | |
22 | + # Get authored or assigned open merge requests | |
23 | + def merge_requests | |
24 | + @merge_requests = current_user.cared_merge_requests.order("created_at DESC").page(params[:page]).per(20) | |
25 | + end | |
26 | + | |
27 | + # Get only assigned issues | |
28 | + def issues | |
29 | + @user = current_user | |
30 | + @issues = current_user.assigned_issues.opened.order("created_at DESC").page(params[:page]).per(20) | |
31 | + @issues = @issues.includes(:author, :project) | |
32 | + | |
33 | + respond_to do |format| | |
34 | + format.html | |
35 | + format.atom { render layout: false } | |
36 | + end | |
37 | + end | |
38 | + | |
39 | + def search | |
40 | + query = params[:search] | |
41 | + | |
42 | + @merge_requests = [] | |
43 | + @issues = [] | |
44 | + | |
45 | + if query.present? | |
46 | + @projects = @projects.search(query).limit(10) | |
47 | + @merge_requests = MergeRequest.where(project_id: project_ids).search(query).limit(10) | |
48 | + @issues = Issue.where(project_id: project_ids).search(query).limit(10) | |
49 | + end | |
50 | + end | |
51 | + | |
52 | + def people | |
53 | + @users = group.users | |
54 | + end | |
55 | + | |
56 | + protected | |
57 | + | |
58 | + def group | |
59 | + @group ||= Group.find_by_code(params[:id]) | |
60 | + end | |
61 | + | |
62 | + def projects | |
63 | + @projects ||= current_user.projects_with_events.where(group_id: @group.id) | |
64 | + end | |
65 | + | |
66 | + def project_ids | |
67 | + projects.map(&:id) | |
68 | + end | |
69 | +end | ... | ... |
... | ... | @@ -0,0 +1,36 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: groups | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) not null | |
7 | +# code :string(255) not null | |
8 | +# owner_id :integer not null | |
9 | +# created_at :datetime not null | |
10 | +# updated_at :datetime not null | |
11 | +# | |
12 | + | |
13 | +class Group < ActiveRecord::Base | |
14 | + attr_accessible :code, :name, :owner_id | |
15 | + | |
16 | + has_many :projects | |
17 | + belongs_to :owner, class_name: "User" | |
18 | + | |
19 | + validates :name, presence: true, uniqueness: true | |
20 | + validates :code, presence: true, uniqueness: true | |
21 | + validates :owner_id, presence: true | |
22 | + | |
23 | + delegate :name, to: :owner, allow_nil: true, prefix: true | |
24 | + | |
25 | + def self.search query | |
26 | + where("name like :query OR code like :query", query: "%#{query}%") | |
27 | + end | |
28 | + | |
29 | + def to_param | |
30 | + code | |
31 | + end | |
32 | + | |
33 | + def users | |
34 | + User.joins(:users_projects).where('users_projects.project_id' => project_ids).uniq | |
35 | + end | |
36 | +end | ... | ... |
app/models/project.rb
... | ... | @@ -11,6 +11,7 @@ class Project < ActiveRecord::Base |
11 | 11 | attr_accessor :error_code |
12 | 12 | |
13 | 13 | # Relations |
14 | + belongs_to :group | |
14 | 15 | belongs_to :owner, class_name: "User" |
15 | 16 | has_many :users, through: :users_projects |
16 | 17 | has_many :events, dependent: :destroy |
... | ... | @@ -25,16 +26,19 @@ class Project < ActiveRecord::Base |
25 | 26 | has_many :wikis, dependent: :destroy |
26 | 27 | has_many :protected_branches, dependent: :destroy |
27 | 28 | |
29 | + delegate :name, to: :owner, allow_nil: true, prefix: true | |
30 | + | |
28 | 31 | # Scopes |
29 | 32 | scope :public_only, where(private_flag: false) |
30 | - scope :without_user, lambda { |user| where("id not in (:ids)", ids: user.projects.map(&:id) ) } | |
33 | + scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.projects.map(&:id) ) } | |
34 | + scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } | |
31 | 35 | |
32 | 36 | def self.active |
33 | 37 | joins(:issues, :notes, :merge_requests).order("issues.created_at, notes.created_at, merge_requests.created_at DESC") |
34 | 38 | end |
35 | 39 | |
36 | 40 | def self.search query |
37 | - where("name like :query or code like :query or path like :query", query: "%#{query}%") | |
41 | + where("name like :query OR code like :query OR path like :query", query: "%#{query}%") | |
38 | 42 | end |
39 | 43 | |
40 | 44 | def self.create_by_user(params, user) |
... | ... | @@ -173,4 +177,6 @@ end |
173 | 177 | # wall_enabled :boolean default(TRUE), not null |
174 | 178 | # merge_requests_enabled :boolean default(TRUE), not null |
175 | 179 | # wiki_enabled :boolean default(TRUE), not null |
180 | +# group_id :integer | |
176 | 181 | # |
182 | + | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | += form_for [:admin, @group] do |f| | |
2 | + - if @group.errors.any? | |
3 | + .alert-message.block-message.error | |
4 | + %span= @group.errors.full_messages.first | |
5 | + .clearfix.group_name_holder | |
6 | + = f.label :name do | |
7 | + Group name is | |
8 | + .input | |
9 | + = f.text_field :name, placeholder: "Example Group", class: "xxlarge" | |
10 | + .clearfix | |
11 | + = f.label :code do | |
12 | + URL | |
13 | + .input | |
14 | + .input-prepend | |
15 | + %span.add-on= web_app_url | |
16 | + = f.text_field :code, placeholder: "example" | |
17 | + | |
18 | + .form-actions | |
19 | + = f.submit 'Save group', class: "btn save-btn" | ... | ... |
... | ... | @@ -0,0 +1,25 @@ |
1 | += render 'admin/shared/projects_head' | |
2 | +%h3.page_title | |
3 | + Groups | |
4 | + = link_to 'New Group', new_admin_group_path, class: "btn small right" | |
5 | +%br | |
6 | += form_tag admin_groups_path, method: :get, class: 'form-inline' do | |
7 | + = text_field_tag :name, params[:name], class: "xlarge" | |
8 | + = submit_tag "Search", class: "btn submit primary" | |
9 | + | |
10 | +%table | |
11 | + %thead | |
12 | + %th Name | |
13 | + %th Code | |
14 | + %th Projects | |
15 | + %th Edit | |
16 | + %th.cred Danger Zone! | |
17 | + | |
18 | + - @groups.each do |group| | |
19 | + %tr | |
20 | + %td= link_to group.name, [:admin, group] | |
21 | + %td= group.code | |
22 | + %td= group.projects.count | |
23 | + %td= link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn small" | |
24 | + %td.bgred= link_to 'Destroy', [:admin, group], confirm: "REMOVE #{group.name}? Are you sure?", method: :delete, class: "btn small danger" | |
25 | += paginate @groups, theme: "admin" | ... | ... |
... | ... | @@ -0,0 +1,52 @@ |
1 | += render 'admin/shared/projects_head' | |
2 | +%h3.page_title | |
3 | + Group: #{@group.name} | |
4 | + = link_to edit_admin_group_path(@group), class: "btn right" do | |
5 | + %i.icon-edit | |
6 | + Edit | |
7 | + | |
8 | +%br | |
9 | +%table.zebra-striped | |
10 | + %thead | |
11 | + %tr | |
12 | + %th Group | |
13 | + %th | |
14 | + %tr | |
15 | + %td | |
16 | + %b | |
17 | + Name: | |
18 | + %td | |
19 | + = @group.name | |
20 | + %tr | |
21 | + %td | |
22 | + %b | |
23 | + Code: | |
24 | + %td | |
25 | + = @group.code | |
26 | + %tr | |
27 | + %td | |
28 | + %b | |
29 | + Owner: | |
30 | + %td | |
31 | + = @group.owner_name | |
32 | +.ui-box | |
33 | + %h5 | |
34 | + Projects | |
35 | + %small | |
36 | + (#{@group.projects.count}) | |
37 | + %ul.unstyled | |
38 | + - @group.projects.each do |project| | |
39 | + %li.wll | |
40 | + %strong | |
41 | + = link_to project.name, [:admin, project] | |
42 | + .right | |
43 | + = link_to 'Remove from group', remove_project_admin_group_path(@group, project_id: project.id), confirm: 'Are you sure?', method: :delete, class: "btn danger small" | |
44 | + .clearfix | |
45 | + | |
46 | +%br | |
47 | +%h3 Add new project | |
48 | +%br | |
49 | += form_tag project_update_admin_group_path(@group), class: "bulk_import", method: :put do | |
50 | + = select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' | |
51 | + .form-actions | |
52 | + = submit_tag 'Add', class: "btn primary" | ... | ... |
app/views/admin/projects/index.html.haml
app/views/admin/projects/show.html.haml
... | ... | @@ -0,0 +1,15 @@ |
1 | +.projects_box | |
2 | + %h5 | |
3 | + Groups | |
4 | + %small | |
5 | + (#{groups.count}) | |
6 | + %ul.unstyled | |
7 | + - groups.each do |group| | |
8 | + %li.wll | |
9 | + = link_to group_path(id: group.code), class: dom_class(group) do | |
10 | + %strong.group_name= truncate(group.name, length: 25) | |
11 | + %span.arrow | |
12 | + → | |
13 | + %span.last_activity | |
14 | + %strong Projects: | |
15 | + %span= group.projects.count | ... | ... |
... | ... | @@ -0,0 +1,21 @@ |
1 | +.projects_box | |
2 | + %h5 | |
3 | + Projects | |
4 | + %small | |
5 | + (#{projects.total_count}) | |
6 | + - if current_user.can_create_project? | |
7 | + %span.right | |
8 | + = link_to new_project_path, class: "btn very_small info" do | |
9 | + %i.icon-plus | |
10 | + New Project | |
11 | + %ul.unstyled | |
12 | + - projects.each do |project| | |
13 | + %li.wll | |
14 | + = link_to project_path(project), class: dom_class(project) do | |
15 | + %strong.project_name= truncate(project.name, length: 25) | |
16 | + %span.arrow | |
17 | + → | |
18 | + %span.last_activity | |
19 | + %strong Last activity: | |
20 | + %span= project_last_activity(project) | |
21 | + .bottom= paginate projects, theme: "gitlab" | ... | ... |
app/views/dashboard/index.html.haml
... | ... | @@ -9,28 +9,9 @@ |
9 | 9 | .loading.hide |
10 | 10 | .side |
11 | 11 | = render "events/event_last_push", event: @last_push |
12 | - .projects_box | |
13 | - %h5 | |
14 | - Projects | |
15 | - %small | |
16 | - (#{@projects.total_count}) | |
17 | - - if current_user.can_create_project? | |
18 | - %span.right | |
19 | - = link_to new_project_path, class: "btn very_small info" do | |
20 | - %i.icon-plus | |
21 | - New Project | |
22 | - %ul.unstyled | |
23 | - - @projects.each do |project| | |
24 | - %li.wll | |
25 | - = link_to project_path(project), class: dom_class(project) do | |
26 | - %strong.project_name= truncate(project.name, length: 25) | |
27 | - %span.arrow | |
28 | - → | |
29 | - %span.last_activity | |
30 | - %strong Last activity: | |
31 | - %span= project_last_activity(project) | |
32 | - .bottom= paginate @projects, theme: "gitlab" | |
33 | - | |
12 | + - if @groups.present? | |
13 | + = render "groups", groups: @groups | |
14 | + = render "projects", projects: @projects | |
34 | 15 | %div |
35 | 16 | %span.rss-icon |
36 | 17 | = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +.projects_box | |
2 | + %h5 | |
3 | + Projects | |
4 | + %small | |
5 | + (#{projects.count}) | |
6 | + %ul.unstyled | |
7 | + - projects.each do |project| | |
8 | + %li.wll | |
9 | + = link_to project_path(project), class: dom_class(project) do | |
10 | + %strong.project_name= truncate(project.name, length: 25) | |
11 | + %span.arrow | |
12 | + → | |
13 | + %span.last_activity | |
14 | + %strong Last activity: | |
15 | + %span= project_last_activity(project) | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +xml.instruct! | |
2 | +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do | |
3 | + xml.title "#{@user.name} issues" | |
4 | + xml.link :href => dashboard_issues_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" | |
5 | + xml.link :href => dashboard_issues_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" | |
6 | + xml.id dashboard_issues_url(:private_token => @user.private_token) | |
7 | + xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? | |
8 | + | |
9 | + @issues.each do |issue| | |
10 | + xml.entry do | |
11 | + xml.id project_issue_url(issue.project, issue) | |
12 | + xml.link :href => project_issue_url(issue.project, issue) | |
13 | + xml.title truncate(issue.title, :length => 80) | |
14 | + xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") | |
15 | + xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(issue.author_email) | |
16 | + xml.author do |author| | |
17 | + xml.name issue.author_name | |
18 | + xml.email issue.author_email | |
19 | + end | |
20 | + xml.summary issue.title | |
21 | + end | |
22 | + end | |
23 | +end | |
24 | + | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | +%h3.page_title | |
2 | + Issues | |
3 | + %small (assigned to you) | |
4 | + %small.right #{@issues.total_count} issues | |
5 | + | |
6 | +%br | |
7 | +.clearfix | |
8 | +- if @issues.any? | |
9 | + - @issues.group_by(&:project).each do |group| | |
10 | + %div.ui-box | |
11 | + - @project = group[0] | |
12 | + %h5= @project.name | |
13 | + %ul.unstyled.issues_table | |
14 | + - group[1].each do |issue| | |
15 | + = render(partial: 'issues/show', locals: {issue: issue}) | |
16 | + %hr | |
17 | + = paginate @issues, theme: "gitlab" | |
18 | +- else | |
19 | + %h3.nothing_here_message Nothing to show here | ... | ... |
... | ... | @@ -0,0 +1,18 @@ |
1 | +%h3.page_title | |
2 | + Merge Requests | |
3 | + %small (authored by or assigned to you) | |
4 | + %small.right #{@merge_requests.total_count} merge requests | |
5 | + | |
6 | +%br | |
7 | +- if @merge_requests.any? | |
8 | + - @merge_requests.group_by(&:project).each do |group| | |
9 | + %ul.unstyled.ui-box | |
10 | + - @project = group[0] | |
11 | + %h5= @project.name | |
12 | + - group[1].each do |merge_request| | |
13 | + = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) | |
14 | + %hr | |
15 | + = paginate @merge_requests, theme: "gitlab" | |
16 | + | |
17 | +- else | |
18 | + %h3.nothing_here_message Nothing to show here | ... | ... |
... | ... | @@ -0,0 +1,75 @@ |
1 | += form_tag search_group_path(@group), method: :get, class: 'form-inline' do |f| | |
2 | + .padded | |
3 | + = label_tag :search do | |
4 | + %strong Looking for | |
5 | + .input | |
6 | + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" | |
7 | + = submit_tag 'Search', class: "btn primary wide" | |
8 | +- if params[:search].present? | |
9 | + %br | |
10 | + %h3 | |
11 | + Search results | |
12 | + %small (#{@projects.count + @merge_requests.count + @issues.count}) | |
13 | + %hr | |
14 | + .search_results | |
15 | + .row | |
16 | + .span6 | |
17 | + %table | |
18 | + %thead | |
19 | + %tr | |
20 | + %th Projects | |
21 | + %tbody | |
22 | + - @projects.each do |project| | |
23 | + %tr | |
24 | + %td | |
25 | + = link_to project do | |
26 | + %strong.term= project.name | |
27 | + %small.cgray | |
28 | + last activity at | |
29 | + = project.last_activity_date.stamp("Aug 25, 2011") | |
30 | + - if @projects.blank? | |
31 | + %tr | |
32 | + %td | |
33 | + %h4.nothing_here_message No Projects | |
34 | + %br | |
35 | + %table | |
36 | + %thead | |
37 | + %tr | |
38 | + %th Merge Requests | |
39 | + %tbody | |
40 | + - @merge_requests.each do |merge_request| | |
41 | + %tr | |
42 | + %td | |
43 | + = link_to [merge_request.project, merge_request] do | |
44 | + %span.badge.badge-info ##{merge_request.id} | |
45 | + – | |
46 | + %strong.term= truncate merge_request.title, length: 50 | |
47 | + %strong.right | |
48 | + %span.label= merge_request.project.name | |
49 | + - if @merge_requests.blank? | |
50 | + %tr | |
51 | + %td | |
52 | + %h4.nothing_here_message No Merge Requests | |
53 | + .span6 | |
54 | + %table | |
55 | + %thead | |
56 | + %tr | |
57 | + %th Issues | |
58 | + %tbody | |
59 | + - @issues.each do |issue| | |
60 | + %tr | |
61 | + %td | |
62 | + = link_to [issue.project, issue] do | |
63 | + %span.badge.badge-info ##{issue.id} | |
64 | + – | |
65 | + %strong.term= truncate issue.title, length: 40 | |
66 | + %strong.right | |
67 | + %span.label= issue.project.name | |
68 | + - if @issues.blank? | |
69 | + %tr | |
70 | + %td | |
71 | + %h4.nothing_here_message No Issues | |
72 | + :javascript | |
73 | + $(function() { | |
74 | + $(".search_results .term").highlight("#{params[:search]}"); | |
75 | + }) | ... | ... |
... | ... | @@ -0,0 +1,29 @@ |
1 | +xml.instruct! | |
2 | +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do | |
3 | + xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" | |
4 | + xml.link :href => projects_url(:atom), :rel => "self", :type => "application/atom+xml" | |
5 | + xml.link :href => projects_url, :rel => "alternate", :type => "text/html" | |
6 | + xml.id projects_url | |
7 | + xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? | |
8 | + | |
9 | + @events.each do |event| | |
10 | + if event.allowed? | |
11 | + event = EventDecorator.decorate(event) | |
12 | + xml.entry do | |
13 | + event_link = event.feed_url | |
14 | + event_title = event.feed_title | |
15 | + | |
16 | + xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" | |
17 | + xml.link :href => event_link | |
18 | + xml.title truncate(event_title, :length => 80) | |
19 | + xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") | |
20 | + xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) | |
21 | + xml.author do |author| | |
22 | + xml.name event.author_name | |
23 | + xml.email event.author_email | |
24 | + end | |
25 | + xml.summary event_title | |
26 | + end | |
27 | + end | |
28 | + end | |
29 | +end | ... | ... |
... | ... | @@ -0,0 +1,30 @@ |
1 | +.projects | |
2 | + .activities.span8 | |
3 | + = link_to dashboard_path, class: 'btn very_small' do | |
4 | + ← To dashboard | |
5 | + | |
6 | + %span.cgray Events and projects are filtered in scope of group | |
7 | + %hr | |
8 | + = render 'shared/no_ssh' | |
9 | + - if @events.any? | |
10 | + .content_list= render @events | |
11 | + - else | |
12 | + %h4.nothing_here_message Projects activity will be displayed here | |
13 | + .loading.hide | |
14 | + .side | |
15 | + = render "events/event_last_push", event: @last_push | |
16 | + = render "projects", projects: @projects | |
17 | + %div | |
18 | + %span.rss-icon | |
19 | + = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do | |
20 | + = image_tag "rss_ui.png", title: "feed" | |
21 | + %strong News Feed | |
22 | + | |
23 | + %hr | |
24 | + .gitlab-promo | |
25 | + = link_to "Homepage", "http://gitlabhq.com" | |
26 | + = link_to "Blog", "http://blog.gitlabhq.com" | |
27 | + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" | |
28 | + | |
29 | +:javascript | |
30 | + $(function(){ Pager.init(20); }); | ... | ... |
app/views/layouts/admin.html.haml
... | ... | @@ -8,7 +8,7 @@ |
8 | 8 | %ul.main_menu |
9 | 9 | = nav_link(controller: :dashboard, html_options: {class: 'home'}) do |
10 | 10 | = link_to "Stats", admin_root_path |
11 | - = nav_link(controller: :projects) do | |
11 | + = nav_link(controller: [:projects, :groups]) do | |
12 | 12 | = link_to "Projects", admin_projects_path |
13 | 13 | = nav_link(controller: :users) do |
14 | 14 | = link_to "Users", admin_users_path | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +!!! 5 | |
2 | +%html{ lang: "en"} | |
3 | + = render "layouts/head" | |
4 | + %body{class: "#{app_theme} application"} | |
5 | + = render "layouts/flash" | |
6 | + = render "layouts/head_panel", title: "#{@group.name}" | |
7 | + .container | |
8 | + %ul.main_menu | |
9 | + = nav_link(path: 'groups#show', html_options: {class: 'home'}) do | |
10 | + = link_to "Home", group_path(@group), title: "Home" | |
11 | + = nav_link(path: 'groups#issues') do | |
12 | + = link_to issues_group_path(@group) do | |
13 | + Issues | |
14 | + %span.count= current_user.assigned_issues.opened.count | |
15 | + = nav_link(path: 'groups#merge_requests') do | |
16 | + = link_to merge_requests_group_path(@group) do | |
17 | + Merge Requests | |
18 | + %span.count= current_user.cared_merge_requests.count | |
19 | + = nav_link(path: 'groups#search') do | |
20 | + = link_to "Search", search_group_path(@group) | |
21 | + = nav_link(path: 'groups#people') do | |
22 | + = link_to "People", people_group_path(@group) | |
23 | + | |
24 | + .content= yield | ... | ... |
app/views/snippets/show.html.haml
... | ... | @@ -7,14 +7,17 @@ |
7 | 7 | = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn small right" |
8 | 8 | |
9 | 9 | %br |
10 | -.file_holder | |
11 | - .file_title | |
12 | - %i.icon-file | |
13 | - %strong= @snippet.file_name | |
14 | - %span.options | |
15 | - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" | |
16 | - .file_content.code | |
17 | - %div{class: current_user.dark_scheme ? "black" : ""} | |
18 | - = raw @snippet.colorize(options: { linenos: 'True'}) | |
10 | +%div | |
11 | + .file_holder | |
12 | + .file_title | |
13 | + %i.icon-file | |
14 | + %strong= @snippet.file_name | |
15 | + %span.options | |
16 | + = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn very_small", target: "_blank" | |
17 | + .file_content.code | |
18 | + %div{class: current_user.dark_scheme ? "black" : ""} | |
19 | + = raw @snippet.colorize(options: { linenos: 'True'}) | |
19 | 20 | |
20 | -= render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" | |
21 | + | |
22 | +%div | |
23 | + = render "notes/notes_with_form", tid: @snippet.id, tt: "snippet" | ... | ... |
config/routes.rb
... | ... | @@ -43,6 +43,12 @@ Gitlab::Application.routes.draw do |
43 | 43 | put :unblock |
44 | 44 | end |
45 | 45 | end |
46 | + resources :groups, constraints: { id: /[^\/]+/ } do | |
47 | + member do | |
48 | + put :project_update | |
49 | + delete :remove_project | |
50 | + end | |
51 | + end | |
46 | 52 | resources :projects, constraints: { id: /[^\/]+/ } do |
47 | 53 | member do |
48 | 54 | get :team |
... | ... | @@ -81,6 +87,19 @@ Gitlab::Application.routes.draw do |
81 | 87 | get "dashboard/issues" => "dashboard#issues" |
82 | 88 | get "dashboard/merge_requests" => "dashboard#merge_requests" |
83 | 89 | |
90 | + | |
91 | + # | |
92 | + # Groups Area | |
93 | + # | |
94 | + resources :groups, constraints: { id: /[^\/]+/ }, only: [:show] do | |
95 | + member do | |
96 | + get :issues | |
97 | + get :merge_requests | |
98 | + get :search | |
99 | + get :people | |
100 | + end | |
101 | + end | |
102 | + | |
84 | 103 | resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] |
85 | 104 | |
86 | 105 | devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks } | ... | ... |
db/schema.rb
... | ... | @@ -11,7 +11,7 @@ |
11 | 11 | # |
12 | 12 | # It's strongly recommended to check this file into your version control system. |
13 | 13 | |
14 | -ActiveRecord::Schema.define(:version => 20120905043334) do | |
14 | +ActiveRecord::Schema.define(:version => 20121002151033) do | |
15 | 15 | |
16 | 16 | create_table "events", :force => true do |t| |
17 | 17 | t.string "target_type" |
... | ... | @@ -25,6 +25,14 @@ ActiveRecord::Schema.define(:version => 20120905043334) do |
25 | 25 | t.integer "author_id" |
26 | 26 | end |
27 | 27 | |
28 | + create_table "groups", :force => true do |t| | |
29 | + t.string "name", :null => false | |
30 | + t.string "code", :null => false | |
31 | + t.integer "owner_id", :null => false | |
32 | + t.datetime "created_at", :null => false | |
33 | + t.datetime "updated_at", :null => false | |
34 | + end | |
35 | + | |
28 | 36 | create_table "issues", :force => true do |t| |
29 | 37 | t.string "title" |
30 | 38 | t.integer "assignee_id" |
... | ... | @@ -108,6 +116,7 @@ ActiveRecord::Schema.define(:version => 20120905043334) do |
108 | 116 | t.boolean "wall_enabled", :default => true, :null => false |
109 | 117 | t.boolean "merge_requests_enabled", :default => true, :null => false |
110 | 118 | t.boolean "wiki_enabled", :default => true, :null => false |
119 | + t.integer "group_id" | |
111 | 120 | end |
112 | 121 | |
113 | 122 | create_table "protected_branches", :force => true do |t| | ... | ... |
features/dashboard/dashboard.feature
... | ... | @@ -10,6 +10,11 @@ Feature: Dashboard |
10 | 10 | Then I should see "Shop" project link |
11 | 11 | Then I should see project "Shop" activity feed |
12 | 12 | |
13 | + Scenario: I should see groups list | |
14 | + Given I have group with projects | |
15 | + And I visit dashboard page | |
16 | + Then I should see groups list | |
17 | + | |
13 | 18 | Scenario: I should see last push widget |
14 | 19 | Then I should see last push widget |
15 | 20 | And I click "Create Merge Request" link | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +class Groups < Spinach::FeatureSteps | |
2 | + include SharedAuthentication | |
3 | + include SharedPaths | |
4 | + | |
5 | + When 'I visit group page' do | |
6 | + visit group_path(current_group) | |
7 | + end | |
8 | + | |
9 | + Then 'I should see projects list' do | |
10 | + current_user.projects.each do |project| | |
11 | + page.should have_link project.name | |
12 | + end | |
13 | + end | |
14 | + | |
15 | + And 'I have group with projects' do | |
16 | + @group = Factory :group | |
17 | + @project = Factory :project, group: @group | |
18 | + @event = Factory :closed_issue_event, project: @project | |
19 | + | |
20 | + @project.add_access current_user, :admin | |
21 | + end | |
22 | + | |
23 | + And 'I should see projects activity feed' do | |
24 | + page.should have_content 'closed issue' | |
25 | + end | |
26 | + | |
27 | + protected | |
28 | + | |
29 | + def current_group | |
30 | + @group ||= Group.first | |
31 | + end | |
32 | +end | ... | ... |
spec/factories.rb
... | ... | @@ -0,0 +1,24 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: groups | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) not null | |
7 | +# code :string(255) not null | |
8 | +# owner_id :integer not null | |
9 | +# created_at :datetime not null | |
10 | +# updated_at :datetime not null | |
11 | +# | |
12 | + | |
13 | +require 'spec_helper' | |
14 | + | |
15 | +describe Group do | |
16 | + let!(:group) { create(:group) } | |
17 | + | |
18 | + it { should have_many :projects } | |
19 | + it { should validate_presence_of :name } | |
20 | + it { should validate_uniqueness_of(:name) } | |
21 | + it { should validate_presence_of :code } | |
22 | + it { should validate_uniqueness_of(:code) } | |
23 | + it { should validate_presence_of :owner_id } | |
24 | +end | ... | ... |
spec/models/project_spec.rb
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: projects | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# name :string(255) | |
7 | +# path :string(255) | |
8 | +# description :text | |
9 | +# created_at :datetime not null | |
10 | +# updated_at :datetime not null | |
11 | +# private_flag :boolean default(TRUE), not null | |
12 | +# code :string(255) | |
13 | +# owner_id :integer | |
14 | +# default_branch :string(255) | |
15 | +# issues_enabled :boolean default(TRUE), not null | |
16 | +# wall_enabled :boolean default(TRUE), not null | |
17 | +# merge_requests_enabled :boolean default(TRUE), not null | |
18 | +# wiki_enabled :boolean default(TRUE), not null | |
19 | +# group_id :integer | |
20 | +# | |
21 | + | |
1 | 22 | require 'spec_helper' |
2 | 23 | |
3 | 24 | describe Project do |
4 | 25 | describe "Associations" do |
26 | + it { should belong_to(:group) } | |
5 | 27 | it { should belong_to(:owner).class_name('User') } |
6 | 28 | it { should have_many(:users) } |
7 | 29 | it { should have_many(:events).dependent(:destroy) } | ... | ... |