Skip to content

Commit

Permalink
New position logic for drag and drop beta version (#716)
Browse files Browse the repository at this point in the history
* Add new position to story model

* Create populate new position task

* Create sort stories route

* Added ordering stories specs

* Added sort stories to redux

* Implemented new position logic

* Update and fix specs

* Update changelog

* Remove a console.log

* Remove unnecessary PunditContext

* Change new route path

* Refactored SortStories reducer

* Fixed beta stories controller spec

* Refactores projectBoard beta model

* Change calculatePositions arguments

* Default value to calculatePosition arguments

* Added logging to populate new position task

* Fixed Story model spec

* Fixed some CodeClimate issues

* Fixed cypress specs
  • Loading branch information
petruspierre authored Feb 19, 2021
1 parent 8398358 commit 8b190c4
Show file tree
Hide file tree
Showing 24 changed files with 330 additions and 121 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
### Fixed
- Bug when change release date of story.

### Added
- New position column to Story

### Changed
- Change drag and drop logic in beta version

## [2.6.0] 2020-03-11

### Added
Expand Down
1 change: 1 addition & 0 deletions app/assets/javascripts/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default keyMirror({
RECEIVE_HISTORY_ERROR: null,
CLOSE_HISTORY: null,
UPDATE_STORY_SUCCESS: null,
SORT_STORIES_SUCCESS: null,
STORY_FAILURE: null,
SET_LOADING_STORY: null,
ADD_TASK: null,
Expand Down
11 changes: 8 additions & 3 deletions app/assets/javascripts/actions/story.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export const updateStorySuccess = (story, from) => ({
from
});

export const sortStoriesSuccess = (stories, from) => ({
type: actionTypes.SORT_STORIES_SUCCESS,
stories,
from
});

export const optimisticallyUpdate = (story, from) => ({
type: actionTypes.OPTIMISTICALLY_UPDATE,
story,
Expand Down Expand Up @@ -183,11 +189,10 @@ export const dragDropStory = (storyId, projectId, newAttributes, from) =>
try {
dispatch(optimisticallyUpdate(newStory, from));

const updatedStory = await Story.update(newStory, projectId);
const updatedStories = await Story.updatePosition(newStory);

await wait(300);

return dispatch(updateStorySuccess(updatedStory, from));
return dispatch(sortStoriesSuccess(updatedStories, from));
}
catch (error) {
dispatch(sendErrorNotification(error))
Expand Down
11 changes: 7 additions & 4 deletions app/assets/javascripts/components/projects/ProjectBoard.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import { CHILLY_BIN, DONE, BACKLOG, EPIC } from "../../models/beta/column";
import PropTypes from "prop-types";
import {
canCloseColumn,
getNewPosition,
getPositions,
getNewSprints,
getNewState,
moveStory,
getSprintColumn,
dragStory
} from '../../models/beta/projectBoard';
import { historyStatus, columns } from 'libs/beta/constants';
import { historyStatus, columns, storyTypes } from 'libs/beta/constants';
import StoryPropTypes from '../shapes/story';
import ProjectBoardPropTypes from '../shapes/projectBoard';
import Notifications from '../Notifications';
Expand Down Expand Up @@ -82,15 +82,17 @@ export const ProjectBoard = ({

if (isSameColumn && sourceIndex === destinationIndex) return;
if (!dropColumn) return;
if (!isSameColumn && dragStory.storyType === storyTypes.FEATURE && !dragStory.estimate) return;

const newPosition = getNewPosition(
const [position, newPosition] = getPositions(
destinationIndex,
sourceIndex,
destinationArray,
isSameColumn,
dragStory.state,
);


const newStories = moveStory(
sourceArray,
destinationArray,
Expand All @@ -112,7 +114,8 @@ export const ProjectBoard = ({
const newState = getNewState(isSameColumn, dropColumn, dragStory.state);

return dragDropStory(dragStory.id, dragStory.projectId, {
position: newPosition,
position,
newPosition,
state: newState,
});
};
Expand Down
73 changes: 42 additions & 31 deletions app/assets/javascripts/models/beta/projectBoard.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,44 +68,43 @@ export const toggleColumn = (projectBoard, column, callback) => {
}

// Drag And drop utils
const calculatePosition = (aboveStory, bellowStory, storyState) => {
const aboveStoryState = aboveStory?.state;
const bellowStoryState = bellowStory?.state;
const aboveStoryPosition = aboveStory?.position;
const bellowStoryPosition = bellowStory?.position;

if (!bellowStory) return Number(aboveStoryPosition) + 1;
if (!aboveStory) return Number(bellowStoryPosition) - 1;
if (aboveStoryState === storyState && bellowStoryState !== storyState) return Number(aboveStoryPosition) + 1;
if (bellowStoryState === storyState && aboveStoryState !== storyState) return Number(bellowStoryPosition) - 1;

return (Number(bellowStoryPosition) + Number(aboveStoryPosition)) / 2;
};
const calculatePositions = (aboveStory = {}, belowStory = {}, storyState = {}) => {
const aboveStoryState = aboveStory.state;
const belowStoryState = belowStory.state;
const aboveStoryPosition = Number(aboveStory.position);
const belowStoryPosition = Number(belowStory.position);
const aboveStoryNewPosition = aboveStory.newPosition;
const belowStoryNewPosition = belowStory.newPosition;

const isFirstStory = !aboveStoryState;
const isLastStory = !belowStoryState;
const isLastStoryInSameState = aboveStoryState === storyState && belowStoryState !== storyState;
const isFirstStoryInSameState = belowStoryState === storyState && aboveStoryState !== storyState;

// return [position, newPosition]
if (isFirstStory) return [belowStoryPosition - 1, 1];
if (isFirstStoryInSameState) return [belowStoryPosition - 1, belowStoryNewPosition - 1];
if (isLastStory || isLastStoryInSameState) return [aboveStoryPosition + 1, aboveStoryNewPosition + 1];

return [(belowStoryPosition + aboveStoryPosition) / 2, aboveStoryNewPosition + 1];
}

export const getNewPosition = (
export const getPositions = (
destinationIndex,
sourceIndex,
storiesArray,
isSameColumn,
storyState,
) => {
if (isEmpty(storiesArray)) {
return 1; // if array is empty than set 1 to story position
return [1, 1];
}

if (!isSameColumn || sourceIndex > destinationIndex) {
return calculatePosition(
storiesArray[destinationIndex - 1],
storiesArray[destinationIndex],
storyState,
);
return calculatePositions(storiesArray[destinationIndex - 1], storiesArray[destinationIndex], storyState);
}

return calculatePosition(
storiesArray[destinationIndex],
storiesArray[destinationIndex + 1],
storyState,
);
return calculatePositions(storiesArray[destinationIndex], storiesArray[destinationIndex + 1], storyState);
};

// reorder the array
Expand All @@ -114,16 +113,28 @@ export const moveStory = (
destinationArray,
sourceIndex,
destinationIndex,
isSameColumn
) => {
const newSourceArray = [...sourceArray];
const [removed] = newSourceArray.splice(sourceIndex, 1);
const newDestinationArray = [...destinationArray];
const filteredArray = newDestinationArray.filter((el, index) => index !== sourceIndex);
filteredArray.splice(destinationIndex, 0, removed);
const removed = sourceArray[sourceIndex];

const newDestinationArray = isSameColumn
? destinationArray.filter((_, index) => index !== sourceIndex)
: [...destinationArray];

return [...filteredArray];
newDestinationArray.splice(destinationIndex, 0, removed);

return sortStories(newDestinationArray, destinationIndex);
};

export const sortStories = (destinationArray, destinationIndex) => {
return destinationArray.map((item, index) => {
if (index >= destinationIndex) {
return { ...item, newPosition: item.newPosition + 1 }
};
return item;
});
}

export const getNewSprints = (newStories, sprints, sprintIndex) =>
sprints.map((sprint, index) =>
index === sprintIndex ? { ...sprint, stories: newStories } : sprint,
Expand Down
23 changes: 17 additions & 6 deletions app/assets/javascripts/models/beta/story.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export const needConfirmation = story =>
story.state === status.RELEASE

export const comparePosition = (a, b) => {
const positionA = parseFloat(a.position);
const positionB = parseFloat(b.position);
const positionA = a.newPosition;
const positionB = b.newPosition;

return compareValues(positionA, positionB);
};
Expand Down Expand Up @@ -108,20 +108,30 @@ export const findById = (stories, id) => {
return stories.find(story => story.id === id)
}

export const updatePosition = async (story) => {
const serializedStory = serialize(story);

const { data } = await httpService.post(`/beta/stories/${story.id}/position`, { story: serializedStory });

const deserializedData = data.map((item) => (deserialize(item.story)));

return deserializedData;
}

export const update = async (story, projectId, options) => {
const newStory = serialize(story);
const serializedStory = serialize(story);

const { data } = await httpService
.put(`/projects/${projectId}/stories/${story.id}`, { story: newStory });
.put(`/projects/${projectId}/stories/${story.id}`, { story: serializedStory });

return deserialize(data.story, options);
};

export const post = async (story, projectId) => {
const newStory = serialize(story);
const serializedStory = serialize(story);

const { data } = await httpService
.post(`/projects/${projectId}/stories`, { story: newStory });
.post(`/projects/${projectId}/stories`, { story: serializedStory });

return deserialize(data.story);
};
Expand Down Expand Up @@ -374,6 +384,7 @@ const emptyStory = {
createdAt: '',
updatedAt: '',
position: '',
newPosition: null,
labels: [],
requestedByName: '',
ownedByName: null,
Expand Down
12 changes: 11 additions & 1 deletion app/assets/javascripts/reducers/stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,24 @@ const storiesReducer = (state = initialState, action) => {
addNewAttributes(story, { ...action.story, needsToSave: false, loading: false })
))
})
case actionTypes.SORT_STORIES_SUCCESS:
return allScopes(state, null, stories => {
return stories.map((story) => {
const editingStory = action.stories.find((incomingStory) => story.id === incomingStory.id)

return editingStory
? addNewAttributes(story, { ...editingStory, needsToSave: false, loading: false })
: story
})
})
case actionTypes.OPTIMISTICALLY_UPDATE:
return allScopes(state, action.story.id, stories => {
return stories.map(
updateIfSameId(action.story.id, story => {
const newStory = setLoadingValue(action.story, true);
return addNewAttributes(story, { ...newStory, needsToSave: false });
}
))
))
})
case actionTypes.STORY_FAILURE:
return {
Expand Down
19 changes: 19 additions & 0 deletions app/controllers/beta/stories_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Beta::StoriesController < ApplicationController
before_action :set_project_and_story
def position
authorize Story
stories = Beta::SortStories.new(@story, allowed_params).call
render json: stories
end

private

def allowed_params
params.require(:story).permit(:position, :new_position, :id, :state, :project_id)
end

def set_project_and_story
@project = policy_scope(Project).find(allowed_params[:project_id])
@story = policy_scope(Story).find(allowed_params[:id])
end
end
2 changes: 1 addition & 1 deletion app/models/story.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def from_csv_row(row)
title release_date accepted_at created_at updated_at delivered_at description
project_id story_type owned_by_id requested_by_id
owned_by_name owned_by_initials requested_by_name estimate
state position id labels
state position id labels new_position
].freeze

JSON_METHODS = %w[errors notes documents tasks].freeze
Expand Down
4 changes: 4 additions & 0 deletions app/policies/story_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def sort?
update?
end

def position?
update?
end

def backlog?
update?
end
Expand Down
39 changes: 39 additions & 0 deletions app/services/beta/sort_stories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Beta::SortStories
def initialize(story, sort_params)
@story = story
@new_position = sort_params[:new_position]
@new_state = sort_params[:state]
@position = sort_params[:position]
end

def call
update_current_story
update_stories_positions
stories
end

private

def update_current_story
@story.update(new_position: @new_position, state: @new_state, position: @position)
end

def stories_to_update
stories.where.not(id: @story.id)
end

def update_stories_positions
stories_to_update.update_all('new_position = new_position + 1')
end

def stories
case @new_state
when 'started', 'finished', 'delivered'
@story.project.stories.in_progress.where('new_position >= ?', @new_position)
when 'unstarted'
@story.project.stories.backlog.where('new_position >= ?', @new_position)
when 'unscheduled'
@story.project.stories.chilly_bin.where('new_position >= ?', @new_position)
end
end
end
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
namespace :beta do
resources :projects, only: :show
resources :project_boards, only: :show
resources :stories, only: [] do
member do
post :position
end
end
end

resources :tag_groups
Expand Down
8 changes: 8 additions & 0 deletions db/migrate/20210201140311_add_new_position_to_story.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddNewPositionToStory < ActiveRecord::Migration[5.2]
def change
add_column :stories, :new_position, :integer, null: true
end
def down
remove_column :stories, :new_position
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_01_14_123043) do
ActiveRecord::Schema.define(version: 2021_02_01_140311) do

# These are extensions that must be enabled in order to support this database
enable_extension "hstore"
Expand Down Expand Up @@ -186,6 +186,7 @@
t.date "release_date"
t.datetime "delivered_at"
t.string "branch"
t.integer "new_position"
end

create_table "tag_groups", id: :serial, force: :cascade do |t|
Expand Down
Loading

0 comments on commit 8b190c4

Please sign in to comment.