Skip to content

Commit

Permalink
Merge pull request kobotoolbox#3057 from kobotoolbox/2767-basic-case-…
Browse files Browse the repository at this point in the history
…management

[Feature] Basic Case Management/Dynamic Data Attachments
  • Loading branch information
jnm authored Jul 27, 2021
2 parents e6cfdb6 + 1d8c1a3 commit 08f134f
Show file tree
Hide file tree
Showing 148 changed files with 8,441 additions and 3,151 deletions.
1 change: 1 addition & 0 deletions dependencies/pip/dev_requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pytest-django
pytest-env
pytest
mock
mongomock
7 changes: 7 additions & 0 deletions dependencies/pip/dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ decorator==4.4.1
# via
# ipython
# traitlets
deepmerge==0.3.0
# via -r dependencies/pip/requirements.in
defusedxml==0.6.0
# via djangorestframework-xml
dicttoxml==1.7.4
Expand Down Expand Up @@ -201,6 +203,8 @@ markdown==3.1.1
# django-markdownx
mock==3.0.5
# via -r dependencies/pip/dev_requirements.in
mongomock==3.22.1
# via -r dependencies/pip/dev_requirements.in
more-itertools==7.2.0
# via
# pytest
Expand Down Expand Up @@ -298,6 +302,8 @@ responses==0.10.6
# via -r dependencies/pip/requirements.in
s3transfer==0.2.1
# via boto3
sentinels==1.0.0
# via mongomock
shortuuid==0.5.0
# via -r dependencies/pip/requirements.in
six==1.13.0
Expand All @@ -307,6 +313,7 @@ six==1.13.0
# django-extensions
# jsonschema
# mock
# mongomock
# packaging
# prompt-toolkit
# pynacl
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 @@ -52,6 +52,8 @@ cryptography==2.8
# via pyopenssl
cssselect==1.1.0
# via pyquery
deepmerge==0.3.0
# via -r dependencies/pip/requirements.in
defusedxml==0.6.0
# via djangorestframework-xml
dicttoxml==1.7.4
Expand Down
5 changes: 4 additions & 1 deletion dependencies/pip/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ tabulate
unicodecsv
uWSGI
Werkzeug
xlrd
xlrde
xlwt
xlutils
XlsxWriter
Expand All @@ -85,3 +85,6 @@ XlsxWriter
pyopenssl
ndg-httpsclient
pyasn1

# This package is only needed for unit tests but MockBackend is loaded even on production environment
deepmerge
2 changes: 2 additions & 0 deletions dependencies/pip/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ cryptography==2.8
# via pyopenssl
cssselect==1.1.0
# via pyquery
deepmerge==0.3.0
# via -r dependencies/pip/requirements.in
defusedxml==0.6.0
# via djangorestframework-xml
dicttoxml==1.7.4
Expand Down
2 changes: 2 additions & 0 deletions dependencies/pip/travis_ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ cryptography==2.8
# via pyopenssl
cssselect==1.1.0
# via pyquery
deepmerge==0.3.0
# via -r dependencies/pip/requirements.in
defusedxml==0.6.0
# via djangorestframework-xml
dicttoxml==1.7.4
Expand Down
5 changes: 2 additions & 3 deletions hub/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from django.shortcuts import get_object_or_404
from markitup.fields import MarkupField

from kpi.models.object_permission import get_anonymous_user
from kpi.utils.object_permission import get_database_user


class SitewideMessage(models.Model):
Expand Down Expand Up @@ -76,8 +76,7 @@ class PerUserSetting(models.Model):
value_when_not_matched = models.CharField(max_length=2048, blank=True)

def user_matches(self, user, ignore_invalid_queries=True):
if user.is_anonymous:
user = get_anonymous_user()
user = get_database_user(user)
manager = user._meta.model.objects
queryset = manager.none()
for user_query in self.user_queries:
Expand Down
2 changes: 2 additions & 0 deletions jsapp/js/actions.es6
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import libraryActions from './actions/library';
import submissionsActions from './actions/submissions';
import formMediaActions from './actions/mediaActions';
import exportsActions from './actions/exportsActions';
import dataShareActions from './actions/dataShareActions';
import {
notify,
replaceSupportEmail,
Expand All @@ -34,6 +35,7 @@ export const actions = {
submissions: submissionsActions,
media: formMediaActions,
exports: exportsActions,
dataShare: dataShareActions,
};

actions.navigation = Reflux.createActions([
Expand Down
126 changes: 126 additions & 0 deletions jsapp/js/actions/dataShareActions.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Dynamic data attachment related actions
*/

import Reflux from 'reflux';
import alertify from 'alertifyjs';
import {dataInterface} from 'js/dataInterface';
import {MAX_DISPLAYED_STRING_LENGTH} from 'js/constants';
import {
getAssetUIDFromUrl,
truncateFile,
truncateString,
} from 'js/utils';

const dataShareActions = Reflux.createActions({
attachToSource: {children: ['started', 'completed', 'failed']},
detachSource: {children: ['completed', 'failed']},
patchSource: {children: ['started', 'completed', 'failed']},
getAttachedSources: {children: ['completed', 'failed']},
getSharingEnabledAssets: {children: ['completed', 'failed']},
toggleDataSharing: {children: ['completed', 'failed']},
updateColumnFilters: {children: ['completed', 'failed']},
});

dataShareActions.attachToSource.listen((assetUid, data) => {
dataInterface.attachToSource(assetUid, data)
.done(dataShareActions.attachToSource.completed)
.fail(dataShareActions.attachToSource.failed);
dataShareActions.attachToSource.started();
});
dataShareActions.attachToSource.failed.listen((response) => {
alertify.error(
response?.responseJSON?.filename[0] ||
response?.responseJSON ||
t('Failed to attach to source')
);
});

dataShareActions.detachSource.listen((attachmentUrl) => {
dataInterface.detachSource(attachmentUrl)
.done(dataShareActions.detachSource.completed)
.fail(dataShareActions.detachSource.failed);
});
dataShareActions.detachSource.failed.listen((response) => {
alertify.error(response?.responseJSON || t('Failed to detach from source'));
});

dataShareActions.patchSource.listen((attachmentUrl, data) => {
dataInterface.patchSource(attachmentUrl, data)
.done(dataShareActions.patchSource.completed)
.fail(dataShareActions.patchSource.failed);
dataShareActions.patchSource.started();
});
dataShareActions.patchSource.failed.listen((response) => {
alertify(response?.responseJSON || t('Failed to patch source'));
});

dataShareActions.getAttachedSources.listen((assetUid) => {
dataInterface.getAttachedSources(assetUid)
.done((response) => {
// We create our own object from backend response because:
// 1. We need to truncate the filename and display this instead
// 2. We need both the current asset URL as well as it's source data URL
let allSources = [];

// TODO: Check is pagination is an issue, if so we should try to use the
// backend response directly
response.results.forEach((source) => {
let sourceUid = getAssetUIDFromUrl(source.source);
allSources.push({
sourceName: truncateString(
source.source__name,
MAX_DISPLAYED_STRING_LENGTH.connect_projects,
),
// Source's asset url
sourceUrl: source.source,
sourceUid: sourceUid,
// Fields that the connecting project has selected to import
linkedFields: source.fields,
filename: truncateFile(
source.filename,
MAX_DISPLAYED_STRING_LENGTH.connect_projects,
),
// Source project attachment endpoint
attachmentUrl: source.url,
});
});

dataShareActions.getAttachedSources.completed(allSources);
})
.fail(dataShareActions.getAttachedSources.failed);
});

dataShareActions.getSharingEnabledAssets.listen(() => {
dataInterface.getSharingEnabledAssets()
.done(dataShareActions.getSharingEnabledAssets.completed)
.fail(dataShareActions.getSharingEnabledAssets.failed);
});
dataShareActions.getSharingEnabledAssets.failed.listen(() => {
alertify.error(t('Failed to retrieve sharing enabled assets'));
});

// The next two actions have the same endpoint but must be handled very
// differently so we leave them as seperate actions

// TODO: Improve the parameters so these functions are clearly different from
// each other
dataShareActions.toggleDataSharing.listen((uid, data) => {
dataInterface.patchDataSharing(uid, data)
.done(dataShareActions.toggleDataSharing.completed)
.fail(dataShareActions.toggleDataSharing.failed);
});
dataShareActions.toggleDataSharing.failed.listen((response) => {
alertify.error(response?.responseJSON?.detail || t('Failed to toggle sharing'))
});

dataShareActions.updateColumnFilters.listen((uid, data) => {
dataInterface.patchDataSharing(uid, data)
.done(dataShareActions.updateColumnFilters.completed)
.fail(dataShareActions.updateColumnFilters.failed);
});
dataShareActions.updateColumnFilters.failed.listen((response) => {
alertify.error(response?.responseJSON || t('Failed to update column filters'));
});

export default dataShareActions;
1 change: 1 addition & 0 deletions jsapp/js/app.es6
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export var routes = (
<IndexRoute component={FormSubScreens} />
<Route path={ROUTES.FORM_MEDIA} component={FormSubScreens} />
<Route path={ROUTES.FORM_SHARING} component={FormSubScreens} />
<Route path={ROUTES.FORM_RECORDS} component={FormSubScreens} />
<Route path={ROUTES.FORM_REST} component={FormSubScreens} />
<Route path={ROUTES.FORM_REST_HOOK} component={FormSubScreens} />
<Route path={ROUTES.FORM_KOBOCAT} component={FormSubScreens} />
Expand Down
1 change: 0 additions & 1 deletion jsapp/js/bem.es6
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ bem.FormView__subs = bem.FormView.__('subs');
// end used in header.es6
bem.FormView__toptabs = bem.FormView.__('toptabs');
bem.FormView__sidetabs = bem.FormView.__('sidetabs');
bem.FormView__tab = bem.FormView.__('tab', '<a>');

bem.FormView__label = bem.FormView.__('label');
bem.FormView__group = bem.FormView.__('group');
Expand Down
Loading

0 comments on commit 08f134f

Please sign in to comment.