From e66ea009c21575b57b47e23da208725fbd1aa755 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Wed, 16 Oct 2019 18:45:36 +0300
Subject: [PATCH 01/14] add times-moved to action items
---
app/helpers/application_helper.rb | 3 +++
.../ActionItem/ActionItemFooter/index.js | 24 ++++++++++++-------
app/javascript/components/ActionItem/index.js | 11 +++++----
app/models/action_item.rb | 1 +
app/views/boards/_previous_action_item.erb | 1 +
app/views/boards/show.html.erb | 3 ++-
...7083034_add_times_moved_to_action_items.rb | 7 ++++++
db/schema.rb | 3 ++-
8 files changed, 38 insertions(+), 15 deletions(-)
create mode 100644 db/migrate/20191017083034_add_times_moved_to_action_items.rb
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 15b06f0f..f918456a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
module ApplicationHelper
+ def calc_life_span(obj)
+ time_ago_in_words(obj.created_at)
+ end
end
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index 062c0cc0..de76d78b 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -26,18 +26,24 @@ class ActionItemFooter extends React.Component {
}
render () {
- const { deletable } = this.props;
+ const { deletable, times_moved } = this.props;
+ const moved = (times_moved != 0);
+ const footerNotEmpty = deletable || moved;
+
const confirmMessage = 'Are you sure you want to delete this ActionItem?';
return (
-
+ <>
+ {footerNotEmpty &&
+
+ }
+ >
);
}
}
diff --git a/app/javascript/components/ActionItem/index.js b/app/javascript/components/ActionItem/index.js
index 73f2a80c..0f49f2e7 100644
--- a/app/javascript/components/ActionItem/index.js
+++ b/app/javascript/components/ActionItem/index.js
@@ -15,14 +15,17 @@ class ActionItem extends React.Component {
}
render () {
- const { id, body, deletable, editable } = this.props;
+ const { id, body, times_moved, deletable, editable } = this.props;
return (
- {deletable && }
+ editable={editable}
+ body={body}/>
+
);
}
diff --git a/app/models/action_item.rb b/app/models/action_item.rb
index 190eade3..266d55c9 100644
--- a/app/models/action_item.rb
+++ b/app/models/action_item.rb
@@ -28,6 +28,7 @@ class ActionItem < ApplicationRecord
def move!(board)
self.board_id = board.id
+ increment(:times_moved)
save
end
end
diff --git a/app/views/boards/_previous_action_item.erb b/app/views/boards/_previous_action_item.erb
index ff98e17f..b3c6811f 100644
--- a/app/views/boards/_previous_action_item.erb
+++ b/app/views/boards/_previous_action_item.erb
@@ -12,4 +12,5 @@
<% if allowed_to?(:reopen?, action_item, context: { board: @board }) %>
<%= link_to "reopen", reopen_board_action_item_path(@board, action_item.id), method: :put, class: 'button is-small is-light' %>
<% end %>
+ AA was moved <%= action_item.times_moved %> times
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb
index 474722ad..a524b046 100644
--- a/app/views/boards/show.html.erb
+++ b/app/views/boards/show.html.erb
@@ -88,10 +88,11 @@
<%= react_component('ActionItem/index', {
id: action_item.id,
body: action_item.body,
+ times_moved: action_item.times_moved,
editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy),
deletable: allowed_to?(:destroy?, action_item, with: API::ActionItemPolicy)
})
- %>
+ %>
<% end %>
diff --git a/db/migrate/20191017083034_add_times_moved_to_action_items.rb b/db/migrate/20191017083034_add_times_moved_to_action_items.rb
new file mode 100644
index 00000000..440b35ab
--- /dev/null
+++ b/db/migrate/20191017083034_add_times_moved_to_action_items.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddTimesMovedToActionItems < ActiveRecord::Migration[6.0]
+ def change
+ add_column :action_items, :times_moved, :integer, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a4a37378..360452cc 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_10_14_150613) do
+ActiveRecord::Schema.define(version: 2019_10_17_083034) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -21,6 +21,7 @@
t.string "status", default: "pending", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.integer "times_moved", default: 0
t.index ["board_id"], name: "index_action_items_on_board_id"
end
From 111269c3b744762cc1420f08ebaf9476fe831568 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Thu, 17 Oct 2019 19:08:31 +0300
Subject: [PATCH 02/14] added chevrons
---
.../components/ActionItem/ActionItem.css | 1 -
.../ActionItemFooter/ActionItemFooter.css | 8 ++++
.../ActionItem/ActionItemFooter/index.js | 40 ++++++++++++++-----
app/javascript/components/ActionItem/index.js | 10 +++--
app/views/boards/_previous_action_item.erb | 2 +-
5 files changed, 44 insertions(+), 17 deletions(-)
create mode 100644 app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
diff --git a/app/javascript/components/ActionItem/ActionItem.css b/app/javascript/components/ActionItem/ActionItem.css
index 81857133..5f12bac0 100644
--- a/app/javascript/components/ActionItem/ActionItem.css
+++ b/app/javascript/components/ActionItem/ActionItem.css
@@ -1,2 +1 @@
.box { margin-bottom: 1.5rem; }
-
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
new file mode 100644
index 00000000..204048f8
--- /dev/null
+++ b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
@@ -0,0 +1,8 @@
+.fa-chevron-right {
+ margin-right: -2px;
+ margin-left: -2px;
+ }
+
+.fa-chevron-right.green {color: hsl(141, 71%, 48%)}
+.fa-chevron-right.yellow {color: hsl(48, 100%, 67%)}
+.fa-chevron-right.red {color: hsl(348, 100%, 61%)}
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index de76d78b..09896f8b 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -1,5 +1,7 @@
import React from "react"
+import "./ActionItemFooter.css"
+
class ActionItemFooter extends React.Component {
constructor(props) {
super(props);
@@ -25,6 +27,26 @@ class ActionItemFooter extends React.Component {
});
}
+ pickColor(num) {
+ switch(true) {
+ case [1,2].includes(num):
+ return 'green';
+ case [3].includes(num):
+ return 'yellow';
+ default:
+ return 'red';
+ }
+ }
+
+ renderChevrons = () => {
+ const times_moved = this.props.times_moved;
+ const icon = ;
+
+ let chevrons = [];
+ for (let i = 0; i < times_moved; i++) chevrons.push(icon);
+ return chevrons
+ };
+
render () {
const { deletable, times_moved } = this.props;
const moved = (times_moved != 0);
@@ -33,17 +55,13 @@ class ActionItemFooter extends React.Component {
const confirmMessage = 'Are you sure you want to delete this ActionItem?';
return (
- <>
- {footerNotEmpty &&
-
- }
- >
+
);
}
}
diff --git a/app/javascript/components/ActionItem/index.js b/app/javascript/components/ActionItem/index.js
index 0f49f2e7..7a8e113b 100644
--- a/app/javascript/components/ActionItem/index.js
+++ b/app/javascript/components/ActionItem/index.js
@@ -17,15 +17,17 @@ class ActionItem extends React.Component {
render () {
const { id, body, times_moved, deletable, editable } = this.props;
+ const footerNotEmpty = deletable || (times_moved != 0);
+
return (
);
}
diff --git a/app/views/boards/_previous_action_item.erb b/app/views/boards/_previous_action_item.erb
index b3c6811f..0a583baa 100644
--- a/app/views/boards/_previous_action_item.erb
+++ b/app/views/boards/_previous_action_item.erb
@@ -12,5 +12,5 @@
<% if allowed_to?(:reopen?, action_item, context: { board: @board }) %>
<%= link_to "reopen", reopen_board_action_item_path(@board, action_item.id), method: :put, class: 'button is-small is-light' %>
<% end %>
- AA was moved <%= action_item.times_moved %> times
+ times previously moved: <%= action_item.times_moved %>
From a5ca4e6f67f8b595a5d58ce5ea3f0cdadc0c1dfc Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Fri, 18 Oct 2019 13:50:38 +0300
Subject: [PATCH 03/14] add aasm specs
---
spec/models/action_item_spec.rb | 47 +++++++++++++++++++++++++++++++++
spec/rails_helper.rb | 1 +
2 files changed, 48 insertions(+)
diff --git a/spec/models/action_item_spec.rb b/spec/models/action_item_spec.rb
index 2fb90113..8d769365 100644
--- a/spec/models/action_item_spec.rb
+++ b/spec/models/action_item_spec.rb
@@ -24,4 +24,51 @@
expect(action_item).to respond_to(:board)
end
end
+
+ describe 'aasm' do
+ subject { action_item }
+
+ context 'status: pending' do
+ let(:action_item) { build_stubbed(:action_item, status: 'pending') }
+
+ it { is_expected.to have_state(:pending) }
+
+ context 'allowed transitions' do
+ it { is_expected.to allow_transition_to(:closed) }
+ it { is_expected.to allow_transition_to(:done) }
+ it { is_expected.to_not allow_transition_to(:pending) }
+ end
+
+ it { is_expected.to transition_from(:pending).to(:closed).on_event(:close) }
+ it { is_expected.to transition_from(:pending).to(:done).on_event(:complete) }
+ end
+
+ context 'status: closed' do
+ let(:action_item) { build_stubbed(:action_item, status: 'closed') }
+
+ it { is_expected.to have_state(:closed) }
+
+ context 'allowed transitions' do
+ it { is_expected.to_not allow_transition_to(:closed) }
+ it { is_expected.to_not allow_transition_to(:done) }
+ it { is_expected.to allow_transition_to(:pending) }
+ end
+
+ it { is_expected.to transition_from(:closed).to(:pending).on_event(:reopen) }
+ end
+
+ context 'status: done' do
+ let(:action_item) { build_stubbed(:action_item, status: 'done') }
+
+ it { is_expected.to have_state(:done) }
+
+ context 'allowed transitions' do
+ it { is_expected.to_not allow_transition_to(:closed) }
+ it { is_expected.to_not allow_transition_to(:done) }
+ it { is_expected.to allow_transition_to(:pending) }
+ end
+
+ it { is_expected.to transition_from(:done).to(:pending).on_event(:reopen) }
+ end
+ end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index d07c9fd1..88791e6c 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -13,6 +13,7 @@
require 'test_prof/recipes/rspec/let_it_be'
require 'devise'
+require 'aasm/rspec'
Dir['./spec/support/**/*.rb'].each { |f| require f }
# Add additional requires below this line. Rails is not loaded until this point!
From 0b66833c8b63398bee7edc863b4bc493969e2799 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Fri, 18 Oct 2019 15:17:43 +0300
Subject: [PATCH 04/14] add action_item model specs
---
spec/models/action_item_spec.rb | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/spec/models/action_item_spec.rb b/spec/models/action_item_spec.rb
index 8d769365..50d252b9 100644
--- a/spec/models/action_item_spec.rb
+++ b/spec/models/action_item_spec.rb
@@ -71,4 +71,24 @@
it { is_expected.to transition_from(:done).to(:pending).on_event(:reopen) }
end
end
+
+ describe '#move!' do
+ let_it_be(:prev_board) { create(:board) }
+ let_it_be(:new_board) { create(:board) }
+ let_it_be(:action_item) { create(:action_item, board: prev_board) }
+
+ subject { action_item.move!(new_board) }
+
+ it 'changes action_item board_id to the newly provided one' do
+ expect { subject }.to change { action_item.board_id }.from(prev_board.id).to(new_board.id)
+ end
+
+ it 'increments times moved' do
+ expect { subject }.to change { action_item.times_moved }.by(1)
+ end
+
+ it 'does not create new action item record' do
+ expect { subject }.to_not change { ActionItem.count }
+ end
+ end
end
From e793933560942f5af8508e8d2209651e982f134f Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Fri, 18 Oct 2019 17:29:46 +0300
Subject: [PATCH 05/14] duplicate action item actions and policies and move
under api namespace
---
.../api/action_items_controller.rb | 37 +++-
app/policies/api/action_item_policy.rb | 20 +-
app/views/boards/show.html.erb | 17 +-
config/routes.rb | 9 +-
spec/policies/api/action_item_policy_spec.rb | 176 ++++++++++++++++++
spec/policies/api/action_items_policy_spec.rb | 74 --------
6 files changed, 254 insertions(+), 79 deletions(-)
create mode 100644 spec/policies/api/action_item_policy_spec.rb
delete mode 100644 spec/policies/api/action_items_policy_spec.rb
diff --git a/app/controllers/api/action_items_controller.rb b/app/controllers/api/action_items_controller.rb
index ffde2583..33c7c98b 100644
--- a/app/controllers/api/action_items_controller.rb
+++ b/app/controllers/api/action_items_controller.rb
@@ -3,9 +3,11 @@
module API
class ActionItemsController < API::ApplicationController
before_action :set_board, :set_action_item
+ before_action do
+ authorize! @action_item, context: { board: @board }
+ end
def update
- authorize! @action_item
if @action_item.update(body: params.permit(:edited_body)[:edited_body])
render json: { updated_body: @action_item.body }, status: :ok
else
@@ -14,7 +16,6 @@ def update
end
def destroy
- authorize! @action_item
if @action_item.destroy
head :no_content
else
@@ -22,6 +23,38 @@ def destroy
end
end
+ def move
+ if @action_item.move!(@board)
+ head :no_content
+ else
+ render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
+ end
+ end
+
+ def close
+ if @action_item.close!
+ head :no_content
+ else
+ render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
+ end
+ end
+
+ def complete
+ if @action_item.complete!
+ head :no_content
+ else
+ render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
+ end
+ end
+
+ def reopen
+ if @action_item.reopen!
+ head :no_content
+ else
+ render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
+ end
+ end
+
private
def set_board
diff --git a/app/policies/api/action_item_policy.rb b/app/policies/api/action_item_policy.rb
index 007a0fab..55f13701 100644
--- a/app/policies/api/action_item_policy.rb
+++ b/app/policies/api/action_item_policy.rb
@@ -2,6 +2,8 @@
module API
class ActionItemPolicy < ApplicationPolicy
+ authorize :board, allow_nil: true
+
def update?
check?(:user_is_creator?)
end
@@ -10,8 +12,24 @@ def destroy?
check?(:user_is_creator?)
end
+ def move?
+ check?(:user_is_creator?) && record.pending?
+ end
+
+ def close?
+ check?(:user_is_creator?) && record.may_close?
+ end
+
+ def complete?
+ check?(:user_is_creator?) && record.may_complete?
+ end
+
+ def reopen?
+ check?(:user_is_creator?) && record.may_reopen?
+ end
+
def user_is_creator?
- record.board.creator?(user)
+ board ? board.creator?(user) : record.board.creator?(user)
end
end
end
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb
index a524b046..2c5a1d06 100644
--- a/app/views/boards/show.html.erb
+++ b/app/views/boards/show.html.erb
@@ -49,7 +49,22 @@
PREVIOUS BOARD
<% @previous_action_items.each do |action_item| %>
- <%= render 'previous_action_item', action_item: action_item %>
+ <%= react_component('ActionItem/index', {
+ id: action_item.id,
+ body: action_item.body,
+ times_moved: action_item.times_moved,
+
+ transitionable: {
+ to_close: allowed_to?(:close?, action_item, context: { board: @board }),
+ to_complete: allowed_to?(:complete?, action_item, context: { board: @board }),
+ to_reopen: allowed_to?(:reopen?, action_item, context: { board: @board })
+ },
+
+ movable: allowed_to?(:move?, action_item, context: { board: @board })
+ #editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy),
+ #deletable: allowed_to?(:destroy?, action_item, with: API::ActionItemPolicy)
+ })
+ %>
<% end %>
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index e28c6365..d4e85d15 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -52,7 +52,14 @@
put 'like'
end
end
- resources :action_items
+ end
+ resources :action_items, only: %i[update destroy] do
+ member do
+ post 'move'
+ put 'close'
+ put 'complete'
+ put 'reopen'
+ end
end
end
end
diff --git a/spec/policies/api/action_item_policy_spec.rb b/spec/policies/api/action_item_policy_spec.rb
new file mode 100644
index 00000000..f648cf87
--- /dev/null
+++ b/spec/policies/api/action_item_policy_spec.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe API::ActionItemPolicy do
+ let_it_be(:creator) { create(:user) }
+ let_it_be(:member) { create(:user) }
+ let(:not_member) { build_stubbed(:user) }
+ let_it_be(:board) { create(:board) }
+ let_it_be(:creatorship) do
+ create(:membership, user_id: creator.id, board_id: board.id, role: 'creator')
+ end
+ let_it_be(:membership) { create(:membership, user_id: member.id, board_id: board.id) }
+ let(:action_item) { build_stubbed(:action_item, board: board) }
+
+ let(:policy) { described_class.new(action_item, user: test_user, board: board) }
+
+ describe '#destroy?' do
+ subject { policy.apply(:destroy?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+ it { is_expected.to eq true }
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#update?' do
+ subject { policy.apply(:update?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+ it { is_expected.to eq true }
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#move?' do
+ subject { policy.apply(:move?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+
+ it 'returns true if aasm state pending?' do
+ allow(action_item).to receive(:pending?).and_return(true)
+
+ is_expected.to eq true
+ expect(action_item).to have_received(:pending?)
+ end
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#close?' do
+ subject { policy.apply(:close?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+
+ it 'returns true if aasm state may transition to closed' do
+ allow(action_item).to receive(:may_close?).and_return(true)
+
+ is_expected.to eq true
+ expect(action_item).to have_received(:may_close?)
+ end
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#complete?' do
+ let(:action_item) { build_stubbed(:action_item, board: board, status: 'pending') }
+ subject { policy.apply(:complete?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+
+ it 'returns true if aasm state may transition to completed' do
+ allow(action_item).to receive(:may_complete?).and_return(true)
+
+ is_expected.to eq true
+ expect(action_item).to have_received(:may_complete?)
+ end
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#reopen?' do
+ let(:action_item) { build_stubbed(:action_item, board: board, status: 'closed') }
+ subject { policy.apply(:reopen?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+
+ it 'returns true if aasm state may transition to pending' do
+ allow(action_item).to receive(:may_reopen?).and_return(true)
+
+ is_expected.to eq true
+ expect(action_item).to have_received(:may_reopen?)
+ end
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+
+ describe '#user_is_creator?' do
+ subject { policy.apply(:user_is_creator?) }
+
+ context 'when user is the board creator' do
+ let(:test_user) { creator }
+ it { is_expected.to eq true }
+ end
+
+ context 'when user is a board member' do
+ let(:test_user) { member }
+ it { is_expected.to eq false }
+ end
+
+ context 'when user is not a board member' do
+ let(:test_user) { not_member }
+ it { is_expected.to eq false }
+ end
+ end
+end
diff --git a/spec/policies/api/action_items_policy_spec.rb b/spec/policies/api/action_items_policy_spec.rb
deleted file mode 100644
index 6bf74660..00000000
--- a/spec/policies/api/action_items_policy_spec.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe API::ActionItemPolicy do
- let_it_be(:creator) { create(:user) }
- let_it_be(:member) { create(:user) }
- let(:not_member) { build_stubbed(:user) }
- let_it_be(:board) { create(:board) }
- let_it_be(:creatorship) do
- create(:membership, user_id: creator.id, board_id: board.id, role: 'creator')
- end
- let_it_be(:membership) { create(:membership, user_id: member.id, board_id: board.id) }
- let(:action_item) { build_stubbed(:action_item, board: board) }
-
- let(:policy) { described_class.new(action_item, user: test_user, board: board) }
-
- describe '#destroy?' do
- subject { policy.apply(:destroy?) }
-
- context 'when user is the board creator' do
- let(:test_user) { creator }
- it { is_expected.to eq true }
- end
-
- context 'when user is a board member' do
- let(:test_user) { member }
- it { is_expected.to eq false }
- end
-
- context 'when user is not a board member' do
- let(:test_user) { not_member }
- it { is_expected.to eq false }
- end
- end
-
- describe '#update?' do
- subject { policy.apply(:update?) }
-
- context 'when user is the board creator' do
- let(:test_user) { creator }
- it { is_expected.to eq true }
- end
-
- context 'when user is a board member' do
- let(:test_user) { member }
- it { is_expected.to eq false }
- end
-
- context 'when user is not a board member' do
- let(:test_user) { not_member }
- it { is_expected.to eq false }
- end
- end
-
- describe '#user_is_creator?' do
- subject { policy.apply(:user_is_creator?) }
-
- context 'when user is the board creator' do
- let(:test_user) { creator }
- it { is_expected.to eq true }
- end
-
- context 'when user is a board member' do
- let(:test_user) { member }
- it { is_expected.to eq false }
- end
-
- context 'when user is not a board member' do
- let(:test_user) { not_member }
- it { is_expected.to eq false }
- end
- end
-end
From fd5099a7fa9a290e6857325e3671c0c21574e68e Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Mon, 21 Oct 2019 13:26:50 +0300
Subject: [PATCH 06/14] modify ActionItem react component to accept new props
---
.../ActionItem/ActionItemFooter/index.js | 9 +++++----
app/javascript/components/ActionItem/index.js | 14 ++++++++++----
config/routes.rb | 14 +++++++-------
3 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index 09896f8b..43d9758e 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -48,10 +48,7 @@ class ActionItemFooter extends React.Component {
};
render () {
- const { deletable, times_moved } = this.props;
- const moved = (times_moved != 0);
- const footerNotEmpty = deletable || moved;
-
+ const { deletable, times_moved, paintActionItem } = this.props;
const confirmMessage = 'Are you sure you want to delete this ActionItem?';
return (
@@ -61,6 +58,10 @@ class ActionItemFooter extends React.Component {
{window.confirm(confirmMessage) && this.handleClick()}} hidden={!deletable}>
delete
+
+ paintActionItem('yellow')}>
+ clickme
+
);
}
diff --git a/app/javascript/components/ActionItem/index.js b/app/javascript/components/ActionItem/index.js
index 7a8e113b..760f0e0a 100644
--- a/app/javascript/components/ActionItem/index.js
+++ b/app/javascript/components/ActionItem/index.js
@@ -13,11 +13,14 @@ class ActionItem extends React.Component {
hideActionItem = () => {
this.setState({ActionItemStyle: {display: 'none'}});
}
+
+ paintActionItem = (color) => {
+ console.log(color);
+ }
render () {
- const { id, body, times_moved, deletable, editable } = this.props;
-
- const footerNotEmpty = deletable || (times_moved != 0);
+ const { id, body, times_moved, deletable, editable, movable, transitionable } = this.props;
+ const footerNotEmpty = deletable || movable || transitionable || (times_moved != 0);
return (
@@ -27,7 +30,10 @@ class ActionItem extends React.Component {
{footerNotEmpty &&
}
+ movable={movable}
+ transitionable={transitionable}
+ hideActionItem={this.hideActionItem}
+ paintActionItem={this.paintActionItem}/>}
);
}
diff --git a/config/routes.rb b/config/routes.rb
index d4e85d15..98a0151d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -52,13 +52,13 @@
put 'like'
end
end
- end
- resources :action_items, only: %i[update destroy] do
- member do
- post 'move'
- put 'close'
- put 'complete'
- put 'reopen'
+ resources :action_items, only: %i[update destroy] do
+ member do
+ post 'move'
+ put 'close'
+ put 'complete'
+ put 'reopen'
+ end
end
end
end
From d734067af1628a2725e10e9b0a34d235a2b224c2 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Mon, 21 Oct 2019 14:52:38 +0300
Subject: [PATCH 07/14] create TransitionButton component
---
.../components/ActionItem/ActionItem.css | 8 +++
.../ActionItemFooter/ActionItemFooter.css | 6 +--
.../ActionItem/ActionItemFooter/index.js | 53 ++++++++++++++-----
.../TransitionButton/TransitionButton.css | 1 +
.../ActionItem/TransitionButton/index.js | 41 ++++++++++++++
app/javascript/components/ActionItem/index.js | 13 +++--
app/javascript/components/Card/Card.css | 1 -
app/policies/api/action_item_policy.rb | 6 +--
app/views/boards/show.html.erb | 27 +++++-----
spec/policies/api/action_item_policy_spec.rb | 2 +-
10 files changed, 118 insertions(+), 40 deletions(-)
create mode 100644 app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
create mode 100644 app/javascript/components/ActionItem/TransitionButton/index.js
diff --git a/app/javascript/components/ActionItem/ActionItem.css b/app/javascript/components/ActionItem/ActionItem.css
index 5f12bac0..10687090 100644
--- a/app/javascript/components/ActionItem/ActionItem.css
+++ b/app/javascript/components/ActionItem/ActionItem.css
@@ -1 +1,9 @@
.box { margin-bottom: 1.5rem; }
+
+.green_font {color: hsl(141, 71%, 48%)}
+.yellow_font {color: hsl(48, 100%, 67%)}
+.red_font {color: hsl(348, 100%, 61%)}
+
+.green_bg {background-color: hsl(141, 71%, 48%)}
+.yellow_bg {background-color: hsl(48, 100%, 67%)}
+.red_bg {background-color: hsl(348, 100%, 61%)}
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
index 204048f8..317770ac 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
+++ b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
@@ -1,8 +1,4 @@
.fa-chevron-right {
margin-right: -2px;
- margin-left: -2px;
+ margin-left: -2px;
}
-
-.fa-chevron-right.green {color: hsl(141, 71%, 48%)}
-.fa-chevron-right.yellow {color: hsl(48, 100%, 67%)}
-.fa-chevron-right.red {color: hsl(348, 100%, 61%)}
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index 43d9758e..18375233 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -1,5 +1,6 @@
import React from "react"
+import TransitionButton from "../TransitionButton"
import "./ActionItemFooter.css"
class ActionItemFooter extends React.Component {
@@ -7,7 +8,7 @@ class ActionItemFooter extends React.Component {
super(props);
}
- handleClick = () => {
+ handleDeleteClick = () => {
fetch(`/api/${window.location.pathname}/action_items/${this.props.id}`, {
method: 'DELETE',
headers: {
@@ -27,6 +28,26 @@ class ActionItemFooter extends React.Component {
});
}
+ handleMoveClick = () => {
+ fetch(`/api/${window.location.pathname}/action_items/${this.props.id}/move`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
+ }
+ }).then((result) => {
+ if (result.status == 204) {
+ window.location.reload();
+ }
+ else { throw result }
+ }).catch((error) => {
+ error.json().then( errorHash => {
+ console.log(errorHash.error)
+ })
+ });
+ }
+
pickColor(num) {
switch(true) {
case [1,2].includes(num):
@@ -38,9 +59,9 @@ class ActionItemFooter extends React.Component {
}
}
- renderChevrons = () => {
+ generateChevrons = () => {
const times_moved = this.props.times_moved;
- const icon = ;
+ const icon = ;
let chevrons = [];
for (let i = 0; i < times_moved; i++) chevrons.push(icon);
@@ -48,20 +69,28 @@ class ActionItemFooter extends React.Component {
};
render () {
- const { deletable, times_moved, paintActionItem } = this.props;
- const confirmMessage = 'Are you sure you want to delete this ActionItem?';
+ const { id, deletable, movable, transitionable } = this.props;
+ const confirmDeleteMessage = 'Are you sure you want to delete this ActionItem?';
+ const confirmMoveMessage = 'Are you sure you want to move this ActionItem?';
return (
);
}
diff --git a/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css b/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
@@ -0,0 +1 @@
+
diff --git a/app/javascript/components/ActionItem/TransitionButton/index.js b/app/javascript/components/ActionItem/TransitionButton/index.js
new file mode 100644
index 00000000..991767c8
--- /dev/null
+++ b/app/javascript/components/ActionItem/TransitionButton/index.js
@@ -0,0 +1,41 @@
+import React from "react"
+
+import "./TransitionButton.css"
+
+class TransitionButton extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {}
+ }
+
+ handleClick = () => {
+ fetch(`/api/${window.location.pathname}/action_items/${this.props.id}/${this.props.action}`, {
+ method: 'PUT',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
+ }
+ }).then((result) => {
+ if (result.status == 204) {
+ console.log(`doing ${this.props.action}!!!`)
+ window.location.reload();
+ }
+ else { throw result }
+ }).catch((error) => {
+ error.json().then( errorHash => {
+ console.log(errorHash.error)
+ })
+ });
+ }
+
+ render () {
+ return (
+
+ );
+ }
+}
+
+export default TransitionButton
diff --git a/app/javascript/components/ActionItem/index.js b/app/javascript/components/ActionItem/index.js
index 760f0e0a..d8c2b003 100644
--- a/app/javascript/components/ActionItem/index.js
+++ b/app/javascript/components/ActionItem/index.js
@@ -14,8 +14,15 @@ class ActionItem extends React.Component {
this.setState({ActionItemStyle: {display: 'none'}});
}
- paintActionItem = (color) => {
- console.log(color);
+ pickColor = () => {
+ switch(this.props.status) {
+ case 'done':
+ return 'green';
+ case 'closed':
+ return 'red';
+ default:
+ return null;
+ }
}
render () {
@@ -23,7 +30,7 @@ class ActionItem extends React.Component {
const footerNotEmpty = deletable || movable || transitionable || (times_moved != 0);
return (
-
+
diff --git a/app/javascript/components/Card/Card.css b/app/javascript/components/Card/Card.css
index 91cda358..c3a71430 100644
--- a/app/javascript/components/Card/Card.css
+++ b/app/javascript/components/Card/Card.css
@@ -4,4 +4,3 @@
width: 1.5rem;
border-radius: 1rem;
}
-
diff --git a/app/policies/api/action_item_policy.rb b/app/policies/api/action_item_policy.rb
index 55f13701..58674786 100644
--- a/app/policies/api/action_item_policy.rb
+++ b/app/policies/api/action_item_policy.rb
@@ -15,15 +15,15 @@ def destroy?
def move?
check?(:user_is_creator?) && record.pending?
end
-
+
def close?
check?(:user_is_creator?) && record.may_close?
end
-
+
def complete?
check?(:user_is_creator?) && record.may_complete?
end
-
+
def reopen?
check?(:user_is_creator?) && record.may_reopen?
end
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb
index 2c5a1d06..4d9b534b 100644
--- a/app/views/boards/show.html.erb
+++ b/app/views/boards/show.html.erb
@@ -29,12 +29,8 @@
-
-
-
+
+
<% if @previous_action_items %>
<% column_class_name = 'column is-one-fifth' %>
@@ -52,17 +48,18 @@
<%= react_component('ActionItem/index', {
id: action_item.id,
body: action_item.body,
+ status: action_item.status,
times_moved: action_item.times_moved,
transitionable: {
- to_close: allowed_to?(:close?, action_item, context: { board: @board }),
- to_complete: allowed_to?(:complete?, action_item, context: { board: @board }),
- to_reopen: allowed_to?(:reopen?, action_item, context: { board: @board })
+ can_close: allowed_to?(:close?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
+ can_complete: allowed_to?(:complete?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
+ can_reopen: allowed_to?(:reopen?, action_item, with: API::ActionItemPolicy, context: { board: @board })
},
-
- movable: allowed_to?(:move?, action_item, context: { board: @board })
- #editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy),
- #deletable: allowed_to?(:destroy?, action_item, with: API::ActionItemPolicy)
+
+ movable: allowed_to?(:move?, action_item, with: API::ActionItemPolicy, context: { board: @board })
+ # editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
+ # deletable: allowed_to?(:destroy?, action_itemwith: API::ActionItemPolicy context: { board: @board })
})
%>
<% end %>
@@ -104,8 +101,8 @@
id: action_item.id,
body: action_item.body,
times_moved: action_item.times_moved,
- editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy),
- deletable: allowed_to?(:destroy?, action_item, with: API::ActionItemPolicy)
+ editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
+ deletable: allowed_to?(:destroy?, action_item, with: API::ActionItemPolicy, context: { board: @board })
})
%>
<% end %>
diff --git a/spec/policies/api/action_item_policy_spec.rb b/spec/policies/api/action_item_policy_spec.rb
index f648cf87..62955748 100644
--- a/spec/policies/api/action_item_policy_spec.rb
+++ b/spec/policies/api/action_item_policy_spec.rb
@@ -135,7 +135,7 @@
context 'when user is the board creator' do
let(:test_user) { creator }
-
+
it 'returns true if aasm state may transition to pending' do
allow(action_item).to receive(:may_reopen?).and_return(true)
From f9ec3be127bb30c27195d7b5ee078cd12544bbce Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 11:42:04 +0300
Subject: [PATCH 08/14] remove leftovers
---
.../boards/action_items_controller.rb | 40 ------
config/routes.rb | 6 -
.../api/action_items_controller_spec.rb | 132 ++++++++++++++++++
.../boards/action_items_controller_spec.rb | 132 ------------------
4 files changed, 132 insertions(+), 178 deletions(-)
diff --git a/app/controllers/boards/action_items_controller.rb b/app/controllers/boards/action_items_controller.rb
index cbd7fc89..20f1e694 100644
--- a/app/controllers/boards/action_items_controller.rb
+++ b/app/controllers/boards/action_items_controller.rb
@@ -2,11 +2,7 @@
module Boards
class ActionItemsController < Boards::ApplicationController
- before_action :set_action_item, except: :create
authorize :board, through: :current_board
- before_action except: :create do
- authorize! @action_item
- end
def create
action_item = @board.action_items.build(action_item_params)
@@ -18,38 +14,6 @@ def create
end
end
- def move
- if @action_item.move!(@board)
- redirect_to @board, notice: 'Action Item was successfully moved'
- else
- redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
- end
- end
-
- def close
- if @action_item.close!
- redirect_to @board, notice: 'Action Item was successfully closed'
- else
- redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
- end
- end
-
- def complete
- if @action_item.complete!
- redirect_to @board, notice: 'Action Item was successfully completed'
- else
- redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
- end
- end
-
- def reopen
- if @action_item.reopen!
- redirect_to @board, notice: 'Action Item was successfully reopend'
- else
- redirect_to @board, alert: @action_item.errors.full_messages.join(', ')
- end
- end
-
private
def current_board
@@ -59,9 +23,5 @@ def current_board
def action_item_params
params.require(:action_item).permit(:status, :body)
end
-
- def set_action_item
- @action_item = ActionItem.find(params[:id])
- end
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 98a0151d..2fce15f8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -15,12 +15,6 @@
resources :cards, only: :create
resources :memberships, only: :create
resources :action_items, only: :create do
- member do
- post 'move'
- put 'close'
- put 'complete'
- put 'reopen'
- end
end
end
end
diff --git a/spec/controllers/api/action_items_controller_spec.rb b/spec/controllers/api/action_items_controller_spec.rb
index c58f09f8..2db57917 100644
--- a/spec/controllers/api/action_items_controller_spec.rb
+++ b/spec/controllers/api/action_items_controller_spec.rb
@@ -81,4 +81,136 @@
it { is_expected.to match_json_schema('api/cards/update') }
end
end
+
+ describe 'POST #move' do
+ subject(:response) { post :move, params: params }
+ let(:params) { { board_slug: board.slug, id: action_item.id } }
+
+ context 'authentication' do
+ it_behaves_like :controllers_api_unauthenticated_action
+ end
+
+ context 'authorization' do
+ before do
+ login_as(not_creator)
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ end
+
+ it_behaves_like :controllers_api_unauthorized_action
+ end
+
+ context 'successful action path' do
+ before do
+ login_as(creator)
+ authorize
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ allow(action_item).to receive(:move!).with(board).and_return(true)
+ end
+
+ it_behaves_like :controllers_api_successful_action, :no_content
+ end
+ end
+
+ describe 'PUT #close' do
+ subject(:response) { put :close, params: params }
+ let(:params) { { board_slug: board.slug, id: action_item.id } }
+
+ context 'authentication' do
+ it_behaves_like :controllers_api_unauthenticated_action
+ end
+
+ context 'authorization' do
+ before do
+ login_as(not_creator)
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ end
+
+ it_behaves_like :controllers_api_unauthorized_action
+ end
+
+ context 'successful action path' do
+ before do
+ login_as(creator)
+ authorize
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ allow(action_item).to receive(:close!).and_return(true)
+ end
+
+ it_behaves_like :controllers_api_successful_action, :no_content
+ end
+ end
+
+ describe 'PUT #complete' do
+ subject(:response) { put :complete, params: params }
+ let(:params) { { board_slug: board.slug, id: action_item.id } }
+
+ context 'authentication' do
+ it_behaves_like :controllers_api_unauthenticated_action
+ end
+
+ context 'authorization' do
+ before do
+ login_as(not_creator)
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ end
+
+ it_behaves_like :controllers_api_unauthorized_action
+ end
+
+ context 'successful action path' do
+ before do
+ login_as(creator)
+ authorize
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ allow(action_item).to receive(:complete!).and_return(true)
+ end
+
+ it_behaves_like :controllers_api_successful_action, :no_content
+ end
+ end
+
+ describe 'PUT #reopen' do
+ subject(:response) { put :reopen, params: params }
+ let(:params) { { board_slug: board.slug, id: action_item.id } }
+
+ context 'authentication' do
+ it_behaves_like :controllers_api_unauthenticated_action
+ end
+
+ context 'authorization' do
+ before do
+ login_as(not_creator)
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ end
+
+ it_behaves_like :controllers_api_unauthorized_action
+ end
+
+ context 'successful action path' do
+ before do
+ login_as(creator)
+ authorize
+
+ allow(Board).to receive(:find_by!).with(slug: board.slug).and_return(board)
+ allow(ActionItem).to receive(:find).with(action_item.id.to_s).and_return(action_item)
+ allow(action_item).to receive(:reopen!).and_return(true)
+ end
+
+ it_behaves_like :controllers_api_successful_action, :no_content
+ end
+ end
end
diff --git a/spec/controllers/boards/action_items_controller_spec.rb b/spec/controllers/boards/action_items_controller_spec.rb
index 5dbc32ea..a5bb2c26 100644
--- a/spec/controllers/boards/action_items_controller_spec.rb
+++ b/spec/controllers/boards/action_items_controller_spec.rb
@@ -57,136 +57,4 @@
end
end
end
-
- describe 'PUT #move' do
- subject(:response) { put :move, params: params }
- let_it_be(:params) do
- { board_slug: board.slug,
- id: action_item.id }
- end
-
- context 'when user is not logged in' do
- it { is_expected.to have_http_status(:redirect) }
- end
-
- context 'when user is logged_in' do
- context 'user is not a board member' do
- before { login_as not_member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is a board member' do
- before { login_as member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is the board creator' do
- before { login_as creator }
- it { is_expected.to have_http_status(:redirect) }
- end
- end
- end
-
- describe 'PUT #close' do
- subject(:response) { put :close, params: params }
- let_it_be(:params) do
- { board_slug: board.slug,
- id: action_item.id }
- end
-
- context 'when user is not logged in' do
- it { is_expected.to have_http_status(:redirect) }
- end
-
- context 'when user is logged_in' do
- context 'user is not a board member' do
- before { login_as not_member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is a board member' do
- before { login_as member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is the board creator' do
- before { login_as creator }
- it { is_expected.to have_http_status(:redirect) }
- end
- end
- end
-
- describe 'PUT #complete' do
- subject(:response) { put :complete, params: params }
- let_it_be(:params) do
- { board_slug: board.slug,
- id: action_item.id }
- end
-
- context 'when user is not logged in' do
- it { is_expected.to have_http_status(:redirect) }
- end
-
- context 'when user is logged_in' do
- context 'user is not a board member' do
- before { login_as not_member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is a board member' do
- before { login_as member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is the board creator' do
- before { login_as creator }
- it { is_expected.to have_http_status(:redirect) }
- end
- end
- end
-
- describe 'PUT #reopen' do
- subject(:response) { put :reopen, params: params }
- let_it_be(:params) do
- { board_slug: board.slug,
- id: closed_action_item.id }
- end
-
- context 'when user is not logged in' do
- it { is_expected.to have_http_status(:redirect) }
- end
-
- context 'when user is logged_in' do
- context 'user is not a board member' do
- before { login_as not_member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is a board member' do
- before { login_as member }
- it 'raises error ActionPolicy::Unauthorized' do
- expect { subject }.to raise_error(ActionPolicy::Unauthorized)
- end
- end
-
- context 'user is the board creator' do
- before { login_as creator }
- it { is_expected.to have_http_status(:redirect) }
- end
- end
- end
end
From 74d403651abf6345182c4e2754ba331b8d794aa3 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 12:26:25 +0300
Subject: [PATCH 09/14] add api action_items controller specs
---
app/views/boards/_previous_action_item.erb | 16 ----------------
app/views/boards/show.html.erb | 8 ++------
2 files changed, 2 insertions(+), 22 deletions(-)
delete mode 100644 app/views/boards/_previous_action_item.erb
diff --git a/app/views/boards/_previous_action_item.erb b/app/views/boards/_previous_action_item.erb
deleted file mode 100644
index 0a583baa..00000000
--- a/app/views/boards/_previous_action_item.erb
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
<%= action_item.body %>
- <% if allowed_to?(:move?, action_item, context: { board: @board }) %>
- <%= link_to "move", move_board_action_item_path(@board, action_item.id), method: :post, class: 'button is-small is-light' %>
- <% end %>
- <% if allowed_to?(:close?, action_item, context: { board: @board }) %>
- <%= link_to "close", close_board_action_item_path(@board, action_item.id), method: :put, class: 'button is-small is-danger' %>
- <% end %>
- <% if allowed_to?(:complete?, action_item, context: { board: @board }) %>
- <%= link_to "done", complete_board_action_item_path(@board, action_item.id), method: :put, class: 'button is-small is-success' %>
- <% end %>
- <% if allowed_to?(:reopen?, action_item, context: { board: @board }) %>
- <%= link_to "reopen", reopen_board_action_item_path(@board, action_item.id), method: :put, class: 'button is-small is-light' %>
- <% end %>
-
times previously moved: <%= action_item.times_moved %>
-
diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb
index 4d9b534b..40e07f31 100644
--- a/app/views/boards/show.html.erb
+++ b/app/views/boards/show.html.erb
@@ -50,16 +50,12 @@
body: action_item.body,
status: action_item.status,
times_moved: action_item.times_moved,
-
+ movable: allowed_to?(:move?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
transitionable: {
can_close: allowed_to?(:close?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
can_complete: allowed_to?(:complete?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
can_reopen: allowed_to?(:reopen?, action_item, with: API::ActionItemPolicy, context: { board: @board })
- },
-
- movable: allowed_to?(:move?, action_item, with: API::ActionItemPolicy, context: { board: @board })
- # editable: allowed_to?(:update?, action_item, with: API::ActionItemPolicy, context: { board: @board }),
- # deletable: allowed_to?(:destroy?, action_itemwith: API::ActionItemPolicy context: { board: @board })
+ }
})
%>
<% end %>
From c4774784766b069a8bc4b835132beee02a2c0689 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 12:32:46 +0300
Subject: [PATCH 10/14] switch off autocomplete for card forms
---
app/views/action_items/_form.html.erb | 2 +-
app/views/cards/_form.html.erb | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/views/action_items/_form.html.erb b/app/views/action_items/_form.html.erb
index 1d5e9b2f..4e580176 100644
--- a/app/views/action_items/_form.html.erb
+++ b/app/views/action_items/_form.html.erb
@@ -1,4 +1,4 @@
<%= form_with(model: action_item, url: board_action_items_path(action_item.board.slug), local: true) do |form| %>
Add new action item
- <%= form.text_field :body, class: 'input' %>
+ <%= form.text_field :body, class: 'input', autocomplete: 'off' %>
<% end %>
diff --git a/app/views/cards/_form.html.erb b/app/views/cards/_form.html.erb
index d8249334..d5775589 100644
--- a/app/views/cards/_form.html.erb
+++ b/app/views/cards/_form.html.erb
@@ -1,5 +1,5 @@
<%= form_with(model: card, url: board_cards_path(card.board.slug), local: true) do |form| %>
Add new '<%= card.kind %>' card
<%= form.hidden_field(:kind) %>
- <%= form.text_field :body, class: 'input' %>
+ <%= form.text_field :body, class: 'input', autocomplete: 'off' %>
<% end %>
From 9868f775d1f12de3321f356849bef195cb4ce9e1 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 12:36:57 +0300
Subject: [PATCH 11/14] add stroke to chevrons
---
app/helpers/application_helper.rb | 3 ---
.../ActionItem/ActionItemFooter/ActionItemFooter.css | 3 +++
config/webpacker.yml | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f918456a..15b06f0f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,7 +1,4 @@
# frozen_string_literal: true
module ApplicationHelper
- def calc_life_span(obj)
- time_ago_in_words(obj.created_at)
- end
end
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
index 317770ac..a4498c11 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
+++ b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
@@ -1,4 +1,7 @@
.fa-chevron-right {
margin-right: -2px;
margin-left: -2px;
+ font-size: 16px;
+ stroke: white;
+ stroke-width: 10;
}
diff --git a/config/webpacker.yml b/config/webpacker.yml
index cc24cafd..1594644e 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -17,7 +17,7 @@ default: &default
cache_manifest: false
# Extract and emit a css file
- extract_css: false
+ extract_css: true
static_assets_extensions:
- .jpg
From 939d17f1d699e34de7a7f9585578d652eb4ab13a Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 13:33:56 +0300
Subject: [PATCH 12/14] restore webpack extract_css property to false
---
config/webpacker.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/webpacker.yml b/config/webpacker.yml
index 1594644e..cc24cafd 100644
--- a/config/webpacker.yml
+++ b/config/webpacker.yml
@@ -17,7 +17,7 @@ default: &default
cache_manifest: false
# Extract and emit a css file
- extract_css: true
+ extract_css: false
static_assets_extensions:
- .jpg
From c4b3bb77f869d89c39bcb430c2f0b8c6c194caca Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Tue, 22 Oct 2019 17:49:17 +0300
Subject: [PATCH 13/14] code review changes
---
app/controllers/api/action_items_controller.rb | 8 ++++----
.../ActionItemBody/ActionItemBody.css | 18 +++++++++---------
.../ActionItemFooter/ActionItemFooter.css | 12 ++++++------
.../ActionItem/ActionItemFooter/index.js | 8 +++-----
.../TransitionButton/TransitionButton.css | 1 -
.../ActionItem/TransitionButton/index.js | 5 +----
.../components/Card/CardBody/CardBody.css | 16 ++++++++--------
...17083034_add_times_moved_to_action_items.rb | 2 +-
db/schema.rb | 2 +-
.../api/action_items_controller_spec.rb | 8 ++++----
10 files changed, 37 insertions(+), 43 deletions(-)
delete mode 100644 app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
diff --git a/app/controllers/api/action_items_controller.rb b/app/controllers/api/action_items_controller.rb
index 33c7c98b..3c55ac20 100644
--- a/app/controllers/api/action_items_controller.rb
+++ b/app/controllers/api/action_items_controller.rb
@@ -25,7 +25,7 @@ def destroy
def move
if @action_item.move!(@board)
- head :no_content
+ head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
@@ -33,7 +33,7 @@ def move
def close
if @action_item.close!
- head :no_content
+ head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
@@ -41,7 +41,7 @@ def close
def complete
if @action_item.complete!
- head :no_content
+ head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
@@ -49,7 +49,7 @@ def complete
def reopen
if @action_item.reopen!
- head :no_content
+ head :ok
else
render json: { error: @action_item.errors.full_messages.join(',') }, status: :bad_request
end
diff --git a/app/javascript/components/ActionItem/ActionItemBody/ActionItemBody.css b/app/javascript/components/ActionItem/ActionItemBody/ActionItemBody.css
index a4213b17..350b138e 100644
--- a/app/javascript/components/ActionItem/ActionItemBody/ActionItemBody.css
+++ b/app/javascript/components/ActionItem/ActionItemBody/ActionItemBody.css
@@ -1,11 +1,11 @@
-textarea {
- overflow: hidden;
- resize: none;
- width: 100%;
- font-size: 1rem;
- font-weight: 400;
- line-height: 1.5;
- color: hsl(0, 0%, 29%);
- }
+textarea {
+ overflow: hidden;
+ resize: none;
+ width: 100%;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: hsl(0, 0%, 29%);
+}
.text { overflow-wrap: break-word; }
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
index a4498c11..44fdd240 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
+++ b/app/javascript/components/ActionItem/ActionItemFooter/ActionItemFooter.css
@@ -1,7 +1,7 @@
.fa-chevron-right {
- margin-right: -2px;
- margin-left: -2px;
- font-size: 16px;
- stroke: white;
- stroke-width: 10;
- }
+ margin-right: -2px;
+ margin-left: -2px;
+ font-size: 16px;
+ stroke: white;
+ stroke-width: 10;
+}
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index 18375233..8cab644d 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -37,7 +37,7 @@ class ActionItemFooter extends React.Component {
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
}
}).then((result) => {
- if (result.status == 204) {
+ if (result.status == 200) {
window.location.reload();
}
else { throw result }
@@ -61,10 +61,8 @@ class ActionItemFooter extends React.Component {
generateChevrons = () => {
const times_moved = this.props.times_moved;
- const icon = ;
-
- let chevrons = [];
- for (let i = 0; i < times_moved; i++) chevrons.push(icon);
+ const icon = ;
+ const chevrons = Array.from({ length: times_moved }, () => icon )
return chevrons
};
diff --git a/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css b/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
deleted file mode 100644
index 8b137891..00000000
--- a/app/javascript/components/ActionItem/TransitionButton/TransitionButton.css
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/app/javascript/components/ActionItem/TransitionButton/index.js b/app/javascript/components/ActionItem/TransitionButton/index.js
index 991767c8..7f2aa765 100644
--- a/app/javascript/components/ActionItem/TransitionButton/index.js
+++ b/app/javascript/components/ActionItem/TransitionButton/index.js
@@ -1,7 +1,5 @@
import React from "react"
-import "./TransitionButton.css"
-
class TransitionButton extends React.Component {
constructor(props) {
super(props)
@@ -17,8 +15,7 @@ class TransitionButton extends React.Component {
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").getAttribute('content')
}
}).then((result) => {
- if (result.status == 204) {
- console.log(`doing ${this.props.action}!!!`)
+ if (result.status == 200) {
window.location.reload();
}
else { throw result }
diff --git a/app/javascript/components/Card/CardBody/CardBody.css b/app/javascript/components/Card/CardBody/CardBody.css
index a4213b17..361fdd99 100644
--- a/app/javascript/components/Card/CardBody/CardBody.css
+++ b/app/javascript/components/Card/CardBody/CardBody.css
@@ -1,11 +1,11 @@
textarea {
- overflow: hidden;
- resize: none;
- width: 100%;
- font-size: 1rem;
- font-weight: 400;
- line-height: 1.5;
- color: hsl(0, 0%, 29%);
- }
+ overflow: hidden;
+ resize: none;
+ width: 100%;
+ font-size: 1rem;
+ font-weight: 400;
+ line-height: 1.5;
+ color: hsl(0, 0%, 29%);
+}
.text { overflow-wrap: break-word; }
diff --git a/db/migrate/20191017083034_add_times_moved_to_action_items.rb b/db/migrate/20191017083034_add_times_moved_to_action_items.rb
index 440b35ab..4f391542 100644
--- a/db/migrate/20191017083034_add_times_moved_to_action_items.rb
+++ b/db/migrate/20191017083034_add_times_moved_to_action_items.rb
@@ -2,6 +2,6 @@
class AddTimesMovedToActionItems < ActiveRecord::Migration[6.0]
def change
- add_column :action_items, :times_moved, :integer, default: 0
+ add_column :action_items, :times_moved, :integer, default: 0, null: false
end
end
diff --git a/db/schema.rb b/db/schema.rb
index 360452cc..fe991ee9 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -21,7 +21,7 @@
t.string "status", default: "pending", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.integer "times_moved", default: 0
+ t.integer "times_moved", default: 0, null: false
t.index ["board_id"], name: "index_action_items_on_board_id"
end
diff --git a/spec/controllers/api/action_items_controller_spec.rb b/spec/controllers/api/action_items_controller_spec.rb
index 2db57917..aeb23566 100644
--- a/spec/controllers/api/action_items_controller_spec.rb
+++ b/spec/controllers/api/action_items_controller_spec.rb
@@ -111,7 +111,7 @@
allow(action_item).to receive(:move!).with(board).and_return(true)
end
- it_behaves_like :controllers_api_successful_action, :no_content
+ it_behaves_like :controllers_api_successful_action
end
end
@@ -144,7 +144,7 @@
allow(action_item).to receive(:close!).and_return(true)
end
- it_behaves_like :controllers_api_successful_action, :no_content
+ it_behaves_like :controllers_api_successful_action
end
end
@@ -177,7 +177,7 @@
allow(action_item).to receive(:complete!).and_return(true)
end
- it_behaves_like :controllers_api_successful_action, :no_content
+ it_behaves_like :controllers_api_successful_action
end
end
@@ -210,7 +210,7 @@
allow(action_item).to receive(:reopen!).and_return(true)
end
- it_behaves_like :controllers_api_successful_action, :no_content
+ it_behaves_like :controllers_api_successful_action
end
end
end
From cbfe7972022598d81980892bb53efa4d91dcff67 Mon Sep 17 00:00:00 2001
From: nikolaiprivalov <52496677+nikolaiprivalov@users.noreply.github.com>
Date: Thu, 24 Oct 2019 12:42:34 +0300
Subject: [PATCH 14/14] Update
app/javascript/components/ActionItem/ActionItemFooter/index.js
Co-Authored-By: Vitaliy Pecherin
---
app/javascript/components/ActionItem/ActionItemFooter/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/javascript/components/ActionItem/ActionItemFooter/index.js b/app/javascript/components/ActionItem/ActionItemFooter/index.js
index 8cab644d..cdb0c078 100644
--- a/app/javascript/components/ActionItem/ActionItemFooter/index.js
+++ b/app/javascript/components/ActionItem/ActionItemFooter/index.js
@@ -62,7 +62,7 @@ class ActionItemFooter extends React.Component {
generateChevrons = () => {
const times_moved = this.props.times_moved;
const icon = ;
- const chevrons = Array.from({ length: times_moved }, () => icon )
+ const chevrons = Array.from({ length: times_moved }, () => icon)
return chevrons
};