Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve activity list #3

Open
wants to merge 3 commits into
base: 2.9
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Paginate activity list for datasets
 * Drop unused files
 * Allow filter activities by type
  • Loading branch information
avdata99 committed Mar 15, 2022
commit c3610ae17c2c9e03cf0fd70a22bee125e490b260
2 changes: 2 additions & 0 deletions ckan/logic/action/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -2542,10 +2542,12 @@ def package_activity_list(context, data_dict):

offset = int(data_dict.get('offset', 0))
limit = data_dict['limit'] # defaulted, limited & made an int by schema
activity_type = data_dict.get('activity_type')

activity_objects = model.activity.package_activity_list(
package.id, limit=limit, offset=offset,
include_hidden_activity=include_hidden_activity,
activity_type=activity_type,
)

return model_dictize.activity_list_dictize(
Expand Down
6 changes: 5 additions & 1 deletion ckan/logic/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,8 @@ def default_dashboard_activity_list_schema(
def default_activity_list_schema(
not_missing, unicode_safe, configured_default,
natural_number_validator, limit_to_configured_maximum,
ignore_missing, boolean_validator, ignore_not_sysadmin):
ignore_missing, boolean_validator, ignore_not_sysadmin,
activity_type_exists):
schema = default_pagination_schema()
schema['id'] = [not_missing, unicode_safe]
schema['limit'] = [
Expand All @@ -620,6 +621,9 @@ def default_activity_list_schema(
limit_to_configured_maximum('ckan.activity_list_limit_max', 100)]
schema['include_hidden_activity'] = [
ignore_missing, ignore_not_sysadmin, boolean_validator]
schema['activity_type'] = [
ignore_missing, activity_type_exists
]
return schema


Expand Down
20 changes: 16 additions & 4 deletions ckan/logic/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,24 +297,36 @@ def activity_type_exists(activity_type):
raise Invalid('%s: %s' % (_('Not found'), _('Activity type')))


# A dictionary mapping activity_type values from activity dicts to functions
# for validating the object_id values from those same activity dicts.
object_id_validators = {
VALIDATORS_PACKAGE_ACTIVITY_TYPES = {
'new package' : package_id_exists,
'changed package' : package_id_exists,
'deleted package' : package_id_exists,
'follow dataset' : package_id_exists,
}

VALIDATORS_USER_ACTIVITY_TYPES = {
'new user' : user_id_exists,
'changed user' : user_id_exists,
'follow user' : user_id_exists,
}

VALIDATORS_GROUP_ACTIVITY_TYPES = {
'new group' : group_id_exists,
'changed group' : group_id_exists,
'deleted group' : group_id_exists,
'new organization' : group_id_exists,
'changed organization' : group_id_exists,
'deleted organization' : group_id_exists,
'follow group' : group_id_exists,
}
}

# A dictionary mapping activity_type values from activity dicts to functions
# for validating the object_id values from those same activity dicts.
object_id_validators = {
**VALIDATORS_PACKAGE_ACTIVITY_TYPES,
**VALIDATORS_USER_ACTIVITY_TYPES,
**VALIDATORS_GROUP_ACTIVITY_TYPES,
}

def object_id_validator(key, activity_dict, errors, context):
'''Validate the 'object_id' value of an activity_dict.
Expand Down
22 changes: 20 additions & 2 deletions ckan/model/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ def _package_activity_query(package_id):


def package_activity_list(
package_id, limit, offset, include_hidden_activity=False):
package_id, limit, offset,
include_hidden_activity=False,
activity_type=None,
):
'''Return the given dataset (package)'s public activity stream.

Returns all activities about the given dataset, i.e. where the given
Expand All @@ -198,6 +201,9 @@ def package_activity_list(
if not include_hidden_activity:
q = _filter_activitites_from_users(q)

if activity_type:
q = _filter_activitites_by_type(q, activity_type)

return _activities_at_offset(q, limit, offset)


Expand Down Expand Up @@ -450,9 +456,21 @@ def recently_changed_packages_activity_list(limit, offset):
return _activities_at_offset(q, limit, offset)


def _filter_activitites_by_type(q, activity_type):
'''
Adds a filter to an existing query object to
only show one activity type
'''
users_to_avoid = _activity_stream_get_filtered_users()
if users_to_avoid:
q = q.filter(ckan.model.Activity.activity_type==activity_type)

return q


def _filter_activitites_from_users(q):
'''
Adds a filter to an existing query object ot avoid activities from users
Adds a filter to an existing query object to avoid activities from users
defined in :ref:`ckan.hide_activity_from_users` (defaults to the site user)
'''
users_to_avoid = _activity_stream_get_filtered_users()
Expand Down
111 changes: 13 additions & 98 deletions ckan/public/base/javascript/modules/activity-stream.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,34 @@
/* Activity stream
* Handle the loading more of activity items within actiivity streams
* Handle the pagination for activity list
*
* Options
* - more: are there more items to load
* - context: what's the context for the ajax calls
* - id: what's the id of the context?
* - offset: what's the current offset?
* - page: current page number
*/
this.ckan.module('activity-stream', function($) {
return {
/* options object can be extended using data-module-* attributes */
options : {
more: null,
id: null,
context: null,
offset: null,
loading: false
},


/* Initialises the module setting up elements and event listeners.
*
* Returns nothing.
*/
initialize: function () {
$.proxyAll(this, /_on/);
var options = this.options;
options.more = (options.more == 'True');
this._onBuildLoadMore();
$(window).on('scroll', this._onScrollIntoView);
this._onScrollIntoView();
},

/* Function that tells if el is within the window viewpost
*
* Returns boolean
*/
elementInViewport: function(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
$('#activity_types_filter_select').on(
'change',
this._onChangeActivityType
);
},

/* Whenever the window scrolls check if the load more button
* exists, if it's in the view and we're not already loading.
* If all conditions are satisfied... fire a click event on
* the load more button.
*
* Returns nothing
*/
_onScrollIntoView: function() {
var el = $('.load-more a', this.el);
if (el.length == 1) {
var in_viewport = this.elementInViewport(el[0]);
if (in_viewport && !this.options.loading) {
el.trigger('click');
}
}
},

/* If we are able to load more... then attach the ajax request
* to the load more button.
*
* Returns nothing
*/
_onBuildLoadMore: function() {
var options = this.options;
if (options.more) {
$('.load-more', this.el).on('click', 'a', this._onLoadMoreClick);
options.offset = $('.item', this.el).length;
}
},

/* Fires when someone clicks the load more button
* ... and if not loading then make the API call to load
* more activities

/* Filter using the selected
* activity type
*
* Returns nothing
*/
_onLoadMoreClick: function (event) {
event.preventDefault();
var options = this.options;
if (!options.loading) {
options.loading = true;
$('.load-more a', this.el).html(this._('Loading...')).addClass('disabled');
this.sandbox.client.call('GET', options.context+'_activity_list_html', '?id='+options.id+'&offset='+options.offset, this._onActivitiesLoaded);
}
_onChangeActivityType: function (event) {
// event.preventDefault();
url = $("#activity_types_filter_select option:selected" ).data('url');
window.location = url;
},

/* Callback for after the API call
*
* Returns nothing
*/
_onActivitiesLoaded: function(json) {
var options = this.options;
var result = $(json.result);
options.more = ( result.data('module-more') == 'True' );
options.offset += 30;
$('.load-less', result).remove();
$('.load-more', this.el).remove();
$('li', result).appendTo(this.el);
this._onBuildLoadMore();
options.loading = false;
}

};
});
26 changes: 0 additions & 26 deletions ckan/templates/activity_streams/activity_stream_items.html

This file was deleted.

65 changes: 64 additions & 1 deletion ckan/templates/package/activity.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,68 @@ <h1 class="hide-heading">
{{ _('Activity Stream') }}
{% endblock %}
</h1>
{% snippet 'snippets/activity_stream.html', activity_stream=activity_stream, id=id, object_type='package' %}

{# allow extensions to continue using this template without new vars #}
{% if activity_types is defined %}

<div
id="activity_types_filter"
style="margin-bottom: 15px;"
data-module="activity-stream"
>
<label for="activity_types_filter_select">Activity type</label>
<select id="activity_types_filter_select">
<option
{% if not activity_type %}selected{% endif %}
data-url="{{ h.url_for('dataset.activity', id=id, page=0) }}"
>{{ _('All activity types') }}</option>
{% for type_ in activity_types %}
<option
{% if activity_type == type_ %}selected{% endif %}
data-url="{{ h.url_for('dataset.activity', id=id, page=0, activity_type=type_) }}"
>{{ type_.title() }}</option>
{% endfor %}
</select>

</div>
{% endif %}

{% if activity_stream|length > 0 %}
{% snippet 'snippets/activity_stream.html', activity_stream=activity_stream, id=id, object_type='package' %}
{% else %}
<p>
{{ _('No activity found') }}
{% if activity_type %}
{{ _('for this type') }}.
{% endif %}
</p>
{% endif %}

{# allow extensions to continue using this template without new vars #}
{% if page is defined %}

<div id="activity_page_buttons" style="margin-top: 25px;">

{% if page > 0 %}
{% set prev_page = page-1 %}
<a
href="{{ h.url_for('dataset.activity', id=id, page=prev_page) }}"
class="btn btn-default btn-rounded"
>{{ _('Previous page') }}
</a>

{% endif %}
{% if has_more %}
{% set next_page = page + 1 %}
<a
href="{{ h.url_for('dataset.activity', id=id, page=next_page) }}"
class="btn btn-default btn-rounded"
>{{ _('Next page') }}
</a>

{% endif %}
</div>

{% endif %}

{% endblock %}
Loading