Commit 7a8ac752 authored by ikuradon's avatar ikuradon 🐈

Merge remote-tracking branch 'upstream/master' into comm.cx

parents 1cedd55f ebe574d5
Pipeline #522 failed with stage
in 60 minutes and 1 second
This diff is collapsed.
......@@ -55,7 +55,7 @@ Private posts, locked accounts, phrase filtering, muting, blocking and all sorts
**OAuth2 and a straightforward REST API**
Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choice!
Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choices!
## Deployment
......
......@@ -4,6 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
......
......@@ -4,6 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
respond_to :json
......
......@@ -4,6 +4,7 @@ class Api::V1::InstancesController < Api::BaseController
respond_to :json
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
def show
expires_in 3.minutes, public: true
......
......@@ -13,7 +13,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
render json: @statuses,
each_serializer: REST::StatusSerializer,
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
status: regeneration_in_progress? ? 206 : 200
status: account_home_feed.regenerating? ? 206 : 200
end
private
......@@ -62,8 +62,4 @@ 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
......@@ -97,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
done();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
......
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
import classNames from 'classnames';
const MissingIndicator = () => (
<div className='regeneration-indicator missing-indicator'>
<div>
<div className='regeneration-indicator__figure' />
const MissingIndicator = ({ fullPage }) => (
<div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
<div className='regeneration-indicator__figure'>
<img src={illustration} alt='' />
</div>
<div className='regeneration-indicator__label'>
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
</div>
<div className='regeneration-indicator__label'>
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
</div>
</div>
);
MissingIndicator.propTypes = {
fullPage: PropTypes.bool,
};
export default MissingIndicator;
import React from 'react';
import { FormattedMessage } from 'react-intl';
import illustration from 'mastodon/../images/elephant_ui_working.svg';
const MissingIndicator = () => (
<div className='regeneration-indicator'>
<div className='regeneration-indicator__figure'>
<img src={illustration} alt='' />
</div>
<div className='regeneration-indicator__label'>
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>
);
export default MissingIndicator;
import { debounce } from 'lodash';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import LoadGap from './load_gap';
import ScrollableList from './scrollable_list';
import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
export default class StatusList extends ImmutablePureComponent {
......@@ -81,18 +81,7 @@ export default class StatusList extends ImmutablePureComponent {
const { isLoading, isPartial } = other;
if (isPartial) {
return (
<div className='regeneration-indicator'>
<div>
<div className='regeneration-indicator__figure' />
<div className='regeneration-indicator__label'>
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading&hellip;' />
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>
</div>
);
return <RegenerationIndicator />;
}
let scrollableContent = (isLoading || statusIds.size > 0) ? (
......
......@@ -83,6 +83,7 @@ class AccountTimeline extends ImmutablePureComponent {
if (!isAccount) {
return (
<Column>
<ColumnBackButton multiColumn={multiColumn} />
<MissingIndicator />
</Column>
);
......
......@@ -4,7 +4,7 @@ import MissingIndicator from '../../components/missing_indicator';
const GenericNotFound = () => (
<Column>
<MissingIndicator />
<MissingIndicator fullPage />
</Column>
);
......
......@@ -3185,37 +3185,27 @@ a.status-card.compact:hover {
cursor: default;
display: flex;
flex: 1 1 auto;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
& > div {
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%);
&,
img {
display: block;
width: auto;
height: 160px;
margin: 0;
}
}
&.missing-indicator {
&--without-header {
padding-top: 20px + 48px;
.regeneration-indicator__figure {
background-image: url('../images/elephant_ui_disappointed.svg');
}
}
&__label {
margin-top: 200px;
margin-top: 30px;
strong {
display: block;
......
......@@ -3,9 +3,10 @@
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background: $ui-base-color;
@media screen and (max-width: 920px) {
background: darken($ui-base-color, 8%);
display: block !important;
}
......
This diff is collapsed.
......@@ -7,19 +7,7 @@ class HomeFeed < Feed
@account = account
end
def get(limit, max_id = nil, since_id = nil, min_id = nil)
if redis.exists("account:#{@account.id}:regeneration")
from_database(limit, max_id, since_id, min_id)
else
super
end
end
private
def from_database(limit, max_id, since_id, min_id)
Status.as_home_timeline(@account)
.paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
.reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
def regenerating?
redis.exists("account:#{@id}:regeneration")
end
end
......@@ -57,6 +57,7 @@ class MediaAttachment < ApplicationRecord
small: {
convert_options: {
output: {
'loglevel' => 'fatal',
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
},
},
......@@ -70,6 +71,7 @@ class MediaAttachment < ApplicationRecord
keep_same_format: true,
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'c:v' => 'copy',
'c:a' => 'copy',
......@@ -84,6 +86,7 @@ class MediaAttachment < ApplicationRecord
content_type: 'audio/mpeg',
convert_options: {
output: {
'loglevel' => 'fatal',
'q:a' => 2,
},
},
......
......@@ -289,10 +289,6 @@ class Status < ApplicationRecord
where(language: nil).or where(language: account.chosen_languages)
end
def as_home_timeline(account)
where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
end
def as_public_timeline(account = nil, local_only = false)
query = timeline_scope(local_only).without_replies
......
# frozen_string_literal: true
class HashtagQueryService < BaseService
LIMIT_PER_MODE = 4
def call(tag, params, account = nil, local = false)
tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
all = tags_for(params[:all])
......@@ -15,6 +17,6 @@ class HashtagQueryService < BaseService
private
def tags_for(names)
Tag.matching_name(names) if names.presence
Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
end
end
......@@ -52,6 +52,6 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
end
def notifications_about_direct_statuses
Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct })
Notification.joins('INNER JOIN mentions ON mentions.id = notifications.activity_id INNER JOIN statuses ON statuses.id = mentions.status_id').where(activity_type: 'Mention', statuses: { visibility: :direct })
end
end
class UpdatePtLocales < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
User.where(locale: 'pt').in_batches.update_all(locale: 'pt-PT')
end
def down
User.where(locale: 'pt-PT').in_batches.update_all(locale: 'pt')
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_10_01_213028) do
ActiveRecord::Schema.define(version: 2019_10_07_013357) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -694,6 +694,30 @@ ActiveRecord::Schema.define(version: 2019_10_01_213028) do
t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
end
create_table "stream_entries", force: :cascade do |t|
t.bigint "activity_id"
t.string "activity_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "hidden", default: false, null: false
t.bigint "account_id"
t.index ["account_id", "activity_type", "id"], name: "index_stream_entries_on_account_id_and_activity_type_and_id"
t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type"
end
create_table "subscriptions", force: :cascade do |t|
t.string "callback_url", default: "", null: false
t.string "secret"
t.datetime "expires_at"
t.boolean "confirmed", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "last_successful_delivery_at"
t.string "domain"
t.bigint "account_id", null: false
t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true
end
create_table "tags", force: :cascade do |t|
t.string "name", default: "", null: false
t.datetime "created_at", null: false
......
......@@ -15,7 +15,12 @@ module Mastodon
end
def parallelize_with_progress(scope)
ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency]
if options[:concurrency] < 1
say('Cannot run with this concurrency setting, must be at least 1', :red)
exit(1)
end
ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency] + 1
progress = create_progress_bar(scope.count)
pool = Concurrent::FixedThreadPool.new(options[:concurrency])
......@@ -27,17 +32,26 @@ module Mastodon
items.each do |item|
futures << Concurrent::Future.execute(executor: pool) do
ActiveRecord::Base.connection_pool.with_connection do
begin
progress.log("Processing #{item.id}") if options[:verbose]
result = yield(item)
aggregate.increment(result) if result.is_a?(Integer)
rescue => e
progress.log pastel.red("Error processing #{item.id}: #{e}")
ensure
progress.increment
begin
if !progress.total.nil? && progress.progress + 1 > progress.total
# The number of items has changed between start and now,
# since there is no good way to predict the final count from
# here, just change the progress bar to an indeterminate one
progress.total = nil
end
progress.log("Processing #{item.id}") if options[:verbose]
result = ActiveRecord::Base.connection_pool.with_connection do
yield(item)
end
aggregate.increment(result) if result.is_a?(Integer)
rescue => e
progress.log pastel.red("Error processing #{item.id}: #{e}")
ensure
progress.increment
end
end
end
......@@ -46,7 +60,7 @@ module Mastodon
futures.map(&:value)
end
progress.finish
progress.stop
[total.value, aggregate.value]
end
......
......@@ -27,7 +27,6 @@ module Mastodon
dry_run = options[:dry_run] ? '(DRY RUN)' : ''
if options[:all] || username.nil?
processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
PrecomputeFeedService.new.call(account) unless options[:dry_run]
end
......
......@@ -34,11 +34,10 @@ RSpec.describe HomeFeed, type: :model do
Redis.current.set("account:#{account.id}:regeneration", true)
end
it 'gets statuses with ids in the range from database' do
it 'returns nothing' do
results = subject.get(3)
expect(results.map(&:id)).to eq [10, 3, 2]
expect(results.first.attributes.keys).to include('id', 'updated_at')
expect(results.map(&:id)).to eq []
end
end
end
......
......@@ -296,49 +296,6 @@ RSpec.describe Status, type: :model do
end
end
describe '.as_home_timeline' do
let(:account) { Fabricate(:account) }
let(:followed) { Fabricate(:account) }
let(:not_followed) { Fabricate(:account) }
before do
Fabricate(:follow, account: account, target_account: followed)
@self_status = Fabricate(:status, account: account, visibility: :public)
@self_direct_status = Fabricate(:status, account: account, visibility: :direct)
@followed_status = Fabricate(:status, account: followed, visibility: :public)
@followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
@not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
@results = Status.as_home_timeline(account)
end
it 'includes statuses from self' do
expect(@results).to include(@self_status)
end
it 'does not include direct statuses from self' do
expect(@results).to_not include(@self_direct_status)
end
it 'includes statuses from followed' do
expect(@results).to include(@followed_status)
end
it 'does not include direct statuses mentioning recipient from followed' do
Fabricate(:mention, account: account, status: @followed_direct_status)
expect(@results).to_not include(@followed_direct_status)
end
it 'does not include direct statuses not mentioning recipient from followed' do
expect(@results).not_to include(@followed_direct_status)
end
it 'does not include statuses from non-followed' do
expect(@results).not_to include(@not_followed_status)
end
end
describe '.as_public_timeline' do
it 'only includes statuses with public visibility' do
public_status = Fabricate(:status, visibility: :public)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment