summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteven Thonus <steven@ln2.nl>2014-01-25 18:15:44 +0100
committerHannes Rosenögger <123haynes@gmail.com>2015-01-24 18:51:16 +0100
commit42bac7f9f27b0e8fb113e452fc2106882262172d (patch)
treebadf9efefbdf95e02cac54c48d1495cd062476e9
parentc8c05edcd500c0a755cb6962b51f97381663bd1a (diff)
downloadgitlab-ce-42bac7f9f27b0e8fb113e452fc2106882262172d.tar.gz
adding avatar to project settings page added avatar removal show project avatar on dashboard, projects page, project page added rspec and feature tests added project avatar from repository new default project icon added added copying af avatar to forking of project added generated icon fixed avatar fork hound fix style fix test fix
-rw-r--r--app/assets/images/no_project_icon.pngbin0 -> 3387 bytes
-rw-r--r--app/assets/javascripts/project.js.coffee10
-rw-r--r--app/assets/stylesheets/generic/avatar.scss13
-rw-r--r--app/assets/stylesheets/sections/dashboard.scss6
-rw-r--r--app/controllers/projects/avatars_controller.rb29
-rw-r--r--app/helpers/application_helper.rb25
-rw-r--r--app/models/project.rb25
-rw-r--r--app/services/projects/fork_service.rb3
-rw-r--r--app/views/dashboard/_project.html.haml2
-rw-r--r--app/views/dashboard/projects.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml1
-rw-r--r--app/views/projects/edit.html.haml28
-rw-r--r--config/routes.rb2
-rw-r--r--db/migrate/20140125162722_add_avatar_to_projects.rb5
-rw-r--r--db/schema.rb1
-rw-r--r--features/project/project.feature13
-rw-r--r--features/steps/project/project.rb47
-rw-r--r--spec/helpers/application_helper_spec.rb22
-rw-r--r--spec/models/project_spec.rb15
-rw-r--r--spec/routing/project_routing_spec.rb7
20 files changed, 252 insertions, 4 deletions
diff --git a/app/assets/images/no_project_icon.png b/app/assets/images/no_project_icon.png
new file mode 100644
index 00000000000..8e9529c67ec
--- /dev/null
+++ b/app/assets/images/no_project_icon.png
Binary files differ
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 5a9cc66c8f0..8588a9d27cd 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -18,3 +18,13 @@ class @Project
$.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide()
e.preventDefault()
+
+ # avatar
+ $('.js-choose-project-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-project-avatar-input").click()
+
+ $('.js-project-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss
index 80514615559..f04848ae6dc 100644
--- a/app/assets/stylesheets/generic/avatar.scss
+++ b/app/assets/stylesheets/generic/avatar.scss
@@ -23,3 +23,16 @@
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
+
+.identicon {
+ text-align: center;
+ vertical-align: top;
+
+ &.s16 { font-size: 12px; line-height: 1.33; }
+ &.s24 { font-size: 18px; line-height: 1.33; }
+ &.s26 { font-size: 20px; line-height: 1.33; }
+ &.s32 { font-size: 24px; line-height: 1.33; }
+ &.s60 { font-size: 45px; line-height: 1.33; }
+ &.s90 { font-size: 68px; line-height: 1.33; }
+ &.s160 { font-size: 120px; line-height: 1.33; }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/sections/dashboard.scss
index 824f136d300..3135056db58 100644
--- a/app/assets/stylesheets/sections/dashboard.scss
+++ b/app/assets/stylesheets/sections/dashboard.scss
@@ -75,6 +75,9 @@
}
}
}
+.project-avatar {
+ float: left;
+}
.project-description {
overflow: hidden;
@@ -92,6 +95,9 @@
}
}
+.dash-project-avatar {
+ float: left;
+}
.dash-project-access-icon {
float: left;
margin-right: 3px;
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
new file mode 100644
index 00000000000..a482b90880d
--- /dev/null
+++ b/app/controllers/projects/avatars_controller.rb
@@ -0,0 +1,29 @@
+class Projects::AvatarsController < Projects::ApplicationController
+ layout 'project'
+
+ before_filter :project
+
+ def show
+ @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git)
+ if @blob
+ headers['X-Content-Type-Options'] = 'nosniff'
+ send_data(
+ @blob.data,
+ type: @blob.mime_type,
+ disposition: 'inline',
+ filename: @blob.name
+ )
+ else
+ not_found!
+ end
+ end
+
+ def destroy
+ @project.remove_avatar!
+
+ @project.save
+ @project.reset_events_cache
+
+ redirect_to edit_project_path(@project)
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f65e04af205..772400d55ec 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -50,6 +50,31 @@ module ApplicationHelper
args.any? { |v| v.to_s.downcase == action_name }
end
+ def project_icon(project_id, options = {})
+ project = Project.find_with_namespace(project_id)
+ if project.avatar.present?
+ image_tag project.avatar.url, options
+ elsif options[:only_uploaded]
+ image_tag '/assets/no_project_icon.png', options
+ elsif project.avatar_in_git
+ image_tag project_avatar_path(project), options
+ else # generated icon
+ project_identicon(project, options)
+ end
+ end
+
+ def project_identicon(project, options = {})
+ options[:class] ||= ''
+ options[:class] << ' identicon'
+ bg_color = Digest::MD5.hexdigest(project.name)[0, 6]
+ brightness = bg_color[0, 2].hex + bg_color[2, 2].hex + bg_color[4, 2].hex
+ text_color = (brightness > 375) ? '#000' : '#fff'
+ content_tag(:div, class: options[:class],
+ style: "background-color: ##{ bg_color }; color: #{ text_color }") do
+ project.name[0, 1].upcase
+ end
+ end
+
def group_icon(group_path)
group = Group.find_by(path: group_path)
if group && group.avatar.present?
diff --git a/app/models/project.rb b/app/models/project.rb
index f102c477404..7160e704aa1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -26,6 +26,7 @@
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
+# avatar :string(255)
#
class Project < ActiveRecord::Base
@@ -119,6 +120,11 @@ class Project < ActiveRecord::Base
if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
+ validate :avatar_type,
+ if: ->(project) { project.avatar && project.avatar_changed? }
+ validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+
+ mount_uploader :avatar, AttachmentUploader
# Scopes
scope :without_user, ->(user) { where("projects.id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
@@ -338,6 +344,24 @@ class Project < ActiveRecord::Base
@ci_service ||= ci_services.select(&:activated?).first
end
+ def avatar_type
+ unless avatar.image?
+ errors.add :avatar, 'only images allowed'
+ end
+ end
+
+ def avatar_in_git
+ @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png')
+ @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg')
+ @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif')
+ @avatar_file
+ end
+
+ # For compatibility with old code
+ def code
+ path
+ end
+
def items_for(entity)
case entity
when 'issue' then
@@ -529,6 +553,7 @@ class Project < ActiveRecord::Base
# Since we do cache @event we need to reset cache in special cases:
# * when project was moved
# * when project was renamed
+ # * when the project avatar changes
# Events cache stored like events/23-20130109142513.
# The cache key includes updated_at timestamp.
# Thus it will automatically generate a new fragment
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 4930660055a..8bb0fcf9474 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -14,6 +14,9 @@ module Projects
project.name = @from_project.name
project.path = @from_project.path
project.creator = @current_user
+ if @from_project.avatar && @from_project.avatar.image?
+ project.avatar = @from_project.avatar
+ end
if namespace = @params[:namespace]
project.namespace = namespace
diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml
index 89ed5102754..7f19fb5a81c 100644
--- a/app/views/dashboard/_project.html.haml
+++ b/app/views/dashboard/_project.html.haml
@@ -1,4 +1,6 @@
= link_to project_path(project), class: dom_class(project) do
+ .dash-project-avatar
+ = project_icon(project.to_param, alt: '', class: 'avatar s24')
.dash-project-access-icon
= visibility_level_icon(project.visibility_level)
%span.str-truncated
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index 944441669e7..f60bcc72e1d 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -11,6 +11,8 @@
- @projects.each do |project|
%li.my-project-row
%h4.project-title
+ .project-avatar
+ = project_icon(project.to_param, alt: '', class: 'avatar s60')
.project-access-icon
= visibility_level_icon(project.visibility_level)
= link_to project_path(project), class: dom_class(project) do
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 30d063c7a36..05910c6038c 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -2,6 +2,7 @@
.project-home-panel{:class => ("empty-project" if empty_repo)}
.project-home-row
.project-home-desc
+ = project_icon(@project.to_param, alt: '', class: 'avatar s32')
- if @project.description.present?
= escaped_autolink(@project.description)
- if can?(current_user, :admin_project, @project)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f2bb56b5664..fc6499de3e2 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -7,7 +7,8 @@
%p.light Some settings, such as "Transfer Project", are hidden inside the danger area below.
%hr
.panel-body
- = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f|
+ = form_for @project, remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
+
%fieldset
.form-group.project_name_holder
= f.label :name, class: 'control-label' do
@@ -80,6 +81,31 @@
= f.check_box :snippets_enabled
%span.descr Share code pastes with others out of git repository
+ %fieldset.features
+ %legend
+ Project avatar:
+ .form-group
+ .col-sm-2
+ .col-sm-10
+ = project_icon(@project.to_param, alt: '', class: 'avatar s160', only_uploaded: true)
+ %p.light
+ - if @project.avatar_in_git
+ Project avatar in repository: #{ @project.avatar_in_git }
+ %p.light
+ - if @project.avatar?
+ You can change your project avatar here
+ - else
+ You can upload an project avatar here
+ %a.choose-btn.btn.btn-small.js-choose-project-avatar-button
+ %i.icon-paper-clip
+ %span Choose File ...
+ &nbsp;
+ %span.file_name.js-avatar-filename File name...
+ = f.file_field :avatar, class: "js-project-avatar-input hidden"
+ .light The maximum file size allowed is 100KB.
+ - if @project.avatar?
+ %hr
+ = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
.form-actions
= f.submit 'Save changes', class: "btn btn-save"
diff --git a/config/routes.rb b/config/routes.rb
index ef3c5aedfcb..32378665c94 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -353,6 +353,8 @@ Gitlab::Application.routes.draw do
delete :delete_attachment
end
end
+
+ resource :avatar, only: [:show, :destroy]
end
end
diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb
new file mode 100644
index 00000000000..9523ac722f2
--- /dev/null
+++ b/db/migrate/20140125162722_add_avatar_to_projects.rb
@@ -0,0 +1,5 @@
+class AddAvatarToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :avatar, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b453164d712..29466f048eb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -327,6 +327,7 @@ ActiveRecord::Schema.define(version: 20150116234544) do
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
+ t.string "avatar"
end
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
diff --git a/features/project/project.feature b/features/project/project.feature
index 7bb24e013a9..3e1fd54bee8 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -5,6 +5,19 @@ Feature: Project
And project "Shop" has push event
And I visit project "Shop" page
+ Scenario: I edit the project avatar
+ Given I visit edit project "Shop" page
+ When I change the project avatar
+ And I should see new project avatar
+ And I should see the "Remove avatar" button
+
+ Scenario: I remove the project avatar
+ Given I visit edit project "Shop" page
+ And I have an project avatar
+ When I remove my project avatar
+ Then I should see the default project avatar
+ And I should not see the "Remove avatar" button
+
@javascript
Scenario: I should see project activity
When I visit project "Shop" page
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 5e7312d90ff..455539d7471 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -17,12 +17,53 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'change project path settings' do
- fill_in "project_path", with: "new-path"
- click_button "Rename"
+ fill_in 'project_path', with: 'new-path'
+ click_button 'Rename'
end
step 'I should see project with new path settings' do
- project.path.should == "new-path"
+ project.path.should == 'new-path'
+ end
+
+ step 'I change the project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I should see new project avatar' do
+ @project.avatar.should be_instance_of AttachmentUploader
+ url = @project.avatar.url
+ url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png"
+ end
+
+ step 'I should see the "Remove avatar" button' do
+ page.should have_link('Remove avatar')
+ end
+
+ step 'I have an project avatar' do
+ attach_file(
+ :project_avatar,
+ File.join(Rails.root, 'public', 'gitlab_logo.png')
+ )
+ click_button 'Save changes'
+ @project.reload
+ end
+
+ step 'I remove my project avatar' do
+ click_link 'Remove avatar'
+ @project.reload
+ end
+
+ step 'I should see the default project avatar' do
+ @project.avatar?.should be_false
+ end
+
+ step 'I should not see the "Remove avatar" button' do
+ page.should_not have_link('Remove avatar')
end
step 'I should see project "Shop" version' do
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 1738f3443cf..ed50bf7c75d 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -56,6 +56,28 @@ describe ApplicationHelper do
end
end
+ describe 'project_icon' do
+ avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
+
+ it 'should return an url for the avatar' do
+ project = create(:project)
+ project.avatar = File.open(avatar_file_path)
+ project.save!
+ project_icon(project.to_param).to_s.should ==
+ "/uploads/project/avatar/#{ project.id }/gitlab_logo.png"
+ end
+
+ it "should give uploaded icon when present" do
+ project = create(:project)
+ project.save!
+
+ Project.any_instance.stub(:avatar_in_git).and_return(true)
+
+ project_icon(project.to_param).to_s.should match(
+ image_tag(project_avatar_path(project)))
+ end
+ end
+
describe "avatar_icon" do
avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 2a278176371..87d26f98b44 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -26,6 +26,7 @@
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
+# avatar :string(255)
#
require 'spec_helper'
@@ -310,4 +311,18 @@ describe Project do
expect(project.star_count).to eq(0)
end
end
+
+ describe :avatar_type do
+ let(:project) { create(:project) }
+
+ it 'should be true if avatar is image' do
+ project.update_attribute(:avatar, 'uploads/avatar.png')
+ project.avatar_type.should be_true
+ end
+
+ it 'should be false if avatar is html page' do
+ project.update_attribute(:avatar, 'uploads/avatar.html')
+ project.avatar_type.should == ['only images allowed']
+ end
+ end
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index f149f3f62a9..67705c6cb42 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -489,4 +489,11 @@ describe Projects::ForksController, "routing" do
it "to #create" do
post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq')
end
+
+# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy
+describe Projects::AvatarsController, 'routing' do
+ it 'to #destroy' do
+ delete('/gitlab/gitlabhq/avatar').should route_to(
+ 'projects/avatars#destroy', project_id: 'gitlab/gitlabhq')
+ end
end