mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-09 05:18:01 +08:00
Merge branch 'decoupling-settings-page' of https://github.com/biosistemika/scinote-web into zd_SCI_1854
This commit is contained in:
commit
0d7c116de1
18 changed files with 252 additions and 142 deletions
|
@ -109,14 +109,14 @@
|
||||||
// Activity feed modal in main navigation menu
|
// Activity feed modal in main navigation menu
|
||||||
var activityModal = $('#activity-modal');
|
var activityModal = $('#activity-modal');
|
||||||
var activityModalBody = activityModal.find('.modal-body');
|
var activityModalBody = activityModal.find('.modal-body');
|
||||||
|
|
||||||
var initMoreBtn = function() {
|
var initMoreBtn = function() {
|
||||||
activityModalBody.find('.btn-more-activities')
|
activityModalBody.find('.btn-more-activities')
|
||||||
.on('ajax:success', function(e, data) {
|
.on('ajax:success', function(e, data) {
|
||||||
$(data.html).insertBefore($(this).parents('li'));
|
$(data.html).insertBefore($(this).parents('li'));
|
||||||
$(this).attr('href', data.next_url);
|
if(data.more_url) {
|
||||||
if (data.activities_number < data.per_page) {
|
$(this).attr('href', data.more_url);
|
||||||
$(this).remove();
|
} else {
|
||||||
|
$(this).remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,47 +1,43 @@
|
||||||
class ActivitiesController < ApplicationController
|
class ActivitiesController < ApplicationController
|
||||||
include ActivityHelper
|
include ActivityHelper
|
||||||
|
|
||||||
before_action :load_vars
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT
|
|
||||||
@activities = current_user.last_activities(@last_activity_id,
|
|
||||||
@per_page + 1)
|
|
||||||
|
|
||||||
@overflown = @activities.length > @per_page
|
|
||||||
|
|
||||||
@activities = current_user.last_activities(@last_activity_id,
|
|
||||||
@per_page)
|
|
||||||
|
|
||||||
# Whether to hide date labels
|
|
||||||
@hide_today = params.include? :from
|
|
||||||
@day = @last_activity.present? ?
|
|
||||||
days_since_1970(@last_activity.created_at) :
|
|
||||||
days_since_1970(DateTime.current + 30.days)
|
|
||||||
|
|
||||||
more_url = url_for(activities_url(format: :json,
|
|
||||||
from: @activities.last.id)) if @overflown
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.json {
|
format.json do
|
||||||
render :json => {
|
render json: {
|
||||||
per_page: @per_page,
|
more_url: local_vars.fetch(:more_activities_url),
|
||||||
activities_number: @activities.length,
|
html: render_to_string(
|
||||||
next_url: more_url,
|
partial: 'index.html.erb', locals: local_vars
|
||||||
html: render_to_string({
|
)
|
||||||
partial: 'index.html.erb',
|
|
||||||
locals: {
|
|
||||||
more_activities_url: more_url,
|
|
||||||
hide_today: @hide_today,
|
|
||||||
day: @day
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_vars
|
private
|
||||||
@last_activity_id = params[:from].to_i || 0
|
|
||||||
@last_activity = Activity.find_by_id(@last_activity_id)
|
def local_vars
|
||||||
|
page = (params[:page] || 1).to_i
|
||||||
|
activities = current_user.last_activities
|
||||||
|
.page(page)
|
||||||
|
.per(Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
|
||||||
|
unless activities.last_page?
|
||||||
|
more_url = url_for(
|
||||||
|
activities_url(
|
||||||
|
format: :json,
|
||||||
|
page: page + 1,
|
||||||
|
last_activity: activities.last.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
# send last activity date of the previus batch
|
||||||
|
previous_activity = Activity.find_by_id(params[:last_activity])
|
||||||
|
previus_date = previous_activity.created_at.to_date if previous_activity
|
||||||
|
{
|
||||||
|
activities: activities,
|
||||||
|
more_activities_url: more_url,
|
||||||
|
page: page,
|
||||||
|
previous_activity_created_at: previus_date
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,11 +14,17 @@ module ClientApi
|
||||||
private
|
private
|
||||||
|
|
||||||
def activities_vars
|
def activities_vars
|
||||||
last_activity_id = params[:from].to_i || 0
|
page = (params[:page] || 1).to_i
|
||||||
per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT
|
activities = current_user
|
||||||
activities = current_user.last_activities(last_activity_id, per_page + 1)
|
.last_activities
|
||||||
more = activities.length > per_page
|
.page(page)
|
||||||
{ activities: activities, more: more }
|
.per(Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
|
||||||
|
{
|
||||||
|
activities: activities,
|
||||||
|
page: page,
|
||||||
|
more: !activities.last_page?,
|
||||||
|
timezone: current_user.time_zone
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,4 +56,15 @@ module ActivityHelper
|
||||||
def days_since_1970(dt)
|
def days_since_1970(dt)
|
||||||
dt.to_i / 86400
|
dt.to_i / 86400
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def calculate_previous_date(activities,
|
||||||
|
index,
|
||||||
|
previus_batch_last_activitiy_date)
|
||||||
|
if index == 1 && !activities.first_page?
|
||||||
|
return previus_batch_last_activitiy_date
|
||||||
|
end
|
||||||
|
activity = activities[index - 1]
|
||||||
|
return activity.created_at.to_date if activity
|
||||||
|
Date.new(1901, 1, 1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Moment from "react-moment";
|
import type { Node } from "react";
|
||||||
|
import { FormattedDate, FormattedMessage } from "react-intl";
|
||||||
import { Tooltip, OverlayTrigger } from "react-bootstrap";
|
import { Tooltip, OverlayTrigger } from "react-bootstrap";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
WHITE_COLOR,
|
WHITE_COLOR,
|
||||||
|
@ -14,8 +14,12 @@ import {
|
||||||
|
|
||||||
import { NAME_TRUNCATION_LENGTH } from "../../../config/constants/numeric";
|
import { NAME_TRUNCATION_LENGTH } from "../../../config/constants/numeric";
|
||||||
|
|
||||||
|
type InputActivity = {
|
||||||
|
activity: Activity
|
||||||
|
}
|
||||||
|
|
||||||
const StyledLi = styled.li`
|
const StyledLi = styled.li`
|
||||||
border-radius: .25em;
|
border-radius: 0.25em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
background-color: ${WHITE_COLOR};
|
background-color: ${WHITE_COLOR};
|
||||||
border: 1px solid ${COLOR_CONCRETE};
|
border: 1px solid ${COLOR_CONCRETE};
|
||||||
|
@ -24,8 +28,8 @@ const TimeSpan = styled.span`
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-top-left-radius: .25em;
|
border-top-left-radius: 0.25em;
|
||||||
border-bottom-left-radius: .25em;
|
border-bottom-left-radius: 0.25em;
|
||||||
border: 3px solid ${BORDER_GRAY_COLOR};
|
border: 3px solid ${BORDER_GRAY_COLOR};
|
||||||
background-color: ${BORDER_GRAY_COLOR};
|
background-color: ${BORDER_GRAY_COLOR};
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
@ -37,52 +41,57 @@ const TextSpan = styled.span`
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
text-align: justify;
|
text-align: justify;
|
||||||
`
|
`;
|
||||||
function truncatedTooltip(id, text) {
|
|
||||||
|
function truncatedTooltip(id: string, text: any): Node {
|
||||||
return (
|
return (
|
||||||
<OverlayTrigger overlay={(
|
<OverlayTrigger
|
||||||
<Tooltip id={id}>
|
overlay={<Tooltip id={id}>{text}</Tooltip>}
|
||||||
{text}
|
placement="bottom"
|
||||||
</Tooltip>
|
>
|
||||||
)} placement="bottom">
|
<span>{text.substring(0, NAME_TRUNCATION_LENGTH)}...</span>
|
||||||
<span>
|
|
||||||
{text.substring(0, NAME_TRUNCATION_LENGTH)}...
|
|
||||||
</span>
|
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskPath(activity) {
|
function taskPath(activity: Activity): Node {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
[ <FormattedMessage id="general.project" />:
|
[ <FormattedMessage id="general.project" />:
|
||||||
{activity.project.length > NAME_TRUNCATION_LENGTH ? (
|
{activity.project.length > NAME_TRUNCATION_LENGTH ? (
|
||||||
truncatedTooltip('activity_modal.long_project_tooltip', activity.project)
|
truncatedTooltip(
|
||||||
):(
|
"activity_modal.long_project_tooltip",
|
||||||
|
activity.project
|
||||||
|
)
|
||||||
|
) : (
|
||||||
<span>{activity.project}</span>
|
<span>{activity.project}</span>
|
||||||
)},
|
)},
|
||||||
<FormattedMessage id="general.task" />:
|
<FormattedMessage id="general.task" />:
|
||||||
{activity.task.length > NAME_TRUNCATION_LENGTH ? (
|
{activity.task.length > NAME_TRUNCATION_LENGTH ? (
|
||||||
truncatedTooltip('activity_modal.long_task_tooltip', activity.task)
|
truncatedTooltip("activity_modal.long_task_tooltip", activity.task)
|
||||||
):(
|
) : (
|
||||||
<span>{activity.task}</span>
|
<span>{activity.task}</span>
|
||||||
)} ]
|
)} ]
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ActivityElement = ({ activity }: InputActivity ): Node => (
|
||||||
const ActivityElement = ({ activity }) =>
|
|
||||||
<StyledLi>
|
<StyledLi>
|
||||||
<TimeSpan>
|
<TimeSpan>
|
||||||
<Moment format="HH.mm">
|
<FormattedDate
|
||||||
{activity.created_at}
|
value={new Date(activity.createdAt)}
|
||||||
</Moment>
|
hour="2-digit"
|
||||||
|
minute="2-digit"
|
||||||
|
timeZone={activity.timezone}
|
||||||
|
hour12={false}
|
||||||
|
/>
|
||||||
</TimeSpan>
|
</TimeSpan>
|
||||||
<TextSpan>
|
<TextSpan>
|
||||||
<span dangerouslySetInnerHTML={{ __html: activity.message }} />
|
<span dangerouslySetInnerHTML={{ __html: activity.message }} />
|
||||||
{activity.task && taskPath(activity)}
|
{activity.task && taskPath(activity)}
|
||||||
</TextSpan>
|
</TextSpan>
|
||||||
</StyledLi>;
|
</StyledLi>
|
||||||
|
);
|
||||||
|
|
||||||
export default ActivityElement;
|
export default ActivityElement;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import type { Element } from "react";
|
import type { Element, Node } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { Button, Modal } from "react-bootstrap";
|
import { Button, Modal } from "react-bootstrap";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
@ -53,7 +53,8 @@ type Props = {
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
activities: Array<Activity>,
|
activities: Array<Activity>,
|
||||||
more: boolean
|
more: boolean,
|
||||||
|
currentPage: number
|
||||||
};
|
};
|
||||||
|
|
||||||
class GlobalActivitiesModal extends Component<Props, State> {
|
class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
|
@ -61,7 +62,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
key: number,
|
key: number,
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
date: Date
|
date: Date
|
||||||
) {
|
): Node {
|
||||||
return [
|
return [
|
||||||
<ActivityDateElement key={date} date={date} />,
|
<ActivityDateElement key={date} date={date} />,
|
||||||
<ActivityElement key={key} activity={activity} />
|
<ActivityElement key={key} activity={activity} />
|
||||||
|
@ -70,7 +71,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { activities: [], more: false };
|
this.state = { activities: [], more: false, currentPage: 1 };
|
||||||
(this: any).displayActivities = this.displayActivities.bind(this);
|
(this: any).displayActivities = this.displayActivities.bind(this);
|
||||||
(this: any).addMoreActivities = this.addMoreActivities.bind(this);
|
(this: any).addMoreActivities = this.addMoreActivities.bind(this);
|
||||||
(this: any).onCloseModalActions = this.onCloseModalActions.bind(this);
|
(this: any).onCloseModalActions = this.onCloseModalActions.bind(this);
|
||||||
|
@ -79,7 +80,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onCloseModalActions(): void {
|
onCloseModalActions(): void {
|
||||||
this.setState({ activities: [], more: false });
|
this.setState({ activities: [], more: false, currentPage: 1 });
|
||||||
this.props.onCloseModal();
|
this.props.onCloseModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,37 +88,43 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
getActivities().then(response => {
|
getActivities().then(response => {
|
||||||
this.setState({
|
this.setState({
|
||||||
activities: response.activities,
|
activities: response.activities,
|
||||||
more: response.more
|
more: response.more,
|
||||||
|
currentPage: response.currentPage
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mapActivities(): Array<*> {
|
mapActivities(): Array<*> {
|
||||||
return this.state.activities.map((activity, i, arr) => {
|
return this.state.activities.map(
|
||||||
// @todo replace key={i} with key={activity.id} !!!!!!!!!!!!!!
|
(activity: Activity, i: number, arr: Array<*>) => {
|
||||||
// when the backend bug will be fixed
|
const newDate = new Date(activity.createdAt);
|
||||||
const newDate = new Date(activity.created_at);
|
// returns a label with "today" if the date of the activity is today
|
||||||
// returns a label with "today" if the date of the activity is today
|
if (i === 0 && newDate.toDateString() === new Date().toDateString()) {
|
||||||
if (i === 0) {
|
return GlobalActivitiesModal.renderActivityDateElement(
|
||||||
return GlobalActivitiesModal.renderActivityDateElement(
|
activity.id,
|
||||||
i,
|
activity,
|
||||||
activity,
|
newDate
|
||||||
newDate
|
);
|
||||||
);
|
}
|
||||||
|
// else checks if the previous activity is newer than current
|
||||||
|
// and displays a label with the date
|
||||||
|
const prevDate =
|
||||||
|
i !== 0 ? new Date(arr[i - 1].createdAt) : new Date(1901, 1, 1);
|
||||||
|
// filter only date from createdAt without minutes and seconds
|
||||||
|
// used to compare dates
|
||||||
|
const parsePrevDate = new Date(prevDate.toDateString());
|
||||||
|
const parseNewDate = new Date(newDate.toDateString());
|
||||||
|
if (parsePrevDate.getTime() > parseNewDate.getTime()) {
|
||||||
|
return GlobalActivitiesModal.renderActivityDateElement(
|
||||||
|
activity.id,
|
||||||
|
activity,
|
||||||
|
newDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// returns the default activity element
|
||||||
|
return <ActivityElement key={activity.id} activity={activity} />;
|
||||||
}
|
}
|
||||||
// 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);
|
|
||||||
if (prevDate.getDate() > newDate.getDate()) {
|
|
||||||
return GlobalActivitiesModal.renderActivityDateElement(
|
|
||||||
i,
|
|
||||||
activity,
|
|
||||||
newDate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// returns the default activity element
|
|
||||||
return <ActivityElement key={i} activity={activity} />;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayActivities() {
|
displayActivities() {
|
||||||
|
@ -136,15 +143,21 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
addMoreActivities(): void {
|
addMoreActivities(): void {
|
||||||
const lastId = _.last(this.state.activities).id;
|
|
||||||
getActivities(
|
getActivities(
|
||||||
lastId
|
this.state.currentPage + 1
|
||||||
).then((response: { activities: Array<Activity>, more: boolean }) => {
|
).then(
|
||||||
this.setState({
|
(response: {
|
||||||
activities: [...this.state.activities, ...response.activities],
|
activities: Array<Activity>,
|
||||||
more: response.more
|
more: boolean,
|
||||||
});
|
currentPage: number
|
||||||
});
|
}) => {
|
||||||
|
this.setState({
|
||||||
|
activities: [...this.state.activities, ...response.activities],
|
||||||
|
more: response.more,
|
||||||
|
currentPage: response.currentPage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addMoreButton(): Element<*> {
|
addMoreButton(): Element<*> {
|
||||||
|
|
|
@ -14,9 +14,9 @@ import { getSciNoteInfo } from "../../../services/api/configurations_api";
|
||||||
import AboutScinoteModal from "./AboutScinoteModal";
|
import AboutScinoteModal from "./AboutScinoteModal";
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
showModal: boolean,
|
|
||||||
scinoteVersion: string,
|
scinoteVersion: string,
|
||||||
addons: Array<string>
|
addons: Array<string>,
|
||||||
|
showModal: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
class InfoDropdown extends Component<*, State> {
|
class InfoDropdown extends Component<*, State> {
|
||||||
|
|
|
@ -3,9 +3,9 @@ import axiosInstance from "./config";
|
||||||
import { ACTIVITIES_PATH } from "./endpoints";
|
import { ACTIVITIES_PATH } from "./endpoints";
|
||||||
|
|
||||||
export function getActivities(
|
export function getActivities(
|
||||||
lastId: number = 0
|
page: number = 1
|
||||||
): Promise<*> {
|
): Promise<*> {
|
||||||
const path = `${ACTIVITIES_PATH}?from=${lastId}`;
|
const path = `${ACTIVITIES_PATH}?page=${page}`;
|
||||||
return axiosInstance.get(path).then(({ data }) => data.global_activities);
|
return axiosInstance.get(path).then(({ data }) => data.global_activities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -324,24 +324,24 @@ class User < ApplicationRecord
|
||||||
# Finds all activities of user that is assigned to project. If user
|
# 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
|
# is not an owner of the project, user must be also assigned to
|
||||||
# module.
|
# module.
|
||||||
def last_activities(last_activity_id = nil,
|
def last_activities
|
||||||
per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT)
|
|
||||||
last_activity_id = Constants::INFINITY if last_activity_id < 1
|
|
||||||
Activity
|
Activity
|
||||||
.joins(project: :user_projects)
|
.joins(project: :user_projects)
|
||||||
.joins("LEFT OUTER JOIN my_modules ON activities.my_module_id = my_modules.id")
|
.joins(
|
||||||
.joins("LEFT OUTER JOIN user_my_modules ON my_modules.id = user_my_modules.my_module_id")
|
'LEFT OUTER JOIN my_modules ON activities.my_module_id = my_modules.id'
|
||||||
.where('activities.id < ?', last_activity_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(user_projects: { user_id: self })
|
||||||
.where(
|
.where(
|
||||||
'activities.my_module_id IS NULL OR ' +
|
'activities.my_module_id IS NULL OR ' \
|
||||||
'user_projects.role = 0 OR ' +
|
'user_projects.role = 0 OR ' \
|
||||||
'user_my_modules.user_id = ?',
|
'user_my_modules.user_id = ?',
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.order(created_at: :desc)
|
.order(created_at: :desc)
|
||||||
.limit(per_page)
|
|
||||||
.uniq
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_by_valid_wopi_token(token)
|
def self.find_by_valid_wopi_token(token)
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<ul class="no-style double-line content-activities">
|
<ul class="no-style double-line content-activities">
|
||||||
<% if @activities.length == 0 then %>
|
<% if activities.empty? %>
|
||||||
<li><em><%= t'activities.index.no_activities' %></em></li>
|
<li><em><%= t'activities.index.no_activities' %></em></li>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= render 'activities/list.html.erb', activities: @activities, hide_today: hide_today, day: @day %>
|
<%= render 'activities/list.html.erb',
|
||||||
|
activities: activities,
|
||||||
|
previous_activity_created_at: previous_activity_created_at %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @last_activity_id < 1 and @overflown %>
|
<% if more_activities_url.present? && page == 1 %>
|
||||||
<li class="text-center">
|
<li class="text-center">
|
||||||
<a class="btn btn-default btn-more-activities" href="<%= more_activities_url %>" data-remote="true">
|
<a class="btn btn-default btn-more-activities"
|
||||||
|
href="<%= more_activities_url %>"
|
||||||
|
data-remote="true">
|
||||||
<%= t'activities.index.more_activities' %></a>
|
<%= t'activities.index.more_activities' %></a>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
<%
|
<% if activities.first_page? && activities.first.created_at.to_date == Date.today %>
|
||||||
current_day = days_since_1970(DateTime.current)
|
|
||||||
%>
|
|
||||||
<% if !hide_today && activities.count > 0 && days_since_1970(activities[0].created_at) == current_day %>
|
|
||||||
<li class="text-center activity-date-item">
|
<li class="text-center activity-date-item">
|
||||||
<span class="label label-primary">
|
<span class="label label-primary">
|
||||||
<%=t "activities.index.today" %>
|
<%=t "activities.index.today" %>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% activities.each do |activity| %>
|
<% activities.each_with_index do |activity, index| %>
|
||||||
<% activity_day = days_since_1970(activity.created_at) %>
|
<% prevDate = calculate_previous_date(activities, index, previous_activity_created_at) %>
|
||||||
|
<% if activity.created_at.to_date < prevDate %>
|
||||||
<% if activity_day < current_day and activity_day < day %>
|
|
||||||
<% day = days_since_1970(activity.created_at) %>
|
|
||||||
<li class="text-center activity-date-item">
|
<li class="text-center activity-date-item">
|
||||||
<span class="label label-primary">
|
<span class="label label-primary">
|
||||||
<%= activity.created_at.strftime('%d.%m.%Y') %>
|
<%= activity.created_at.strftime('%d.%m.%Y') %>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
json.global_activities do
|
json.global_activities do
|
||||||
json.more more
|
json.more more
|
||||||
|
json.currentPage page
|
||||||
json.activities activities do |activity|
|
json.activities activities do |activity|
|
||||||
json.id activity.id
|
json.id activity.id
|
||||||
json.message activity.message
|
json.message activity.message
|
||||||
|
json.createdAt activity.created_at
|
||||||
|
json.timezone timezone
|
||||||
if activity.my_module
|
if activity.my_module
|
||||||
json.project activity.my_module.experiment.project.name
|
json.project activity.my_module.experiment.project.name
|
||||||
json.task activity.my_module.name
|
json.task activity.my_module.name
|
||||||
end
|
end
|
||||||
json.created_at activity.created_at
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
7
flow-typed/types.js
vendored
7
flow-typed/types.js
vendored
|
@ -33,9 +33,10 @@ export type ValidationErrors = string | Array<string> | Array<ValidationError>;
|
||||||
export type Activity = {
|
export type Activity = {
|
||||||
id: number,
|
id: number,
|
||||||
message: string,
|
message: string,
|
||||||
created_at: string,
|
createdAt: string,
|
||||||
project?: string,
|
timezone: string,
|
||||||
task?: string
|
project: string,
|
||||||
|
task: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Notification = {
|
export type Notification = {
|
||||||
|
|
20
spec/controllers/client_api/activities_controller_spec.rb
Normal file
20
spec/controllers/client_api/activities_controller_spec.rb
Normal 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
|
7
spec/factories/activity.rb
Normal file
7
spec/factories/activity.rb
Normal 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
|
5
spec/factories/user_project.rb
Normal file
5
spec/factories/user_project.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FactoryGirl.define do
|
||||||
|
factory :user_project do
|
||||||
|
role 'owner'
|
||||||
|
end
|
||||||
|
end
|
|
@ -179,4 +179,19 @@ describe User, type: :model do
|
||||||
it { is_expected.to respond_to(:recent_email_notification) }
|
it { is_expected.to respond_to(:recent_email_notification) }
|
||||||
it { is_expected.to respond_to(:system_message_email_notification) }
|
it { is_expected.to respond_to(:system_message_email_notification) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#last_activities' do
|
||||||
|
let!(:user) { create :user }
|
||||||
|
let!(:project) { create :project }
|
||||||
|
let!(:user_projects) { create :user_project, project: project, user: user }
|
||||||
|
let!(:activity_one) { create :activity, user: user, project: project }
|
||||||
|
let!(:activity_two) { create :activity, user: user, project: project }
|
||||||
|
|
||||||
|
it 'is expected to return an array of user\'s activities' do
|
||||||
|
activities = user.last_activities
|
||||||
|
expect(activities.count).to eq 2
|
||||||
|
expect(activities).to include activity_one
|
||||||
|
expect(activities).to include activity_two
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
26
spec/support/api/schemas/activities.json
Normal file
26
spec/support/api/schemas/activities.json
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue