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 ( -
-
-
-  {window.confirm(confirmMessage) && this.handleClick()}}> - delete - -
-
+ <> + {footerNotEmpty && +
+
+ +  {window.confirm(confirmMessage) && this.handleClick()}} hidden={!deletable}> + delete + +
+ } + ); } } 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 && -
-
- -  {window.confirm(confirmMessage) && this.handleClick()}} hidden={!deletable}> - delete - -
- } - +
+
+
{this.renderChevrons()}
+  {window.confirm(confirmMessage) && this.handleClick()}} hidden={!deletable}> + delete + +
); } } 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 (
- + {footerNotEmpty && }
); } 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 (

-
{this.renderChevrons()}
-  {window.confirm(confirmMessage) && this.handleClick()}} hidden={!deletable}> - delete - +
{this.generateChevrons()}
+ + {transitionable && transitionable.can_close && } + {transitionable && transitionable.can_complete && } + {transitionable && transitionable.can_reopen && } + {movable && } + +
+ {deletable &&  {window.confirm(confirmDeleteMessage) && this.handleDeleteClick()}}> + delete + } +
- paintActionItem('yellow')}> - clickme -
); } 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 };