Skip to content
This repository has been archived by the owner on Jan 4, 2019. It is now read-only.

Commit

Permalink
Fileupload (#45)
Browse files Browse the repository at this point in the history
* [KFI]feat(Actions): Add upload request, success and failure actions

* [KFI]test(Actions): Add tests for testing the new upload related actions

* [KFI]feat(Epics): Add new epic for upload a file

* [KFI]test(Epics): Add test for testing the upload epic

* [KFI]feat(Reducers): Change ids and entities reducer to handle UPLOAD_CONTENT_SUCCESS

* [KFI]test(Reducers): Add upload related reducer tests

* [KFI]chore: Update sn-client-js to 2.5.0

* [KFI]test(Epics): Fix epic tests related to sn-client-js upgrade

* [KFI]chore: Update version number to 3.3.0
  • Loading branch information
herflis authored Oct 24, 2017
1 parent e6c2218 commit ef944c6
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 306 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sn-redux",
"version": "3.2.2",
"version": "3.3.0",
"description": "A set of redux actions, reducers and redux-ovbservable epics for Sense/Net ECM",
"main": "dist/src/sn-redux.js",
"scripts": {
Expand Down Expand Up @@ -85,7 +85,7 @@
"redux-mock-store": "^1.2.3",
"redux-observable": "^0.16.0",
"semantic-release": "^8.0.0",
"sn-client-js": "^2.4.0",
"sn-client-js": "^2.5.0",
"tslint": "^5.6.0",
"typedoc": "^0.9.0",
"typedoc-md-theme": "^1.0.1",
Expand Down
56 changes: 47 additions & 9 deletions src/Actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { normalize } from 'normalizr';
import { Schemas } from './Schema';
import { Content, ODataApi, ODataHelper, Repository } from 'sn-client-js';
import { Content, ODataApi, ODataHelper, Repository, ContentTypes } from 'sn-client-js';

/**
* Module that contains the action creators.
Expand Down Expand Up @@ -723,9 +723,9 @@ export module Actions {
* Action creator for clearing the array of selected content
* @returns {Object} Returns a redux action.
*/
export const ClearSelection = () => ({
type: 'CLEAR_SELECTION'
})
export const ClearSelection = () => ({
type: 'CLEAR_SELECTION'
})
/**
* Action creator for a request for get actions of a content by a given scenario.
* @param content {Content} The name of the scenario
Expand All @@ -742,12 +742,13 @@ export module Actions {
* @param response {any} JSON response of the ajax request.
* @returns {Object} Returns a redux action with a response.
*/
export const RequestContentActionsSuccess = (response: any, id: number) => {
export const RequestContentActionsSuccess = (response: any, id: number) => {
return ({
type: 'REQUEST_CONTENT_ACTIONS_SUCCESS',
response: response,
id
})}
type: 'REQUEST_CONTENT_ACTIONS_SUCCESS',
response: response,
id
})
}
/**
* Action creator for the step when getting the actions of a content is failed
* @param error {any} JSON response of the ajax request.
Expand All @@ -757,4 +758,41 @@ export module Actions {
type: 'REQUEST_CONTENT_ACTIONS_FAILURE',
message: error.message
})
/**
* Action creator for uploading a Content into the Content Repository.
* @param {Content} content The parent Content
* @param file The file that should be uploaded
* @param {ContentTypes.ContentType} [contentType=ContentTypes.File] ContentType of the Content that should be created with the binary (default is File)
* @param {boolean} [overwrite=true] Determines whether the existing file with a same name should be overwritten or not (default is true)
* @param {Object} [body=null] Contains extra stuff to request body
* @param {string} [propertyName='Binary'] Name of the field where the binary should be saved
* @returns {Object} Returns a redux action with the properties type, content, file, contentType, overwrite, body and propertyName.
*/
export const UploadRequest = (content: Content, file, contentType?, overwrite?: boolean, body?, propertyName?: string) => ({
type: 'UPLOAD_CONTENT_REQUEST',
content,
file,
contentType: contentType || ContentTypes.File,
overwrite: typeof overwrite !== 'undefined' ? overwrite : true,
body: body ? body : null,
propertyName: propertyName ? propertyName : 'Binary'
})
/**
* Action creator for the step when a content was uploaded successfully.
* @param response {any} JSON response of the ajax request.
* @returns {Object} Returns a redux action with a response.
*/
export const UploadSuccess = (response) => ({
type: 'UPLOAD_CONTENT_SUCCESS',
response
})
/**
* Action creator for the step when uploading a content is failed
* @param error {any} JSON response of the ajax request.
* @returns {Object} Returns a redux action with a response.
*/
export const UploadFailure = (error: any) => ({
type: 'UPLOAD_CONTENT_FAILURE',
message: error.message
})
}
39 changes: 30 additions & 9 deletions src/Epics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ export module Epics {
// .map(result => {

dependencies.repository.GetCurrentUser().subscribe(user => {
if (user.Name === 'Visitor'){
if (user.Name === 'Visitor') {
store.dispatch(Actions.UserLoginFailure({ message: null }))
}
else{
else {
store.dispatch(Actions.UserChanged(user))
store.dispatch(Actions.UserLoginSuccess(user))
}
Expand Down Expand Up @@ -339,13 +339,13 @@ export module Epics {
}
export const userLoginBufferEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => {
return action$.ofType('USER_LOGIN_BUFFER')
.mergeMap(action => {
return dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor')
.map(result => {
Actions.UserLoginSuccess(result)
.mergeMap(action => {
return dependencies.repository.GetCurrentUser().skipWhile(u => u.Name === 'Visitor')
.map(result => {
Actions.UserLoginSuccess(result)
})
.catch(error => Observable.of(Actions.UserLoginFailure(error)))
})
.catch(error => Observable.of(Actions.UserLoginFailure(error)))
})
}
/**
* Epic to logout a user from a sensenet portal. It is related to three redux actions, returns ```LogoutUser``` action and sends the response to the
Expand All @@ -368,6 +368,26 @@ export module Epics {
.catch(error => Observable.of(Actions.RequestContentActionsFailure(error)))
})
}
/**
* Epic to upload a file to the Content Repository. It is related to three redux actions, returns ```UploadContent``` action and sends the response to the
* ```UploadSuccess``` action if the ajax request ended successfully or catches the error if the request failed and sends the error message to the ```UploadFailure``` action.
*/
export const uploadFileEpic = (action$, store, dependencies?: { repository: Repository.BaseRepository }) => {
return action$.ofType('UPLOAD_CONTENT_REQUEST')
.mergeMap(action => {
return action.content.UploadFile({
File: action.file,
ContentType: action.contentType,
OverWrite: action.overwrite,
Body: action.body,
PropertyName: action.propertyName
})
.map((response) => {
return Actions.UploadSuccess(response)
})
.catch(error => Observable.of(Actions.UploadFailure(error)))
})
}
/**
* sn-redux root Epic, the main Epic combination that is used on a default sensenet application. Contains Epics related to CRUD operations and thr other built-in sensenet
* [OData Actions and Function](http://wiki.sensenet.com/Built-in_OData_actions_and_functions).
Expand All @@ -392,7 +412,8 @@ export module Epics {
userLoginEpic,
userLogoutEpic,
checkLoginStateEpic,
getContentActions
getContentActions,
uploadFileEpic
);
}

12 changes: 11 additions & 1 deletion src/Reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ export module Reducers {
return action.response.result;
case 'CREATE_CONTENT_SUCCESS':
return [...state, action.response.result];
case 'UPLOAD_CONTENT_SUCCESS':
if (state.indexOf(action.response.CreatedContent.Id) === -1)
return [...state, action.response.CreatedContent.Id];
else
return state
case 'DELETE_CONTENT_SUCCESS':
return [...state.slice(0, action.index), ...state.slice(action.index + 1)]
default:
Expand All @@ -199,7 +204,8 @@ export module Reducers {
action.type !== 'USER_LOGIN_BUFFER' &&
action.type !== 'LOAD_CONTENT_SUCCESS' &&
action.type !== 'REQUEST_CONTENT_ACTIONS_SUCCESS' &&
action.type !== 'UPDATE_CONTENT_SUCCESS')) {
action.type !== 'UPDATE_CONTENT_SUCCESS' &&
action.type !== 'UPLOAD_CONTENT_SUCCESS')) {
return (<any>Object).assign({}, state, action.response.entities.entities);
}
switch (action.type) {
Expand All @@ -210,6 +216,10 @@ export module Reducers {
case 'UPDATE_CONTENT_SUCCESS':
state[action.response.Id] = action.response
return state
case 'UPLOAD_CONTENT_SUCCESS':
if (typeof state[action.response.CreatedContent.Id] === 'undefined')
state[action.response.CreatedContent.Id] = action.response.CreatedContent
return state
default:
return state;
}
Expand Down
87 changes: 85 additions & 2 deletions test/ActionsTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe('Actions', () => {
it('should create an action to delete content success', () => {
const expectedAction = {
type: 'DELETE_CONTENT_SUCCESS',
index: 0,
index: 0,
id: 123
}
expect(Actions.DeleteSuccess(0, 123)).to.deep.equal(expectedAction)
Expand Down Expand Up @@ -581,7 +581,7 @@ describe('Actions', () => {
})
})
describe('RequestContentActions', () => {

const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo)
it('should return the RequestContentActions action', () => {
const expectedAction = {
Expand Down Expand Up @@ -611,4 +611,87 @@ describe('Actions', () => {
expect(Actions.RequestContentActionsFailure({ message: 'error' })).to.deep.equal(expectedAction)
});
})
describe('UploadContentActions', () => {
const content = Content.Create({ DisplayName: 'My content', Id: 123 }, ContentTypes.Task, repo)
const file = {
lastModified: 1499931166346,
name: 'README.md',
size: 75,
type: ''
}
it('should return the upload content action set only content and file', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_REQUEST',
content,
file,
overwrite: true,
propertyName: 'Binary',
contentType: ContentTypes.File,
body: null
}
expect(Actions.UploadRequest(content, file)).to.deep.equal(expectedAction)
})
it('should return the upload content action set content, file and contentType to Folder', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_REQUEST',
content,
contentType: ContentTypes.Folder,
file,
overwrite: true,
propertyName: 'Binary',
body: null
}
expect(Actions.UploadRequest(content, file, ContentTypes.Folder)).to.deep.equal(expectedAction)
})
it('should return the upload content action set content, file and overwrite to false', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_REQUEST',
content,
contentType: ContentTypes.File,
file,
overwrite: false,
propertyName: 'Binary',
body: null
}
expect(Actions.UploadRequest(content, file, undefined, false)).to.deep.equal(expectedAction)
})
it('should return the upload content action set content, file and propertyName to Avatar', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_REQUEST',
content,
contentType: ContentTypes.File,
file,
overwrite: true,
propertyName: 'Avatar',
body: null
}
expect(Actions.UploadRequest(content, file, undefined, undefined, undefined, 'Avatar')).to.deep.equal(expectedAction)
})
it('should return the upload content action set content, file and body', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_REQUEST',
content,
contentType: ContentTypes.File,
file,
overwrite: true,
propertyName: 'Binary',
body: { vmi: 'aaa' }
}
expect(Actions.UploadRequest(content, file, undefined, undefined, { vmi: 'aaa' })).to.deep.equal(expectedAction)
})
it('should create an action to upload content success', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_SUCCESS',
response: []
}
expect(Actions.UploadSuccess([])).to.deep.equal(expectedAction)
});
it('should create an action to content upload request failure', () => {
const expectedAction = {
type: 'UPLOAD_CONTENT_FAILURE',
message: 'error'
}
expect(Actions.UploadFailure({ message: 'error' })).to.deep.equal(expectedAction)
});
})
});
Loading

0 comments on commit ef944c6

Please sign in to comment.