mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-26 15:05:39 +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
|
||||
var activityModal = $('#activity-modal');
|
||||
var activityModalBody = activityModal.find('.modal-body');
|
||||
|
||||
var initMoreBtn = function() {
|
||||
activityModalBody.find('.btn-more-activities')
|
||||
.on('ajax:success', function(e, data) {
|
||||
$(data.html).insertBefore($(this).parents('li'));
|
||||
$(this).attr('href', data.next_url);
|
||||
if (data.activities_number < data.per_page) {
|
||||
$(this).remove();
|
||||
if(data.more_url) {
|
||||
$(this).attr('href', data.more_url);
|
||||
} else {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,47 +1,43 @@
|
|||
class ActivitiesController < ApplicationController
|
||||
include ActivityHelper
|
||||
|
||||
before_action :load_vars
|
||||
|
||||
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|
|
||||
format.json {
|
||||
render :json => {
|
||||
per_page: @per_page,
|
||||
activities_number: @activities.length,
|
||||
next_url: more_url,
|
||||
html: render_to_string({
|
||||
partial: 'index.html.erb',
|
||||
locals: {
|
||||
more_activities_url: more_url,
|
||||
hide_today: @hide_today,
|
||||
day: @day
|
||||
}
|
||||
})
|
||||
format.json do
|
||||
render json: {
|
||||
more_url: local_vars.fetch(:more_activities_url),
|
||||
html: render_to_string(
|
||||
partial: 'index.html.erb', locals: local_vars
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_vars
|
||||
@last_activity_id = params[:from].to_i || 0
|
||||
@last_activity = Activity.find_by_id(@last_activity_id)
|
||||
private
|
||||
|
||||
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
|
||||
|
|
|
@ -14,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: !activities.last_page?,
|
||||
timezone: current_user.time_zone
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,4 +56,15 @@ module ActivityHelper
|
|||
def days_since_1970(dt)
|
||||
dt.to_i / 86400
|
||||
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
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @flow
|
||||
|
||||
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 styled from "styled-components";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import {
|
||||
WHITE_COLOR,
|
||||
|
@ -14,8 +14,12 @@ import {
|
|||
|
||||
import { NAME_TRUNCATION_LENGTH } from "../../../config/constants/numeric";
|
||||
|
||||
type InputActivity = {
|
||||
activity: Activity
|
||||
}
|
||||
|
||||
const StyledLi = styled.li`
|
||||
border-radius: .25em;
|
||||
border-radius: 0.25em;
|
||||
margin-bottom: 1em;
|
||||
background-color: ${WHITE_COLOR};
|
||||
border: 1px solid ${COLOR_CONCRETE};
|
||||
|
@ -24,8 +28,8 @@ const TimeSpan = styled.span`
|
|||
min-width: 150px;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
border-top-left-radius: .25em;
|
||||
border-bottom-left-radius: .25em;
|
||||
border-top-left-radius: 0.25em;
|
||||
border-bottom-left-radius: 0.25em;
|
||||
border: 3px solid ${BORDER_GRAY_COLOR};
|
||||
background-color: ${BORDER_GRAY_COLOR};
|
||||
padding-left: 10px;
|
||||
|
@ -37,52 +41,57 @@ const TextSpan = styled.span`
|
|||
display: table-cell;
|
||||
padding: 3px 10px;
|
||||
text-align: justify;
|
||||
`
|
||||
function truncatedTooltip(id, text) {
|
||||
`;
|
||||
|
||||
function truncatedTooltip(id: string, text: any): Node {
|
||||
return (
|
||||
<OverlayTrigger overlay={(
|
||||
<Tooltip id={id}>
|
||||
{text}
|
||||
</Tooltip>
|
||||
)} placement="bottom">
|
||||
<span>
|
||||
{text.substring(0, NAME_TRUNCATION_LENGTH)}...
|
||||
</span>
|
||||
<OverlayTrigger
|
||||
overlay={<Tooltip id={id}>{text}</Tooltip>}
|
||||
placement="bottom"
|
||||
>
|
||||
<span>{text.substring(0, NAME_TRUNCATION_LENGTH)}...</span>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function taskPath(activity) {
|
||||
function taskPath(activity: Activity): Node {
|
||||
return (
|
||||
<span>
|
||||
[ <FormattedMessage id="general.project" />:
|
||||
<span>
|
||||
[ <FormattedMessage id="general.project" />:
|
||||
{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>
|
||||
)},
|
||||
<FormattedMessage id="general.task" />:
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const ActivityElement = ({ activity }) =>
|
||||
const ActivityElement = ({ activity }: InputActivity ): Node => (
|
||||
<StyledLi>
|
||||
<TimeSpan>
|
||||
<Moment format="HH.mm">
|
||||
{activity.created_at}
|
||||
</Moment>
|
||||
<FormattedDate
|
||||
value={new Date(activity.createdAt)}
|
||||
hour="2-digit"
|
||||
minute="2-digit"
|
||||
timeZone={activity.timezone}
|
||||
hour12={false}
|
||||
/>
|
||||
</TimeSpan>
|
||||
<TextSpan>
|
||||
<span dangerouslySetInnerHTML={{ __html: activity.message }} />
|
||||
{activity.task && taskPath(activity)}
|
||||
</TextSpan>
|
||||
</StyledLi>;
|
||||
</StyledLi>
|
||||
);
|
||||
|
||||
export default ActivityElement;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from "react";
|
||||
import type { Element } from "react";
|
||||
import type { Element, Node } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Button, Modal } from "react-bootstrap";
|
||||
import _ from "lodash";
|
||||
|
@ -53,7 +53,8 @@ type Props = {
|
|||
|
||||
type State = {
|
||||
activities: Array<Activity>,
|
||||
more: boolean
|
||||
more: boolean,
|
||||
currentPage: number
|
||||
};
|
||||
|
||||
class GlobalActivitiesModal extends Component<Props, State> {
|
||||
|
@ -61,7 +62,7 @@ class GlobalActivitiesModal extends Component<Props, State> {
|
|||
key: number,
|
||||
activity: Activity,
|
||||
date: Date
|
||||
) {
|
||||
): Node {
|
||||
return [
|
||||
<ActivityDateElement key={date} date={date} />,
|
||||
<ActivityElement key={key} activity={activity} />
|
||||
|
@ -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,37 +88,43 @@ 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);
|
||||
// returns a label with "today" if the date of the activity is today
|
||||
if (i === 0) {
|
||||
return GlobalActivitiesModal.renderActivityDateElement(
|
||||
i,
|
||||
activity,
|
||||
newDate
|
||||
);
|
||||
return this.state.activities.map(
|
||||
(activity: Activity, i: number, arr: Array<*>) => {
|
||||
const newDate = new Date(activity.createdAt);
|
||||
// returns a label with "today" if the date of the activity is today
|
||||
if (i === 0 && newDate.toDateString() === new Date().toDateString()) {
|
||||
return GlobalActivitiesModal.renderActivityDateElement(
|
||||
activity.id,
|
||||
activity,
|
||||
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() {
|
||||
|
@ -136,15 +143,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<*> {
|
||||
|
|
|
@ -14,9 +14,9 @@ import { getSciNoteInfo } from "../../../services/api/configurations_api";
|
|||
import AboutScinoteModal from "./AboutScinoteModal";
|
||||
|
||||
type State = {
|
||||
showModal: boolean,
|
||||
scinoteVersion: string,
|
||||
addons: Array<string>
|
||||
addons: Array<string>,
|
||||
showModal: boolean
|
||||
};
|
||||
|
||||
class InfoDropdown extends Component<*, State> {
|
||||
|
|
|
@ -3,9 +3,9 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
<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>
|
||||
<% 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 %>
|
||||
<% if @last_activity_id < 1 and @overflown %>
|
||||
<% if more_activities_url.present? && page == 1 %>
|
||||
<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>
|
||||
</li>
|
||||
<% end %>
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
<%
|
||||
current_day = days_since_1970(DateTime.current)
|
||||
%>
|
||||
<% if !hide_today && activities.count > 0 && days_since_1970(activities[0].created_at) == current_day %>
|
||||
<% if activities.first_page? && activities.first.created_at.to_date == Date.today %>
|
||||
<li class="text-center activity-date-item">
|
||||
<span class="label label-primary">
|
||||
<%=t "activities.index.today" %>
|
||||
</span>
|
||||
</li>
|
||||
<% end %>
|
||||
<% activities.each do |activity| %>
|
||||
<% activity_day = days_since_1970(activity.created_at) %>
|
||||
|
||||
<% if activity_day < current_day and activity_day < day %>
|
||||
<% day = days_since_1970(activity.created_at) %>
|
||||
<% activities.each_with_index do |activity, index| %>
|
||||
<% prevDate = calculate_previous_date(activities, index, previous_activity_created_at) %>
|
||||
<% if activity.created_at.to_date < prevDate %>
|
||||
<li class="text-center activity-date-item">
|
||||
<span class="label label-primary">
|
||||
<%= activity.created_at.strftime('%d.%m.%Y') %>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
json.global_activities do
|
||||
json.more more
|
||||
json.currentPage page
|
||||
json.activities activities do |activity|
|
||||
json.id activity.id
|
||||
json.message activity.message
|
||||
json.createdAt activity.created_at
|
||||
json.timezone timezone
|
||||
if activity.my_module
|
||||
json.project activity.my_module.experiment.project.name
|
||||
json.task activity.my_module.name
|
||||
end
|
||||
json.created_at activity.created_at
|
||||
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 = {
|
||||
id: number,
|
||||
message: string,
|
||||
created_at: string,
|
||||
project?: string,
|
||||
task?: string
|
||||
createdAt: string,
|
||||
timezone: string,
|
||||
project: string,
|
||||
task: string
|
||||
};
|
||||
|
||||
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(:system_message_email_notification) }
|
||||
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
|
||||
|
|
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