Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8d10241
Add Pagy dependency
myabc Jul 11, 2025
874bdeb
initializer
myabc Jul 11, 2025
2468d4d
Set up POC for infinite scrolling on AT (Messy Commit!)
akabiru Sep 18, 2025
447a45b
Add infinite scrolling stimulus controller
akabiru Oct 1, 2025
aabd15b
Abort infinite scrolling for 1 page collections
akabiru Oct 1, 2025
aa7901d
Preserve scroll during infinite scroll page render
akabiru Oct 1, 2025
4f47f31
Lower the intersection threshold to 25%
akabiru Oct 1, 2025
c1d070a
Extract `journal_sorting.{asc?,desc?}` inquiry
akabiru Oct 2, 2025
cab44aa
Specify scrollable container as the intended target whose bounding re…
akabiru Oct 2, 2025
05c20e4
Tidy up function calls including guard clauses
akabiru Oct 3, 2025
2484b48
Switch from infinite scrolling to managed lazy loaded pages
akabiru Oct 3, 2025
40404ab
Delay loading to allow rapid scrolling without triggering loads
akabiru Oct 6, 2025
79673c5
Support paginated deep linking of comments
akabiru Oct 6, 2025
45b4178
DRY up activity anchor url parsing
akabiru Oct 7, 2025
815beba
Prefer structured `URL` builder
akabiru Oct 7, 2025
406d83d
Decouple observer setup from scroll setup
akabiru Oct 7, 2025
13853fd
Re-initialize pagination JIT for tab replacement
akabiru Oct 7, 2025
2cffdfc
Fix timing issue in Activities#type_comment for lazy-loaded pages
akabiru Oct 7, 2025
448b328
Create enough comments > 1 page
akabiru Oct 7, 2025
790f9a4
Extract work package activity tab pagination
akabiru Oct 7, 2025
7940486
Add feature flag for activity tab lazy pagination
akabiru Oct 8, 2025
cfe3a2d
Group pagy with (legacy) will_paginate and remove version constraint
akabiru Oct 9, 2025
feb364e
Allow pagy options in paginator for overrides in test
akabiru Oct 9, 2025
12a5f55
Clarify scroll preservation behaviour based on UI layout sorting order
akabiru Oct 10, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/pullpreview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
echo "OPENPROJECT_ENTERPRISE__CHARGEBEE__SITE=openproject-enterprise-test" >> .env.pullpreview
echo "OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST=https://start.openproject-edge.com" >> .env.pullpreview
echo "OPENPROJECT_FEATURE_BLOCK_NOTE_EDITOR=true" >> .env.pullpreview
echo "OPENPROJECT_FEATURE_WP_ACTIVITY_TAB_LAZY_PAGINATION_ACTIVE=true" >> .env.pullpreview
- name: Boot as BIM edition
if: contains(github.ref, 'bim/') || contains(github.head_ref, 'bim/')
run: |
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ gem "request_store", "~> 1.7.0"
gem "warden", "~> 1.2"
gem "warden-basic_auth", "~> 0.2.1"

gem "pagy"
gem "will_paginate", "~> 4.0.0"

gem "friendly_id", "~> 5.5.0"
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ GEM
ostruct (0.6.3)
ox (2.14.23)
bigdecimal (>= 3.0)
pagy (9.4.0)
paper_trail (16.0.0)
activerecord (>= 6.1)
request_store (~> 1.4)
Expand Down Expand Up @@ -1710,6 +1711,7 @@ DEPENDENCIES
opentelemetry-sdk (~> 1.9)
overviews!
ox
pagy
paper_trail (~> 16.0.0)
parallel_tests (~> 4.0)
pdf-inspector (~> 1.2)
Expand Down Expand Up @@ -2147,6 +2149,7 @@ CHECKSUMS
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
overviews (1.0.0)
ox (2.14.23) sha256=4a9aedb4d6c78c5ebac1d7287dc7cc6808e14a8831d7adb727438f6a1b461b66
pagy (9.4.0) sha256=db3f2e043f684155f18f78be62a81e8d033e39b9f97b1e1a8d12ad38d7bce738
paper_trail (16.0.0) sha256=e9b9f0fb1b8b590c8231cfa931b282ba92f90e066e393930a5e1c61ae4c5019d
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++

module WorkPackages
module ActivitiesTab
module JournalSortingInquirable
extend ActiveSupport::Concern

def journal_sorting
ActiveSupport::StringInquirer
.new(User.current.preference&.comments_sorting || OpenProject::Configuration.default_comment_sort_order)
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def auto_scrolling_stimulus_controller(suffix = nil) = "#{stimulus_controller_na
def editor_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--editor#{suffix}"
def index_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--index#{suffix}"
def internal_comment_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--internal-comment#{suffix}"
def lazy_page_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--lazy-page#{suffix}"
def polling_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--polling#{suffix}"
def stems_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--stems#{suffix}"
def quote_comments_stimulus_controller(suffix = nil) = "#{stimulus_controller_namespace}--quote-comment#{suffix}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@
classes: "work-packages-activities-tab-index-component--journals-container work-packages-activities-tab-index-component--journals-container_with-initial-input-compensation",
data: { "work-packages--activities-tab--index-target": "journalsContainer" }
) do
render(
WorkPackages::ActivitiesTab::Journals::IndexComponent.new(work_package:, filter:)
)
render(list_journals_component)
end

if adding_comment_allowed?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def self.index_content_wrapper_key = WorkPackages::ActivitiesTab::StimulusContro
def self.add_comment_wrapper_key = "work-packages-activities-tab-add-comment-component"
delegate :index_content_wrapper_key, :add_comment_wrapper_key, to: :class

def list_journals_component
WorkPackages::ActivitiesTab::Journals::IndexComponent.new(work_package:, filter:)
end

private

attr_reader :work_package, :filter, :last_server_timestamp, :deferred
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<%=
component_wrapper(class: "work-packages-activities-tab-journals-index-component") do
flex_layout(data: { test_selector: "op-wp-journals-#{filter}-#{journal_sorting}" }) do |journals_index_wrapper_container|
journals_index_wrapper_container.with_row(
classes: "work-packages-activities-tab-journals-index-component--journals-inner-container",
mb: inner_container_margin_bottom
) do
flex_layout(
data: { test_selector: "op-wp-journals-container" }
) do |journals_index_container|
if empty_state?
journals_index_container.with_row(mt: 2, mb: 3) do
render(WorkPackages::ActivitiesTab::Journals::EmptyComponent.new)
end
else
journals_index_container.with_row(id: insert_target_modifier_id) do
pages.map { concat render(it) }
end
end
end
end

unless empty_state?
journals_index_wrapper_container
.with_row(classes: "work-packages-activities-tab-journals-index-component--stem-connection")
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++

module WorkPackages
module ActivitiesTab
module Journals
class LazyIndexComponent < IndexComponent
def initialize(work_package:, journals:, paginator:, filter: :all)
super(work_package:, filter:, deferred: false)

@journals = journals
@paginator = paginator
end

def pages
current_page = paginator.page

result = (1..paginator.pages).map do |page|
if page == current_page
page_component(page)
else
lazy_page_component(page)
end
end

result.tap { it.reverse! if journal_sorting.asc? && paginator.pages > 1 }
end

def page_component(page)
WorkPackages::ActivitiesTab::Journals::PageComponent
.new(journals:, emoji_reactions: wp_journals_grouped_emoji_reactions, page:, filter:)
end

def lazy_page_component(page)
WorkPackages::ActivitiesTab::Journals::LazyPageComponent.new(work_package:, page:)
end

def self.insert_target_modifier_id = "#{wrapper_key}-pages"
delegate :insert_target_modifier_id, to: :class

private

attr_reader :journals, :paginator

def insert_target_modified?
true
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<%# -- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.

OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

See COPYRIGHT and LICENSE files for more details.

++# %>

<%=
component_wrapper(data: wrapper_data_attributes) do
flex_layout(
class: "work-packages-activities-tab-journals-item-component",
bg: :default
) do |skeleton|
5.times do
skeleton.with_row(mb: 2) do
render(Primer::Alpha::SkeletonBox.new(width: "25%", height: "40px"))
end

skeleton.with_row(mb: 3) do
render(Primer::Alpha::SkeletonBox.new(width: "100%", height: "150px"))
end
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

module WorkPackages
module ActivitiesTab
module Journals
class LazyPageComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include WorkPackages::ActivitiesTab::StimulusControllers

def initialize(work_package:, page:)
super
@work_package = work_package
@page = page
end

def self.wrapper_key
WorkPackages::ActivitiesTab::Journals::PageComponent.wrapper_key
end

def wrapper_uniq_by
page
end

private

attr_reader :work_package, :page

def wrapper_data_attributes
{
controller: lazy_page_stimulus_controller,
lazy_page_stimulus_controller("-insert-target-id-value") => wrapper_key,
lazy_page_stimulus_controller("-page-value") => page,
lazy_page_stimulus_controller("-url-value") => page_streams_url,
lazy_page_stimulus_controller("-is-loaded-value") => false
}
end

def page_streams_url
page_streams_work_package_activities_path(work_package, format: :turbo_stream)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<%=
component_wrapper do
flex_layout do |flex_container|
journals.each do |record|
flex_container.with_row do
if record.is_a?(Changeset)
render(WorkPackages::ActivitiesTab::Journals::RevisionComponent.new(changeset: record, filter:))
else
render(
WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal: record, filter:,
grouped_emoji_reactions: emoji_reactions.fetch(record.id, {})
)
)
end
end
end
end
end
%>
Loading
Loading