diff --git a/app/controllers/client_api/activities_controller.rb b/app/controllers/client_api/activities_controller.rb index 5099af467..d83bfd9b5 100644 --- a/app/controllers/client_api/activities_controller.rb +++ b/app/controllers/client_api/activities_controller.rb @@ -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 diff --git a/app/javascript/src/components/Navigation/components/ActivityElement.jsx b/app/javascript/src/components/Navigation/components/ActivityElement.jsx index e8e8aa5e3..8ad1bb843 100644 --- a/app/javascript/src/components/Navigation/components/ActivityElement.jsx +++ b/app/javascript/src/components/Navigation/components/ActivityElement.jsx @@ -39,7 +39,7 @@ const ActivityElement = ({ activity }: Activity ): Node => ( , - more: boolean + more: boolean, + currentPage: number }; class GlobalActivitiesModal extends Component { @@ -70,7 +71,7 @@ class GlobalActivitiesModal extends Component { 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 { } 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 { 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 { } // 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 { ); } // returns the default activity element - return ; + return ; }); } @@ -136,15 +137,21 @@ class GlobalActivitiesModal extends Component { } addMoreActivities(): void { - const lastId = _.last(this.state.activities).id; getActivities( - lastId - ).then((response: { activities: Array, more: boolean }) => { - this.setState({ - activities: [...this.state.activities, ...response.activities], - more: response.more - }); - }); + this.state.currentPage + 1 + ).then( + (response: { + activities: Array, + more: boolean, + currentPage: number + }) => { + this.setState({ + activities: [...this.state.activities, ...response.activities], + more: response.more, + currentPage: response.currentPage + }); + } + ); } addMoreButton(): Element<*> { diff --git a/app/javascript/src/components/Navigation/components/InfoDropdown.jsx b/app/javascript/src/components/Navigation/components/InfoDropdown.jsx index e01328f56..86fbf0247 100644 --- a/app/javascript/src/components/Navigation/components/InfoDropdown.jsx +++ b/app/javascript/src/components/Navigation/components/InfoDropdown.jsx @@ -14,9 +14,9 @@ import { getSciNoteInfo } from "../../../services/api/configurations_api"; import AboutScinoteModal from "./AboutScinoteModal"; type State = { - modalOpen: boolean, scinoteVersion: string, - addons: Array + addons: Array, + showModal: boolean }; class InfoDropdown extends Component<*, State> { diff --git a/app/javascript/src/services/api/activities_api.js b/app/javascript/src/services/api/activities_api.js index cb42249ad..0bc411411 100644 --- a/app/javascript/src/services/api/activities_api.js +++ b/app/javascript/src/services/api/activities_api.js @@ -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); } diff --git a/app/models/user.rb b/app/models/user.rb index 6d76d455b..38bdc0973 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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) diff --git a/app/views/client_api/activities/index.json.jbuilder b/app/views/client_api/activities/index.json.jbuilder index eed05cda4..a87946aa7 100644 --- a/app/views/client_api/activities/index.json.jbuilder +++ b/app/views/client_api/activities/index.json.jbuilder @@ -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 diff --git a/flow-typed/types.js b/flow-typed/types.js index 3faf0b45d..130932be8 100644 --- a/flow-typed/types.js +++ b/flow-typed/types.js @@ -33,7 +33,8 @@ export type ValidationErrors = string | Array | Array; export type Activity = { id?: number, message: string, - created_at: string + createdAt: string, + timezone: string }; export type State = { diff --git a/spec/controllers/client_api/activities_controller_spec.rb b/spec/controllers/client_api/activities_controller_spec.rb new file mode 100644 index 000000000..bd0a8b093 --- /dev/null +++ b/spec/controllers/client_api/activities_controller_spec.rb @@ -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 diff --git a/spec/factories/activity.rb b/spec/factories/activity.rb new file mode 100644 index 000000000..8ae4343d6 --- /dev/null +++ b/spec/factories/activity.rb @@ -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 diff --git a/spec/support/api/schemas/activities.json b/spec/support/api/schemas/activities.json new file mode 100644 index 000000000..c0570f640 --- /dev/null +++ b/spec/support/api/schemas/activities.json @@ -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" } + } + } + } + } + } + } +}