Skip to content

Commit

Permalink
added loading state to story when updated
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielRMuller committed Feb 11, 2019
1 parent 08e4234 commit a429f82
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 22 deletions.
1 change: 1 addition & 0 deletions app/assets/javascripts/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default keyMirror({
EDIT_STORY: null,
UPDATE_STORY: null,
UPDATE_STORY_SUCCESS: null,
SET_LOADING_STORY: null,
ADD_TASK: null,
REMOVE_TASK: null,
TOGGLE_TASK: null,
Expand Down
19 changes: 13 additions & 6 deletions app/assets/javascripts/actions/note.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import actionTypes from './actionTypes';
import { setLoadingStory } from './story';

export const deleteNoteSuccess = (storyId, noteId) => ({
type: actionTypes.DELETE_NOTE,
Expand All @@ -13,11 +14,17 @@ export const createNoteSuccess = (storyId, note) => ({
});

export const deleteNote = (projectId, storyId, noteId) =>
(dispatch, getState, { Note }) =>
Note.destroy(projectId, storyId, noteId)
.then(() => dispatch(deleteNoteSuccess(storyId, noteId)));
(dispatch, getState, { Note }) => {
dispatch(setLoadingStory(storyId));
return Note.destroy(projectId, storyId, noteId)
.then(() => dispatch(deleteNoteSuccess(storyId, noteId)))
.catch(() => dispatch(setLoadingStory(storyId)))
}

export const createNote = (projectId, storyId, note) =>
(dispatch, getState, { Note }) =>
Note.post(projectId, storyId, note)
.then((note) => dispatch(createNoteSuccess(storyId, note)));
(dispatch, getState, { Note }) => {
dispatch(setLoadingStory(storyId));
return Note.post(projectId, storyId, note)
.then((note) => dispatch(createNoteSuccess(storyId, note)))
.catch(() => dispatch(setLoadingStory(storyId)))
}
28 changes: 18 additions & 10 deletions app/assets/javascripts/actions/story.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,32 @@ export const editStory = (id, newAttributes) => ({
newAttributes
});

export const setLoadingStory = (id) => ({
type: actionTypes.SET_LOADING_STORY,
id
});

export const updateStory = (story, projectId) =>
(dispatch, getState, { Story }) => {
if (story._editing._isDirty) {
dispatch(setLoadingStory(story.id))
return Story.update(story._editing, projectId)
.then((story) => {
dispatch(updateStorySuccess(story));
});
dispatch(updateStorySuccess(story))
})
.catch(() => dispatch(setLoadingStory(story.id)))
}
return dispatch(toggleStory(story.id));
};

export const deleteStory = (storyId, projectId) =>
(dispatch, getState, { Story }) =>
Story.deleteStory(storyId, projectId)
(dispatch, getState, { Story }) => {
dispatch(setLoadingStory(storyId))
return Story.deleteStory(storyId, projectId)
.then(() => dispatch(deleteStorySuccess(storyId)))
.catch(
error => {
// TODO: dispatch an action to notify user on error
alert(error.message);
}
);
.catch( (error) => {
dispatch(setLoadingStory(storyId))
// TODO: dispatch an action to notify user on error
alert(error.message);
});
}
9 changes: 7 additions & 2 deletions app/assets/javascripts/actions/task.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import actionTypes from './actionTypes';
import { setLoadingStory } from './story';

export const createTaskSuccess = (task, storyId) => ({
type: actionTypes.ADD_TASK,
Expand All @@ -20,19 +21,23 @@ export const toggleTaskSuccess = (task, story) => ({

export const createTask = (projectId, storyId, task) => {
return (dispatch, getState, { Task }) => {
dispatch((setLoadingStory(storyId)))
return Task.post(projectId, storyId, task)
.then(task => {
dispatch(createTaskSuccess(task, storyId))
});
})
.catch(() => dispatch(setLoadingStory(storyId)))
};
};

export const deleteTask = (projectId, storyId, taskId) => {
return (dispatch, getState, { Task }) => {
dispatch(setLoadingStory(storyId))
return Task.destroy(projectId, storyId, taskId)
.then(() => {
dispatch(deleteTaskSuccess(taskId, storyId))
});
})
.catch(() => dispatch(setLoadingStory(storyId)))
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ export const ExpandedStory = ({
addLabel,
removeLabel
}) => {
const loading = story._editing.loading ? "Story__enable-loading" : "";
return (
<div className="Story Story--expanded">
<div className={`Story Story--expanded ${loading}`} >
<div className="Story__loading"></div>
<ExpandedStoryControls
onCancel={onToggle}
onSave={() => updateStory(story, project.id)}
Expand Down Expand Up @@ -99,7 +101,7 @@ export const ExpandedStory = ({

<ExpandedStoryTask
story={story}
onToggle={ (task, status) => toggleTask(project.id, story, task, status)}
onToggle={(task, status) => toggleTask(project.id, story, task, status)}
onDelete={(taskId) => deleteTask(project.id, story.id, taskId)}
onSave={(task) => createTask(project.id, story.id, task)}
/>
Expand Down
8 changes: 8 additions & 0 deletions app/assets/javascripts/models/beta/note.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export const post = (projectId, storyId, note) =>
export const addNote = (story, note) => (
{
...story,
_editing: {
...story._editing,
loading: false
},
notes: [
...story.notes,
note
Expand All @@ -25,6 +29,10 @@ export const addNote = (story, note) => (
export const deleteNote = (story, noteId) => (
{
...story,
_editing: {
...story._editing,
loading: false
},
notes: story.notes.filter(note => note.id !== noteId)
}
);
10 changes: 9 additions & 1 deletion app/assets/javascripts/models/beta/story.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const updateStory = (story, newAttributes) => {
};

export const toggleStory = (story) => {
const editing = story.collapsed ? { ...story, _isDirty: false } : null;
const editing = story.collapsed ? { ...story, _isDirty: false, loading: false } : null;

return {
...story,
Expand All @@ -119,6 +119,14 @@ export const editStory = (story, newAttributes) => {
};
};

export const setLoadingStory = (story) => ({
...story,
_editing: {
...story._editing,
loading: !story._editing.loading
}
});

export const deserialize = (story) => ({
...story,
labels: Label.splitLabels(story.labels),
Expand Down
8 changes: 8 additions & 0 deletions app/assets/javascripts/models/beta/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export const destroy = (projectId, storyId, taskId) => {
export const addTask = (story, task) => (
{
...story,
_editing: {
...story._editing,
loading: false
},
tasks: [
...story.tasks,
task
Expand All @@ -59,5 +63,9 @@ export const toggleTask = (story, changedTask) => {

export const deleteTask = (taskId, story) => ({
...story,
_editing: {
...story._editing,
loading: false
},
tasks: story.tasks.filter((task) => task.id !== taskId)
});
5 changes: 4 additions & 1 deletion app/assets/javascripts/reducers/stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import actionTypes from 'actions/actionTypes';
import { toggleStory, editStory, updateStory } from 'models/beta/story'
import { toggleStory, editStory, updateStory, setLoadingStory } from 'models/beta/story'
import * as Note from 'models/beta/note';
import { updateIfSameId } from '../services/updateIfSameId';
import * as Task from 'models/beta/task';
Expand All @@ -24,6 +24,9 @@ const storiesReducer = (state = initialState, action) => {
updateIfSameId(action.story.id, (story) => {
return updateStory(story, action.story);
}));
case actionTypes.SET_LOADING_STORY:
return state.map(
updateIfSameId(action.id, setLoadingStory));
case actionTypes.ADD_TASK:
return state.map(
updateIfSameId(action.storyId, (story) => {
Expand Down
22 changes: 22 additions & 0 deletions app/assets/stylesheets/new_board/_story.scss
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
padding: 8px 10px;
background-color: $butter-2;
color: $aluminium-6;
position: relative;

.Story {
&__flex {
Expand Down Expand Up @@ -265,6 +266,27 @@
@extend .btn-xs;
}
}

&__loading {
background: black;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 3;
opacity: 0.3;
display: none;
}
}
}
&__enable-loading {
cursor: wait;
border-top: 0;
border-bottom: 0;

.Story__loading {
display: block;
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions spec/javascripts/actions/note_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ describe('Note Actions', () => {
done();
});
});

it('dispatches setLoadingStory when promise fails', () => {
const FakeNote = {
post: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Note.createNote(projectId, storyId, note)(fakeDispatch, null, { Note: FakeNote })
.then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(storyId));

done();
});
});
});

describe('deleteNote', () => {
Expand Down Expand Up @@ -71,5 +86,20 @@ describe('Note Actions', () => {
done();
});
});

it('dispatches setLoadingStory when promise fails', () => {
const FakeNote = {
destroy: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Note.deleteNote(projectId, storyId, noteId)(fakeDispatch, null, { Note: FakeNote })
.then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(storyId));

done();
});
});
});
})
37 changes: 37 additions & 0 deletions spec/javascripts/actions/story_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ describe('updateStory', () => {
done();
});
});

it('dispatch setLoadingStory when promise fails', (done) => {
const editedStory = {
...story,
_editing: {
...story,
loading: true,
_isDirty: true
}
};

const FakeStory = {
update: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Story.updateStory(editedStory, projectId)(fakeDispatch, null, { Story: FakeStory }).then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(story.id));

done();
});
});
});

describe('deleteStory', () => {
Expand Down Expand Up @@ -104,4 +127,18 @@ describe('deleteStory', () => {
done();
});
});

it('dispatches setLoadingStory when promise fails', () => {
const FakeStory = {
deleteStory: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Story.deleteStory(storyId, projectId)(fakeDispatch, null, { Story: FakeStory }).then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(storyId));

done();
});
});
});
30 changes: 30 additions & 0 deletions spec/javascripts/actions/task_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ describe('Task Actions', () => {
done();
});
});

it('dispatch setLoadingStory when promise fails', () => {
const FakeTask = {
post: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Task.createTask(projectId, story.id, task)(fakeDispatch, null, { Task: FakeTask })
.then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(story.id));

done();
});
});
});

describe('deleteTask', () => {
Expand Down Expand Up @@ -71,6 +86,21 @@ describe('Task Actions', () => {
done();
});
});

it('dispatch setLoadingStory when promise fails', () => {
const FakeTask = {
destroy: sinon.stub().rejects()
};

const fakeDispatch = sinon.stub().resolves({});

Task.deleteTask(projectId, story.id, task)(fakeDispatch, null, { Task: FakeTask })
.then(() => {
expect(fakeDispatch).toHaveBeenCalledWith(Story.setLoadingStory(story.id));

done();
});
});
});

describe('toggleTask', () => {
Expand Down
Loading

0 comments on commit a429f82

Please sign in to comment.