From 7badad7797b487b411a2ab34e0f7413741974bb4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 17 Jan 2018 23:56:03 +0100 Subject: [PATCH] Fix home regeneration (#6251) * Fix regeneration marker not being removed after completion * Return HTTP 206 from /api/v1/timelines/home if regeneration in progress Prioritize RegenerationWorker by putting it into default queue * Display loading indicator and poll home timeline while it regenerates * Add graphic to regeneration message * Make "not found" indicator consistent with home regeneration --- .../api/v1/timelines/home_controller.rb | 10 +++- app/javascript/images/elephant-friend.png | Bin 24466 -> 0 bytes .../images/elephant_ui_disappointed.svg | 1 + app/javascript/images/elephant_ui_working.svg | 1 + app/javascript/images/mastodon-not-found.png | Bin 19560 -> 0 bytes app/javascript/mastodon/actions/timelines.js | 13 ++++-- .../mastodon/components/missing_indicator.js | 9 +++- .../mastodon/components/status_list.js | 21 ++++++++- .../mastodon/features/home_timeline/index.js | 37 ++++++++++++++- .../mastodon/features/list_timeline/index.js | 8 +++- .../ui/containers/status_list_container.js | 1 + app/javascript/mastodon/reducers/timelines.js | 5 +- .../styles/mastodon/components.scss | 43 ++++++++++++++++-- app/services/precompute_feed_service.rb | 1 + app/workers/regeneration_worker.rb | 2 +- .../concerns/user_tracking_concern_spec.rb | 40 ++++++++++++---- 16 files changed, 165 insertions(+), 27 deletions(-) delete mode 100644 app/javascript/images/elephant-friend.png create mode 100644 app/javascript/images/elephant_ui_disappointed.svg create mode 100644 app/javascript/images/elephant_ui_working.svg delete mode 100644 app/javascript/images/mastodon-not-found.png diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index db6cd8568..bbbcf7f90 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -9,7 +9,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController def show @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + + render json: @statuses, + each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + status: regeneration_in_progress? ? 206 : 200 end private @@ -57,4 +61,8 @@ class Api::V1::Timelines::HomeController < Api::BaseController def pagination_since_id @statuses.first.id end + + def regeneration_in_progress? + Redis.current.exists("account:#{current_account.id}:regeneration") + end end diff --git a/app/javascript/images/elephant-friend.png b/app/javascript/images/elephant-friend.png deleted file mode 100644 index 3c5145ba987ff14621d0632cec74e02eb3886d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24466 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 \ No newline at end of file diff --git a/app/javascript/images/elephant_ui_working.svg b/app/javascript/images/elephant_ui_working.svg new file mode 100644 index 000000000..8ba475db0 --- /dev/null +++ b/app/javascript/images/elephant_ui_working.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/images/mastodon-not-found.png b/app/javascript/images/mastodon-not-found.png deleted file mode 100644 index 76108d41f69272a51209faa631f6db3bc000a797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19560 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 diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index f8843d1d9..df6a36379 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; -export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { +export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) { return { type: TIMELINE_REFRESH_SUCCESS, timeline, statuses, skipLoading, next, + partial, }; }; @@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) { return function (dispatch, getState) { const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); - if (timeline.get('isLoading') || timeline.get('online')) { + if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) { return; } @@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) { dispatch(refreshTimelineRequest(timelineId, skipLoading)); api(getState).get(path, { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); + if (response.status === 206) { + dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); + } else { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); + } }).catch(error => { dispatch(refreshTimelineFail(timelineId, error, skipLoading)); }); diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js index 87df7f61c..70d8c3b98 100644 --- a/app/javascript/mastodon/components/missing_indicator.js +++ b/app/javascript/mastodon/components/missing_indicator.js @@ -2,9 +2,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; const MissingIndicator = () => ( -

+
- +
+ +
+ + +
); diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 58a7b228a..5acaf714e 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from './scrollable_list'; +import { FormattedMessage } from 'react-intl'; export default class StatusList extends ImmutablePureComponent { @@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: PropTypes.bool, shouldUpdateScroll: PropTypes.func, isLoading: PropTypes.bool, + isPartial: PropTypes.bool, hasMore: PropTypes.bool, prepend: PropTypes.node, emptyMessage: PropTypes.node, @@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, ...other } = this.props; - const { isLoading } = other; + const { statusIds, ...other } = this.props; + const { isLoading, isPartial } = other; + + if (isPartial) { + return ( +
+
+
+ +
+ + +
+
+
+ ); + } const scrollableContent = (isLoading || statusIds.size > 0) ? ( statusIds.map((statusId) => ( diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index a4bc60fac..31f5a3c8b 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { expandHomeTimeline } from '../../actions/timelines'; +import { expandHomeTimeline, refreshHomeTimeline } from '../../actions/timelines'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; @@ -16,6 +16,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, + isPartial: state.getIn(['timelines', 'home', 'isPartial'], false), }); @connect(mapStateToProps) @@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, + isPartial: PropTypes.bool, columnId: PropTypes.string, multiColumn: PropTypes.bool, }; @@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent { this.props.dispatch(expandHomeTimeline()); } + componentDidMount () { + this._checkIfReloadNeeded(false, this.props.isPartial); + } + + componentDidUpdate (prevProps) { + this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial); + } + + componentWillUnmount () { + this._stopPolling(); + } + + _checkIfReloadNeeded (wasPartial, isPartial) { + const { dispatch } = this.props; + + if (wasPartial === isPartial) { + return; + } else if (!wasPartial && isPartial) { + this.polling = setInterval(() => { + dispatch(refreshHomeTimeline()); + }, 3000); + } else if (wasPartial && !isPartial) { + this._stopPolling(); + } + } + + _stopPolling () { + if (this.polling) { + clearInterval(this.polling); + this.polling = null; + } + } + render () { const { intl, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index ae136e48f..3b97ac62a 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent { if (typeof list === 'undefined') { return ( - +
+ +
); } else if (list === false) { return ( - +
+ +
); } diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index a0aec4403..59b53d823 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -47,6 +47,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { timelineId }) => ({ statusIds: getStatusIds(state, { type: timelineId }), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), + isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: !!state.getIn(['timelines', timelineId, 'next']), }); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 9984c3b5d..7b7b5470f 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({ items: ImmutableList(), }); -const normalizeTimeline = (state, timeline, statuses, next) => { +const normalizeTimeline = (state, timeline, statuses, next, isPartial) => { const oldIds = state.getIn([timeline, 'items'], ImmutableList()); const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); const wasLoaded = state.getIn([timeline, 'loaded']); @@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => { mMap.set('isLoading', false); if (!hadNext) mMap.set('next', next); mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids); + mMap.set('isPartial', isPartial); })); }; @@ -124,7 +125,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next); + return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_EXPAND_SUCCESS: return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_UPDATE: diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index dbb277af9..6fbecee7c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2303,7 +2303,7 @@ } } -.missing-indicator { +.regeneration-indicator { text-align: center; font-size: 16px; font-weight: 500; @@ -2314,11 +2314,46 @@ flex: 1 1 auto; align-items: center; justify-content: center; + padding: 20px; & > div { - background: url('../images/mastodon-not-found.png') no-repeat center -50px; - padding-top: 210px; width: 100%; + background: transparent; + padding-top: 0; + } + + &__figure { + background: url('../images/elephant_ui_working.svg') no-repeat center 0; + width: 100%; + height: 160px; + background-size: contain; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &.missing-indicator { + padding-top: 20px + 48px; + + .regeneration-indicator__figure { + background-image: url('../images/elephant_ui_disappointed.svg'); + } + } + + &__label { + margin-top: 200px; + + strong { + display: block; + margin-bottom: 10px; + color: lighten($ui-base-color, 34%); + } + + span { + font-size: 15px; + font-weight: 400; + } } } @@ -2749,7 +2784,6 @@ @keyframes heartbeat { from { transform: scale(1); - transform-origin: center center; animation-timing-function: ease-out; } @@ -2775,6 +2809,7 @@ } .pulse-loading { + transform-origin: center center; animation: heartbeat 1.5s ease-in-out infinite both; } diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index 36aabaa00..4f771ff72 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -3,5 +3,6 @@ class PrecomputeFeedService < BaseService def call(account) FeedManager.instance.populate_feed(account) + Redis.current.del("account:#{account.id}:regeneration") end end diff --git a/app/workers/regeneration_worker.rb b/app/workers/regeneration_worker.rb index 8cee21ae1..5c6a040bd 100644 --- a/app/workers/regeneration_worker.rb +++ b/app/workers/regeneration_worker.rb @@ -3,7 +3,7 @@ class RegenerationWorker include Sidekiq::Worker - sidekiq_options queue: 'pull', backtrace: true, unique: :until_executed + sidekiq_options unique: :until_executed def perform(account_id, _ = :home) account = Account.find(account_id) diff --git a/spec/controllers/concerns/user_tracking_concern_spec.rb b/spec/controllers/concerns/user_tracking_concern_spec.rb index 168d44ba6..d08095ef8 100644 --- a/spec/controllers/concerns/user_tracking_concern_spec.rb +++ b/spec/controllers/concerns/user_tracking_concern_spec.rb @@ -43,15 +43,39 @@ describe ApplicationController, type: :controller do expect_updated_sign_in_at(user) end - it 'regenerates feed when sign in is older than two weeks' do - allow(RegenerationWorker).to receive(:perform_async) - user.update(current_sign_in_at: 3.weeks.ago) - sign_in user, scope: :user - get :show + describe 'feed regeneration' do + before do + alice = Fabricate(:account) + bob = Fabricate(:account) - expect_updated_sign_in_at(user) - expect(Redis.current.get("account:#{user.account_id}:regeneration")).to eq 'true' - expect(RegenerationWorker).to have_received(:perform_async) + user.account.follow!(alice) + user.account.follow!(bob) + + Fabricate(:status, account: alice, text: 'hello world') + Fabricate(:status, account: bob, text: 'yes hello') + Fabricate(:status, account: user.account, text: 'test') + + user.update(last_sign_in_at: 'Tue, 04 Jul 2017 14:45:56 UTC +00:00', current_sign_in_at: 'Wed, 05 Jul 2017 22:10:52 UTC +00:00') + + sign_in user, scope: :user + end + + it 'sets a regeneration marker while regenerating' do + allow(RegenerationWorker).to receive(:perform_async) + get :show + + expect_updated_sign_in_at(user) + expect(Redis.current.get("account:#{user.account_id}:regeneration")).to eq 'true' + expect(RegenerationWorker).to have_received(:perform_async) + end + + it 'regenerates feed when sign in is older than two weeks' do + get :show + + expect_updated_sign_in_at(user) + expect(Redis.current.zcard(FeedManager.instance.key(:home, user.account_id))).to eq 3 + expect(Redis.current.get("account:#{user.account_id}:regeneration")).to be_nil + end end def expect_updated_sign_in_at(user)