diff options
author | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2016-02-22 02:36:41 +0000 |
---|---|---|
committer | Douglas Barbosa Alexandre <dbalexandre@gmail.com> | 2016-02-22 02:36:41 +0000 |
commit | 04d1b412587028260ac219a32bdf3b03ac022308 (patch) | |
tree | 7486a02a074be0444320abc5b67837f9d2b39cb8 /app | |
parent | 28f9b54372cd2d7b7ac930c2a4968ca435382851 (diff) | |
parent | 2ef1ea501f48e29399d94ec5843984e4f7014b99 (diff) | |
download | gitlab-ce-04d1b412587028260ac219a32bdf3b03ac022308.tar.gz |
Merge branch 'tasks' into 'master'
Add Todos
Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/2425
Tasks:
- Prepare database
- [X] Create a new table (`todos`)
- Tasks Queue view
- [X] Add a number icon showing the number of todos on the top right next to the new and logout button that will redirect the user to the todos page
- [X] Add a chronological list of todos, with the 'Todos' tab active by default
- [X] Add a 'Done' button to each todo
- [x] Add filters (project, author, type, and action)
- Todos generation
- [X] When user issue/mr is assgined to someone
- [x] When user is mentioned on (issues/mr's/comments)
- Mark todo as `done`
- [X] When clicks on the 'Done' button
- [X] When edit issue/mr
- [X] When left/edit a comment
- [X] When reassign issue/mr
- [X] When add/remove labels to issue/mr
- [X] When issue/mr is closed
- [X] When mr is merged
- [X] When added an emoji
- [X] When changed the issue/mr milestone
* Screenshot:

See merge request !2817
Diffstat (limited to 'app')
24 files changed, 754 insertions, 14 deletions
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss new file mode 100644 index 00000000000..2f57f21963d --- /dev/null +++ b/app/assets/stylesheets/pages/todos.scss @@ -0,0 +1,124 @@ +/** + * Dashboard Todos + * + */ + +.navbar-nav { + li { + .badge.todos-pending-count { + background-color: #7f8fa4; + margin-top: -5px; + } + } +} + +.todos { + .panel { + border-top: none; + margin-bottom: 0; + } +} + +.todo-item { + font-size: $gl-font-size; + padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top); + border-bottom: 1px solid $table-border-color; + color: #7f8fa4; + + &.todo-inline { + .avatar { + position: relative; + top: -2px; + } + + .todo-title { + line-height: 40px; + } + } + + a { + color: #4c4e54; + } + + .avatar { + margin-left: -($gl-avatar-size + $gl-padding-top); + } + + .todo-title { + @include str-truncated(calc(100% - 174px)); + font-weight: 600; + + .author_name { + color: #333; + } + } + + .todo-body { + margin-right: 174px; + + .todo-note { + word-wrap: break-word; + + .md { + color: #7f8fa4; + font-size: $gl-font-size; + + p { + color: #5c5d5e; + } + } + + pre { + border: none; + background: #f9f9f9; + border-radius: 0; + color: #777; + margin: 0 20px; + overflow: hidden; + } + + .note-image-attach { + margin-top: 4px; + margin-left: 0px; + max-width: 200px; + float: none; + } + + p:last-child { + margin-bottom: 0; + } + } + + .todo-note-icon { + color: #777; + float: left; + font-size: $gl-font-size; + line-height: 16px; + margin-right: 5px; + } + } + + &:last-child { border:none } +} + +@media (max-width: $screen-xs-max) { + .todo-item { + padding-left: $gl-padding; + + .todo-title { + white-space: normal; + overflow: visible; + max-width: 100%; + } + + .avatar { + display: none; + } + + .todo-body { + margin: 0; + border-left: 2px solid #DDD; + padding-left: 10px; + } + } +} diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb new file mode 100644 index 00000000000..9ee9039f004 --- /dev/null +++ b/app/controllers/dashboard/todos_controller.rb @@ -0,0 +1,35 @@ +class Dashboard::TodosController < Dashboard::ApplicationController + before_action :find_todos, only: [:index, :destroy_all] + + def index + @todos = @todos.page(params[:page]).per(PER_PAGE) + end + + def destroy + todo.done! + + respond_to do |format| + format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } + format.js { render nothing: true } + end + end + + def destroy_all + @todos.each(&:done) + + respond_to do |format| + format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } + format.js { render nothing: true } + end + end + + private + + def todo + @todo ||= current_user.todos.find(params[:id]) + end + + def find_todos + @todos = TodosFinder.new(current_user, params).execute + end +end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 86b8e7bdf2e..5fe21694605 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -181,6 +181,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController return end + TodoService.new.merge_merge_request(merge_request, current_user) + @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active? diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb new file mode 100644 index 00000000000..3ba27c40504 --- /dev/null +++ b/app/finders/todos_finder.rb @@ -0,0 +1,129 @@ +# TodosFinder +# +# Used to filter Todos by set of params +# +# Arguments: +# current_user - which user use +# params: +# action_id: integer +# author_id: integer +# project_id; integer +# state: 'pending' or 'done' +# type: 'Issue' or 'MergeRequest' +# + +class TodosFinder + NONE = '0' + + attr_accessor :current_user, :params + + def initialize(current_user, params) + @current_user = current_user + @params = params + end + + def execute + items = current_user.todos + items = by_action_id(items) + items = by_author(items) + items = by_project(items) + items = by_state(items) + items = by_type(items) + + items + end + + private + + def action_id? + action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED].include?(action_id.to_i) + end + + def action_id + params[:action_id] + end + + def author? + params[:author_id].present? + end + + def author + return @author if defined?(@author) + + @author = + if author? && params[:author_id] != NONE + User.find(params[:author_id]) + else + nil + end + end + + def project? + params[:project_id].present? + end + + def project + return @project if defined?(@project) + + if project? + @project = Project.find(params[:project_id]) + + unless Ability.abilities.allowed?(current_user, :read_project, @project) + @project = nil + end + else + @project = nil + end + + @project + end + + def type? + type.present? && ['Issue', 'MergeRequest'].include?(type) + end + + def type + params[:type] + end + + def by_action_id(items) + if action_id? + items = items.where(action: action_id) + end + + items + end + + def by_author(items) + if author? + items = items.where(author_id: author.try(:id)) + end + + items + end + + def by_project(items) + if project? + items = items.where(project: project) + end + + items + end + + def by_state(items) + case params[:state] + when 'done' + items.done + else + items.pending + end + end + + def by_type(items) + if type? + items = items.where(target_type: type) + end + + items + end +end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb new file mode 100644 index 00000000000..4b745a5b969 --- /dev/null +++ b/app/helpers/todos_helper.rb @@ -0,0 +1,87 @@ +module TodosHelper + def todos_pending_count + current_user.todos.pending.count + end + + def todos_done_count + current_user.todos.done.count + end + + def todo_action_name(todo) + case todo.action + when Todo::ASSIGNED then 'assigned you' + when Todo::MENTIONED then 'mentioned you on' + end + end + + def todo_target_link(todo) + target = todo.target_type.titleize.downcase + link_to "#{target} #{todo.target.to_reference}", todo_target_path(todo) + end + + def todo_target_path(todo) + anchor = dom_id(todo.note) if todo.note.present? + + polymorphic_path([todo.project.namespace.becomes(Namespace), + todo.project, todo.target], anchor: anchor) + end + + def todos_filter_params + { + state: params[:state], + project_id: params[:project_id], + author_id: params[:author_id], + type: params[:type], + action_id: params[:action_id], + } + end + + def todos_filter_path(options = {}) + without = options.delete(:without) + + options = todos_filter_params.merge(options) + + if without.present? + without.each do |key| + options.delete(key) + end + end + + path = request.path + path << "?#{options.to_param}" + path + end + + def todo_actions_options + actions = [ + OpenStruct.new(id: '', title: 'Any Action'), + OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'), + OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned') + ] + + options_from_collection_for_select(actions, 'id', 'title', params[:action_id]) + end + + def todo_projects_options + projects = current_user.authorized_projects.sorted_by_activity.non_archived + projects = projects.includes(:namespace) + + projects = projects.map do |project| + OpenStruct.new(id: project.id, title: project.name_with_namespace) + end + + projects.unshift(OpenStruct.new(id: '', title: 'Any Project')) + + options_from_collection_for_select(projects, 'id', 'title', params[:project_id]) + end + + def todo_types_options + types = [ + OpenStruct.new(title: 'Any Type', name: ''), + OpenStruct.new(title: 'Issue', name: 'Issue'), + OpenStruct.new(title: 'Merge Request', name: 'MergeRequest') + ] + + options_from_collection_for_select(types, 'name', 'title', params[:type]) + end +end diff --git a/app/models/note.rb b/app/models/note.rb index b3809ad81e0..d287e0f3c6d 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -37,6 +37,8 @@ class Note < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :updated_by, class_name: "User" + has_many :todos, dependent: :destroy + delegate :name, to: :project, prefix: true delegate :name, :email, to: :author, prefix: true diff --git a/app/models/todo.rb b/app/models/todo.rb new file mode 100644 index 00000000000..34d71c1b0d3 --- /dev/null +++ b/app/models/todo.rb @@ -0,0 +1,53 @@ +# == Schema Information +# +# Table name: todos +# +# id :integer not null, primary key +# user_id :integer not null +# project_id :integer not null +# target_id :integer not null +# target_type :string not null +# author_id :integer +# note_id :integer +# action :integer not null +# state :string not null +# created_at :datetime +# updated_at :datetime +# + +class Todo < ActiveRecord::Base + ASSIGNED = 1 + MENTIONED = 2 + + belongs_to :author, class_name: "User" + belongs_to :note + belongs_to :project + belongs_to :target, polymorphic: true, touch: true + belongs_to :user + + delegate :name, :email, to: :author, prefix: true, allow_nil: true + + validates :action, :project, :target, :user, presence: true + + default_scope { reorder(id: :desc) } + + scope :pending, -> { with_state(:pending) } + scope :done, -> { with_state(:done) } + + state_machine :state, initial: :pending do + event :done do + transition pending: :done + end + + state :pending + state :done + end + + def body + if note.present? + note.note + else + target.title + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 9fe94b13e52..02ff2456f2b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -140,7 +140,7 @@ class User < ActiveRecord::Base has_one :abuse_report, dependent: :destroy has_many :spam_logs, dependent: :destroy has_many :builds, dependent: :nullify, class_name: 'Ci::Build' - + has_many :todos, dependent: :destroy # # Validations diff --git a/app/services/base_service.rb b/app/services/base_service.rb index b48ca67d4d2..8563633816c 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -23,6 +23,10 @@ class BaseService EventCreateService.new end + def todo_service + TodoService.new + end + def log_info(message) Gitlab::AppLogger.info message end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 2556f06e2d3..ca87dca4a70 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -54,7 +54,7 @@ class IssuableBaseService < BaseService if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) issuable.reset_events_cache handle_common_system_notes(issuable, old_labels: old_labels) - handle_changes(issuable) + handle_changes(issuable, old_labels: old_labels) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') end @@ -71,6 +71,19 @@ class IssuableBaseService < BaseService end end + def has_changes?(issuable, options = {}) + valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch] + + attrs_changed = valid_attrs.any? do |attr| + issuable.previous_changes.include?(attr.to_s) + end + + old_labels = options[:old_labels] + labels_changed = old_labels && issuable.labels != old_labels + + attrs_changed || labels_changed + end + def handle_common_system_notes(issuable, options = {}) if issuable.previous_changes.include?('title') create_title_change_note(issuable, issuable.previous_changes['title'].first) diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index a1a20e47681..78254b49af3 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -3,6 +3,7 @@ module Issues def execute(issue, commit = nil) if project.jira_tracker? && project.jira_service.active project.jira_service.execute(commit, issue) + todo_service.close_issue(issue, current_user) return issue end @@ -10,6 +11,7 @@ module Issues event_service.close_issue(issue, current_user) create_note(issue, commit) notification_service.close_issue(issue, current_user) + todo_service.close_issue(issue, current_user) execute_hooks(issue, 'close') end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index bcb380d3215..10787e8873c 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -9,6 +9,7 @@ module Issues if issue.save issue.update_attributes(label_ids: label_params) notification_service.new_issue(issue, current_user) + todo_service.new_issue(issue, current_user) event_service.open_issue(issue, current_user) issue.create_cross_references!(current_user) execute_hooks(issue, 'open') diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index a55a04dd5e0..51ef9dfe610 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -4,7 +4,16 @@ module Issues update(issue) end - def handle_changes(issue) + def handle_changes(issue, options = {}) + if has_changes?(issue, options) + todo_service.mark_pending_todos_as_done(issue, current_user) + end + + if issue.previous_changes.include?('title') || + issue.previous_changes.include?('description') + todo_service.update_issue(issue, current_user) + end + if issue.previous_changes.include?('milestone_id') create_milestone_note(issue) end @@ -12,6 +21,7 @@ module Issues if issue.previous_changes.include?('assignee_id') create_assignee_note(issue) notification_service.reassigned_issue(issue, current_user) + todo_service.reassigned_issue(issue, current_user) end end diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 47454f9f0c2..27ee81fe3e7 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -9,6 +9,7 @@ module MergeRequests event_service.close_mr(merge_request, current_user) create_note(merge_request) notification_service.close_mr(merge_request, current_user) + todo_service.close_merge_request(merge_request, current_user) execute_hooks(merge_request, 'close') end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 009d5a6867e..33609d01f20 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -2,7 +2,7 @@ module MergeRequests class CreateService < MergeRequests::BaseService def execute # @project is used to determine whether the user can set the merge request's - # assignee, milestone and labels. Whether they can depends on their + # assignee, milestone and labels. Whether they can depends on their # permissions on the target project. source_project = @project @project = Project.find(params[:target_project_id]) if params[:target_project_id] @@ -18,6 +18,7 @@ module MergeRequests merge_request.update_attributes(label_ids: label_params) event_service.open_mr(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user) + todo_service.new_merge_request(merge_request, current_user) merge_request.create_cross_references!(current_user) execute_hooks(merge_request) end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 5ff2cc03dda..6319ad805b6 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -14,7 +14,16 @@ module MergeRequests update(merge_request) end - def handle_changes(merge_request) + def handle_changes(merge_request, options = {}) + if has_changes?(merge_request, options) + todo_service.mark_pending_todos_as_done(merge_request, current_user) + end + + if merge_request.previous_changes.include?('title') || + merge_request.previous_changes.include?('description') + todo_service.update_merge_request(merge_request, current_user) + end + if merge_request.previous_changes.include?('target_branch') create_branch_change_note(merge_request, 'target', merge_request.previous_changes['target_branch'].first, @@ -28,6 +37,7 @@ module MergeRequests if merge_request.previous_changes.include?('assignee_id') create_assignee_note(merge_request) notification_service.reassigned_merge_request(merge_request, current_user) + todo_service.reassigned_merge_request(merge_request, current_user) end if merge_request.previous_changes.include?('target_branch') || diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 8d9661167b5..b970439b921 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -8,6 +8,7 @@ module Notes if note.save # Finish the harder work in the background NewNoteWorker.perform_in(2.seconds, note.id, params) + TodoService.new.new_note(note, current_user) end note diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb index f37d3c50cdd..e818f58d13c 100644 --- a/app/services/notes/post_process_service.rb +++ b/app/services/notes/post_process_service.rb @@ -1,6 +1,5 @@ module Notes class PostProcessService - attr_accessor :note def initialize(note) @@ -25,6 +24,5 @@ module Notes @note.project.execute_hooks(note_data, :note_hooks) @note.project.execute_services(note_data, :note_hooks) end - end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 72e2f78008d..1361b1e0300 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -7,6 +7,10 @@ module Notes note.create_new_cross_references!(current_user) note.reset_events_cache + if note.previous_changes.include?('note') + TodoService.new.update_note(note, current_user) + end + note end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb new file mode 100644 index 00000000000..dc270602ebc --- /dev/null +++ b/app/services/todo_service.rb @@ -0,0 +1,170 @@ +# TodoService class +# +# Used for creating todos after certain user actions +# +# Ex. +# TodoService.new.new_issue(issue, current_user) +# +class TodoService + # When create an issue we should: + # + # * create a todo for assignee if issue is assigned + # * create a todo for each mentioned user on issue + # + def new_issue(issue, current_user) + new_issuable(issue, current_user) + end + + # When update an issue we should: + # + # * mark all pending todos related to the issue for the current user as done + # + def update_issue(issue, current_user) + create_mention_todos(issue.project, issue, current_user) + end + + # When close an issue we should: + # + # * mark all pending todos related to the target for the current user as done + # + def close_issue(issue, current_user) + mark_pending_todos_as_done(issue, current_user) + end + + # When we reassign an issue we should: + # + # * create a pending todo for new assignee if issue is assigned + # + def reassigned_issue(issue, current_user) + create_assignment_todo(issue, current_user) + end + + # When create a merge request we should: + # + # * creates a pending todo for assignee if merge request is assigned + # * create a todo for each mentioned user on merge request + # + def new_merge_request(merge_request, current_user) + new_issuable(merge_request, current_user) + end + + # When update a merge request we should: + # + # * create a todo for each mentioned user on merge request + # + def update_merge_request(merge_request, current_user) + create_mention_todos(merge_request.project, merge_request, current_user) + end + + # When close a merge request we should: + # + # * mark all pending todos related to the target for the current user as done + # + def close_merge_request(merge_request, current_user) + mark_pending_todos_as_done(merge_request, current_user) + end + + # When we reassign a merge request we should: + # + # * creates a pending todo for new assignee if merge request is assigned + # + def reassigned_merge_request(merge_request, current_user) + create_assignment_todo(merge_request, current_user) + end + + # When merge a merge request we should: + # + # * mark all pending todos related to the target for the current user as done + # + def merge_merge_request(merge_request, current_user) + mark_pending_todos_as_done(merge_request, current_user) + end + + # When create a note we should: + # + # * mark all pending todos related to the noteable for the note author as done + # * create a todo for each mentioned user on note + # + def new_note(note, current_user) + handle_note(note, current_user) + end + + # When update a note we should: + # + # * mark all pending todos related to the noteable for the current user as done + # * create a todo for each new user mentioned on note + # + def update_note(note, current_user) + handle_note(note, current_user) + end + + # When marking pending todos as done we should: + # + # * mark all pending todos related to the target for the current user as done + # + def mark_pending_todos_as_done(target, user) + pending_todos(user, target.project, target).update_all(state: :done) + end + + private + + def create_todos(project, target, author, users, action, note = nil) + Array(users).each do |user| + next if pending_todos(user, project, target).exists? + + Todo.create( + project: project, + user_id: user.id, + author_id: author.id, + target_id: target.id, + target_type: target.class.name, + action: action, + note: note + ) + end + end + + def new_issuable(issuable, author) + create_assignment_todo(issuable, author) + create_mention_todos(issuable.project, issuable, author) + end + + def handle_note(note, author) + # Skip system notes, like status changes and cross-references + return if note.system + + project = note.project + target = note.noteable + + mark_pending_todos_as_done(target, author) + create_mention_todos(project, target, author, note) + end + + def create_assignment_todo(issuable, author) + if issuable.assignee && issuable.assignee != author + create_todos(issuable.project, issuable, author, issuable.assignee, Todo::ASSIGNED) + end + end + + def create_mention_todos(project, issuable, author, note = nil) + mentioned_users = filter_mentioned_users(project, note || issuable, author) + create_todos(project, issuable, author, mentioned_users, Todo::MENTIONED, note) + end + + def filter_mentioned_users(project, target, author) + mentioned_users = target.mentioned_users.select do |user| + user.can?(:read_project, project) + end + + mentioned_users.delete(author) + mentioned_users.uniq + end + + def pending_todos(user, project, target) + user.todos.pending.where( + project_id: project.id, + target_id: target.id, + target_type: target.class.name + ) + end +end diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml new file mode 100644 index 00000000000..6975f6ed0db --- /dev/null +++ b/app/views/dashboard/todos/_todo.html.haml @@ -0,0 +1,21 @@ +%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo) } + .todo-item{class: 'todo-block'} + = image_tag avatar_icon(todo.author_email, 40), class: 'avatar s40', alt:'' + + .todo-title + %span.author_name + = link_to_author todo + %span.todo_label + = todo_action_name(todo) + = todo_target_link(todo) + + · #{time_ago_with_tooltip(todo.created_at)} + + - if todo.pending? + .todo-actions.pull-right + = link_to 'Done', [:dashboard, todo], method: :delete, class: 'btn' + + .todo-body + .todo-note + .md + = event_note(todo.body, project: todo.project) diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml new file mode 100644 index 00000000000..946d7df3933 --- /dev/null +++ b/app/views/dashboard/todos/index.html.haml @@ -0,0 +1,62 @@ +- page_title "Todos" +- header_title "Todos", dashboard_todos_path + +.top-area + %ul.nav-links + %li{class: ('active' if params[:state].blank? || params[:state] == 'pending')} + = link_to todos_filter_path(state: 'pending') do + %span + To do + %span{class: 'badge'} + = todos_pending_count + %li{class: ('active' if params[:state] == 'done')} + = link_to todos_filter_path(state: 'done') do + %span + Done + %span{class: 'badge'} + = todos_done_count + + .nav-controls + - if @todos.any?(&:pending?) + = link_to 'Mark all as done', destroy_all_dashboard_todos_path(todos_filter_params), class: 'btn', method: :delete + +.todos-filters + .gray-content-block.second-block + = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do + .filter-item.inline + = select_tag('project_id', todo_projects_options, + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Project'}) + .filter-item.inline + = users_select_tag(:author_id, selected: params[:author_id], + placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) + .filter-item.inline + = select_tag('type', todo_types_options, + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Type'}) + .filter-item.inline.actions-filter + = select_tag('action_id', todo_actions_options, + class: 'select2 trigger-submit', include_blank: true, + data: {placeholder: 'Action'}) + +.prepend-top-default + - if @todos.any? + - @todos.group_by(&:project).each do |group| + .panel.panel-default.panel-small + - project = group[0] + .panel-heading + = link_to project.name_with_namespace, namespace_project_path(project.namespace, project) + + %ul.well-list.todos-list + = render group[1] + = paginate @todos, theme: "gitlab" + - else + .nothing-here-block You're all done! + +:javascript + new UsersSelect(); + + $('form.filter-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '&' + $(this).serialize()); + }); diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index fcb6b835a7e..4781ff23507 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -21,6 +21,10 @@ %li = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('wrench fw') + %li + = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + %span.badge.todos-pending-count + = todos_pending_count - if current_user.can_create_project? %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do @@ -39,4 +43,4 @@ = render 'shared/outdated_browser' - if @project && !@project.empty_repo? :javascript - var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}";
\ No newline at end of file + var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, @ref || @project.repository.root_ref)}"; diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 106abd24a56..db0cf393922 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -4,6 +4,12 @@ = icon('home fw') %span Projects + = nav_link(controller: :todos) do + = link_to dashboard_todos_path, title: 'Todos' do + = icon('bell fw') + %span + Todos + %span.count= number_with_delimiter(todos_pending_count) = nav_link(path: 'dashboard#activity') do = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity' do = icon('dashboard fw') @@ -25,12 +31,12 @@ %span Issues %span.count= number_with_delimiter(current_user.assigned_issues.opened.count) - = nav_link(path: 'dashboard#merge_requests') do - = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do - = icon('tasks fw') - %span - Merge Requests - %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) + = nav_link(path: 'dashboard#merge_requests') do + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do + = icon('tasks fw') + %span + Merge Requests + %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count) = nav_link(controller: :snippets) do = link_to dashboard_snippets_path, title: 'Snippets' do = icon('clipboard fw') |