improve activity feed

This commit is contained in:
Alexander Machehin 2015-05-05 17:25:29 +05:00
parent 86b929328b
commit d7727fe6a5
41 changed files with 558 additions and 368 deletions

View File

@ -1,201 +0,0 @@
RosaABF.controller('ActivityCtrl', ['$scope', '$http', '$timeout', '$q', '$filter', '$location',
function($scope, $http, $timeout, $q, $filter, $location) {
$scope.activity_tab = { title: 'activity_menu.activity_feed', filter: 'all',
all: {}, code: {}, tracker: {}, build: {}, wiki: {} };
$scope.tracker_tab = { title: 'activity_menu.tracker', content: [] ,
filter: { all: true, assigned: false, created: false, name: 'all',
all_count: 0, assigned_count: 0, created_count: 0, closed_count: 0 },
sort: { sort: 'updated', direction: 'desc', updated_class: 'fa-chevron-up' },
status: 'open', pagination: { page: 1, total_count: 0 } };
$scope.pull_requests_tab = { title: 'activity_menu.pull_requests', content: [] ,
filter: { all: true, assigned: false, created: false, name: 'all',
all_count: 0, assigned_count: 0, created_count: 0, closed_count: 0 },
sort: { sort: 'updated', direction: 'desc', updated_class: 'fa-chevron-up' },
status: 'open', pagination: { page: 1, total_count: 0 } };
$scope.init = function(active_tab) {
if(active_tab === 'activity') {
$scope.activity_tab.active = true;
}
else if(active_tab === 'issues') {
$scope.tracker_tab.active = true;
}
else if(active_tab === 'pull_requests') {
$scope.pull_requests_tab.active = true;
};
};
$scope.getContent = function(tab){
var cur_tab = $scope.$eval(tab+'_tab');
var tmp = $location.path();
if (tab === 'activity') {
$scope.activity_tab.active = true;
$scope.tracker_tab.active = false;
$scope.pull_requests_tab.active = false;
$scope.getActivityContent();
if($location.path() !== '/') {
$location.path('/').replace()
};
}
else if (tab === 'tracker') {
$scope.activity_tab.active = false;
$scope.tracker_tab.active = true;
$scope.pull_requests_tab.active = false;
$scope.getIssuesContent();
if($location.path() !== '/issues') {
$location.path('/issues').replace()
};
}
else if (tab === 'pull_requests') {
$scope.activity_tab.active = false;
$scope.tracker_tab.active = false;
$scope.pull_requests_tab.active = true;
$scope.getIssuesContent();
if($location.path() !== '/pull_requests') {
$location.path('/pull_requests').replace()
};
};
};
getIssuesTab = function(kind) {
if(kind === 'tracker') {
return $scope.tracker_tab;
}
else if(kind === 'pull_requests') {
return $scope.pull_requests_tab;
};
};
$scope.getTimeLinefaClass = function(content) {
var template = 'btn-warning fa-question';
switch(content.kind) {
case 'build_list_notification':
template = 'btn-success fa-gear';
break;
case 'new_comment_notification':
case 'new_comment_commit_notification':
template = 'btn-warning fa-comment';
break;
case 'git_new_push_notification':
template = 'bg-primary fa-sign-in';
break;
case 'new_issue_notification':
template = 'btn-warning fa-check-square-o';
break;
};
return template;
}
$scope.getCurActivity = function() {
return $scope.activity_tab[$scope.activity_tab.filter];
};
$scope.needShowTimeLabel = function(index) {
var feed = $scope.getCurActivity().feed;
if (feed === undefined) {
return false;
};
var cur_date = $filter('amDateFormat')(feed[index].date, 'll');
var prev_date = index == 0 || $filter('amDateFormat')(feed[index-1].date, 'll');
return cur_date !== prev_date;
};
$scope.getTemplate = function(content) {
return content.kind + '.html';
};
$scope.load_more = function() {
var cur_tab = $scope.getCurActivity();
var path = cur_tab.next_page_link;
if(!path) {
return;
};
$http.get(path).then(function(res){
cur_tab.feed.push.apply(cur_tab.feed, res.data.feed);
cur_tab.next_page_link = res.data.next_page_link;
});
};
$scope.changeActivityFilter = function(filter) {
$scope.activity_tab.filter = filter;
$scope.getActivityContent();
};
$scope.getActivityContent = function() {
var path = Routes.root_path({ filter: $scope.activity_tab.filter, format: 'json' });
$http.get(path).then(function(res) {
$scope.getCurActivity().feed = res.data.feed;
$scope.getCurActivity().next_page_link = res.data.next_page_link;
});
};
$scope.setIssuesFilter = function(kind, issues_filter) {
var filter = getIssuesTab(kind).filter;
filter.all = false;
filter.assigned = false;
filter.created = false;
filter[issues_filter] = true;
filter.name = issues_filter;
$scope.getIssuesContent();
};
$scope.getIssuesContent = function() {
if($scope.tracker_tab.active) {
var tab = $scope.tracker_tab;
var path = Routes.issues_path({ filter: tab.filter.name,
sort: tab.sort.sort,
direction: tab.sort.direction,
status: tab.status,
page: tab.pagination.page,
format: 'json' });
}
else if($scope.pull_requests_tab.active) {
var tab = $scope.pull_requests_tab;
var path = Routes.pull_requests_path({ filter: tab.filter.name,
sort: tab.sort.sort,
direction: tab.sort.direction,
status: tab.status,
page: tab.pagination.page,
format: 'json' });
};
$http.get(path).then(function(res) {
tab.content = res.data.content;
tab.filter.all_count = res.data.all_count;
tab.filter.assigned_count = res.data.assigned_count;
tab.filter.created_count = res.data.created_count;
tab.filter.closed_count = res.data.closed_count;
tab.filter.open_count = res.data.open_count;
tab.pagination.page = res.data.page;
tab.pagination.total_items = parseInt(res.data.issues_count, 10);
});
};
$scope.setIssuesSort = function(kind, issues_sort) {
var tab = getIssuesTab(kind);
if(tab.sort.direction === 'desc') {
tab.sort = { sort: issues_sort, direction: 'asc' };
var sort_class = 'fa-chevron-down';
}
else {
tab.sort = { sort: issues_sort, direction: 'desc' };
var sort_class = 'fa-chevron-up';
};
tab.sort[issues_sort+'_class'] = sort_class;
$scope.getIssuesContent();
};
$scope.setIssuesStatus = function(kind, issues_status) {
var tab = getIssuesTab(kind);
tab.status = issues_status;
tab.pagination.page = 1;
$scope.getIssuesContent();
};
$scope.selectPage = function(kind, page) {
$scope.getIssuesContent();
};
}]);

View File

@ -0,0 +1,244 @@
ActivityController = ($scope, $http, $timeout, $q, $filter, $location, ActivityFilter) ->
getIssuesTab = (kind)->
return vm.tracker_tab if kind is 'tracker'
return vm.pull_requests_tab if kind is 'pull_requests'
vm = this
vm.activity_tab =
filter: 'all'
all: {}
code: {}
tracker: {}
build: {}
wiki: {}
owner_filter: null
project_name_filter: null
owner_uname_filter_tmp: null
project_name_filter_tmp: null
vm.own_activity_tab = $.extend({}, vm.activity_tab)
vm.current_activity_tab = vm.activity_tab
vm.tracker_tab =
content: []
filter:
all: true
assigned: false
created: false
name: 'all'
all_count: 0
assigned_count: 0
created_count: 0
closed_count: 0
sort:
sort: 'updated'
direction: 'desc'
updated_class: 'fa-chevron-up'
status: 'open'
pagination:
page: 1
total_count: 0
vm.pull_requests_tab = $.extend({}, vm.tracker_tab)
vm.init = (active_tab)->
switch active_tab
when 'activity'
vm.activity_tab.active = true
vm.current_activity_tab = vm.activity_tab
when 'own_activity'
vm.own_activity_tab.active = true
vm.current_activity_tab = vm.own_activity_tab
when 'issues'
vm.tracker_tab.active = true
when active_tab is 'pull_requests'
vm.pull_requests_tab.active = true
true
vm.getContent = (tab)->
switch tab
when 'activity'
vm.activity_tab.active = true
vm.own_activity_tab.active = false
vm.tracker_tab.active = false
vm.pull_requests_tab.active = false
vm.current_activity_tab = vm.activity_tab
vm.getActivityContent()
if $location.path() isnt '/'
$location.path('/').replace()
when 'own_activity'
vm.activity_tab.active = false
vm.own_activity_tab.active = true
vm.tracker_tab.active = false
vm.pull_requests_tab.active = false
vm.current_activity_tab = vm.own_activity_tab
vm.getActivityContent()
if $location.path() isnt '/own_activity'
$location.path('/own_activity').replace()
when 'tracker'
vm.activity_tab.active = false
vm.own_activity_tab.active = false
vm.tracker_tab.active = true
vm.pull_requests_tab.active = false
vm.getIssuesContent()
if $location.path() isnt '/issues'
$location.path('/issues').replace()
when 'pull_requests'
vm.activity_tab.active = false
vm.own_activity_tab.active = false
vm.tracker_tab.active = false
vm.pull_requests_tab.active = true
vm.getIssuesContent()
if $location.path() isnt '/pull_requests'
$location.path('/pull_requests').replace()
vm.getTimeLinefaClass = (content)->
template = switch content.kind
when 'build_list_notification' then 'btn-success fa-gear'
when 'new_comment_notification', 'new_comment_commit_notification' then 'btn-warning fa-comment'
when 'git_new_push_notification' then 'bg-primary fa-sign-in'
when 'new_issue_notification' then 'btn-warning fa-check-square-o'
else 'btn-warning fa-question'
template
vm.getCurActivity = ()->
vm.current_activity_tab[vm.current_activity_tab.filter]
vm.needShowTimeLabel = (index)->
feed = vm.getCurActivity().feed
return false unless feed
cur_date = $filter('amDateFormat')(feed[index].date, 'll')
prev_date = index is 0 or $filter('amDateFormat')(feed[index-1].date, 'll')
return cur_date isnt prev_date
vm.getTemplate = (content)->
content.kind + '.html'
vm.load_more = ()->
cur_tab = vm.getCurActivity()
path = cur_tab.next_page_link
return unless path
$http.get(path).then (res)->
cur_tab.feed.push.apply(cur_tab.feed, res.data.feed)
cur_tab.next_page_link = res.data.next_page_link
vm.changeActivityFilter = (filter)->
return if vm.current_activity_tab.filter is filter
vm.current_activity_tab.filter = filter
vm.getActivityContent()
vm.getActivityContent = ()->
options =
filter: vm.current_activity_tab.filter
owner_filter: vm.current_activity_tab.owner_filter
project_name_filter: vm.current_activity_tab.project_name_filter
format: 'json'
if vm.activity_tab.active
path = Routes.root_path(options)
else
path = Routes.own_activity_path(options)
$http.get(path).then (res)->
vm.getCurActivity().feed = res.data.feed
vm.getCurActivity().next_page_link = res.data.next_page_link
vm.setIssuesFilter = (kind, issues_filter)->
filter = getIssuesTab(kind).filter
filter.all = false
filter.assigned = false
filter.created = false
filter[issues_filter] = true
filter.name = issues_filter
vm.getIssuesContent()
vm.getIssuesContent = ()->
if vm.tracker_tab.active
tab = vm.tracker_tab
path = Routes.issues_path(
filter: tab.filter.name
sort: tab.sort.sort
direction: tab.sort.direction
status: tab.status
page: tab.pagination.page
format: 'json')
else if vm.pull_requests_tab.active
tab = vm.pull_requests_tab
path = Routes.pull_requests_path(
filter: tab.filter.name
sort: tab.sort.sort
direction: tab.sort.direction
status: tab.status
page: tab.pagination.page
format: 'json')
$http.get(path).then (res)->
tab.content = res.data.content
tab.filter.all_count = res.data.all_count
tab.filter.assigned_count = res.data.assigned_count
tab.filter.created_count = res.data.created_count
tab.filter.closed_count = res.data.closed_count
tab.filter.open_count = res.data.open_count
tab.pagination.page = res.data.page
tab.pagination.total_items = parseInt(res.data.issues_count, 10)
vm.setIssuesSort = (kind, issues_sort)->
tab = getIssuesTab(kind)
if tab.sort.direction is 'desc'
tab.sort = { sort: issues_sort, direction: 'asc' }
sort_class = 'fa-chevron-down'
else
tab.sort = { sort: issues_sort, direction: 'desc' }
sort_class = 'fa-chevron-up'
tab.sort[issues_sort+'_class'] = sort_class
vm.getIssuesContent()
vm.setIssuesStatus = (kind, issues_status)->
tab = getIssuesTab(kind)
tab.status = issues_status
tab.pagination.page = 1
vm.getIssuesContent()
vm.selectPage = (kind, page)->
vm.getIssuesContent()
vm.getOwnersList = (value)->
return [] if value.length < 1
ActivityFilter.get_owners(value)
vm.selectOwnerFilter = (item, model, label)->
return if vm.current_activity_tab.owner_filter is item.uname
vm.current_activity_tab.owner_filter = item.uname
vm.current_activity_tab.project_name_filter = null
vm.current_activity_tab.project_name_filter_tmp = null
vm.getActivityContent()
true
vm.getProjectNamesList = (value)->
return [] if value.length < 1
ActivityFilter.get_project_names(vm.current_activity_tab.owner_filter, value)
vm.selectProjectNameFilter = (item, model, label)->
return if vm.current_activity_tab.project_name_filter is item.name
vm.current_activity_tab.project_name_filter = item.name
vm.getActivityContent()
true
angular
.module("RosaABF")
.controller "ActivityController", ActivityController
ActivityController.$inject = ['$scope', '$http', '$timeout', '$q', '$filter', '$location', 'ActivityFilter']

View File

@ -19,7 +19,6 @@ var _locales = {
<%= BuildList::STATUSES.map{|s| "'build_list.status.#{s}': '#{BuildList.human_status(s)}'"}.join(',') %>,
//
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>,
'tracker.filter.all': '<%= I18n.t('layout.issues.all') %>',
'tracker.filter.assigned': '<%= I18n.t('layout.issues.assigned') %>',
'tracker.filter.created': '<%= I18n.t('layout.issues.created') %>',
@ -66,7 +65,6 @@ var _locales = {
<%= BuildList::STATUSES.map{|s| "'build_list.status.#{s}': '#{BuildList.human_status(s)}'"}.join(',') %>,
//
<%= I18n.t('activity_menu').map{ |k,v| "'activity_menu.#{k}': '#{v}'" }.join(',') %>,
'tracker.filter.all': '<%= I18n.t('layout.issues.all') %>',
'tracker.filter.assigned': '<%= I18n.t('layout.issues.assigned') %>',
'tracker.filter.created': '<%= I18n.t('layout.issues.created') %>',

View File

@ -0,0 +1,27 @@
ActivityFilterService = ($http) ->
get_owners: (val) ->
path = Routes.get_owners_list_path(
{
term: val
}
)
$http.get(path).then (response) ->
response.data
get_project_names: (owner, val) ->
path = Routes.get_project_names_list_path(
{
owner_uname: owner
term: val
}
)
$http.get(path).then (response) ->
response.data
angular
.module("RosaABF")
.factory "ActivityFilter", ActivityFilterService
ActivityFilterService.$inject = ['$http']

View File

@ -38,7 +38,7 @@ $mini-avatar-height: 30px
.panel-title a,
i.fa-question-circle[data-toggle='modal'],
span.fa.fa-times,
.navbar a
.navbar a, ul.dropdown-menu a
cursor: pointer
footer

View File

@ -1,5 +1,5 @@
class HomeController < ApplicationController
before_action :authenticate_user!, only: [:activity, :issues, :pull_requests]
before_action :authenticate_user!, except: [:root]
skip_after_action :verify_authorized
def root
@ -8,21 +8,34 @@ class HomeController < ApplicationController
end
end
def activity
def activity(is_my_activity = false)
@filter = t('feed_menu').has_key?(params[:filter].try(:to_sym)) ? params[:filter].to_sym : :all
@activity_feeds = current_user.activity_feeds
.by_project_name(params[:project_name_filter])
.by_owner_uname(params[:owner_filter])
@activity_feeds = @activity_feeds.where(kind: "ActivityFeed::#{@filter.upcase}".constantize) unless @filter == :all
@activity_feeds = @activity_feeds.where(user_id: current_user) if @own_filter == :created
@activity_feeds = @activity_feeds.where.not(user_id: current_user) if @own_filter == :not_created
@activity_feeds = if is_my_activity
@activity_feeds.where(creator_id: current_user)
else
@activity_feeds.where.not(creator_id: current_user)
end
@activity_feeds = @activity_feeds.paginate page: current_page
respond_to do |format|
format.html { render 'activity' }
format.json {}
format.json { render 'activity' }
format.atom
end
end
def own_activity
activity(true)
end
def issues
@created_issues = current_user.issues
@assigned_issues = Issue.where(assignee_id: current_user.id)
@ -69,4 +82,31 @@ class HomeController < ApplicationController
def pull_requests
issues
end
def get_owners_list
if params[:term].present?
users = User.opened.search(params[:term]).pluck(:uname).first(5)
groups = Group.opened.search(params[:term]).pluck(:uname).first(5)
@owners = users | groups
end
respond_to do |format|
format.json {}
end
end
def get_project_names_list
if params[:term].present?
@projects = ProjectPolicy::Scope.new(current_user, Project).membered
@projects = @projects.where(owner_uname: params[:owner_uname]) if params[:owner_uname].present?
@projects = @projects.by_name("%#{params[:term]}%")
.distinct
.pluck(:name)
.first(10)
end
respond_to do |format|
format.json {}
end
end
end

View File

@ -11,7 +11,7 @@ module ActivityFeedsHelper
end
def get_user_from_activity_item(item)
email = item.data[:user_email]
email = item.data[:creator_email]
User.where(email: email).first || User.new(email: email) if email.present?
end

View File

@ -6,16 +6,19 @@ class ActivityFeed < ActiveRecord::Base
WIKI = %w(wiki_new_commit_notification)
belongs_to :user
serialize :data
belongs_to :creator, class_name: 'User'
serialize :data
attr_accessible :user, :kind, :data
default_scope { order created_at: :desc }
scope :outdated, -> { offset(100) }
scope :outdated, -> { offset(200) }
scope :by_project_name, ->(name) { where(project_name: name) if name.present? }
scope :by_owner_uname, ->(owner) { where(project_owner: owner) if owner.present? }
self.per_page = 10
self.per_page = 20
def partial
'home/partials/' + self.kind
"home/partials/#{self.kind}"
end
end

View File

@ -199,7 +199,7 @@ class Comment < ActiveRecord::Base
if issue_comment?
commentable.subscribes.create(user: user) if !commentable.subscribes.exists?(user_id: user.id)
elsif commit_comment?
recipients = project.admins
recipients = project.all_members
recipients << user << User.find_by(email: commentable.try(:committer).try(:email)) # commentor and committer
recipients.compact.uniq.each do |user|
options = {project_id: project.id, subscribeable_id: commentable_id, subscribeable_type: commentable.class.name, user_id: user.id}

View File

@ -20,20 +20,20 @@ module Feed::BuildList
)
updater = publisher || user
(project.all_members | [publisher].compact).each do |recipient|
(project.all_members | [publisher]).compact.each do |recipient|
ActivityFeed.create(
user: recipient,
kind: 'build_list_notification',
user: recipient,
kind: 'build_list_notification',
project_owner: project.owner_uname,
project_name: project.name,
creator_id: updater.id,
data: {
build_list_id: id,
status: status,
updated_at: updated_at,
project_id: project_id,
project_name: project.name,
project_owner: project.owner.uname,
user_name: updater.name,
user_email: updater.email,
user_id: updater.id
creator_name: updater.name,
creator_email: updater.email
}
)
end

View File

@ -18,19 +18,19 @@ module Feed::Comment
UserMailer.new_comment_notification(self, subscribe.user_id).deliver
ActivityFeed.create(
{
user_id: subscribe.user_id,
kind: 'new_comment_notification',
data: {
user_name: user.name,
user_email: user.email,
user_id: user_id,
user_id: subscribe.user_id,
kind: 'new_comment_notification',
project_owner: project.owner_uname,
project_name: project.name,
creator_id: user_id,
data: {
creator_name: user.name,
creator_email: user.email,
comment_body: body.truncate(100, omission: '…'),
issue_title: commentable.title,
issue_serial_id: commentable.serial_id,
project_id: commentable.project.id,
comment_id: id,
project_name: project.name,
project_owner: project.owner.uname
comment_id: id
}
}, without_protection: true
)
@ -47,19 +47,20 @@ module Feed::Comment
end
ActivityFeed.create(
{
user_id: subscribe.user_id,
kind: 'new_comment_commit_notification',
user_id: subscribe.user_id,
kind: 'new_comment_commit_notification',
project_owner: project.owner_uname,
project_name: project.name,
creator_id: user_id,
data: {
user_name: user.name,
user_email: user.email,
user_id: user_id,
creator_name: user.name,
creator_email: user.email,
comment_body: body.truncate(100, omission: '…'),
commit_message: commentable.message.truncate(70, omission: '…'),
commit_id: commentable.id,
project_id: project.id,
comment_id: id,
project_name: project.name,
project_owner: project.owner.uname
comment_id: id
}
}, without_protection: true
)

View File

@ -13,8 +13,7 @@ module Feed::Git
if change_type == 'delete'
kind = 'git_delete_branch_notification'
options = {project_id: record.project.id, project_name: record.project.name, branch_name: branch_name,
change_type: change_type, project_owner: record.project.owner.uname}
options = {project_id: record.project.id, branch_name: branch_name, change_type: change_type}
else
if record.message # online update
last_commits, commits = [[record.newrev, record.message.truncate(70, omission: '…')]], []
@ -26,8 +25,8 @@ module Feed::Git
end
kind = 'git_new_push_notification'
options = {project_id: record.project.id, project_name: record.project.name, last_commits: last_commits,
branch_name: branch_name, change_type: change_type, project_owner: record.project.owner.uname}
options = {project_id: record.project.id, last_commits: last_commits,
branch_name: branch_name, change_type: change_type}
if commits.count > 3
commits = commits[0...-3]
options.merge!({other_commits_count: commits.count, other_commits: "#{commits[0].sha[0..9]}...#{commits[-1].sha[0..9]}"})
@ -44,13 +43,16 @@ module Feed::Git
Comment.create_link_on_issues_from_item(record, all_commits)
end
end
options.merge!({user_id: record.user.id, user_name: record.user.name, user_email: record.user.email}) if record.user
options.merge!({creator_name: record.user.name, creator_email: record.user.email}) if record.user
record.project.admins.each do |recipient|
record.project.all_members.each do |recipient|
ActivityFeed.create!(
user: recipient,
kind: kind,
data: options
user: recipient,
kind: kind,
project_owner: record.project.owner_uname,
project_name: record.project.name,
creator_id: record.user.id,
data: options
)
next if record.user && record.user.id == recipient.id
if recipient.notifier.can_notify && recipient.notifier.update_code
@ -62,12 +64,15 @@ module Feed::Git
actor = User.find_by! uname: record[:actor_name]
project = Project.find record[:project_id]
project.admins.each do |recipient|
project.all_members.each do |recipient|
ActivityFeed.create!(
user: recipient,
kind: 'wiki_new_commit_notification',
data: {user_id: actor.id, user_name: actor.name, user_email: actor.email, project_id: project.id,
project_name: project.name, commit_sha: record[:commit_sha], project_owner: project.owner.uname}
project_owner: project.owner_uname,
project_name: project.name,
creator_id: actor.id,
data: {creator_name: actor.name, creator_email: actor.email,
project_id: project.id, commit_sha: record[:commit_sha]}
)
end
end

View File

@ -20,17 +20,17 @@ module Feed::Issue
UserMailer.new_issue_notification(id, recipient.id).deliver
end
ActivityFeed.create(
user: recipient,
kind: 'new_issue_notification',
user: recipient,
kind: 'new_issue_notification',
project_owner: project.owner_uname,
project_name: project.name,
creator_id: user_id,
data: {
user_name: user.name,
user_email: user.email,
user_id: user_id,
creator_name: user.name,
creator_email: user.email,
issue_serial_id: serial_id,
issue_title: title,
project_id: project.id,
project_name: project.name,
project_owner: project.owner.uname
project_id: project.id
}
)
end
@ -47,14 +47,14 @@ module Feed::Issue
ActivityFeed.create(
user: assignee,
kind: 'issue_assign_notification',
project_owner: project.owner_uname,
project_name: project.name,
data: {
user_name: assignee.name,
user_email: assignee.email,
issue_serial_id: serial_id,
issue_title: title,
project_id: project.id,
project_name: project.name,
project_owner: project.owner.uname
project_id: project.id
}
)
end

View File

@ -13,8 +13,9 @@ module Project::Finders
q = q.to_s.strip
by_name("%#{q}%").search_order if q.present?
}
scope :by_name, ->(name) { where('projects.name ILIKE ?', name) if name.present? }
scope :by_owner, ->(name) { where('projects.owner_uname ILIKE ?', "%#{name}%") if name.present? }
scope :by_name, ->(name) { where('projects.name ILIKE ?', name) if name.present? }
scope :by_owner, ->(name) { where('projects.owner_uname ILIKE ?', "%#{name}%") if name.present? }
scope :by_owner_and_name, ->(*params) {
term = params.map(&:strip).join('/').downcase
where("lower(concat(owner_uname, '/', name)) ILIKE ?", "%#{term}%") if term.present?

View File

@ -99,7 +99,7 @@ class Issue < ActiveRecord::Base
end
def collect_recipients
recipients = self.project.admins
recipients = self.project.all_members
recipients = recipients | [self.assignee] if self.assignee
recipients
end

View File

@ -1,4 +1,6 @@
tab heading = "{{activity_tab.title | i18n}}" active = "activity_tab.active" select = "getContent('activity')"
tab[ heading= t('activity_menu.activity_feed')
active= "actCtrl.activity_tab.active"
select = "actCtrl.getContent('activity')" ]
.row
.col-md-3.offset10== render 'sidebar'
.col-md-9.offset10
@ -6,22 +8,4 @@ tab heading = "{{activity_tab.title | i18n}}" active = "activity_tab.active" sel
= t 'layout.activity_feed.header'
= link_to image_tag('rss.ico', width: '15px', height: '15px', class: 'atom_icon'),
atom_activity_feeds_path(format: 'atom', token: current_user.authentication_token)
tabset
- (collection = t 'feed_menu').each do |base, title|
tab heading = title active = (@filter == base) select = "changeActivityFilter('#{base}')"
/ The time line
.row.offset10
.col-md-12.col-sm-12
ul.timeline
/ timeline time label
li.time-label ng-repeat-start = 'item in getCurActivity().feed'
span ng-show = "needShowTimeLabel($index)"
| {{item.date | amDateFormat:'ll'}}
/ timeline item
li ng-include = "getTemplate(item)"
.hide ng-repeat-end = true
li
i.img-circle.bg-primary.fa.fa-clock-o
hr
btn.center-block.btn.btn-primary ng-show = 'getCurActivity().next_page_link' ng-click = "load_more()"
= t('layout.activity_feed.load_messages')
== render 'activity_tabsets'

View File

@ -0,0 +1,20 @@
tabset
- (collection = t 'feed_menu').each do |base, title|
tab heading= title active= (@filter == base) select= "actCtrl.changeActivityFilter('#{base}')"
/ The time line
.row.offset10
.col-md-12.col-sm-12
ul.timeline
/ timeline time label
li.time-label ng-repeat-start= 'item in actCtrl.getCurActivity().feed'
span ng-show= "actCtrl.needShowTimeLabel($index)"
| {{item.date | amDateFormat:'ll'}}
/ timeline item
li ng-include= "actCtrl.getTemplate(item)"
.hide ng-repeat-end= true
li
i.img-circle.bg-primary.fa.fa-clock-o
hr
btn.center-block.btn.btn-primary[ ng-show= 'actCtrl.getCurActivity().next_page_link'
ng-click= "actCtrl.load_more()" ]
= t('layout.activity_feed.load_messages')

View File

@ -1,15 +1,17 @@
hr.offset10
h3=t('layout.relations.filters')
input.form-control[ name = 'search'
size = 30
type = 'text'
ng-model = 'search'
placeholder = t('layout.find_project')
ng-change = 'filterProjects()' ]
ul.nav.nav-pills.nav-stacked
- options_for_filters(@projects, @groups, @owners).each do |options|
li{ 'ng-class' => "{active: #{options[:class_name]}_filter_#{options[:id]}_class}" }
a{ href: '#', 'ng-click' => "change_#{options[:class_name]}_filter(#{options[:id]})" }
= options[:uname]
hr
h4=t('layout.relations.filters')
.form-group
= text_field_tag :owner_uname, nil,
class: 'form-control',
placeholder: t('search.placeholders.owner_filter'),
'ng-model' => 'actCtrl.current_activity_tab.owner_uname_filter_tmp',
'typeahead' => 'owner.uname for owner in actCtrl.getOwnersList($viewValue)',
'typeahead-on-select' => 'actCtrl.selectOwnerFilter($item, $model, $label)'
.form-group
= text_field_tag :project_name, nil,
class: 'form-control',
placeholder: t('search.placeholders.name_filter'),
'ng-model' => 'actCtrl.current_activity_tab.project_name_filter_tmp',
'ng-value' => 'actCtrl.current_activity_tab.project_name_filter_tmp',
'typeahead' => 'project.name for project in actCtrl.getProjectNamesList($viewValue)',
'typeahead-on-select' => 'actCtrl.selectProjectNameFilter($item, $model, $label)'

View File

@ -0,0 +1,9 @@
tab[ heading= t('activity_menu.own_activity')
active= "actCtrl.own_activity_tab.active"
select = "actCtrl.getContent('own_activity')" ]
.row
.col-md-3.offset10== render 'sidebar'
.col-md-9.offset10
h3
= t 'layout.activity_feed.own_header'
== render 'activity_tabsets'

View File

@ -1,6 +1,7 @@
p
= link_to t('layout.activity_feed.new_project'), new_project_path,
class: 'btn btn-primary btn-small', role: 'button'
== render 'filters'
hr
h5= t('layout.activity_feed.my_last_projects')

View File

@ -1,70 +1,70 @@
- %w(tracker pull_requests).each do |kind|
tab[ heading = "{{#{kind}_tab.title | i18n}}"
active = "#{kind}_tab.active"
select = "getContent('#{kind}')" ]
tab[ heading= t("activity_menu.#{kind}")
active= "actCtrl.#{kind}_tab.active"
select= "actCtrl.getContent('#{kind}')" ]
.row
.col-md-3.offset10
ul.nav.nav-pills.nav-stacked
- %w(all assigned created).each do |kind_filter|
li ng-class = "{ active: #{kind}_tab.filter.#{kind_filter} }"
a ng-click = "setIssuesFilter('#{kind}', '#{kind_filter}')"
span.badge.pull-right= "{{ #{kind}_tab.filter.#{kind_filter}_count }}"
li ng-class= "{ active: actCtrl.#{kind}_tab.filter.#{kind_filter} }"
a ng-click= "actCtrl.setIssuesFilter('#{kind}', '#{kind_filter}')"
span.badge.pull-right= "{{ actCtrl.#{kind}_tab.filter.#{kind_filter}_count }}"
= "{{'#{kind}.filter.#{kind_filter}' | i18n}}"
.col-md-9.offset10
tabset.boffset10
- %w(open closed).each do |status|
- count_issues = "({{#{kind}_tab.filter.#{status}_count}})"
tab[ heading = "#{t "layout.issues.statuses.#{status}"} #{count_issues}"
active = "#{kind}_tab.status_#{status}"
ng-click = "setIssuesStatus('#{kind}', '#{status}')" ]
- count_issues = "({{actCtrl.#{kind}_tab.filter.#{status}_count}})"
tab[ heading= "#{t "layout.issues.statuses.#{status}"} #{count_issues}"
active= "actCtrl.#{kind}_tab.status_#{status}"
ng-click= "actCtrl.setIssuesStatus('#{kind}', '#{status}')" ]
.pull-right.boffset10
button.btn.btn-default.roffset5[ type = 'button'
ng-click = "setIssuesSort('#{kind}', 'updated')" ]
span.fa ng-class = "#{kind}_tab.sort.updated_class"
button.btn.btn-default.roffset5[ type= 'button'
ng-click= "actCtrl.setIssuesSort('#{kind}', 'updated')" ]
span.fa ng-class= "actCtrl.#{kind}_tab.sort.updated_class"
=> t('layout.issues.sort.updated')
button.btn.btn-default[ type = 'button'
ng-click = "setIssuesSort('#{kind}', 'submitted')" ]
span.fa ng-class = "#{kind}_tab.sort.submitted_class"
button.btn.btn-default[ type= 'button'
ng-click= "actCtrl.setIssuesSort('#{kind}', 'submitted')" ]
span.fa ng-class= "actCtrl.#{kind}_tab.sort.submitted_class"
=> t('layout.issues.sort.submitted')
table.table
tr ng-repeat = "issue in #{kind}_tab.content"
tr ng-repeat= "issue in actCtrl.#{kind}_tab.content"
td
a ng-href = "{{issue.issue_url}}"
a ng-href= "{{issue.issue_url}}"
span.text-info
= '{{issue.project_name}} '
| {{issue.title}}
span.label.small.loffset5[ ng-repeat = "label in issue.labels"
ng-style = "{background: label.color}" ]
span.label.small.loffset5[ ng-repeat= "label in issue.labels"
ng-style= "{background: label.color}" ]
| {{label.name}}
.small
= t 'layout.issues.created_by'
a>[ ng-href = "{{issue.user.path}}" ] {{issue.user.uname}}
span.text-muted[ ng-show = '#{kind}_tab.sort.sort == "submitted"'
title = "{{issue.created_at_utc}}" ]
a>[ ng-href= "{{issue.user.path}}" ] {{issue.user.uname}}
span.text-muted[ ng-show= 'actCtrl.#{kind}_tab.sort.sort == "submitted"'
title= "{{issue.created_at_utc}}" ]
| {{issue.created_at | amDateFormat:'YYYY-MM-DD HH:mm'}} (
span am-time-ago = 'issue.created_at'
span am-time-ago= 'issue.created_at'
| )
span> class = 'text-muted' ng-show = '#{kind}_tab.sort.sort == "updated"'
span> class= 'text-muted' ng-show= 'actCtrl.#{kind}_tab.sort.sort == "updated"'
= t 'layout.issues.updated_at'
span.text-muted[ ng-show = '#{kind}_tab.sort.sort == "updated"'
title = "{{issue.updated_at_utc}}" ]
span.text-muted[ ng-show= 'actCtrl.#{kind}_tab.sort.sort == "updated"'
title= "{{issue.updated_at_utc}}" ]
| {{issue.updated_at | amDateFormat:'YYYY-MM-DD HH:mm'}} (
span am-time-ago = 'issue.updated_at'
span am-time-ago= 'issue.updated_at'
| )
td
a ng-href = "{{issue.issue_url + '#comments'}}"
a ng-href= "{{issue.issue_url + '#comments'}}"
span.fa.fa-comments.text-primary
= " {{issue.comments_count}}"
td
a>[ ng-href = '{{issue.assignee.link}}'
title = "#{t('layout.issues.assigned_to')} {{issue.assignee.fullname}}" ]
img ng-src = '{{issue.assignee.image}}'
a>[ ng-href= '{{issue.assignee.link}}'
title= "#{t('layout.issues.assigned_to')} {{issue.assignee.fullname}}" ]
img ng-src= '{{issue.assignee.image}}'
span.text-muted.roffset5
| {{'#' + issue.serial_id}}
= angularjs_paginate( total_items: "#{kind}_tab.pagination.total_items",
page: "#{kind}_tab.pagination.page",
= angularjs_paginate( total_items: "actCtrl.#{kind}_tab.pagination.total_items",
page: "actCtrl.#{kind}_tab.pagination.page",
per_page: Issue.per_page,
select_page: "selectPage('#{kind}', page)" )
select_page: "actCtrl.selectPage('#{kind}', page)" )

View File

@ -4,14 +4,17 @@ atom_feed do |feed|
@activity_feeds.each do |activity_feed|
feed.entry(activity_feed, url: root_url(anchor: "feed#{activity_feed.id}")) do |entry|
feed_content = raw(render(inline: true, partial: activity_feed.partial, locals: activity_feed.data.merge(activity_feed: activity_feed)))
feed_content = raw(render(inline: true, partial: activity_feed.partial,
locals: activity_feed.data.merge(activity_feed: activity_feed,
project_owner: activity_feed.project_owner,
project_name: activity_feed.project_name)))
entry.title(truncate(get_feed_title_from_content(feed_content), length: 50))
entry.content(feed_content, type: 'html')
entry.author do |author|
author.name(activity_feed.data[:user_name])
author.email(activity_feed.data[:user_email])
author.name(activity_feed.data[:creator_name])
author.email(activity_feed.data[:creator_email])
end if activity_feed.kind != 'git_delete_branch_notification'
end
end

View File

@ -1,6 +1,8 @@
-set_meta_tags title: nil
.row
.col-xs-12.col-md-10.col-md-offset-1 ng-controller = 'ActivityCtrl' ng-init = "init('#{action_name}')"
tabset.offset10 ng-cloak = true
.col-xs-12.col-md-10.col-md-offset-1[ ng-controller= 'ActivityController as actCtrl'
ng-init= "actCtrl.init('#{action_name}')" ]
tabset.offset10 ng-cloak= true
== render 'activity_tab'
== render 'tracker_and_pulls_tabs'
== render 'own_activity_tab'

View File

@ -1,5 +1,9 @@
if @activity_feeds.next_page
json.next_page_link root_path(filter: @filter, page: @activity_feeds.next_page, format: :json)
json.next_page_link root_path(filter: @filter,
page: @activity_feeds.next_page,
owner_filter: @owner_filter,
project_name_filter: @project_name_filter,
format: :json)
end
json.feed do
@ -14,7 +18,7 @@ json.feed do
json.uname (user.fullname || user.email)
end if user
project_name_with_owner = "#{item.data[:project_owner]}/#{item.data[:project_name]}"
project_name_with_owner = "#{item.project_owner}/#{item.project_name}"
@project = Project.find_by_owner_and_name(item.data[:project_owner], item.data[:project_name])
json.project_name_with_owner project_name_with_owner

View File

@ -0,0 +1,3 @@
json.array!(@owners) do |uname|
json.uname uname
end

View File

@ -0,0 +1,3 @@
json.array!(@projects) do |name|
json.name name
end

View File

@ -1,4 +1,4 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.persisted?
.text

View File

@ -1,9 +1,9 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.try(:persisted?)
.text
%span
-_user_link = defined?(user_email) ? user_link(user, defined?(user_name) ? user_name : user_email) : nil
-_user_link = defined?(creator_email) ? user_link(user, defined?(creator_name) ? creator_name : creator_email) : nil
= t('notifications.bodies.delete_branch', branch_name: branch_name, user_link: _user_link).html_safe
- name_with_owner = "#{project_owner}/#{project_name}"
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )

View File

@ -1,10 +1,10 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
- name_with_owner = "#{project_owner}/#{project_name}"
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.try(:persisted?)
.text
%span
-_user_link = defined?(user_email) ? user_link(user, defined?(user_name) ? user_name : user_email) : nil
-_user_link = defined?(creator_email) ? user_link(user, defined?(creator_name) ? creator_name : creator_email) : nil
= raw t("notifications.bodies.#{change_type}_branch", {branch_name: branch_name, user_link: _user_link})
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )
.both

View File

@ -1,10 +1,10 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
- name_with_owner = "#{project_owner}/#{project_name}"
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.persisted?
.text
%span
= raw t("notifications.bodies.new_comment_notification.title", user_link: user_link(user, user_name))
= raw t("notifications.bodies.new_comment_notification.title", user_link: user_link(user, creator_name))
= raw t("notifications.bodies.new_comment_notification.commit_content", {commit_link: link_to(commit_message, commit_path(name_with_owner, commit_id) + "#comment#{comment_id}")})
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )
.both

View File

@ -1,10 +1,10 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
- name_with_owner = "#{project_owner}/#{project_name}"
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.persisted?
.text
%span
= raw t("notifications.bodies.new_comment_notification.title", {user_link: user_link(user, user_name)})
= raw t("notifications.bodies.new_comment_notification.title", {user_link: user_link(user, creator_name)})
= raw t("notifications.bodies.new_comment_notification.content", {issue_link: link_to(issue_title, project_issue_path(name_with_owner, issue_serial_id) + "#comment#{comment_id}")})
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )
.both

View File

@ -1,11 +1,11 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
- name_with_owner = "#{project_owner}/#{project_name}"
- issue_path = issue_serial_id.present? ? project_issue_path(name_with_owner, issue_serial_id) : '#'
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.persisted?
.text
%span
= raw t("notifications.bodies.new_issue_notification", { user_link: user_link(user, user_name), issue_link: issue_path })
= raw t("notifications.bodies.new_issue_notification", { user_link: user_link(user, creator_name), issue_link: issue_path })
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )
.both
= datetime_moment activity_feed.created_at, tag: :span, class: 'date'

View File

@ -1,10 +1,10 @@
-user= User.where(email: user_email).first || User.new(email: user_email) if defined?(user_email)
-user= User.where(email: creator_email).first || User.new(email: creator_email) if defined?(creator_email)
- name_with_owner = "#{project_owner}/#{project_name}"
.top
.image= link_to(image_tag(avatar_url(user, :small), alt: 'avatar'), user_path(user)) if user.persisted?
.text
%span
= raw t("notifications.bodies.wiki_new_commit_notification", {user_link: user_link(user, user_name), history_link: link_to("wiki", history_project_wiki_index_path(name_with_owner))})
= raw t("notifications.bodies.wiki_new_commit_notification", {user_link: user_link(user, creator_name), history_link: link_to("wiki", history_project_wiki_index_path(name_with_owner))})
= raw t("notifications.bodies.project", project_link: link_to(name_with_owner, project_path(name_with_owner)) )
.both
= datetime_moment activity_feed.created_at, tag: :span, class: 'date'

View File

@ -14,7 +14,11 @@ ru:
platforms: Платформы
project:
updated: "Обновлен:"
updated: "Обновлен:"
placeholders:
owner_filter: Владелец проекта
name_filter: Имя проекта
simple_form:
placeholders:

View File

@ -40,6 +40,7 @@ en:
activity_feed: Activity Feed
tracker: Tracker
pull_requests: Pull Requests
own_activity: Own activity
feed_menu:
all: All
code: Code

View File

@ -40,6 +40,7 @@ ru:
activity_feed: Лента активности
tracker: Трекер
pull_requests: Пул реквесты
own_activity: Мои действия
feed_menu:
all: Все
code: Код

View File

@ -2,6 +2,7 @@ en:
layout:
activity_feed:
header: Activity Feed
own_header: Own Activity Feed
my_last_projects: My last projects
all_my_projects: All my projects
all_my_builds: All my builds
@ -9,10 +10,6 @@ en:
new_project: Create project
load_messages: show previous messages
atom_title: Activity Feed
filters:
all: All
my: Mine
others: Others
notifications:
subjects:

View File

@ -2,6 +2,7 @@ ru:
layout:
activity_feed:
header: Лента активности
own_header: Мои действия
my_last_projects: Мои последние проекты
all_my_projects: Все мои проекты
all_my_builds: Все мои сборки
@ -9,10 +10,6 @@ ru:
new_project: Создать проект
load_messages: показать предыдущие сообщения
atom_title: Лента активности
filters:
all: Вся
my: Моя
others: Чужая
notifications:
subjects:

View File

@ -144,9 +144,12 @@ Rosa::Application.routes.draw do
get '/tour/:id' => 'pages#tour_inside', as: 'tour_inside', id: /projects|sources|builds/
#match '/invite.html' => redirect('/register_requests/new')
get '/activity_feeds.:format' => 'home#activity', as: 'atom_activity_feeds', format: /atom/
get '/issues' => 'home#issues'
get '/pull_requests' => 'home#pull_requests'
get '/activity_feeds.:format' => 'home#activity', as: 'atom_activity_feeds', format: /atom/
get '/own_activity' => 'home#own_activity', as: 'own_activity'
get '/issues' => 'home#issues'
get '/pull_requests' => 'home#pull_requests'
get '/get_owners_list' => 'home#get_owners_list'
get '/get_project_names_list' => 'home#get_project_names_list'
if APP_CONFIG['anonymous_access']
authenticated do

View File

@ -0,0 +1,32 @@
class AddOwnerNameAndCreatorToActivityFeeds < ActiveRecord::Migration
def up
add_column :activity_feeds, :project_owner, :string
add_column :activity_feeds, :project_name, :string
add_column :activity_feeds, :creator_id, :integer
add_index :activity_feeds, :project_owner
add_index :activity_feeds, :project_name
add_index :activity_feeds, :creator_id
ActivityFeed.reset_column_information
ActivityFeed.find_each do |feed|
feed.project_owner = feed.data[:project_owner]
feed.project_name = feed.data[:project_name]
feed.creator_id = feed.data[:user_id]
feed.data[:creator_name] = feed.data[:user_name]
feed.data[:creator_email] = feed.data[:user_email]
feed.data[:project_owner] = nil
feed.data[:project_name] = nil
feed.data[:user_id] = nil
feed.data[:user_name] = nil
feed.data[:user_email] = nil
feed.save
end
end
def down
remove_column :activity_feeds, :project_owner
remove_column :activity_feeds, :project_name
remove_column :activity_feeds, :creator_id
end
end

View File

@ -11,18 +11,24 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150218231015) do
ActiveRecord::Schema.define(version: 20150502145718) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "hstore"
create_table "activity_feeds", force: true do |t|
t.integer "user_id", null: false
t.integer "user_id", null: false
t.string "kind"
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
t.string "project_owner"
t.string "project_name"
t.integer "creator_id"
t.index ["creator_id"], :name => "index_activity_feeds_on_creator_id"
t.index ["project_name"], :name => "index_activity_feeds_on_project_name"
t.index ["project_owner"], :name => "index_activity_feeds_on_project_owner"
t.index ["user_id", "kind"], :name => "index_activity_feeds_on_user_id_and_kind"
end