From eba749a1932189d049c8e9f74ca623623b7f76e3 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 19 Nov 2025 12:48:36 -0600 Subject: [PATCH 1/7] Expand activity columns --- app/assets/stylesheets/events.css | 44 +++++++++++++++++++ .../events/day_timeline/columns_controller.rb | 22 ++++++++++ app/models/user/day_timeline/column.rb | 4 +- .../events/day_timeline/_column.html.erb | 11 ++++- .../events/day_timeline/columns/show.html.erb | 25 +++++++++++ config/routes.rb | 3 ++ 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 app/controllers/events/day_timeline/columns_controller.rb create mode 100644 app/views/events/day_timeline/columns/show.html.erb diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 20fb53217..1dbdf0997 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -19,6 +19,33 @@ --events-day-header-height: 1.75rem; } + .events--grid { + --events-grid-gap: 1rem; + --events-grid-columns: 1; + + container-type: inline-size; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: var(--events-grid-gap); + justify-content: center; + margin: var(--block-space) auto; + max-inline-size: var(--main-width); + + @media (min-width: 640px) { + --events-grid-columns: 2; + } + + @media (min-width: 960px) { + --events-grid-columns: 3; + } + + .event { + inline-size: calc((100% - var(--events-grid-gap) * (var(--events-grid-columns) - 1) ) / var(--events-grid-columns)) !important; + margin: 0 !important; + } + } + .events__activity-summary { border: solid var(--color-ink-lighter); border-width: 1px 1px 0 1px; @@ -206,6 +233,23 @@ padding: var(--block-space-half) var(--inline-space); } + .events__maximize-button { + inset: calc(var(--events-gap) * 3) 0 auto auto; + outline-offset: -2px; + position: absolute; + transform: translateY(-10%); + z-index: 1; + + @media (any-hover: hover ) { + opacity: 0; + + .events__column-header:hover &, + &:focus-visible { + opacity: 1; + } + } + } + .events__time-block { align-content: end; display: grid; diff --git a/app/controllers/events/day_timeline/columns_controller.rb b/app/controllers/events/day_timeline/columns_controller.rb new file mode 100644 index 000000000..362521634 --- /dev/null +++ b/app/controllers/events/day_timeline/columns_controller.rb @@ -0,0 +1,22 @@ +class Events::DayTimeline::ColumnsController < ApplicationController + include DayTimelinesScoped + + def show + @column = column_for_id(params[:id]) + fresh_when @day_timeline + end + + private + def column_for_id(id) + case id.to_s.downcase + when "added", "1" + @day_timeline.added_column + when "updated", "2" + @day_timeline.updated_column + when "closed", "3" + @day_timeline.closed_column + else + head :not_found + end + end +end diff --git a/app/models/user/day_timeline/column.rb b/app/models/user/day_timeline/column.rb index fe42c68dc..0331a83ff 100644 --- a/app/models/user/day_timeline/column.rb +++ b/app/models/user/day_timeline/column.rb @@ -1,7 +1,7 @@ class User::DayTimeline::Column include ActionView::Helpers::TagHelper, ActionView::Helpers::OutputSafetyHelper, TimeHelper - attr_reader :index + attr_reader :index, :base_title, :day_timeline, :events def initialize(day_timeline, base_title, index, events) @day_timeline = day_timeline @@ -30,8 +30,6 @@ class User::DayTimeline::Column end private - attr_reader :base_title, :day_timeline, :events - def limited_events @limited_events ||= events.limit(100).load end diff --git a/app/views/events/day_timeline/_column.html.erb b/app/views/events/day_timeline/_column.html.erb index 6e036a412..a9e14c558 100644 --- a/app/views/events/day_timeline/_column.html.erb +++ b/app/views/events/day_timeline/_column.html.erb @@ -1,5 +1,14 @@
-

<%= column.title %>

+

+ <%= column.title %> + + <% if column.events_by_hour.any? %> + <%= link_to events_day_timeline_column_path(id: column.index, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless" do %> + <%= icon_tag "grid", class: "translucent" %> + Expand column + <% end %> + <% end %> +

<% column.events_by_hour.each do |hour, events| %> <%= events_at_hour_container(column, hour) do %> <%= render partial: "events/event", collection: events, cached: true %> diff --git a/app/views/events/day_timeline/columns/show.html.erb b/app/views/events/day_timeline/columns/show.html.erb new file mode 100644 index 000000000..56a7d3c4c --- /dev/null +++ b/app/views/events/day_timeline/columns/show.html.erb @@ -0,0 +1,25 @@ +<% @page_title = @column.base_title %> + +<% content_for :header do %> +
+ <%= link_to events_path, class: "btn btn--back", data: { controller: "hotkey", action: "keydown.left@document->hotkey#click" } do %> + + <%= icon_tag "arrow-left" %> + Back to Actvity + + + <% end %> +
+ +

+ <%= @column.title %> +

+<% end %> + +
+ <% @column.events_by_hour.each do |hour, events| %> + <% if events.any? %> + <%= render partial: "events/event", collection: events, cached: true %> + <% end %> + <% end %> +
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index e165802e0..85b26c17a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -130,6 +130,9 @@ Rails.application.routes.draw do resources :events, only: :index namespace :events do resources :days + namespace :day_timeline do + resources :columns, only: :show + end end resources :qr_codes From d97a7b9be80d80c3f920453076dbd9873c13688e Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Wed, 19 Nov 2025 13:18:24 -0600 Subject: [PATCH 2/7] Break out of frame --- app/views/events/day_timeline/_column.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/day_timeline/_column.html.erb b/app/views/events/day_timeline/_column.html.erb index a9e14c558..812b27827 100644 --- a/app/views/events/day_timeline/_column.html.erb +++ b/app/views/events/day_timeline/_column.html.erb @@ -3,7 +3,7 @@ <%= column.title %> <% if column.events_by_hour.any? %> - <%= link_to events_day_timeline_column_path(id: column.index, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless" do %> + <%= link_to events_day_timeline_column_path(id: column.index, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "grid", class: "translucent" %> Expand column <% end %> From 133a17a75e2ca7aea614c085254016787daa556e Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Thu, 20 Nov 2025 11:43:17 -0600 Subject: [PATCH 3/7] Just pass the column name string, no need to both with numbers --- .../events/day_timeline/columns_controller.rb | 11 +---------- app/views/events/day_timeline/_column.html.erb | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/controllers/events/day_timeline/columns_controller.rb b/app/controllers/events/day_timeline/columns_controller.rb index 362521634..6cc00c08c 100644 --- a/app/controllers/events/day_timeline/columns_controller.rb +++ b/app/controllers/events/day_timeline/columns_controller.rb @@ -8,15 +8,6 @@ class Events::DayTimeline::ColumnsController < ApplicationController private def column_for_id(id) - case id.to_s.downcase - when "added", "1" - @day_timeline.added_column - when "updated", "2" - @day_timeline.updated_column - when "closed", "3" - @day_timeline.closed_column - else - head :not_found - end + @day_timeline.try("#{id}_column") or head :not_found end end diff --git a/app/views/events/day_timeline/_column.html.erb b/app/views/events/day_timeline/_column.html.erb index 812b27827..ff8285b36 100644 --- a/app/views/events/day_timeline/_column.html.erb +++ b/app/views/events/day_timeline/_column.html.erb @@ -3,7 +3,7 @@ <%= column.title %> <% if column.events_by_hour.any? %> - <%= link_to events_day_timeline_column_path(id: column.index, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= link_to events_day_timeline_column_path(id: column.base_title.downcase, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "grid", class: "translucent" %> Expand column <% end %> From b0d732675c61d32b90959a339d4e82c95ef98de8 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Fri, 21 Nov 2025 10:32:32 +0100 Subject: [PATCH 4/7] Extract partial --- app/views/events/day_timeline/columns/_events.html.erb | 7 +++++++ app/views/events/day_timeline/columns/show.html.erb | 8 +------- 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 app/views/events/day_timeline/columns/_events.html.erb diff --git a/app/views/events/day_timeline/columns/_events.html.erb b/app/views/events/day_timeline/columns/_events.html.erb new file mode 100644 index 000000000..ac14102af --- /dev/null +++ b/app/views/events/day_timeline/columns/_events.html.erb @@ -0,0 +1,7 @@ +
+ <% column.events_by_hour.each do |hour, events| %> + <% if events.any? %> + <%= render partial: "events/event", collection: events, cached: true %> + <% end %> + <% end %> +
diff --git a/app/views/events/day_timeline/columns/show.html.erb b/app/views/events/day_timeline/columns/show.html.erb index 56a7d3c4c..3ef8e09e2 100644 --- a/app/views/events/day_timeline/columns/show.html.erb +++ b/app/views/events/day_timeline/columns/show.html.erb @@ -16,10 +16,4 @@ <% end %> -
- <% @column.events_by_hour.each do |hour, events| %> - <% if events.any? %> - <%= render partial: "events/event", collection: events, cached: true %> - <% end %> - <% end %> -
\ No newline at end of file +<%= render "events/day_timeline/columns/events", column: @column %> \ No newline at end of file From 93ee8899a05cd36195ff0c15fe9c784a03c13102 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Fri, 21 Nov 2025 10:38:16 +0100 Subject: [PATCH 5/7] More robust implementation to prevent method injection attacks --- .../events/day_timeline/columns_controller.rb | 14 +++++++-- .../day_timeline/columns_controller_test.rb | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 test/controllers/events/day_timeline/columns_controller_test.rb diff --git a/app/controllers/events/day_timeline/columns_controller.rb b/app/controllers/events/day_timeline/columns_controller.rb index 6cc00c08c..db30c44d1 100644 --- a/app/controllers/events/day_timeline/columns_controller.rb +++ b/app/controllers/events/day_timeline/columns_controller.rb @@ -1,13 +1,21 @@ class Events::DayTimeline::ColumnsController < ApplicationController include DayTimelinesScoped + before_action :ensure_valid_column + before_action :set_column + def show - @column = column_for_id(params[:id]) fresh_when @day_timeline end private - def column_for_id(id) - @day_timeline.try("#{id}_column") or head :not_found + VALID_COLUMNS = %w[ added updated closed ] + + def ensure_valid_column + head :not_found unless VALID_COLUMNS.include?(params[:id]) + end + + def set_column + @column = @day_timeline.public_send("#{params[:id]}_column") end end diff --git a/test/controllers/events/day_timeline/columns_controller_test.rb b/test/controllers/events/day_timeline/columns_controller_test.rb new file mode 100644 index 000000000..9ee459cbd --- /dev/null +++ b/test/controllers/events/day_timeline/columns_controller_test.rb @@ -0,0 +1,30 @@ +require "test_helper" + +class Events::DayTimeline::ColumnsControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in_as :kevin + end + + test "show added column" do + get events_day_timeline_column_path("added") + assert_response :success + assert_select "h1", text: /Added/ + end + + test "show updated column" do + get events_day_timeline_column_path("updated") + assert_response :success + assert_select "h1", text: /Updated/ + end + + test "show closed column" do + get events_day_timeline_column_path("closed") + assert_response :success + assert_select "h1", text: /Closed/ + end + + test "show returns not found for invalid column" do + get events_day_timeline_column_path("invalid") + assert_response :not_found + end +end From 49e801fdb1a994cf38db9d712c883d9eb0c214ee Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Fri, 21 Nov 2025 10:40:37 +0100 Subject: [PATCH 6/7] Just the date makes for better-looking URLs --- app/views/events/day_timeline/_column.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/events/day_timeline/_column.html.erb b/app/views/events/day_timeline/_column.html.erb index ff8285b36..23bf7e34e 100644 --- a/app/views/events/day_timeline/_column.html.erb +++ b/app/views/events/day_timeline/_column.html.erb @@ -3,7 +3,7 @@ <%= column.title %> <% if column.events_by_hour.any? %> - <%= link_to events_day_timeline_column_path(id: column.base_title.downcase, day: column.day_timeline.day.iso8601), class: "events__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> + <%= link_to events_day_timeline_column_path(id: column.base_title.downcase, day: column.day_timeline.day.to_date), class: "events__maximize-button btn btn--circle txt-x-small borderless", data: { turbo_frame: "_top" } do %> <%= icon_tag "grid", class: "translucent" %> Expand column <% end %> From 6e20c8c68fc6f9c9be769b83e3726a262336e2ad Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Fri, 21 Nov 2025 10:42:08 +0100 Subject: [PATCH 7/7] Add brakeman rule --- config/brakeman.ignore | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 6c2eb7f67..d51b4a4f3 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -1,5 +1,28 @@ { "ignored_warnings": [ + { + "warning_type": "Dangerous Send", + "warning_code": 23, + "fingerprint": "746ab8227e04231f0002e099e2f670bcc2b8927af85cdc69fab1440002d5c2a6", + "check_name": "Send", + "message": "User controlled method execution", + "file": "app/controllers/events/day_timeline/columns_controller.rb", + "line": 19, + "link": "https://brakemanscanner.org/docs/warning_types/dangerous_send/", + "code": "Current.user.timeline_for(day, :filter => ((Current.user.filters.find(params[:filter_id]) or Current.user.filters.from_params(filter_params)))).public_send(\"#{params[:id]}_column\")", + "render_path": null, + "location": { + "type": "method", + "class": "Events::DayTimeline::ColumnsController", + "method": "set_column" + }, + "user_input": "params[:id]", + "confidence": "High", + "cwe_id": [ + 77 + ], + "note": "" + }, { "warning_type": "Mass Assignment", "warning_code": 70, @@ -47,5 +70,5 @@ "note": "" } ], - "brakeman_version": "7.1.1" + "brakeman_version": "7.1.0" }