diff options
25 files changed, 301 insertions, 35 deletions
diff --git a/CHANGELOG b/CHANGELOG index 66244ae0723..95eee7421a5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 7.1.0 - Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs! - Show error message in case of timeout in satellite when create MR - Show first 100 files for huge diff instead of hiding all + - Add project stars (Ciro Santilli) v 7.0.0 - The CPU no longer overheats when you hold down the spacebar diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index 23f206ce3dc..034692a67e1 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -4,3 +4,9 @@ .js-details-container .content.hide { display: block; } .js-details-container.open .content { display: block; } .js-details-container.open .content.hide { display: none; } + +// Toggle between two states. +.js-toggler-container .turn-on { display: inline-block; } +.js-toggler-container .turn-off { display: none; } +.js-toggler-container.on .turn-on { display: none; } +.js-toggler-container.on .turn-off { display: inline-block; } diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index 046e5040fb2..d098f1ecaa2 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -6,7 +6,7 @@ vertical-align: middle; cursor: pointer; background-image: none; - border: 1px solid transparent; + border: $btn-border; white-space: nowrap; padding: 6px 12px; font-size: 13px; @@ -19,7 +19,6 @@ user-select: none; color: #444444; background-color: #fff; - border-color: #ccc; text-shadow: none; &.hover, diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index c49f7db788e..02ce2c8338f 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -10,6 +10,8 @@ $hover: #D9EDF7; $link_color: #446e9b; $link_hover_color: #2FA0BB; +$btn-border: 1px solid #ccc; + /* * Success colors (green) */ diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index c9188fb751c..eecc49620fd 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -50,6 +50,22 @@ margin-left: 10px; font-weight: 500; } + + .star .btn { + font-weight: bold; + line-height: 22px; + padding: 0px; + $margin-x: 6px; + .toggle { + display: inline-block; + padding: 0px $margin-x; + } + .count { + border-left: $btn-border; + display: inline-block; + padding: 0px $margin-x; + } + } } } diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 597efa40ded..cf7209b05e6 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -167,6 +167,11 @@ class ProjectsController < ApplicationController end end + def toggle_star + current_user.toggle_star(@project) + render json: { star_count: @project.star_count } + end + private def upload_path diff --git a/app/models/project.rb b/app/models/project.rb index 339143196a7..fdd7840aac6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -81,6 +81,8 @@ class Project < ActiveRecord::Base has_many :users, through: :users_projects has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects + has_many :users_star_projects, dependent: :destroy + has_many :starrers, through: :users_star_projects, source: :user delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true @@ -575,4 +577,8 @@ class Project < ActiveRecord::Base def update_repository_size update_attribute(:repository_size, repository.size) end + + def star_count + starrers.count + end end diff --git a/app/models/user.rb b/app/models/user.rb index 19104336598..f5aab99c6a9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -91,6 +91,8 @@ class User < ActiveRecord::Base has_many :personal_projects, through: :namespace, source: :projects has_many :projects, through: :users_projects has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' + has_many :users_star_projects, dependent: :destroy + has_many :starred_projects, through: :users_star_projects, source: :project has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" has_many :users_projects, dependent: :destroy @@ -508,4 +510,17 @@ class User < ActiveRecord::Base def system_hook_service SystemHooksService.new end + + def starred?(project) + starred_projects.exists?(project) + end + + def toggle_star(project) + user_star_project = users_star_projects.where(project: project).take + if user_star_project + user_star_project.destroy + else + UsersStarProject.create!(project: project, user: self) + end + end end diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb new file mode 100644 index 00000000000..1f5ae37b02b --- /dev/null +++ b/app/models/users_star_project.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: users_star_projects +# +# id :integer not null, primary key +# starrer_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +class UsersStarProject < ActiveRecord::Base + belongs_to :project + belongs_to :user + + validates :user, presence: true + validates :user_id, uniqueness: { scope: [:project_id] } + validates :project, presence: true +end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index ddf815ebd99..6d7f859c22a 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -27,10 +27,30 @@ = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do = readme.name - - unless empty_repo - .col-md-5 - .project-home-links + .col-md-5 + .project-home-links + - unless empty_repo = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref) = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project) = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project) %span.light.prepend-left-20= repository_size + %span.star.js-toggler-container.on + - if current_user + = render 'link_to_toggle_star', + title: 'Star this project.', + starred: false, + signed_in: true + = render 'link_to_toggle_star', + title: 'Unstar this project.', + starred: true, + signed_in: true + :coffeescript + $('.star').on 'ajax:success', (e, data, status, xhr) -> + $(@).toggleClass('on').find('.count').html(data.star_count) + .on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + - else + = render 'link_to_toggle_star', + title: 'You must sign in to star a project.', + starred: false, + signed_in: false diff --git a/app/views/projects/_link_to_toggle_star.html.haml b/app/views/projects/_link_to_toggle_star.html.haml new file mode 100644 index 00000000000..7d41578c8f3 --- /dev/null +++ b/app/views/projects/_link_to_toggle_star.html.haml @@ -0,0 +1,13 @@ +- cls = 'btn' +- cls += ' disabled' unless signed_in +%span{class: starred ? 'turn-on' : 'turn-off'} + = link_to toggle_star_project_path(@project), title: title, + class: cls, method: :post, remote: true, data: {type: 'json'} do + %span.toggle<> + %i.icon-star + - if starred + Unstar + - else + Star + %span.count<> + = @project.star_count diff --git a/config/routes.rb b/config/routes.rb index f73fd7a87fa..bd639e18932 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -175,6 +175,7 @@ Gitlab::Application.routes.draw do post :archive post :unarchive post :upload_image + post :toggle_star get :autocomplete_sources get :import put :retry_import diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb new file mode 100644 index 00000000000..70475535d54 --- /dev/null +++ b/db/migrate/20140625115202_create_users_star_projects.rb @@ -0,0 +1,13 @@ +class CreateUsersStarProjects < ActiveRecord::Migration + def change + create_table :users_star_projects do |t| + t.integer :project_id, null: false + t.integer :user_id, null: false + t.timestamps + end + + add_index :users_star_projects, :user_id + add_index :users_star_projects, :project_id + add_index :users_star_projects, [:user_id, :project_id], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 345b6fd3b68..fd0f15a7fa6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140611135229) do +ActiveRecord::Schema.define(version: 20140625115202) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -369,6 +369,17 @@ ActiveRecord::Schema.define(version: 20140611135229) do add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree + create_table "users_star_projects", force: true do |t| + t.integer "project_id", null: false + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree + add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree + add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree + create_table "web_hooks", force: true do |t| t.string "url" t.integer "project_id" diff --git a/features/project/star.feature b/features/project/star.feature new file mode 100644 index 00000000000..e6cbc78125b --- /dev/null +++ b/features/project/star.feature @@ -0,0 +1,48 @@ +Feature: Project Star + Scenario: New projects have 0 stars + Given public project "Community" + When I visit project "Community" page + Then The project has 0 stars + + Scenario: Empty projects show star count + Given public empty project "Empty Public Project" + When I visit empty project page + Then The project has 0 stars + + Scenario: Signed off users can't star projects + Given public project "Community" + And I visit project "Community" page + When I click on the star toggle button + Then The project has 0 stars + + @javascript + Scenario: Signed in users can toggle star + Given I sign in as "John Doe" + And public project "Community" + And I visit project "Community" page + When I click on the star toggle button + Then The project has 1 star + When I click on the star toggle button + Then The project has 0 stars + + @javascript + Scenario: Star count sums stars + Given I sign in as "John Doe" + And public project "Community" + And I visit project "Community" page + And I click on the star toggle button + And I logout + And I sign in as "Mary Jane" + And I visit project "Community" page + When I click on the star toggle button + Then The project has 2 stars + + @javascript + Scenario: If an user deletes his account his stars are destroyed + Given I sign in as "John Doe" + And public project "Community" + And I visit project "Community" page + And I click on the star toggle button + And I delete my account + When I visit project "Community" page + Then The project has 0 stars diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb new file mode 100644 index 00000000000..11102900ed4 --- /dev/null +++ b/features/steps/project/star.rb @@ -0,0 +1,29 @@ +class Spinach::Features::ProjectStar < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include SharedUser + + step "The project has 0 stars" do + has_n_stars(0) + end + + step "The project has 1 star" do + has_n_stars(1) + end + + step "The project has 2 stars" do + has_n_stars(2) + end + + # Requires @javascript + step "I click on the star toggle button" do + page.find(".star .toggle", visible: true).click + end + + protected + + def has_n_stars(n) + expect(page).to have_css(".star .count", text: /^#{n}$/, visible: true) + end +end diff --git a/features/steps/public/projects.rb b/features/steps/public/projects.rb index 7c7311bb91c..f6ed838d2dc 100644 --- a/features/steps/public/projects.rb +++ b/features/steps/public/projects.rb @@ -3,10 +3,6 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps include SharedPaths include SharedProject - step 'public empty project "Empty Public Project"' do - create :empty_project, :public, name: 'Empty Public Project' - end - step 'I should see project "Empty Public Project"' do page.should have_content "Empty Public Project" end @@ -20,16 +16,6 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps page.should have_content 'README.md' end - step 'I visit empty project page' do - project = Project.find_by(name: 'Empty Public Project') - visit project_path(project) - end - - step 'I visit project "Community" page' do - project = Project.find_by(name: 'Community') - visit project_path(project) - end - step 'I should see empty public project details' do page.should have_content 'Git global setup' end @@ -48,22 +34,12 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps end end - step 'I visit project "Enterprise" page' do - project = Project.find_by(name: 'Enterprise') - visit project_path(project) - end - step 'I should see project "Community" home page' do within '.project-home-title' do page.should have_content 'Community' end end - step 'I visit project "Internal" page' do - project = Project.find_by(name: 'Internal') - visit project_path(project) - end - step 'I should see project "Internal" home page' do within '.project-home-title' do page.should have_content 'Internal' diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index b8c11ce0a23..b48021dc146 100644 --- a/features/steps/shared/authentication.rb +++ b/features/steps/shared/authentication.rb @@ -24,6 +24,10 @@ module SharedAuthentication current_path.should == new_user_session_path end + step "I logout" do + logout + end + def current_user @user || User.first end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 2090b642059..dc1f3eb64a0 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -320,6 +320,34 @@ module SharedPaths end # ---------------------------------------- + # Visibility Projects + # ---------------------------------------- + + step 'I visit project "Community" page' do + project = Project.find_by(name: "Community") + visit project_path(project) + end + + step 'I visit project "Internal" page' do + project = Project.find_by(name: "Internal") + visit project_path(project) + end + + step 'I visit project "Enterprise" page' do + project = Project.find_by(name: "Enterprise") + visit project_path(project) + end + + # ---------------------------------------- + # Empty Projects + # ---------------------------------------- + + step "I visit empty project page" do + project = Project.find_by(name: "Empty Public Project") + visit project_path(project) + end + + # ---------------------------------------- # Public Projects # ---------------------------------------- @@ -327,10 +355,6 @@ module SharedPaths visit public_root_path end - step 'I visit public page for "Community" project' do - visit public_project_path(Project.find_by(name: "Community")) - end - # ---------------------------------------- # Snippets # ---------------------------------------- diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index ddb87daeeb7..ba6f090a706 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -122,4 +122,12 @@ module SharedProject project ||= create :empty_project, :public, name: 'Community', namespace: user.namespace project.team << [user, :master] end + + # ---------------------------------------- + # Empty projects + # ---------------------------------------- + + step 'public empty project "Empty Public Project"' do + create :empty_project, :public, name: "Empty Public Project" + end end diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb index 209d77c7acf..f52551c9dc7 100644 --- a/features/steps/shared/user.rb +++ b/features/steps/shared/user.rb @@ -9,6 +9,11 @@ module SharedUser user_exists("Mary Jane", {username: "mary_jane"}) end + step "I delete my account" do + visit profile_account_path + click_link "Delete account" + end + protected def user_exists(name, options = {}) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 944df5314bd..c7a48898c56 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -2,6 +2,7 @@ require('spec_helper') describe ProjectsController do let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } let(:user) { create(:user) } let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } @@ -40,4 +41,17 @@ describe ProjectsController do end end end + + describe "POST #toggle_star" do + it "increases star count if user is signed in" do + sign_in(user) + post :toggle_star, id: public_project.to_param + expect(public_project.star_count).to eq(1) + end + + it "does nothing if user is not signed in" do + post :toggle_star, id: public_project.to_param + expect(public_project.star_count).to eq(0) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c3263ed0fe7..c2ebfbd9229 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -240,4 +240,22 @@ describe Project do it { project.open_branches.map(&:name).should include('bootstrap') } it { project.open_branches.map(&:name).should_not include('master') } end + + describe "#count_star" do + it "counts stars" do + user1 = create :user + user2 = create :user + project = create :project, :public + + expect(project.star_count).to eq(0) + user1.toggle_star(project) + expect(project.star_count).to eq(1) + user2.toggle_star(project) + expect(project.star_count).to eq(2) + user1.toggle_star(project) + expect(project.star_count).to eq(1) + user2.toggle_star(project) + expect(project.star_count).to eq(0) + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index a36b57a95de..b6ffb6ac24b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -355,4 +355,17 @@ describe User do expect(user.short_website_url).to eq 'test.com' end end + + describe "#toggle_star" do + it "toggles stars" do + user = create :user + project = create :project, :public + + expect(user.starred?(project)).to be_false + user.toggle_star(project) + expect(user.starred?(project)).to be_true + user.toggle_star(project) + expect(user.starred?(project)).to be_false + end + end end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 7713e9f17d7..88e4d0419cb 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -20,6 +20,6 @@ module LoginHelpers end def logout - click_link "Logout" rescue nil + page.find(:css, ".icon-signout").click rescue nil end end |