Skip to content

Commit

Permalink
Merge pull request #1762 from tactilenews/1753_add_ability_to_delete_…
Browse files Browse the repository at this point in the history
…planned_requests

[Feature] Add ability to delete planned requests
  • Loading branch information
mattwr18 authored Dec 15, 2023
2 parents 07694a6 + 74fb298 commit 3f34b10
Show file tree
Hide file tree
Showing 18 changed files with 297 additions and 4 deletions.
10 changes: 10 additions & 0 deletions app/components/button/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@
--text-color: var(--color-orange-darkest);
}

.Button--destroy {
--background-color: var(--color-red-lightest);
--border-color: var(--color-red);
--text-color: var(--color-red-dark);
}

.Button--destroy:focus {
--color-focus: var(--color-red-light);
}

.Button:disabled {
background-color: var(--color-gray);
pointer-events: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.DestroyPlannedRequestModal-footer {
display: flex;
margin-top: var(--spacing-unit);
}

.DestroyPlannedRequestModal-footer .Button:first-child {
margin-right: var(--spacing-unit-xs);
}

.DestroyPlannedRequestModal-footer .Button:last-child {
margin-left: var(--spacing-unit-xs);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= c 'modal', **attrs do %>
<%= c 'heading', tag: :h2 do %>
<%= t('.heading', request_title: planned_request.title) %>
<% end %>
<footer class="DestroyPlannedRequestModal-footer">
<%= c 'button',
type: 'button',
styles: [:block, :secondary],
data: { action: 'request-form#closeModal' } do %>
<%= t('.cancel') %>
<% end %>
<%= c 'button',
link: request_path(planned_request),
styles: [:block, :destroy],
data: { method: :delete } do %>
<%= t('.confirm') %>
<% end %>
</footer>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module DestroyPlannedRequestModal
class DestroyPlannedRequestModal < ApplicationComponent
def initialize(planned_request:, **)
super

@planned_request = planned_request
end

attr_reader :planned_request
end
end
1 change: 0 additions & 1 deletion app/components/profile_header/profile_header.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default class extends Controller {
}

closeModal() {
console.log('im closing...');
this.modalTarget.close();
}
}
10 changes: 10 additions & 0 deletions app/components/request_form/request_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@
<% end %>
<% end %>

<% if @request.planned? %>
<%= c 'button',
type: 'button',
styles: [:block, :destroy],
data: { action: 'request-form#openModal' } do %>
<%= t('.planned_request.destroy.button_text') %>
<% end%>
<%= c 'destroy_planned_request_modal', planned_request: @request, data: { controller: 'modal', request_form_target: 'modal' } %>
<% end %>

<%= c 'button',
type: 'submit',
styles: [:block, :primary],
Expand Down
9 changes: 9 additions & 0 deletions app/components/request_form/request_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default class extends Controller {
'filenames',
'submitButton',
'characterCounter',
'modal',
];
static values = {
membersCountMessage: String,
Expand Down Expand Up @@ -217,4 +218,12 @@ export default class extends Controller {
? this.submitButtonTarget.setAttribute('disabled', isInvalid)
: this.submitButtonTarget.removeAttribute('disabled');
}

openModal() {
this.modalTarget.showModal();
}

closeModal() {
this.modalTarget.close();
}
}
5 changes: 5 additions & 0 deletions app/components/request_row/request_row.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
justify-content: space-between;
}

.RequestRow-editableWrapper .Icon:first-of-type {
margin-left: auto;
margin-right: var(--spacing-unit-s);
}

.RequestRow:target {
border-color: var(--color-focus);
box-shadow: var(--input-focus-shadow);
Expand Down
1 change: 1 addition & 0 deletions app/components/request_row/request_row.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<%= request.title %>
<% end %>
<%= c 'icon', icon: 'pen', styles: [:inline] %>
<%= c 'icon', icon: 'bin', styles: [:inline] %>
</div>
<% else %>
<%= c 'heading', tag: :h2 do %>
Expand Down
17 changes: 16 additions & 1 deletion app/controllers/requests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# frozen_string_literal: true

class RequestsController < ApplicationController
before_action :set_request, only: %i[show show_contributor_messages edit update notifications]
before_action :set_request, only: %i[show show_contributor_messages edit update notifications destroy]
before_action :set_contributor, only: %i[show_contributor_messages]
before_action :notifications_params, only: :notifications
before_action :disallow_edit, only: %i[edit update]
before_action :disallow_destroy, only: :destroy

def index
@filter = filter_param
Expand Down Expand Up @@ -57,6 +58,14 @@ def update
end
end

def destroy
if @request.destroy
redirect_to requests_url(filter: :planned), notice: t('request.destroy.successful', request_title: @request.title)
else
render :edit, status: :unprocessable_entity
end
end

def show_contributor_messages
@chat_messages = @contributor.conversation_about(@request)
end
Expand Down Expand Up @@ -102,6 +111,12 @@ def disallow_edit
redirect_to requests_path, flash: { error: I18n.t('request.editing_disallowed') }
end

def disallow_destroy
return if @request.planned?

redirect_to requests_path, flash: { error: I18n.t('request.destroy.broadcasted_request_unallowed', request_title: @request.title) }
end

def filter_param
value = params.permit(:filter)[:filter]&.to_sym

Expand Down
3 changes: 2 additions & 1 deletion app/jobs/broadcast_request_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ class BroadcastRequestJob < ApplicationJob
queue_as :broadcast_request

def perform(request_id)
request = Request.find(request_id)
request = Request.where(id: request_id).first
return unless request
return if request.broadcasted_at.present?

if request.planned? # rescheduled for future after this job was created
Expand Down
10 changes: 10 additions & 0 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ de:
insert_placeholder_button: Vornamen einfügen
attach_image_to_message: Bilder anhängen
attached_images: Angehängte Bilder
planned_request:
destroy:
button_text: Diese geplante Frage löschen
chat_preview:
status: jetzt online
alt: >
Expand Down Expand Up @@ -382,6 +385,10 @@ de:
onboarding_channels_checkboxes:
legend: Aktive Onboarding-Channel
help_text: Achtung, hier deaktivierst du Kanäle. Mitglieder können sich nur noch auf aktiven Kanälen anmelden.
destroy_planned_request_modal:
heading: "Bist du sicher, dass du „%{request_title}” dauerhaft löschen möchtest?"
cancel: abbrechen
confirm: löschen

mailer:
unsubscribe:
Expand Down Expand Up @@ -600,6 +607,9 @@ de:
one: Deine Frage wurde erfolgreich an ein Mitglied in der Community gesendet
other: Deine Frage wurde erfolgreich an %{count} Mitglieder in der Community gesendet
editing_disallowed: Sie können eine bereits verschickte Frage nicht mehr bearbeiten.
destroy:
successful: "Deine Frage „%{request_title}” wurde erfolgreich gelöscht"
broadcasted_request_unallowed: "Frage %{request_title} wurde bereits gesendet und kann nicht gelöscht werden"
replies: Alle Antworten
created_at: Frage gestellt %{datetime}
planned_for: Frage geplant für %{datetime}
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

telegram_webhook Telegram::WebhookController

resources :requests, only: %i[index show new create edit update], concerns: :paginatable do
resources :requests, only: %i[index show new create edit update destroy], concerns: :paginatable do
member do
get 'notifications', format: /json/
end
Expand Down
1 change: 1 addition & 0 deletions public/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions spec/components/request_form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,21 @@

let(:params) { { request: build(:request) } }
it { should have_css('.RequestForm') }
it {
is_expected.not_to have_css('button[data-action="request-form#openModal"]',
text: I18n.t('components.request_form.planned_request.destroy.button_text'))
}

context 'planned request' do
let(:params) { { request: create(:request, broadcasted_at: nil, schedule_send_for: 1.day.from_now) } }

it 'renders a button to open a confirm destroy modal' do
expect(subject).to have_css('button[data-action="request-form#openModal"]',
text: I18n.t('components.request_form.planned_request.destroy.button_text'))
end

it 'renders a destroy planned request modal' do
expect(subject).to have_css('.DestroyPlannedRequestModal')
end
end
end
58 changes: 58 additions & 0 deletions spec/jobs/broadcast_request_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe BroadcastRequestJob do
describe '#perform_later(request_id)' do
subject { -> { described_class.new.perform(request.id) } }

let!(:contributor) { create(:contributor) }
let(:request) { create(:request, broadcasted_at: nil) }

context 'given the request has been deleted' do
before { request.destroy }

it 'does not raise an error' do
expect { subject.call }.not_to raise_error
end

it 'does not create a Message instance' do
expect { subject.call }.not_to change(Message, :count)
end
end

context 'given a request has been broadcast' do
before { request.update(broadcasted_at: 5.minutes.ago) }

it 'does not create a Message instance' do
expect { subject.call }.not_to change(Message, :count)
end

it 'does not update the broadcasted_at attr' do
expect { subject.call }.not_to(change { request.reload.broadcasted_at })
end
end

context 'given a request has been rescheduled for the future' do
before { request.update(schedule_send_for: 1.day.from_now) }
let(:expected_params) { { request_id: request.id } }

it 'enqueues a job to broadcast the request, and broadcast it when called again' do
expect { subject.call }.to change(DelayedJob, :count).from(0).to(1)
expect(Delayed::Job.last.run_at).to be_within(1.second).of(request.schedule_send_for)

Timecop.travel(1.day.from_now + 2.minutes)

expect { subject.call }.to change { request.reload.broadcasted_at }.from(nil).to(kind_of(ActiveSupport::TimeWithZone))
end

it 'does not create a Message instance' do
expect { subject.call }.not_to change(Message, :count)
end

it 'does not update the broadcasted_at attr' do
expect { subject.call }.not_to(change { request.reload.broadcasted_at })
end
end
end
end
46 changes: 46 additions & 0 deletions spec/requests/requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,52 @@
end
end

describe 'DELETE /requests/:id' do
subject { -> { delete "/requests/#{request.id}?as=#{user.id}" } }

let(:user) { create(:user) }

context 'broadcasted request' do
let!(:request) { create(:request) }

it 'does not delete the request' do
expect { subject.call }.not_to change(Request, :count)
end

it 'redirects to requests path' do
subject.call

expect(response).to redirect_to requests_path
end

it 'shows error message' do
subject.call

expect(flash[:error]).to eq(I18n.t('request.destroy.broadcasted_request_unallowed', request_title: request.title))
end
end

context 'planned request' do
let!(:request) { create(:request, broadcasted_at: nil, schedule_send_for: 1.day.from_now) }

it 'deletes the request' do
expect { subject.call }.to change(Request, :count).from(1).to(0)
end

it 'redirects to requests path with planned filter' do
subject.call

expect(response).to redirect_to requests_path(filter: :planned)
end

it 'shows a notice that it was successful' do
subject.call

expect(flash[:notice]).to eq(I18n.t('request.destroy.successful', request_title: request.title))
end
end
end

describe 'GET /notifications' do
let(:request) { create(:request) }
let!(:older_message) { create(:message, request_id: request.id, created_at: 2.minutes.ago) }
Expand Down
Loading

0 comments on commit 3f34b10

Please sign in to comment.