Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add My Tasks view & dashboard widget #1285

Open
wants to merge 82 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
88a624a
Add scaffold for "My Tasks" view
nicolachr Aug 9, 2024
644dca9
Add scaffold for my tasks dashboard widget
nicolachr Aug 9, 2024
7e607bc
Add task list to dashboard widget
nicolachr Aug 9, 2024
c7b8922
Add color sorting - dashboard widget
nicolachr Aug 9, 2024
a43a4a2
Add methodology badge - dashboard widget
nicolachr Aug 9, 2024
e9afc01
Add link to "My Tasks" view to sidebar
nicolachr Aug 9, 2024
48f32a8
Add table placeholder to "My Tasks" view
nicolachr Aug 9, 2024
128bf3d
lint
MattBudz Aug 12, 2024
428d648
add dataTable
MattBudz Aug 12, 2024
9e705ec
ensure `due_date` exists before applying classes
MattBudz Aug 12, 2024
bc81e2c
simplify view by moving due date logic to helper
MattBudz Aug 12, 2024
a337a33
Add styling to tasks datable fields
nicolachr Aug 13, 2024
d358240
Add icon to All tasks link - dashboard widget
nicolachr Aug 13, 2024
4062aea
define `@tasks` for the dashboard widget
MattBudz Aug 13, 2024
4d7a59f
Merge branch 'tasks/my-tasks' of https://github.com/dradis/dradis-ce …
nicolachr Aug 13, 2024
6116d33
Update code to dynamically show tasks - dashboard widget
nicolachr Aug 13, 2024
f213f74
Update widget color sorting to match default badges' colours
nicolachr Aug 13, 2024
85d98e6
fix indent size
MattBudz Aug 13, 2024
1a584c3
loop over tasks once to improve performance
MattBudz Aug 13, 2024
da05974
sort tasks in the widget and limit to 3 per group
MattBudz Aug 13, 2024
6379502
DRY up the view
MattBudz Aug 13, 2024
ef411b3
add `+ more` counters
MattBudz Aug 13, 2024
8534790
slim down the widget height
MattBudz Aug 13, 2024
e6b8aab
Change Tasks link name to My Tasks - sidebar
nicolachr Aug 14, 2024
0a6a432
check if tomorrow/future records exist
MattBudz Aug 14, 2024
8ab41fd
use a single list of tasks limited to 5
MattBudz Aug 14, 2024
5258657
Fix contrast issues with badges
nicolachr Aug 14, 2024
e1581c6
Merge branch 'tasks/my-tasks' of https://github.com/dradis/dradis-ce …
nicolachr Aug 14, 2024
636b1d2
sort due date column by date in my tasks view
MattBudz Aug 14, 2024
5e079c2
Merge branch 'tasks/my-tasks' of https://github.com/dradis/dradis-ce …
MattBudz Aug 14, 2024
2ad638e
Remove color sorting borders from tasks - dashboard widget
nicolachr Aug 14, 2024
0e45cda
add specs for my tasks view
MattBudz Aug 14, 2024
03e481e
Add due-date badge in tasks - dashboard widget
nicolachr Aug 14, 2024
afb08e6
Add tooltip to avatar images in "My Tasks" view
nicolachr Aug 14, 2024
7b26c07
Align avatar images when wrapped
nicolachr Aug 14, 2024
f6b98a8
clean up WIP code
MattBudz Aug 14, 2024
38cc0eb
refactor `due_date_badge` for consistency
MattBudz Aug 14, 2024
720578d
Make Methodology less prominent - dashboard widget
nicolachr Aug 16, 2024
4216070
Update task link color & background color (hover) - dashboard widget
nicolachr Aug 16, 2024
003acd7
Remove unused dark-orange warning badge
nicolachr Aug 16, 2024
d4b8c10
Add empty states - dashboard widget & "My Tasks" view
nicolachr Aug 16, 2024
5d00861
update empty state copy
MattBudz Aug 16, 2024
d696d84
update specs
MattBudz Aug 16, 2024
cf5bb6c
remove unused style
MattBudz Aug 16, 2024
8876d60
prevent linking whitespace
MattBudz Aug 16, 2024
5a948cd
remove blank line
MattBudz Aug 16, 2024
75d251a
revert changes to `db/schema.rb`
MattBudz Aug 16, 2024
80a8d1b
Update Changelog
nicolachr Aug 16, 2024
023d203
Correct template of Changelog
nicolachr Aug 16, 2024
212fb07
fix text-truncate not working
MattBudz Aug 16, 2024
488547a
only render records for the current_project
MattBudz Aug 16, 2024
9496a9d
guard against project having no cards when displaying assigned tasks
caitmich Aug 19, 2024
54533ca
improve parity with pro
MattBudz Aug 20, 2024
e49d147
Revert change to '$orange' variable
nicolachr Aug 21, 2024
610d87b
improve pro parity
MattBudz Aug 21, 2024
1857d78
update controller for better pro parity
MattBudz Aug 21, 2024
af0c23f
Update app/controllers/tasks_controller.rb
nicolachr Aug 28, 2024
c047e18
get assigned tasks from the user
MattBudz Aug 29, 2024
ce7841d
clean up controller action
MattBudz Aug 29, 2024
314d4cf
remove pro-only code
MattBudz Aug 29, 2024
0686b46
use modern syntax
MattBudz Aug 29, 2024
dd1a74c
remove `@tasks_limit` and use helper methods
MattBudz Aug 29, 2024
80b50a7
Merge branch 'develop' into tasks/my-tasks
MattBudz Aug 29, 2024
3289473
set `@tasks` with a shared helper method
MattBudz Aug 29, 2024
2f606d1
Merge branch 'tasks/my-tasks' of https://github.com/dradis/dradis-ce …
MattBudz Aug 29, 2024
8d8d36f
consistency
MattBudz Aug 29, 2024
ba6643d
remove pro-only block
MattBudz Sep 5, 2024
7d14d91
apply code review suggestion
MattBudz Sep 5, 2024
2a75a5b
improve naming
MattBudz Sep 5, 2024
a31aea6
fix tasks not being scoped in project-level widget
MattBudz Sep 9, 2024
fee1bfc
fix cell y-alignment - was uneven due to avatar
MattBudz Sep 9, 2024
d4c5137
simplify tasks helper
MattBudz Sep 12, 2024
790039a
improve pro parity
MattBudz Sep 12, 2024
8336f8b
fix `+ N more` count
MattBudz Sep 12, 2024
a507cf3
improve readability
MattBudz Sep 12, 2024
18e2d53
remove old comment
MattBudz Sep 12, 2024
6425e54
ensure users have permission to view cards
MattBudz Sep 23, 2024
f60c9b4
use `private` for consistency
MattBudz Sep 23, 2024
42cd66a
Merge branch 'develop' into tasks/my-tasks
MattBudz Sep 23, 2024
81ee43b
improve readability and pro-parity
MattBudz Sep 23, 2024
ef97e54
re-add `List` column to give users and quick way to see completed car…
MattBudz Oct 2, 2024
f6e34b2
add list to default columns
MattBudz Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
[v#.#.#] ([month] [YYYY])
- Kit Import: Use file name sequencing when a template file with the same name exists
- Tasks:
- Add view with user assigned tasks
- Add dashboard widget for quick access to assigned tasks
- Upgraded gems:
- rexml
- Bugs fixes:
Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/tylium/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ a {
background-color: $primaryColor !important;
}

.bg-warning {
color: $defaultText;
}

blockquote {
border-left: 3px solid $primaryColor;
padding-left: 1rem;
Expand Down
47 changes: 41 additions & 6 deletions app/assets/stylesheets/tylium/modules/projects.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ body.projects {
#boards-summary {
display: flex;
flex-wrap: wrap;

.pie-chart {
width: 50%;
}

&.loading {
background: image_url('loading.gif') no-repeat center;
}
Expand Down Expand Up @@ -35,7 +35,7 @@ body.projects {
display: flex;
justify-content: center;
align-items: center;

svg text {
font-size: 14px;
}
Expand All @@ -48,7 +48,6 @@ body.projects {
margin-bottom: 1rem;

.card-body {

.list-group-item {
border: none;
transition: background-color 0.2s ease-in-out;
Expand All @@ -65,7 +64,7 @@ body.projects {

.card-header {
color: $white;

h5 {
margin-bottom: 0;
}
Expand All @@ -80,7 +79,7 @@ body.projects {

.card-body li {
color: #222;
};
}

.card-header {
background-color: #222;
Expand All @@ -89,4 +88,40 @@ body.projects {
}
}
}

.tasks {
.list-group-item {
border: 1px solid $borderColor;
border-radius: 0.5rem;
margin-bottom: 1rem;
padding: 1rem;
pointer-events: none;

.badge {
margin-top: 0.5rem;
width: max-content;
}

&:hover {
background-color: $secondaryBgColor;
}

&:last-of-type {
margin-bottom: 0;
}

.list-group-item-action {
color: $linkColor;
padding-bottom: 0.5rem;
pointer-events: auto;

&:active,
&:focus,
&:hover {
background-color: $secondaryBgColor;
color: $linkColorHover;
}
}
}
}
}
8 changes: 5 additions & 3 deletions app/controllers/projects_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class ProjectsController < AuthenticatedController
include NotificationsReader
include TasksHelper

before_action :set_project

Expand All @@ -16,12 +17,13 @@ def show
@authors = [current_user]
@boards = current_project.methodology_library.boards
@issues = current_project.issues.includes(:tags).sort
@methodologies = current_project.methodology_library.notes.map{|n| Methodology.new(filename: n.id, content: n.text)}
@methodologies = current_project.methodology_library.notes.map { |n| Methodology.new(filename: n.id, content: n.text) }
@nodes = current_project.nodes.in_tree
@tags = current_project.tags
@tasks = assigned_cards(current_project.id)

@count_by_tag = { unassigned: 0 }
@issues_by_tag = Hash.new{|h,k| h[k] = [] }
@issues_by_tag = Hash.new { |h, k| h[k] = [] }

@tag_names = @tags.map do |tag|
@count_by_tag[tag.name] = 0
Expand All @@ -41,7 +43,7 @@ def show
end

respond_to do |format|
format.html { render layout: 'tylium' if !request.xhr?}
format.html { render layout: 'tylium' if !request.xhr? }
format.json { render json: @boards }
end
end
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/tasks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class TasksController < AuthenticatedController
include ProjectScoped
include TasksHelper

skip_before_action :set_project, unless: -> { current_project }
skip_before_action :set_nodes, unless: -> { current_project }

def index
@default_columns = ['Title', 'Methodology', 'List', 'Due Date', 'Assigned']

if current_project
@local_storage_key = "project.ce.project_#{current_project.id}.tasks_datatable"
MattBudz marked this conversation as resolved.
Show resolved Hide resolved
@tasks = assigned_cards(current_project.id)
end
end

private

# ProjectScoped always calls current_project. We are overwriting it here to
# prevent errors in Pro when viewing tasks outside of projects.
def current_project
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of including it and opting out of several methods, what if we don't include ProjectScoped and call only the methods we need when we need them?

ex. Can we instead look up the project here using params[:project_id] if it's present?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that but we also need access to current_project in the views and ProjectScoped also sets @nodes for us (used by the main sidebar).

This is also the approach we take in https://github.com/dradis/dradis-ce/blob/develop/app/controllers/notifications_controller.rb

return if params[:project_id].blank?

super
end
end
42 changes: 42 additions & 0 deletions app/helpers/tasks_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module TasksHelper
TASKS_LIMIT = 5

def assigned_cards(project_id = nil)
@assigned_cards ||= begin
# Using Arel.sql to sort the records by due_date with null due_date records last
cards = current_user.cards.order(Arel.sql('due_date IS NULL, due_date ASC'))

if project_id
cards.select { |card| card.project.id == current_project.id }
Copy link
Contributor

@caitmich caitmich Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're passing project_id we should use it here otherwise it could have unexpected results

Suggested change
cards.select { |card| card.project.id == current_project.id }
cards.select { |card| card.project.id == project_id }

else
valid_project_ids = Project.kept.current.accessible_by(current_ability).ids
cards.select { |card| card.project.id.in? valid_project_ids }
end
end
end

def overflow_tasks_count(tasks)
return 0 unless tasks.size > TASKS_LIMIT

tasks.size - TASKS_LIMIT
end

def due_date_badge(task)
return unless task.due_date

badge_class =
case task.due_date
when Date.today
'bg-warning'
when ->(date) { date < Date.today }
'bg-danger'
else
'bg-success'
end

content_tag(:span, class: "badge-due-date badge #{badge_class}") do
content_tag(:i, nil, class: 'fa-regular fa-clock me-1') +
content_tag(:span, task.due_date.strftime('%b %e'))
end
end
end
6 changes: 3 additions & 3 deletions app/jobs/notifications_reader_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def perform(notifiable_id:, notifiable_type:, user_id:)
Notification.transaction do
@notifiable = notifiable_type.constantize.find_by(id: notifiable_id)

mark_notifications_from_assignments if NOTIFIABLE_TYPES.include?(notifiable_type)
mark_notifications_from_tasks if NOTIFIABLE_TYPES.include?(notifiable_type)
mark_notifications_from_comments

if Notification.unread.where(recipient_id: user_id).count == 0
Expand All @@ -20,8 +20,8 @@ def perform(notifiable_id:, notifiable_type:, user_id:)

private

# Mark all assignment notifications from the notifiable as read
def mark_notifications_from_assignments
# Mark all task notifications from the notifiable as read
def mark_notifications_from_tasks
@notifiable.notifications.unread.where(recipient_id: @user_id, action: :assign).
mark_all_as_read!
end
Expand Down
2 changes: 2 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class User < ApplicationRecord
validates_associated :preferences

# -- Relationships --------------------------------------------------------
has_and_belongs_to_many :cards

has_many :activities
has_many :comments, dependent: :nullify
has_many :notifications, foreign_key: 'recipient_id', dependent: :destroy
Expand Down
13 changes: 9 additions & 4 deletions app/views/layouts/tylium/_sidebar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@

<div class="scrollable-content">
<ul id="main-menu" class="main-menu">
<%= content_tag :li, :class => controller_path == 'projects' ? 'active' : '' do %>
<%= content_tag :li, class: controller_path == 'projects' ? 'active' : '' do %>
<%= link_to main_app.project_path(current_project), class: 'sidebar-link', data: { behavior: 'sidebar-link' } do %>
<i class="fa-solid fa-tachometer sidebar-link-icon"></i> <span class="sidebar-link-label">Dashboard</span>
<% end %>
<% end %>
<%= content_tag :li, :class => controller_path.include?('issues') ? 'active' : '' do %>
<%= content_tag :li, class: controller_path.include?('issues') ? 'active' : '' do %>
<%= link_to main_app.project_issues_path(current_project), class: 'sidebar-link', data: { behavior: 'sidebar-link' } do %>
<i class="fa-solid fa-bug sidebar-link-icon"></i> <span class="sidebar-link-label">All issues</span>
<% end %>
<% end %>
<%= content_tag :li, :class => controller_path == 'boards' || controller_path == 'cards' ? 'active' : '' do %>
<%= content_tag :li, class: controller_path == 'boards' || controller_path == 'cards' ? 'active' : '' do %>
<%= link_to main_app.project_boards_path(current_project), class: 'sidebar-link', data: { behavior: 'sidebar-link' } do %>
<i class="fa-brands fa-trello sidebar-link-icon"></i> <span class="sidebar-link-label">Methodologies</span>
<% end %>
<% end %>
<%= content_tag :li, :class => action_name == 'trash' ? 'active' : '' do %>
<%= content_tag :li, class: controller_path == 'tasks' ? 'active' : '' do %>
<%= link_to main_app.project_tasks_path(current_project), class: 'sidebar-link', data: { behavior: 'sidebar-link' } do %>
<i class="fa-solid fa-tasks sidebar-link-icon"></i> <span class="sidebar-link-label">My Tasks</span>
<% end %>
<% end %>
<%= content_tag :li, class: action_name == 'trash' ? 'active' : '' do %>
<%= link_to main_app.project_trash_path(current_project), class: 'sidebar-link', data: { behavior: 'sidebar-link' } do %>
<i class="fa-solid fa-trash sidebar-link-icon" aria-hidden="true"></i> <span class="sidebar-link-label">Trash</span>
<% end %>
Expand Down
3 changes: 3 additions & 0 deletions app/views/projects/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<div class="col-12 col-lg-6 col-xxl-8 p-0">
<div class="row m-0">
<div class="col-xxl-6 p-0">
<div class="content-container mt-0 mt-lg-3 ms-lg-3 mx-xxl-3 pb-2">
<%= render partial: 'projects/tasks/summary' %>
</div>
<div class="content-container mt-0 mt-lg-3 ms-lg-3 mx-xxl-3">
<%= render partial: 'projects/boards/summary' %>
</div>
Expand Down
38 changes: 38 additions & 0 deletions app/views/projects/tasks/_summary.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<section class="summary-panel">
<h4 class="header-underline">My Tasks
<span class="actions pt-1">
<div class="action">
<%= link_to project_tasks_path(current_project) do %>
<i class="fa-solid fa-tasks sidebar-link-icon"></i> All Tasks
<% end %>
</div>
</span>
</h4>
<% if @tasks.any? %>
<ul class="list-group tasks">
<% @tasks.first(TasksHelper::TASKS_LIMIT).each do |task| %>
<li class="d-flex flex-column list-group-item">
<%= link_to project_board_list_card_path(current_project, task.board, task.list, task), class: "list-group-item-action text-truncate" do %>
Copy link
Contributor

@caitmich caitmich Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to extend this to include other assignable record types in the future correct?

In order to leave this open for extension as easily as possible, we should check for record type before rendering the link_to and the <li> content since it will vary between record types

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a switch statement or a new helper method for this once we introduce another record type as a task

<%= task.name %>
<% end %>
<span class="small text-muted"><%= task.board.name %></span>
<%= due_date_badge(task) %>
</li>
<% end %>
</ul>

<p class="small mt-2 mb-0 text-center">&nbsp;
<% if overflow_tasks_count(@tasks) > 0 %>
+ <%= overflow_tasks_count(@tasks) %> more
<% end %>
</p>
<% else %>
<div class="mb-3">
<%= render 'shared/empty_state',
name: 'task',
docs_link: 'https://dradis.com/support/guides/projects/methodologies.html#task',
text: "Use this space to stay on top of tasks once they're assigned."
%>
</div>
<% end %>
</section>
53 changes: 53 additions & 0 deletions app/views/tasks/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<% content_for :title, 'My Tasks' %>

<div class="content-container mt-3">
<% if @tasks.any? %>
<table class="table table-striped align-middle"
data-behavior="dradis-datatable"
data-default-columns="<%= @default_columns %>"
data-local-storage-key="<%= @local_storage_key %>">
<thead>
<tr>
<th>Title</th>
<% unless params[:project_id] %>
<th>Project</th>
<% end %>
<th>Methodology</th>
<th>List</th>
<th>Due Date</th>
<th>Assigned</th>
</tr>
</thead>
<tbody>
<% @tasks.each do |task| %>
<tr>
<td data-sort="<%= task.name %>">
<%= link_to task.name, project_board_list_card_path(task.project, task.board, task.list, task) %>
</td>
<% unless params[:project_id] %>
Comment on lines +26 to +27
Copy link
Contributor

@caitmich caitmich Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to the 2-controller comment above
#suggestion

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is still a basically identical view shared between layouts, I went with the notifications_controller approach which also shares a view between layouts. Happy to change this if you feel strongly about it.

<td data-sort="<%= task.project.name %>"><%= task.project.name %></td>
<% end %>
<td data-sort="<%= task.board.name %>"><%= task.board.name %></td>
<td data-sort="<%= task.list.name %>"><%= task.list.name %></td>
<td data-sort="<%= task.due_date || 'no-due-date' %>"><%= due_date_badge(task) %></td>
<td>
<ul class="d-flex flex-wrap m-0 p-0">
<% task.assignees.each do |assignee| %>
<li class="d-inline-flex" data-bs-toggle="tooltip" data-bs-placement="top" title="<%= assignee.name %>">
<%= avatar_image(assignee, size: 30) %>
</li>
<% end %>
</ul>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<%= render 'shared/empty_state',
name: 'task',
docs_link: 'https://dradis.com/support/guides/projects/methodologies.html#task',
text: "Use this space to stay on top of tasks once they're assigned."
%>
<% end %>
</div>
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@

resources :tags, except: [:show]

resources :tasks, only: :index

namespace :qa do
resources :issues, only: [:edit, :index, :show, :update], concerns: [:multiple_update, :previewable]
end
Expand Down
Loading
Loading