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

Feature/40437 emoji reactions to work package comments #16020

Merged
merged 55 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
aed51f9
[#40437] Easy emoji reactions, e.g "thumbs up" on activity entries
jjabari-op Jun 24, 2024
53aa61c
introduce simple emoji reactions
jjabari-op Jul 1, 2024
2b27488
Merge branch 'feature/54733-primerise-the-activity-panel' into epic/4…
jjabari-op Jul 2, 2024
9e73506
implemented feedback provided by @psatyal and sorting fix
jjabari-op Jul 2, 2024
7da1bf5
avoid n+1 queries
jjabari-op Jul 2, 2024
3c9da77
fixing focus loss on mobile
jjabari-op Jul 3, 2024
dbae3ac
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Jul 10, 2024
81d62c3
merged and fixed base branch
jjabari-op Jul 10, 2024
ee29224
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Jul 10, 2024
19f3669
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Sep 17, 2024
71966de
fixed comment rendering
jjabari-op Sep 17, 2024
6f0dc4a
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Sep 26, 2024
560d7f9
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Oct 1, 2024
e86f322
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Oct 7, 2024
86d5656
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Oct 8, 2024
338174f
rubocop auto-amendments
akabiru Oct 7, 2024
0f17cee
applied invisible style to reactions where the current user itself ha…
jjabari-op Oct 8, 2024
acc920a
Merge branch 'feature/54733-primerise-the-activity-panel' into featur…
jjabari-op Oct 8, 2024
14108bb
fixed merge
jjabari-op Oct 8, 2024
6ef94ef
tests[Op#40437]: begin emoji reactions feature tests 👍🏾
akabiru Oct 8, 2024
a3d0747
feat[Op#40437]: Introduce view only reactions covered by feature specs
akabiru Oct 10, 2024
31e75b3
chore[Op#40437]: set view only reactions as invisible
akabiru Oct 11, 2024
9751869
chore[Op#40437]: correct test example
akabiru Oct 14, 2024
6094509
tests[Op#40437]: add emoji reaction contract specs
akabiru Oct 14, 2024
7392afa
tests[Op#40437]: add service specs
akabiru Oct 14, 2024
f777c52
tests[Op#40437]: add model specs
akabiru Oct 14, 2024
c09cffc
fix[Op#58142]: render error flash in error case
akabiru Oct 14, 2024
371f2f3
Merge branch 'dev' into feature/40437-emoji-reactions-to-work-package…
akabiru Oct 17, 2024
c7b5e9e
Merge branch 'dev' into feature/40437-emoji-reactions-to-work-package…
akabiru Oct 18, 2024
994be68
chore[Op#40437]: store emoji in uncode format backed by string enum
akabiru Oct 21, 2024
b29c277
feat[Op#58250]: Store reactions as indexed enums later mapped to unicode
akabiru Oct 22, 2024
5f57d6f
fix[Op#40437]: Update reactions text
akabiru Oct 22, 2024
7b632c3
chore[Op#40437]: remove unused constant, touch up formatting
akabiru Oct 22, 2024
9f08b83
feat[Op#40437]: Introduce single emoji reactions aggregation query to…
akabiru Oct 23, 2024
29f0155
feat[Op#40437]: define emoji reactions aggregation in index component
akabiru Oct 24, 2024
3856149
fix[Op#40437]: Update streams to use grouped emoji reactions query
akabiru Oct 24, 2024
65c2aa2
chore[Op#40437]: add timestamp spec
akabiru Oct 24, 2024
2a0b25f
fix[Op#40437]: Safely parse last updated at timestamp
akabiru Oct 24, 2024
340895a
feat[Op#40437]: update tab controller to pick out emoji reactions
akabiru Oct 24, 2024
b08ed5b
fix[Op#40437]: set emoji reactions correctly
akabiru Oct 24, 2024
30b831f
chore[Op#40437]: ensure emoji reactions returns empty hash when no re…
akabiru Oct 25, 2024
e6aed59
fix[Op#40437]: ensure emojis and user reactions are ordered in asc order
akabiru Oct 25, 2024
fb419dd
chore[Op#40437]: remove redundant reactions single update, rely on fu…
akabiru Oct 25, 2024
232df80
fix[Op#40437]: remove support for emoji reactions timeslicing
akabiru Oct 25, 2024
e22d22d
Merge branch 'dev' into feature/40437-emoji-reactions-to-work-package…
akabiru Oct 25, 2024
a302f3f
fix[Op#40437]: Reinstate emoji reactions streams
akabiru Oct 25, 2024
80cea6e
chore[Op#40437]: improve var naming; make less generic
akabiru Oct 25, 2024
d57d69f
fix[Op#40437]: ensure all wp journals are updated including removals
akabiru Oct 25, 2024
2a22e8b
chore[Op#40437]: use meaningful var names
akabiru Oct 25, 2024
e58b9bd
chore[Op#40437]: favor contract validations as opposed to AR validations
akabiru Oct 25, 2024
54c0ff6
chore[Op#40437]: remap model validations names to be more suitable
akabiru Oct 25, 2024
2627f2c
fix[Op#40437]: switch to i18n pluralization
akabiru Oct 25, 2024
88047f1
chore[Op#40437]: humanize reaction instead
akabiru Oct 25, 2024
a6427d3
fix[Op#40437]: Use fully qualified ❤️
akabiru Oct 25, 2024
840deff
chore[Op#40437]: extend wait time for journal change
akabiru Oct 25, 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
1 change: 1 addition & 0 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "work_packages/activities_tab/journals/index_component"
@import "work_packages/activities_tab/journals/item_component"
@import "work_packages/activities_tab/journals/item_component/details"
@import "work_packages/activities_tab/journals/item_component/reactions"
@import "shares/modal_body_component"
@import "shares/invite_user_form_component"
@import "work_packages/details/tab_component"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
<%=
component_wrapper(class: "work-packages-activities-tab-journals-index-component") do
flex_layout 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(id: insert_target_modifier_id, data: { "test_selector": "op-wp-journals-container" }) do |journals_index_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(id: insert_target_modifier_id,
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()
WorkPackages::ActivitiesTab::Journals::EmptyComponent.new
)
end
end

journals.each do |journal|
journals_index_container.with_row do
render(
WorkPackages::ActivitiesTab::Journals::ItemComponent.new(journal:, filter:)
)
render(WorkPackages::ActivitiesTab::Journals::ItemComponent.new(
journal:, filter:,
grouped_emoji_reactions: wp_journals_grouped_emoji_reactions[journal.id]
))
end
end
end
end
journals_index_wrapper_container.with_row(classes: "work-packages-activities-tab-journals-index-component--stem-connection") unless empty_state? || journal_sorting == "desc"

unless empty_state? || journal_sorting_desc?
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
Expand Up @@ -59,6 +59,10 @@ def journal_sorting
User.current.preference&.comments_sorting || "desc"
end

def journal_sorting_desc?
journal_sorting == "desc"
end

def journals
work_package.journals.includes(:user, :notifications).reorder(version: journal_sorting)
end
Expand All @@ -67,12 +71,16 @@ def journal_with_notes
journals.where.not(notes: "")
end

def wp_journals_grouped_emoji_reactions
@wp_journals_grouped_emoji_reactions ||= Journal.grouped_work_package_journals_emoji_reactions(work_package)
end

def empty_state?
filter == :only_comments && journal_with_notes.empty?
end

def inner_container_margin_bottom
if journal_sorting == "desc"
if journal_sorting_desc?
3
else
0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
<%=
component_wrapper(data: wrapper_data_attributes, class: "work-packages-activities-tab-journals-item-component") do
flex_layout(data: { "test_selector": "op-wp-journal-entry-#{journal.id}" }) do |journal_container|
flex_layout(data: { test_selector: "op-wp-journal-entry-#{journal.id}" }) do |journal_container|
if show_comment_container?
journal_container.with_row do
render(border_box_container(
id: "activity-anchor-#{journal.version}",
padding: :condensed,
"aria-label": I18n.t("activities.work_packages.activity_tab.commented")
)) do |border_box_component|
id: "activity-anchor-#{journal.version}",
padding: :condensed,
"aria-label": I18n.t("activities.work_packages.activity_tab.commented")
)) do |border_box_component|
border_box_component.with_header(px: 2, py: 1, data: { test_selector: "op-journal-notes-header" }) do
flex_layout(align_items: :center, justify_content: :space_between) do |header_container|
header_container.with_column(flex_layout: true, classes: "work-packages-activities-tab-journals-item-component--header-start-container ellipsis") do |header_start_container|
header_container.with_column(flex_layout: true,
classes: "work-packages-activities-tab-journals-item-component--header-start-container ellipsis") do |header_start_container|
header_start_container.with_column(mr: 2) do
render Users::AvatarComponent.new(user: journal.user, show_name: false, size: :mini)
end
header_start_container.with_column(mr: 1, flex_layout: true, classes: "work-packages-activities-tab-journals-item-component--user-name-container hidden-for-desktop") do |user_name_container|
header_start_container.with_column(mr: 1, flex_layout: true,
classes: "work-packages-activities-tab-journals-item-component--user-name-container hidden-for-desktop") do |user_name_container|
user_name_container.with_row(classes: "work-packages-activities-tab-journals-item-component--user-name ellipsis") do
truncated_user_name(journal.user)
end
user_name_container.with_row do
render(Primer::Beta::Text.new(font_size: :small, color: :subtle, mt: 1)) { format_time(journal.created_at) }
end
end
end
header_start_container.with_column(mr: 1, classes: "work-packages-activities-tab-journals-item-component--user-name ellipsis hidden-for-mobile") do
header_start_container.with_column(mr: 1,
classes: "work-packages-activities-tab-journals-item-component--user-name ellipsis hidden-for-mobile") do
truncated_user_name(journal.user)
end
header_start_container.with_column(mr: 1, classes: "hidden-for-mobile") do
Expand All @@ -33,32 +36,33 @@
if has_unread_notifications?
header_end_container.with_column(mr: 2, pt: 1) do
render(Primer::Beta::Octicon.new(
:"dot-fill", # color is set via CSS as requested by UI/UX Team
classes: "work-packages-activities-tab-journals-item-component--notification-dot-icon",
size: :medium,
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
))
:"dot-fill", # color is set via CSS as requested by UI/UX Team
classes: "work-packages-activities-tab-journals-item-component--notification-dot-icon",
size: :medium,
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
))
end
end
header_end_container.with_column do
render(Primer::Beta::Link.new(
href: "#",
scheme: :secondary,
underline: false,
font_size: :small,
data: {
turbo: false,
action: "click->work-packages--activities-tab--index#setAnchor:prevent",
"work-packages--activities-tab--index-id-param": journal.version
}
)) do
href: "#",
scheme: :secondary,
underline: false,
font_size: :small,
data: {
turbo: false,
action: "click->work-packages--activities-tab--index#setAnchor:prevent",
"work-packages--activities-tab--index-id-param": journal.version
}
)) do
"##{journal.version}"
end
end
header_end_container.with_column(ml: 1, classes: "work-packages-activities-tab-journals-item-component--action-menu") do
render(Primer::Alpha::ActionMenu.new(data: { "test_selector": "op-wp-journal-#{journal.id}-action-menu" })) do |menu|
header_end_container.with_column(ml: 1,
classes: "work-packages-activities-tab-journals-item-component--action-menu") do
render(Primer::Alpha::ActionMenu.new(data: { test_selector: "op-wp-journal-#{journal.id}-action-menu" })) do |menu|
menu.with_show_button(icon: "kebab-horizontal",
'aria-label': I18n.t(:button_actions),
"aria-label": I18n.t(:button_actions),
scheme: :invisible)
copy_url_action_item(menu)
edit_action_item(menu) if allowed_to_edit?
Expand All @@ -70,24 +74,28 @@
end
border_box_component.with_body(
classes: "work-packages-activities-tab-journals-item-component--journal-notes-body",
data: { "test_selector": "op-journal-notes-body" }
data: { test_selector: "op-journal-notes-body" }
) do
unless noop?
if noop?
render(Primer::Beta::Text.new(font_style: :italic, color: :subtle, mt: 1)) do
I18n.t(:"journals.changes_retracted")
end
else
case state
when :show
render(WorkPackages::ActivitiesTab::Journals::ItemComponent::Show.new(journal:, filter:))
render(WorkPackages::ActivitiesTab::Journals::ItemComponent::Show.new(journal:, filter:,
grouped_emoji_reactions:))
when :edit
render(WorkPackages::ActivitiesTab::Journals::ItemComponent::Edit.new(journal:, filter:))
end
else
render(Primer::Beta::Text.new(font_style: :italic, color: :subtle, mt: 1)) { I18n.t(:"journals.changes_retracted") }
end
end
end
end
end
journal_container.with_row do
render(WorkPackages::ActivitiesTab::Journals::ItemComponent::Details.new(journal:, has_unread_notifications: notification_on_details?, filter:))
render(WorkPackages::ActivitiesTab::Journals::ItemComponent::Details.new(journal:,
has_unread_notifications: notification_on_details?, filter:))
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,18 @@ class ItemComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

def initialize(journal:, filter:, state: :show)
def initialize(journal:, filter:, grouped_emoji_reactions:, state: :show)
super

@journal = journal
@state = state
@filter = filter
@grouped_emoji_reactions = grouped_emoji_reactions
@state = state
end

private

attr_reader :journal, :state, :filter
attr_reader :journal, :state, :filter, :grouped_emoji_reactions

def wrapper_uniq_by
journal.id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<%=
component_wrapper do
if journal.available_emoji_reactions.any?
render(Primer::Alpha::Overlay.new(
title: I18n.t("reactions.action_title"),
padding: :condensed,
anchor_side: :outside_top,
visually_hide_title: true
)) do |overlay|
overlay.with_show_button(
icon: "smiley",
"aria-label": I18n.t("reactions.add_reaction"),
title: I18n.t("reactions.add_reaction"),
mr: 2,
test_selector: "add-reactions-button"
)

overlay.with_body(pt: 2) do
flex_layout do |add_reactions_container|
journal.available_emoji_reactions.each do |emoji, reaction|
add_reactions_container.with_column(mr: 2) do
render(Primer::Beta::Button.new(
scheme: :invisible,
id: "#{journal.id}-#{reaction}",
tag: :a,
href: toggle_reaction_work_package_activity_path(journal.journable.id, id: journal.id, reaction:),
data: { "turbo-stream": true, "turbo-method": :put },
"aria-label": I18n.t("reactions.react_with", reaction: reaction.to_s.humanize(capitalize: false))
)) do
emoji
end
end
end
end
end
end
end
end
%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 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 ItemComponent::AddReactions < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable

def initialize(journal:)
super

@journal = journal
end

def render?
User.current.allowed_in_work_package?(:add_work_package_notes, work_package)
end

private

attr_reader :journal

def work_package = journal.journable
def wrapper_uniq_by = journal.id
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<%=
component_wrapper do
if grouped_emoji_reactions.present?
flex_layout(test_selector: "emoji-reactions") do |reactions_container|
grouped_emoji_reactions.each do |reaction, data|
reactions_container.with_column(mr: 2) do
render(Primer::Beta::Button.new(
scheme: button_scheme(data[:users]),
color: :default,
bg: counter_color(data[:users]),
id: "#{journal.id}-#{reaction}",
test_selector: "reaction-#{reaction}",
tag: :a,
href: href(reaction:),
data: { turbo_stream: true, turbo_method: :put },
aria: { label: aria_label_text(reaction, data[:users]) },
disabled: current_user_cannot_react?,
classes: "op-reactions-button"
)) do |button|
button.with_tooltip(text: number_of_user_reactions_text(data[:users]),
test_selector: "reaction-tooltip-#{reaction}") do
button.with_icon(EmojiReactions.emoji(reaction), size: :small)
end
"#{EmojiReaction.emoji(reaction)} #{data[:count]}"
end
end
end
end
end
end
%>
Loading