Skip to content

Commit

Permalink
implemented reminder creation/update modal on workpackag page and bas…
Browse files Browse the repository at this point in the history
…ic spec coverage
  • Loading branch information
jjabari-op committed Dec 3, 2024
1 parent b9f09cd commit 8c08239
Show file tree
Hide file tree
Showing 33 changed files with 1,343 additions and 3 deletions.
1 change: 1 addition & 0 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@import "work_packages/activities_tab/journals/item_component/add_reactions"
@import "work_packages/activities_tab/journals/item_component/reactions"
@import "shares/modal_body_component"
@import "work_packages/reminder/modal_body_component"
@import "shares/invite_user_form_component"
@import "work_packages/details/tab_component"
@import "work_packages/progress/modal_body_component"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<%= component_wrapper(tag: "turbo-frame") do %>
<%= primer_form_with(
model: reminder,
url: submit_path,
id: FORM_ID
) do |f| %>
<%= flex_layout(classes: "reminder-modal-body--form-flex-container") do |form_flex_container| %>
<%= form_flex_container.with_row do %>
<%= render(WorkPackages::Reminder::RemindAtDate.new(f, initial_value: remind_at_date_initial_value)) %>
<% end %>
<%= form_flex_container.with_row do %>
<%= render(WorkPackages::Reminder::RemindAtTime.new(f, initial_value: remind_at_time_initial_value)) %>
<% end %>
<%= form_flex_container.with_row do %>
<%= render(WorkPackages::Reminder::Note.new(f)) %>
<% end %>
<% if errors.present? %>
<%= form_flex_container.with_row(flex_layout: true) do |errors_container| %>
<% errors.full_messages.each do |error_message| %>
<%= errors_container.with_row(flex_layout: true) do |errors_row| %>
<%= errors_row.with_column(mr: 2) do %>
<%= render(Primer::Beta::Octicon.new(icon: :'alert-fill', color: :danger)) %>
<% end %>
<%= errors_row.with_column do %>
<%= render(Primer::Beta::Text.new(color: :danger)) do %>
<%= error_message %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% if reminder.persisted? %>
<%= form_flex_container.with_row(flex_layout: true,
justify_content: :space_between) do |actions_row| %>
<%= actions_row.with_column do %>
<%= render(Primer::Beta::Button.new(
scheme: :danger,
type: :submit,
formaction: work_package_reminder_path(remindable, reminder),
formmethod: :delete
)) { I18n.t(:button_delete_reminder) } %>
<% end %>
<%= actions_row.with_column do %>
<%= render(Primer::Beta::Button.new(scheme: :primary,
type: :submit)) { I18n.t(:button_save) } %>
<% end %>
<% end %>
<% else%>
<%= form_flex_container.with_row(flex_layout: true,
justify_content: :flex_end) do |actions_row| %>
<%= actions_row.with_column do %>
<%= render(Primer::Beta::Button.new(scheme: :primary,
type: :submit)) { I18n.t(:button_set_reminder) } %>
<% end %>
<% end %>
<% end%>
<% end %>
<% end %>
<% end %>
81 changes: 81 additions & 0 deletions app/components/work_packages/reminder/modal_body_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 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 Reminder
class ModalBodyComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers

FORM_ID = "reminder-form"

attr_reader :remindable, :reminder, :errors

def initialize(remindable:, reminder:, errors: nil)
super

@remindable = remindable
@reminder = reminder
@errors = errors
end

class << self
def wrapper_key
"reminder_modal_body"
end
end

def submit_path
if @reminder.persisted?
work_package_reminder_path(@remindable, @reminder)
else
work_package_reminders_path(@remindable)
end
end

def submit_button_text
if @reminder.persisted?
I18n.t(:button_save)
else
I18n.t(:button_set_reminder)
end
end

def remind_at_date_initial_value
format_time_as_date(@reminder.remind_at, format: "%Y-%m-%d")
end

def remind_at_time_initial_value
format_time(@reminder.remind_at, include_date: false, format: "%H:%M")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.reminder-modal-body
&--form-flex-container
gap: 1rem
5 changes: 5 additions & 0 deletions app/contracts/reminders/base_contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class BaseContract < ::ModelContract
validate :validate_acting_user
validate :validate_remindable_exists
validate :validate_manage_reminders_permissions
validate :validate_remind_at_present
validate :validate_remind_at_is_in_future
validate :validate_note_length

Expand All @@ -59,6 +60,10 @@ def validate_remindable_exists
errors.add :remindable, :not_found if model.remindable.blank?
end

def validate_remind_at_present
errors.add :remind_at, :blank if model.remind_at.blank?
end

def validate_remind_at_is_in_future
if model.remind_at.present? && model.remind_at < Time.current
errors.add :remind_at, :datetime_must_be_in_future
Expand Down
206 changes: 206 additions & 0 deletions app/controllers/work_packages/reminders_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# 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.
# ++

class WorkPackages::RemindersController < ApplicationController
include OpTurbo::ComponentStream
layout false
before_action :find_work_package
before_action :build_or_find_reminder, only: %i[modal_body create]
before_action :find_reminder, only: %i[update destroy]

before_action :authorize

def modal_body
render modal_component_class.new(
remindable: @work_package,
reminder: @reminder
)
end

def create
service_result = Reminders::CreateService.new(user: current_user)
.call(reminder_params)

if service_result.success?
update_flash_message_via_turbo_stream(
message: I18n.t("work_package.reminders.success_creation_message"),
scheme: :success
)
respond_with_turbo_streams
else
prepare_errors_from_result(service_result)

replace_via_turbo_stream(
component: modal_component_class.new(
remindable: @work_package,
reminder: @reminder,
errors: @errors
)
)

respond_with_turbo_streams(status: :unprocessable_entity)
end
end

def update
service_result = Reminders::UpdateService.new(user: current_user,
model: @reminder)
.call(reminder_params)

if service_result.success?
update_flash_message_via_turbo_stream(
message: I18n.t("work_package.reminders.success_update_message"),
scheme: :success
)
respond_with_turbo_streams
else
prepare_errors_from_result(service_result)

replace_via_turbo_stream(
component: modal_component_class.new(
remindable: @work_package,
reminder: @reminder,
errors: @errors
)
)

respond_with_turbo_streams(status: :unprocessable_entity)
end
end

def destroy
service_result = Reminders::DeleteService.new(user: current_user,
model: @reminder)
.call

if service_result.success?
update_flash_message_via_turbo_stream(
message: I18n.t("work_package.reminders.success_deletion_message"),
scheme: :success
)
respond_with_turbo_streams
else
update_flash_message_via_turbo_stream(
message: service_result.errors.full_messages,
scheme: :danger
)
respond_with_turbo_streams(status: :unprocessable_entity)
end
end

private

def modal_component_class
WorkPackages::Reminder::ModalBodyComponent
end

def find_work_package
@work_package = WorkPackage.visible.find(params[:work_package_id])
end

# At the form level, we split the date and time into two form fields.
# In order to be a bit more informative of which field is causing
# the remind_at attribute to be in the past/invalid, we need to
# remap the error attribute to the appropriate field.
def prepare_errors_from_result(service_result)
# We set the reminder here for "create" case
# as the record comes from the service.
@reminder = service_result.result
@errors = service_result.errors

case @errors.find { |error| error.attribute == :remind_at }&.type
when :blank
handle_blank_error
when :datetime_must_be_in_future
handle_future_error
end

@errors.delete(:remind_at)
end

def handle_blank_error
@errors.add(:remind_at_date, :blank) if remind_at_date.blank?
@errors.add(:remind_at_time, :blank) if remind_at_time.blank?
end

def handle_future_error
@errors.add(:remind_at_date, :datetime_must_be_in_future) if @reminder.remind_at.to_date < today_in_user_time_zone
@errors.add(:remind_at_time, :datetime_must_be_in_future) if @reminder.remind_at < now_in_user_time_zone
end

def now_in_user_time_zone
@now_in_user_time_zone ||= Time.current
.in_time_zone(User.current.time_zone)
end

def today_in_user_time_zone
@today_in_user_time_zone ||= now_in_user_time_zone.to_date
end

# We assume for now that there is only one reminder per work package
def build_or_find_reminder
@reminder = @work_package.reminders
.upcoming_and_visible_to(User.current)
.last || @work_package.reminders.build
end

def find_reminder
@reminder = @work_package.reminders
.upcoming_and_visible_to(User.current)
.find(params[:id])
end

def reminder_params
params.require(:reminder)
.permit(%i[remind_at_date remind_at_time note])
.tap do |initial_params|
date = initial_params.delete(:remind_at_date)
time = initial_params.delete(:remind_at_time)

initial_params[:remind_at] = build_remind_at_from_params(date, time)
initial_params[:remindable] = @work_package
initial_params[:creator] = User.current
end
end

def build_remind_at_from_params(remind_at_date, remind_at_time)
if remind_at_date.present? && remind_at_time.present?
DateTime.parse("#{remind_at_date} #{User.current.time_zone.parse(remind_at_time)}")
end
end

def remind_at_date
params[:reminder][:remind_at_date]
end

def remind_at_time
params[:reminder][:remind_at_time]
end
end
Loading

0 comments on commit 8c08239

Please sign in to comment.