setup new pagination for activities

This commit is contained in:
zmagod 2017-12-14 14:02:40 +01:00
parent 3b1cb51be9
commit 219ef3baf6
11 changed files with 107 additions and 40 deletions

View file

@ -2,7 +2,6 @@ module ClientApi
class ActivitiesController < ApplicationController
def index
@timezone = current_user.time_zone
respond_to do |format|
format.json do
render template: '/client_api/activities/index',
@ -15,11 +14,17 @@ module ClientApi
private
def activities_vars
last_activity_id = params[:from].to_i || 0
per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT
activities = current_user.last_activities(last_activity_id, per_page + 1)
more = activities.length > per_page
{ activities: activities, more: more }
page = (params[:page] || 1).to_i
activities = current_user
.last_activities
.page(page)
.per(Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
{
activities: activities,
page: page,
more: !current_user.last_activities.page(page).last_page?,
timezone: current_user.time_zone
}
end
end
end

View file

@ -39,7 +39,7 @@ const ActivityElement = ({ activity }: Activity ): Node => (
<StyledLi>
<TimeSpan>
<FormattedDate
value={new Date(activity.created_at)}
value={new Date(activity.createdAt)}
hour="2-digit"
minute="2-digit"
timeZone={activity.timezone}

View file

@ -53,7 +53,8 @@ type Props = {
type State = {
activities: Array<Activity>,
more: boolean
more: boolean,
currentPage: number
};
class GlobalActivitiesModal extends Component<Props, State> {
@ -70,7 +71,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { activities: [], more: false };
this.state = { activities: [], more: false, currentPage: 1 };
(this: any).displayActivities = this.displayActivities.bind(this);
(this: any).addMoreActivities = this.addMoreActivities.bind(this);
(this: any).onCloseModalActions = this.onCloseModalActions.bind(this);
@ -79,7 +80,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
}
onCloseModalActions(): void {
this.setState({ activities: [], more: false });
this.setState({ activities: [], more: false, currentPage: 1 });
this.props.onCloseModal();
}
@ -87,16 +88,16 @@ class GlobalActivitiesModal extends Component<Props, State> {
getActivities().then(response => {
this.setState({
activities: response.activities,
more: response.more
more: response.more,
currentPage: response.currentPage
});
});
}
mapActivities(): Array<*> {
return this.state.activities.map((activity, i, arr) => {
// @todo replace key={i} with key={activity.id} !!!!!!!!!!!!!!
// when the backend bug will be fixed
const newDate = new Date(activity.created_at);
const newDate = new Date(activity.createdAt);
// returns a label with "today" if the date of the activity is today
if (i === 0) {
return GlobalActivitiesModal.renderActivityDateElement(
@ -107,7 +108,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
}
// else checks if the previous activity is newer than current
// and displays a label with the date
const prevDate = new Date(arr[i - 1].created_at);
const prevDate = new Date(arr[i - 1].createdAt);
if (prevDate.getDate() > newDate.getDate()) {
return GlobalActivitiesModal.renderActivityDateElement(
i,
@ -116,7 +117,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
);
}
// returns the default activity element
return <ActivityElement key={i} activity={activity} />;
return <ActivityElement key={activity.id} activity={activity} />;
});
}
@ -136,15 +137,21 @@ class GlobalActivitiesModal extends Component<Props, State> {
}
addMoreActivities(): void {
const lastId = _.last(this.state.activities).id;
getActivities(
lastId
).then((response: { activities: Array<Activity>, more: boolean }) => {
this.setState({
activities: [...this.state.activities, ...response.activities],
more: response.more
});
});
this.state.currentPage + 1
).then(
(response: {
activities: Array<Activity>,
more: boolean,
currentPage: number
}) => {
this.setState({
activities: [...this.state.activities, ...response.activities],
more: response.more,
currentPage: response.currentPage
});
}
);
}
addMoreButton(): Element<*> {

View file

@ -14,9 +14,9 @@ import { getSciNoteInfo } from "../../../services/api/configurations_api";
import AboutScinoteModal from "./AboutScinoteModal";
type State = {
modalOpen: boolean,
scinoteVersion: string,
addons: Array<string>
addons: Array<string>,
showModal: boolean
};
class InfoDropdown extends Component<*, State> {

View file

@ -3,8 +3,8 @@ import axiosInstance from "./config";
import { ACTIVITIES_PATH } from "./endpoints";
export function getActivities(
lastId: number = 0
page: number = 1
): Promise<*> {
const path = `${ACTIVITIES_PATH}?from=${lastId}`;
const path = `${ACTIVITIES_PATH}?page=${page}`;
return axiosInstance.get(path).then(({ data }) => data.global_activities);
}

View file

@ -324,24 +324,24 @@ class User < ApplicationRecord
# Finds all activities of user that is assigned to project. If user
# is not an owner of the project, user must be also assigned to
# module.
def last_activities(last_activity_id = nil,
per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
last_activity_id = Constants::INFINITY if last_activity_id < 1
def last_activities
Activity
.joins(project: :user_projects)
.joins("LEFT OUTER JOIN my_modules ON activities.my_module_id = my_modules.id")
.joins("LEFT OUTER JOIN user_my_modules ON my_modules.id = user_my_modules.my_module_id")
.where('activities.id < ?', last_activity_id)
.joins(
'LEFT OUTER JOIN my_modules ON activities.my_module_id = my_modules.id'
)
.joins(
'LEFT OUTER JOIN user_my_modules ON my_modules.id = ' \
'user_my_modules.my_module_id'
)
.where(user_projects: { user_id: self })
.where(
'activities.my_module_id IS NULL OR ' +
'user_projects.role = 0 OR ' +
'activities.my_module_id IS NULL OR ' \
'user_projects.role = 0 OR ' \
'user_my_modules.user_id = ?',
id
)
.order(created_at: :desc)
.limit(per_page)
.uniq
end
def self.find_by_valid_wopi_token(token)

View file

@ -1,9 +1,10 @@
json.global_activities do
json.more more
json.currentPage page
json.activities activities do |activity|
json.id activity.id
json.message activity.message
json.created_at activity.created_at
json.timezone @timezone
json.createdAt activity.created_at
json.timezone timezone
end
end

3
flow-typed/types.js vendored
View file

@ -33,7 +33,8 @@ export type ValidationErrors = string | Array<string> | Array<ValidationError>;
export type Activity = {
id?: number,
message: string,
created_at: string
createdAt: string,
timezone: string
};
export type State = {

View file

@ -0,0 +1,20 @@
require 'rails_helper'
describe ClientApi::ActivitiesController, type: :controller do
login_user
render_views
before do
project = create :project, created_by: User.first
UserProject.create(user: User.first, project: project, role: 2)
create :activity, user: User.first, project: project
end
describe '#index' do
it 'returns a valid object' do
get :index, format: :json
expect(response.status).to eq(200)
expect(response.body).to match_response_schema('activities')
end
end
end

View file

@ -0,0 +1,7 @@
FactoryGirl.define do
factory :activity do
type_of :create_project
message Faker::Lorem.sentence(10)
project { Project.first || create(:project) }
end
end

View file

@ -0,0 +1,26 @@
{
"type": "object",
"required": ["global_activities"],
"properties": {
"global_activities": {
"type": "object",
"required": ["activities", "more", "currentPage"],
"properties": {
"more": { "type": "boolean" },
"currentPage": { "type": "integer" },
"activities": {
"type": "array",
"items":{
"required": ["id", "message", "createdAt", "timezone"],
"properties": {
"id": { "type": "integer" },
"message": { "type": "string" },
"createdAt": { "type": "string" },
"timezone": { "type": "string" }
}
}
}
}
}
}
}