Skip to content

Commit

Permalink
Merge pull request #1864 from kobotoolbox/REST-UI
Browse files Browse the repository at this point in the history
Feature: REST Services
  • Loading branch information
jnm authored Oct 3, 2018
2 parents 0831eae + e2cb641 commit 0563544
Show file tree
Hide file tree
Showing 77 changed files with 3,508 additions and 212 deletions.
4 changes: 2 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
}
},
"rules": {
"strict": 0,
"curly": 0,
"quotes": ["warn", "single"],
"quotes": ["warn", "single", {"avoidEscape": true}],
"no-underscore-dangle": 0,
"camelcase": [0],
"new-cap": 0,
Expand Down
2 changes: 2 additions & 0 deletions dependencies/pip/dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ cookies==2.2.1 # via responses
cryptography==2.2.2 # via paramiko, pyopenssl
cssselect==1.0.3 # via pyquery
cyordereddict==1.0.0
defusedxml==0.5.0 # via djangorestframework-xml
dj-database-url==0.4.2
dj-static==0.0.6
django-braces==1.11.0
Expand All @@ -44,6 +45,7 @@ django-taggit==0.22.0
django-toolbelt==0.0.1
django-webpack-loader==0.4.1
django==1.8.17
djangorestframework-xml==1.3.0
djangorestframework==3.5.4
docutils==0.13.1 # via botocore, statistics
drf-extensions==0.3.1
Expand Down
2 changes: 2 additions & 0 deletions dependencies/pip/external_services.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ cookies==2.2.1 # via responses
cryptography==2.2.2 # via pyopenssl
cssselect==1.0.3 # via pyquery
cyordereddict==1.0.0
defusedxml==0.5.0 # via djangorestframework-xml
dj-database-url==0.4.1
dj-static==0.0.6
django-braces==1.8.1
Expand All @@ -45,6 +46,7 @@ django-taggit==0.22.0
django-toolbelt==0.0.1
django-webpack-loader==0.3.0
django==1.8.13
djangorestframework-xml==1.3.0
djangorestframework==3.3.3
docutils==0.12 # via botocore, statistics
drf-extensions==0.3.1
Expand Down
1 change: 1 addition & 0 deletions dependencies/pip/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ django-taggit
django-storages
django-private-storage
djangorestframework
djangorestframework-xml
drf-extensions
gunicorn
jsonfield
Expand Down
2 changes: 2 additions & 0 deletions dependencies/pip/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ cookies==2.2.1 # via responses
cryptography==2.2.2 # via pyopenssl
cssselect==1.0.3 # via pyquery
cyordereddict==1.0.0
defusedxml==0.5.0 # via djangorestframework-xml
dj-database-url==0.4.1
dj-static==0.0.6
django-braces==1.8.1
Expand All @@ -44,6 +45,7 @@ django-taggit==0.22.0
django-toolbelt==0.0.1
django-webpack-loader==0.3.0
django==1.8.13
djangorestframework-xml==1.3.0
djangorestframework==3.3.3
docutils==0.12 # via botocore, statistics
drf-extensions==0.3.1
Expand Down
157 changes: 157 additions & 0 deletions jsapp/js/actions.es6
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ actions.permissions = Reflux.createActions({
},
});

actions.hooks = Reflux.createActions({
getAll: {children: ['completed', 'failed']},
add: {children: ['completed', 'failed']},
update: {children: ['completed', 'failed']},
delete: {children: ['completed', 'failed']},
getLogs: {children: ['completed', 'failed']},
retryLog: {children: ['completed', 'failed']},
retryLogs: {children: ['completed', 'failed']},
});

actions.misc = Reflux.createActions({
checkUsername: {
asyncResult: true,
Expand Down Expand Up @@ -695,4 +705,151 @@ actions.resources.updateSubmissionValidationStatus.listen(function(uid, sid, dat
});
});

actions.hooks.getAll.listen((assetUid, callbacks = {}) => {
dataInterface.getHooks(assetUid)
.done((...args) => {
actions.hooks.getAll.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.getAll.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});

actions.hooks.add.listen((assetUid, data, callbacks = {}) => {
dataInterface.addExternalService(assetUid, data)
.done((...args) => {
actions.hooks.getAll(assetUid);
actions.hooks.add.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.add.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});
actions.hooks.add.completed.listen((response) => {
notify(t('REST Service added successfully'));
});
actions.hooks.add.failed.listen((response) => {
notify(t('Failed adding REST Service'), 'error');
});

actions.hooks.update.listen((assetUid, hookUid, data, callbacks = {}) => {
dataInterface.updateExternalService(assetUid, hookUid, data)
.done((...args) => {
actions.hooks.getAll(assetUid);
actions.hooks.update.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.update.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});
actions.hooks.update.completed.listen((response) => {
notify(t('REST Service updated successfully'));
});
actions.hooks.update.failed.listen((response) => {
notify(t('Failed saving REST Service'), 'error');
});

actions.hooks.delete.listen((assetUid, hookUid, callbacks = {}) => {
dataInterface.deleteExternalService(assetUid, hookUid)
.done((...args) => {
actions.hooks.getAll(assetUid);
actions.hooks.delete.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.delete.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});
actions.hooks.delete.completed.listen((response) => {
notify(t('REST Service deleted permanently'));
});
actions.hooks.delete.failed.listen((response) => {
notify(t('Could not delete REST Service'), 'error');
});

actions.hooks.getLogs.listen((assetUid, hookUid, callbacks = {}) => {
dataInterface.getHookLogs(assetUid, hookUid)
.done((...args) => {
actions.hooks.getLogs.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.getLogs.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});

actions.hooks.retryLog.listen((assetUid, hookUid, lid, callbacks = {}) => {
dataInterface.retryExternalServiceLog(assetUid, hookUid, lid)
.done((...args) => {
actions.hooks.getLogs(assetUid, hookUid);
actions.hooks.retryLog.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.retryLog.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});
actions.hooks.retryLog.completed.listen((response) => {
notify(t('Submission retried successfully'));
});
actions.hooks.retryLog.failed.listen((response) => {
notify(t('Retrying submission failed'), 'error');
});

actions.hooks.retryLogs.listen((assetUid, hookUid, callbacks = {}) => {
dataInterface.retryExternalServiceLogs(assetUid, hookUid)
.done((...args) => {
actions.hooks.retryLogs.completed(...args);
if (typeof callbacks.onComplete === 'function') {
callbacks.onComplete(...args);
}
})
.fail((...args) => {
actions.hooks.getLogs(assetUid, hookUid);
actions.hooks.retryLogs.failed(...args);
if (typeof callbacks.onFail === 'function') {
callbacks.onFail(...args);
}
});
});
actions.hooks.retryLogs.completed.listen((response) => {
notify(t(response.detail), 'warning');
});
actions.hooks.retryLogs.failed.listen((response) => {
notify(t('Retrying all submissions failed'), 'error');
});

module.exports = actions;
5 changes: 4 additions & 1 deletion jsapp/js/app.es6
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,11 @@ export var routes = (

<Route path='settings'>
<IndexRoute component={FormSubScreens} />
<Route path='kobocat' component={FormSubScreens} />
<Route path='media' component={FormSubScreens} />
<Route path='sharing' component={FormSubScreens} />
<Route path='rest' component={FormSubScreens} />
<Route path='rest/:hookUid' component={FormSubScreens} />
<Route path='kobocat' component={FormSubScreens} />
</Route>

{/* used to force refresh form screens */}
Expand Down
20 changes: 20 additions & 0 deletions jsapp/js/bem.es6
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ bem.Loading = BEM('loading');
bem.Loading__inner = bem.Loading.__('inner');
bem.Loading__msg = bem.Loading.__('msg');

bem.EmptyContent = BEM('empty-content', '<section>');
bem.EmptyContent__icon = bem.EmptyContent.__('icon', '<i>');
bem.EmptyContent__title = bem.EmptyContent.__('title', '<h1>');
bem.EmptyContent__message = bem.EmptyContent.__('message', '<p>');
bem.EmptyContent__button = bem.EmptyContent.__('button', '<button>');

bem.AssetRow = BEM('asset-row', '<li>');
bem.AssetRow__cell = bem.AssetRow.__('cell');
bem.AssetRow__cellmeta = bem.AssetRow.__('cellmeta');
Expand All @@ -22,6 +28,10 @@ bem.AssetRow__actionIcon = bem.AssetRow.__('action-icon', '<a>');
bem.AssetRow__buttons = bem.AssetRow.__('buttons');
bem.AssetRow__typeIcon = bem.AssetRow.__('type-icon', '<span>');

bem.ServiceRow = BEM('service-row');
bem.ServiceRow__column = bem.ServiceRow.__('column');
bem.ServiceRow__actionButton = bem.ServiceRow.__('action-button', '<button>');

bem.FormBuilder = bem('formBuilder');
bem.FormBuilder__row = bem.FormBuilder.__('row');
bem.FormBuilder__contents = bem.FormBuilder.__('contents');
Expand Down Expand Up @@ -239,6 +249,16 @@ bem.TextBox__input = bem.TextBox.__('input', '<input>');
bem.TextBox__description = bem.TextBox.__('description');
bem.TextBox__error = bem.TextBox.__('error');

bem.Checkbox = bem('checkbox');
bem.Checkbox__wrapper = bem.Checkbox.__('wrapper', '<label>');
bem.Checkbox__input = bem.Checkbox.__('input', '<input>');
bem.Checkbox__label = bem.Checkbox.__('label', '<span>');

bem.Radio = bem('radio');
bem.Radio__wrapper = bem.Radio.__('wrapper', '<label>');
bem.Radio__input = bem.Radio.__('input', '<input>');
bem.Radio__label = bem.Radio.__('label', '<span>');

bem.PrintOnly = BEM('print-only');

bem.GitRev = BEM('git-rev');
Expand Down
33 changes: 33 additions & 0 deletions jsapp/js/components/RESTServices.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import autoBind from 'react-autobind';
import DocumentTitle from 'react-document-title';
import bem from '../bem';
import RESTServicesList from './RESTServices/RESTServicesList'
import RESTServiceLogs from './RESTServices/RESTServiceLogs'
import {t} from '../utils';

export default class RESTServices extends React.Component {
constructor(props){
super(props);
autoBind(this);
}

render() {
const docTitle = this.props.asset.name || t('Untitled');
if (this.props.hookUid) {
return (
<DocumentTitle title={`${docTitle} | KoboToolbox`}>
<bem.FormView m={'form-settings'} className='rest-services'>
<RESTServiceLogs assetUid={this.props.asset.uid} hookUid={this.props.hookUid} />
</bem.FormView>
</DocumentTitle>
);
} else {
return (
<DocumentTitle title={`${docTitle} | KoboToolbox`}>
<RESTServicesList assetUid={this.props.asset.uid} />
</DocumentTitle>
);
}
}
};
Loading

0 comments on commit 0563544

Please sign in to comment.