Commit 81d603e2 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents 78fe7270 e24da359
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 7.10.0 (unreleased) v 7.10.0 (unreleased)
- Include missing events and fix save functionality in admin service template settings form (Stan Hu)
- Fix "Import projects from" button to show the correct instructions (Stan Hu) - Fix "Import projects from" button to show the correct instructions (Stan Hu)
- Fix dots in Wiki slugs causing errors (Stan Hu) - Fix dots in Wiki slugs causing errors (Stan Hu)
- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu) - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu)
...@@ -27,6 +28,7 @@ v 7.10.0 (unreleased) ...@@ -27,6 +28,7 @@ v 7.10.0 (unreleased)
- Restrict permissions on backup files - Restrict permissions on backup files
- Improve oauth accounts UI in profile page - Improve oauth accounts UI in profile page
- Add ability to unlink connected accounts - Add ability to unlink connected accounts
- Replace commits calendar with faster contribution calendar that includes issues and merge requests
v 7.9.0 v 7.9.0
- Add HipChat integration documentation (Stan Hu) - Add HipChat integration documentation (Stan Hu)
......
...@@ -7,7 +7,7 @@ class @calendar ...@@ -7,7 +7,7 @@ class @calendar
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) -> constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap() cal = new CalHeatMap()
cal.init cal.init
itemName: ["commit"] itemName: ["contribution"]
data: timestamps data: timestamps
start: new Date(starting_year, starting_month) start: new Date(starting_year, starting_month)
domainLabelFormat: "%b" domainLabelFormat: "%b"
...@@ -27,7 +27,6 @@ class @calendar ...@@ -27,7 +27,6 @@ class @calendar
legendCellPadding: 3 legendCellPadding: 3
onClick: (date, count) -> onClick: (date, count) ->
formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() formated_date = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate()
$(".calendar_commit_activity").fadeOut 400
$.ajax $.ajax
url: calendar_activities_path url: calendar_activities_path
data: data:
...@@ -36,6 +35,4 @@ class @calendar ...@@ -36,6 +35,4 @@ class @calendar
dataType: "html" dataType: "html"
success: (data) -> success: (data) ->
$(".user-calendar-activities").html data $(".user-calendar-activities").html data
$(".calendar_commit_activity").find(".js-toggle-content").hide()
$(".calendar_commit_activity").fadeIn 400
.user-calendar-activities { .user-calendar-activities {
.calendar_commit_activity {
padding: 5px 0 0;
}
.calendar_onclick_hr { .calendar_onclick_hr {
padding: 0; padding: 0;
margin: 10px 0; margin: 10px 0;
} }
.calendar_commit_date {
color: #999;
}
.calendar_activity_summary {
font-size: 14px;
}
.str-truncated { .str-truncated {
max-width: 70%; max-width: 70%;
...@@ -31,14 +18,6 @@ ...@@ -31,14 +18,6 @@
background-color: #ddd; background-color: #ddd;
} }
} }
.commit-row-message {
color: #333;
&:hover {
color: #444;
text-decoration: underline;
}
}
} }
/** /**
* This overwrites the default values of the cal-heatmap gem * This overwrites the default values of the cal-heatmap gem
......
...@@ -46,7 +46,9 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -46,7 +46,9 @@ class Admin::ServicesController < Admin::ApplicationController
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type, :build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :description, :issues_url, :new_issue_url, :restrict_to_branch,
:send_from_committer_email, :disable_diffs :send_from_committer_email, :disable_diffs,
:push_events, :tag_push_events, :note_events, :issues_events,
:merge_requests_events
]) ])
end end
end end
...@@ -4,10 +4,7 @@ class UsersController < ApplicationController ...@@ -4,10 +4,7 @@ class UsersController < ApplicationController
layout :determine_layout layout :determine_layout
def show def show
@contributed_projects = Project. @contributed_projects = contributed_projects.joined(@user).
where(id: authorized_projects_ids & @user.contributed_projects_ids).
in_group_namespace.
includes(:namespace).
reject(&:forked?) reject(&:forked?)
@projects = @user.personal_projects. @projects = @user.personal_projects.
...@@ -31,9 +28,7 @@ class UsersController < ApplicationController ...@@ -31,9 +28,7 @@ class UsersController < ApplicationController
end end
def calendar def calendar
projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids) calendar = contributions_calendar
calendar = Gitlab::CommitsCalendar.new(projects, @user)
@timestamps = calendar.timestamps @timestamps = calendar.timestamps
@starting_year = calendar.starting_year @starting_year = calendar.starting_year
@starting_month = calendar.starting_month @starting_month = calendar.starting_month
...@@ -42,20 +37,13 @@ class UsersController < ApplicationController ...@@ -42,20 +37,13 @@ class UsersController < ApplicationController
end end
def calendar_activities def calendar_activities
projects = Project.where(id: authorized_projects_ids & @user.contributed_projects_ids) @calendar_date = Date.parse(params[:date]) rescue nil
@events = []
date = Date.parse(params[:date]) rescue nil if @calendar_date
if date @events = contributions_calendar.events_by_date(@calendar_date)
@calendar_activities = Gitlab::CommitsCalendar.get_commits_for_date(projects, @user, date)
else
@calendar_activities = {}
end end
# get the total number of unique commits
@commit_count = @calendar_activities.values.flatten.map(&:id).uniq.count
@calendar_date = date
render 'calendar_activities', layout: false render 'calendar_activities', layout: false
end end
...@@ -82,4 +70,15 @@ class UsersController < ApplicationController ...@@ -82,4 +70,15 @@ class UsersController < ApplicationController
@authorized_projects_ids ||= @authorized_projects_ids ||=
ProjectsFinder.new.execute(current_user).pluck(:id) ProjectsFinder.new.execute(current_user).pluck(:id)
end end
def contributed_projects
@contributed_projects = Project.
where(id: authorized_projects_ids & @user.contributed_projects_ids).
includes(:namespace)
end
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.
new(contributed_projects.reject(&:forked?), @user)
end
end end
...@@ -55,6 +55,12 @@ class Event < ActiveRecord::Base ...@@ -55,6 +55,12 @@ class Event < ActiveRecord::Base
order('id DESC').limit(100). order('id DESC').limit(100).
update_all(updated_at: Time.now) update_all(updated_at: Time.now)
end end
def contributions
where("action = ? OR (target_type in (?) AND action in (?))",
Event::PUSHED, ["MergeRequest", "Issue"],
[Event::CREATED, Event::CLOSED, Event::MERGED])
end
end end
def proper? def proper?
......
class ProjectContributions
attr_reader :project, :user
def initialize(project, user)
@project, @user = project, user
end
def commits_log
repository = project.repository
if !repository.exists? || repository.empty?
return {}
end
Rails.cache.fetch(cache_key) do
repository.commits_per_day_for_user(user)
end
end
def user_commits_on_date(date)
repository = @project.repository
if !repository.exists? || repository.empty?
return []
end
commits = repository.commits_by_user_on_date_log(@user, date)
end
def cache_key
"#{Date.today.to_s}-commits-log-#{project.id}-#{user.email}"
end
end
...@@ -149,41 +149,6 @@ class Repository ...@@ -149,41 +149,6 @@ class Repository
end end
end end
def timestamps_by_user_log(user)
author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')'
args = %W(git log -E --author=#{author_emails} --since=#{(Date.today - 1.year).to_s} --branches --pretty=format:%cd --date=short)
dates = Gitlab::Popen.popen(args, path_to_repo).first.split("\n")
if dates.present?
dates
else
[]
end
end
def commits_by_user_on_date_log(user, date)
# format the date string for git
start_date = date.strftime("%Y-%m-%d 00:00:00")
end_date = date.strftime("%Y-%m-%d 23:59:59")
author_emails = '(' + user.all_emails.map{ |e| Regexp.escape(e) }.join('|') + ')'
args = %W(git log -E --author=#{author_emails} --after=#{start_date.to_s} --until=#{end_date.to_s} --branches --pretty=format:%h)
commits = Gitlab::Popen.popen(args, path_to_repo).first.split("\n")
commits.map! do |commit_id|
commit(commit_id)
end
end
def commits_per_day_for_user(user)
timestamps_by_user_log(user).
group_by { |commit_date| commit_date }.
inject({}) do |hash, (timestamp_date, commits)|
hash[timestamp_date] = commits.count
hash
end
end
def lookup_cache def lookup_cache
@lookup_cache ||= {} @lookup_cache ||= {}
end end
......
...@@ -603,13 +603,10 @@ class User < ActiveRecord::Base ...@@ -603,13 +603,10 @@ class User < ActiveRecord::Base
end end
def contributed_projects_ids def contributed_projects_ids
Event.where(author_id: self). Event.contributions.where(author_id: self).
where("created_at > ?", Time.now - 1.year). where("created_at > ?", Time.now - 1.year).
where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)",
pushed: Event::PUSHED, created: Event::CREATED).
reorder(project_id: :desc). reorder(project_id: :desc).
select(:project_id). select(:project_id).
uniq uniq.map(&:project_id)
.map(&:project_id)
end end
end end
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
= preserve do = preserve do
= markdown @service.help = markdown @service.help
.form-group
= f.label :active, "Active", class: "control-label"
.col-sm-10
= f.check_box :active
- if @service.supported_events.length > 1 - if @service.supported_events.length > 1
.form-group .form-group
= f.label :url, "Trigger", class: 'control-label' = f.label :url, "Trigger", class: 'control-label'
...@@ -34,6 +39,14 @@ ...@@ -34,6 +39,14 @@
%strong Tag push events %strong Tag push events
%p.light %p.light
This url will be triggered when a new tag is pushed to the repository This url will be triggered when a new tag is pushed to the repository
- if @service.supported_events.include?("note")
%div
= f.check_box :note_events, class: 'pull-left'
.prepend-left-20
= f.label :note_events, class: 'list-label' do
%strong Comments
%p.light
This url will be triggered when someone adds a comment
- if @service.supported_events.include?("issue") - if @service.supported_events.include?("issue")
%div %div
= f.check_box :issues_events, class: 'pull-left' = f.check_box :issues_events, class: 'pull-left'
......
- if @contributed_projects.present? - if @contributed_projects.present?
.panel.panel-default .panel.panel-default.contributed-projects
.panel-heading Projects contributed to .panel-heading Projects contributed to
= render 'shared/projects_list', = render 'shared/projects_list',
projects: @contributed_projects.sort_by(&:star_count).reverse, projects: @contributed_projects.sort_by(&:star_count).reverse,
......
%h4 Commits calendar %h4
Contributions calendar
.pull-right
%small Issues, merge requests and push events
#cal-heatmap.calendar #cal-heatmap.calendar
:javascript :javascript
new calendar( new calendar(
......
.calendar_commit_activity %h4.prepend-top-20
%hr %span.light Contributions for
%h4 %strong #{@calendar_date.to_s(:short)}
Commit Activity
%strong %ul.bordered-list
- if @commit_count == 0 - @events.sort_by(&:created_at).each do |event|
no %li
- else %span.light
= @commit_count %i.fa.fa-clock-o
%span.calendar_commit_date = event.created_at.to_s(:time)
unique - if event.push?
= 'commit'.pluralize(@commit_count) #{event.action_name} #{event.ref_type} #{event.ref_name}
on - else
= @calendar_date.strftime("%b %d, %Y") rescue '' = event_action_name(event)
-unless @commit_count == 0 - if event.target
%hr %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
- @calendar_activities.each do |project, commits|
- next if commits.empty? at
%div.js-toggle-container
%strong %strong
= pluralize(commits.count, 'commit') - if event.project
in project = link_to_project event.project
= link_to project.name_with_namespace, project_path(project) - else
%a.text-expander.js-toggle-button &hellip; = event.project_name
%hr
%div.js-toggle-content
- commits.each do |commit|
%span.monospace
= commit.committed_date.strftime("%H:%M")
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to commit.message, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message str-truncated"
%br
%hr
...@@ -11,7 +11,7 @@ RUN apt-get update -q \ ...@@ -11,7 +11,7 @@ RUN apt-get update -q \
# If the Omnibus package version below is outdated please contribute a merge request to update it. # If the Omnibus package version below is outdated please contribute a merge request to update it.
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. # If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
RUN TMP_FILE=$(mktemp); \ RUN TMP_FILE=$(mktemp); \
wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.8.3-omnibus-1_amd64.deb \ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.0-omnibus.2-1_amd64.deb \
&& dpkg -i $TMP_FILE \ && dpkg -i $TMP_FILE \
&& rm -f $TMP_FILE && rm -f $TMP_FILE
......
...@@ -7,3 +7,10 @@ Feature: Admin Settings ...@@ -7,3 +7,10 @@ Feature: Admin Settings
Scenario: Change application settings Scenario: Change application settings
When I modify settings and save form When I modify settings and save form
Then I should see application settings saved Then I should see application settings saved
Scenario: Change Slack Service Template settings
When I click on "Service Templates"
And I click on "Slack" service
Then I check all events and submit form
And I should see service template settings saved
And I should see all checkboxes checked
...@@ -15,4 +15,33 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps ...@@ -15,4 +15,33 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
current_application_settings.home_page_url.should == 'https://about.gitlab.com/' current_application_settings.home_page_url.should == 'https://about.gitlab.com/'
page.should have_content 'Application settings saved successfully' page.should have_content 'Application settings saved successfully'
end end
step 'I click on "Service Templates"' do
click_link 'Service Templates'
end
step 'I click on "Slack" service' do
click_link 'Slack'
end
step 'I check all events and submit form' do
page.check('Active')
page.check('Push events')
page.check('Tag push events')
page.check('Comments')
page.check('Issues events')
page.check('Merge Request events')
fill_in 'Webhook', with: "http://localhost"
click_on 'Save'
end
step 'I should see service template settings saved' do
page.should have_content 'Application settings saved successfully'
end
step 'I should see all checkboxes checked' do
all('input[type=checkbox]').each do |checkbox|
checkbox.should be_checked
end
end
end end
...@@ -7,4 +7,37 @@ class Spinach::Features::User < Spinach::FeatureSteps ...@@ -7,4 +7,37 @@ class Spinach::Features::User < Spinach::FeatureSteps
step 'I should see user "John Doe" page' do step 'I should see user "John Doe" page' do
expect(title).to match(/^\s*John Doe/) expect(title).to match(/^\s*John Doe/)
end end
step '"John Doe" has contributions' do
user = User.find_by(name: 'John Doe')
project = contributed_project
# Issue controbution
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(project, user, issue_params).execute
# Push code contribution
push_params = {
project: project,
action: Event::PUSHED,
author_id: user.id,
data: { commit_count: 3 }
}
Event.create(push_params)
end
step 'I should see contributed projects' do
within '.contributed-projects' do
page.should have_content(@contributed_project.name)
end
end
step 'I should see contributions calendar' do
page.should have_css('.cal-heatmap-container')
end
def contributed_project
@contributed_project ||= create(:project, :public)
end
end end
...@@ -67,3 +67,12 @@ Feature: User ...@@ -67,3 +67,12 @@ Feature: User
And I should see project "Enterprise" And I should see project "Enterprise"
And I should not see project "Internal" And I should not see project "Internal"
And I should not see project "Community" And I should not see project "Community"
@javascript
Scenario: "John Doe" contribution profile
Given I sign in as a user
And "John Doe" has contributions
When I visit user "John Doe" page
Then I should see user "John Doe" page
And I should see contributed projects
And I should see contributions calendar
module Gitlab
class CommitsCalendar
attr_reader :timestamps
def initialize(projects, user)
@timestamps = {}
date_timestamps = []
projects.reject(&:forked?).each do |project|
date_timestamps << ProjectContributions.new(project, user).commits_log
end
# Sumarrize commits from all projects per days
date_timestamps = date_timestamps.inject do |collection, date|
collection.merge(date) { |k, old_v, new_v| old_v + new_v }
end
date_timestamps ||= []
date_timestamps.each do |date, commits|
timestamp = Date.parse(date).to_time.to_i.to_s rescue nil
@timestamps[timestamp] = commits if timestamp
end
end
def self.get_commits_for_date(projects, user, date)
user_commits = {}
projects.reject(&:forked?).each do |project|
user_commits[project] = ProjectContributions.new(project, user).user_commits_on_date(date)
end
user_commits
end
def starting_year
(Time.now - 1.year).strftime("%Y")
end
def starting_month
Date.today.strftime("%m").to_i
end
end
end
module Gitlab
class ContributionsCalendar
attr_reader :timestamps, :projects, :user
def initialize(projects, user)
@projects = projects
@user = user
end
def timestamps
return @timestamps if @timestamps.present?
@timestamps = {}
date_from = 1.year.ago
date_to = Date.today
events = Event.reorder(nil).contributions.where(author_id: user.id).
where("created_at > ?", date_from).where(project_id: projects).
group('date(created_at)').
select('date(created_at), count(id) as total_amount').
map(&:attributes)
dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a
dates.each do |date|
date_id = date.to_time.to_i.to_s
@timestamps[date_id] = 0
day_events = events.find { |day_events| day_events["date"] == date }
if day_events
@timestamps[date_id] = day_events["total_amount"]
end
end
@timestamps
end
def events_by_date(date)
events = Event.contributions.where(author_id: user.id).
where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day).
where(project_id: projects)
events.select do |event|
event.push? || event.issue? || event.merge_request?
end
end
def starting_year
(Time.now - 1.year).strftime("%Y")
end
def starting_month
Date.today.strftime("%m").to_i
end
end
end
...@@ -25,34 +25,21 @@ describe UsersController do ...@@ -25,34 +25,21 @@ describe UsersController do
end end
describe 'GET #calendar_activities' do describe 'GET #calendar_activities' do
include RepoHelpers let!(:project) { create(:project) }
let(:project) { create(:project) } let!(:user) { create(:user) }
let(:calendar_user) { create(:user, email: sample_commit.author_email) }
let(:commit1) { '0ed8c6c6752e8c6ea63e7b92a517bf5ac1209c80' }
let(:commit2) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
before do before do
allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id])
project.team << [user, :developer] project.team << [user, :developer]
end end
it 'assigns @commit_count' do
get :calendar_activities, username: calendar_user.username, date: '2014-07-31'
expect(assigns(:commit_count)).to eq(2)
end
it 'assigns @calendar_date' do it 'assigns @calendar_date' do
get :calendar_activities, username: calendar_user.username, date: '2014-07-31' get :calendar_activities, username: user.username, date: '2014-07-31'
expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31'))
end end
it 'assigns @calendar_activities' do
get :calendar_activities, username: calendar_user.username, date: '2014-07-31'
expect(assigns(:calendar_activities).values.flatten.map(&:id)).to eq([commit1, commit2])
end
it 'renders calendar_activities' do it 'renders calendar_activities' do
get :calendar_activities, username: calendar_user.username get :calendar_activities, username: user.username
expect(response).to render_template('calendar_activities') expect(response).to render_template('calendar_activities')
end end
end end
......
...@@ -13,47 +13,16 @@ describe Repository do ...@@ -13,47 +13,16 @@ describe Repository do
it { is_expected.not_to include('fix') } it { is_expected.not_to include('fix') }
end end
describe :last_commit_for_path do describe :tag_names_contains do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } subject { repository.tag_names_contains(sample_commit.id) }
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
end
context :timestamps_by_user_log do
before do
Date.stub(:today).and_return(Date.new(2015, 03, 01))
end
describe 'single e-mail for user' do
let(:user) { create(:user, email: sample_commit.author_email) }
subject { repository.timestamps_by_user_log(user) }
it { is_expected.to eq(['2014-08-06', '2014-07-31', '2014-07-31']) } it { is_expected.to include('v1.1.0') }
end it { is_expected.not_to include('v1.0.0') }
describe 'multiple emails for user' do
let(:email_alias) { create(:email, email: another_sample_commit.author_email) }
let(:user) { create(:user, email: sample_commit.author_email, emails: [email_alias]) }
subject { repository.timestamps_by_user_log(user) }
it { is_expected.to eq(['2015-01-10', '2014-08-06', '2014-07-31', '2014-07-31']) }
end
end end
context :commits_by_user_on_date_log do describe :last_commit_for_path do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
describe 'single e-mail for user' do
let(:user) { create(:user, email: sample_commit.author_email) }
let(:commit1) { '0ed8c6c6752e8c6ea63e7b92a517bf5ac1209c80' }
let(:commit2) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
subject { repository.commits_by_user_on_date_log(user,Date.new(2014, 07, 31)) }
it 'contains the exepected commits' do it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
expect(subject.flatten.map(&:id)).to eq([commit1, commit2])
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment