Merge commit '0ddc895282
' into kb_migration
This commit is contained in:
commit
9027195ae3
66 changed files with 1037 additions and 250 deletions
3
Gemfile
3
Gemfile
|
@ -20,7 +20,7 @@ gem 'dotenv-rails', '~> 2.8'
|
|||
gem 'aws-sdk-s3', '~> 1.123', require: false
|
||||
gem 'fog-core', '<= 2.4.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b'
|
||||
gem 'kt-paperclip', '~> 7.2'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
|
@ -60,7 +60,6 @@ gem 'kaminari', '~> 1.2'
|
|||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
|
||||
gem 'nokogiri', '~> 1.15'
|
||||
gem 'nsa', '~> 0.2'
|
||||
gem 'oj', '~> 3.14'
|
||||
gem 'ox', '~> 2.14'
|
||||
gem 'parslet'
|
||||
|
|
27
Gemfile.lock
27
Gemfile.lock
|
@ -7,18 +7,6 @@ GIT
|
|||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/kreeti/kt-paperclip.git
|
||||
revision: 11abf222dc31bff71160a1d138b445214f434b2b
|
||||
ref: 11abf222dc31bff71160a1d138b445214f434b2b
|
||||
specs:
|
||||
kt-paperclip (7.1.1)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
marcel (~> 1.0.1)
|
||||
mime-types
|
||||
terrapin (~> 0.6.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/mastodon/rails-settings-cached.git
|
||||
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
|
||||
|
@ -380,6 +368,12 @@ GEM
|
|||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
kt-paperclip (7.2.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
marcel (~> 1.0.1)
|
||||
mime-types
|
||||
terrapin (~> 0.6.0)
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.8.1)
|
||||
|
@ -442,11 +436,6 @@ GEM
|
|||
nokogiri (1.15.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
activesupport (>= 4.2, < 7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.14.3)
|
||||
omniauth (1.9.2)
|
||||
hashie (>= 3.4.6)
|
||||
|
@ -682,7 +671,6 @@ GEM
|
|||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.25)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (3.0.1)
|
||||
redlock (~> 1.0)
|
||||
strong_migrations (0.8.0)
|
||||
|
@ -819,7 +807,7 @@ DEPENDENCIES
|
|||
json-ld-preloaded (~> 3.2)
|
||||
json-schema (~> 4.0)
|
||||
kaminari (~> 1.2)
|
||||
kt-paperclip (~> 7.1)!
|
||||
kt-paperclip (~> 7.2)
|
||||
letter_opener (~> 1.8)
|
||||
letter_opener_web (~> 2.0)
|
||||
link_header (~> 0.0)
|
||||
|
@ -831,7 +819,6 @@ DEPENDENCIES
|
|||
net-http (~> 0.3.2)
|
||||
net-ldap (~> 0.18)
|
||||
nokogiri (~> 1.15)
|
||||
nsa (~> 0.2)
|
||||
oj (~> 3.14)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-cas (~> 2.0)
|
||||
|
|
|
@ -44,6 +44,6 @@ class Api::V1::ListsController < Api::BaseController
|
|||
end
|
||||
|
||||
def list_params
|
||||
params.permit(:title, :replies_policy)
|
||||
params.permit(:title, :replies_policy, :exclusive)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,15 +11,15 @@ class BackupsController < ApplicationController
|
|||
def download
|
||||
case Paperclip::Attachment.default_options[:storage]
|
||||
when :s3
|
||||
redirect_to @backup.dump.expiring_url(10)
|
||||
redirect_to @backup.dump.expiring_url(10), allow_other_host: true
|
||||
when :fog
|
||||
if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
|
||||
redirect_to @backup.dump.expiring_url(Time.now.utc + 10)
|
||||
redirect_to @backup.dump.expiring_url(Time.now.utc + 10), allow_other_host: true
|
||||
else
|
||||
redirect_to full_asset_url(@backup.dump.url)
|
||||
redirect_to full_asset_url(@backup.dump.url), allow_other_host: true
|
||||
end
|
||||
when :filesystem
|
||||
redirect_to full_asset_url(@backup.dump.url)
|
||||
redirect_to full_asset_url(@backup.dump.url), allow_other_host: true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ export function normalizePollOptionTranslation(translation, poll) {
|
|||
|
||||
export function normalizeAnnouncement(announcement) {
|
||||
const normalAnnouncement = { ...announcement };
|
||||
const emojiMap = makeEmojiMap.emojis(normalAnnouncement);
|
||||
const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
|
||||
|
||||
normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
|
||||
|
||||
|
|
|
@ -151,10 +151,10 @@ export const createListFail = error => ({
|
|||
error,
|
||||
});
|
||||
|
||||
export const updateList = (id, title, shouldReset, replies_policy) => (dispatch, getState) => {
|
||||
export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
|
||||
dispatch(updateListRequest(id));
|
||||
|
||||
api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy }).then(({ data }) => {
|
||||
api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
|
||||
dispatch(updateListSuccess(data));
|
||||
|
||||
if (shouldReset) {
|
||||
|
|
|
@ -16,6 +16,7 @@ import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
|||
import { me } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import Button from './button';
|
||||
import { DisplayName } from './display_name';
|
||||
import { IconButton } from './icon_button';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
|
@ -23,13 +24,13 @@ import { RelativeTimestamp } from './relative_timestamp';
|
|||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
|
||||
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
||||
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
||||
});
|
||||
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
@ -97,39 +98,39 @@ class Account extends ImmutablePureComponent {
|
|||
|
||||
let buttons;
|
||||
|
||||
if (actionIcon) {
|
||||
if (onActionClick) {
|
||||
if (actionIcon && onActionClick) {
|
||||
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
|
||||
}
|
||||
} else if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
} else if (!actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
const blocking = account.getIn(['relationship', 'blocking']);
|
||||
const muting = account.getIn(['relationship', 'muting']);
|
||||
|
||||
if (requested) {
|
||||
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
||||
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={this.handleFollow} />;
|
||||
} else if (blocking) {
|
||||
buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
|
||||
} else if (muting) {
|
||||
let hidingNotificationsButton;
|
||||
|
||||
if (account.getIn(['relationship', 'muting_notifications'])) {
|
||||
hidingNotificationsButton = <IconButton active icon='bell' title={intl.formatMessage(messages.unmute_notifications, { name: account.get('username') })} onClick={this.handleUnmuteNotifications} />;
|
||||
hidingNotificationsButton = <Button text={intl.formatMessage(messages.unmute_notifications)} onClick={this.handleUnmuteNotifications} />;
|
||||
} else {
|
||||
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
|
||||
hidingNotificationsButton = <Button text={intl.formatMessage(messages.mute_notifications)} onClick={this.handleMuteNotifications} />;
|
||||
}
|
||||
|
||||
buttons = (
|
||||
<>
|
||||
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
|
||||
<Button text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />
|
||||
{hidingNotificationsButton}
|
||||
</>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
||||
} else if (defaultAction === 'block') {
|
||||
buttons = <IconButton icon='lock' title={intl.formatMessage(messages.block, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
||||
} else if (!account.get('moved') || following) {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class Explore extends PureComponent {
|
|||
<Search />
|
||||
</div>
|
||||
|
||||
<div className='scrollable scrollable--flex'>
|
||||
<div className='scrollable scrollable--flex' data-nosnippet>
|
||||
{isSearching ? (
|
||||
<SearchResults />
|
||||
) : (
|
||||
|
|
|
@ -8,6 +8,8 @@ import { Helmet } from 'react-helmet';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Toggle from 'react-toggle';
|
||||
|
||||
import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||
import { fetchList, deleteList, updateList } from 'mastodon/actions/lists';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
@ -145,7 +147,13 @@ class ListTimeline extends PureComponent {
|
|||
handleRepliesPolicyChange = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateList(id, undefined, false, target.value));
|
||||
dispatch(updateList(id, undefined, false, undefined, target.value));
|
||||
};
|
||||
|
||||
onExclusiveToggle = ({ target }) => {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
dispatch(updateList(id, undefined, false, target.checked, undefined));
|
||||
};
|
||||
|
||||
render () {
|
||||
|
@ -154,6 +162,7 @@ class ListTimeline extends PureComponent {
|
|||
const pinned = !!columnId;
|
||||
const title = list ? list.get('title') : id;
|
||||
const replies_policy = list ? list.get('replies_policy') : undefined;
|
||||
const isExclusive = list ? list.get('exclusive') : undefined;
|
||||
|
||||
if (typeof list === 'undefined') {
|
||||
return (
|
||||
|
@ -191,6 +200,13 @@ class ListTimeline extends PureComponent {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className='setting-toggle'>
|
||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home' />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{ replies_policy !== undefined && (
|
||||
<div role='group' aria-labelledby={`list-${id}-replies-policy`}>
|
||||
<span id={`list-${id}-replies-policy`} className='column-settings__section'>
|
||||
|
|
|
@ -120,7 +120,7 @@ class Onboarding extends ImmutablePureComponent {
|
|||
|
||||
<div className='onboarding__steps'>
|
||||
<Step onClick={this.handleProfileClick} href='/settings/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
||||
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Follow {count, plural, one {one person} other {# people}}' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own feed. Let's fill it with interesting people." />} />
|
||||
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Find at least {count, plural, one {one person} other {# people}} to follow' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own home feed. Let's fill it with interesting people." />} />
|
||||
<Step onClick={this.handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' />} />
|
||||
<Step onClick={this.handleShareClick} completed={shareClicked} icon='copy' label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
||||
</div>
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
"account.badges.group": "Group",
|
||||
"account.block": "Block @{name}",
|
||||
"account.block_domain": "Block domain {domain}",
|
||||
"account.block_short": "Block",
|
||||
"account.blocked": "Blocked",
|
||||
"account.browse_more_on_origin_server": "Browse more on the original profile",
|
||||
"account.cancel_follow_request": "Withdraw follow request",
|
||||
"account.cancel_follow_request": "Cancel follow",
|
||||
"account.direct": "Privately mention @{name}",
|
||||
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
||||
"account.domain_blocked": "Domain blocked",
|
||||
|
@ -50,7 +51,8 @@
|
|||
"account.mention": "Mention @{name}",
|
||||
"account.moved_to": "{name} has indicated that their new account is now:",
|
||||
"account.mute": "Mute @{name}",
|
||||
"account.mute_notifications": "Mute notifications from @{name}",
|
||||
"account.mute_notifications_short": "Mute notifications",
|
||||
"account.mute_short": "Mute",
|
||||
"account.muted": "Muted",
|
||||
"account.open_original_page": "Open original page",
|
||||
"account.posts": "Posts",
|
||||
|
@ -67,7 +69,7 @@
|
|||
"account.unendorse": "Don't feature on profile",
|
||||
"account.unfollow": "Unfollow",
|
||||
"account.unmute": "Unmute @{name}",
|
||||
"account.unmute_notifications": "Unmute notifications from @{name}",
|
||||
"account.unmute_notifications_short": "Unmute notifications",
|
||||
"account.unmute_short": "Unmute",
|
||||
"account_note.placeholder": "Click to add note",
|
||||
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
|
||||
|
@ -362,6 +364,7 @@
|
|||
"lists.delete": "Delete list",
|
||||
"lists.edit": "Edit list",
|
||||
"lists.edit.submit": "Change title",
|
||||
"lists.exclusive": "Hide these posts from home",
|
||||
"lists.new.create": "Add list",
|
||||
"lists.new.title_placeholder": "New list title",
|
||||
"lists.replies_policy.followed": "Any followed user",
|
||||
|
@ -469,8 +472,8 @@
|
|||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "You've made it!",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.follow_people.body": "You curate your own home feed. Let's fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Find at least {count, plural, one {one person} other {# people}} to follow",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the world.",
|
||||
"onboarding.steps.publish_status.title": "Make your first post",
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
|
|
|
@ -48,6 +48,7 @@ export const IntlProvider: React.FC<
|
|||
locale={locale}
|
||||
messages={messages}
|
||||
onError={onProviderError}
|
||||
textComponent='span'
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -25,6 +25,7 @@ const initialState = ImmutableMap({
|
|||
isSubmitting: false,
|
||||
isChanged: false,
|
||||
title: '',
|
||||
isExclusive: false,
|
||||
|
||||
accounts: ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
|
@ -46,6 +47,7 @@ export default function listEditorReducer(state = initialState, action) {
|
|||
return state.withMutations(map => {
|
||||
map.set('listId', action.list.get('id'));
|
||||
map.set('title', action.list.get('title'));
|
||||
map.set('isExclusive', action.list.get('is_exclusive'));
|
||||
map.set('isSubmitting', false);
|
||||
});
|
||||
case LIST_EDITOR_TITLE_CHANGE:
|
||||
|
|
|
@ -1581,6 +1581,7 @@ body > [data-popper-placement] {
|
|||
.account__wrapper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
|
@ -1649,9 +1650,10 @@ a .account__avatar {
|
|||
}
|
||||
|
||||
.account__relationship {
|
||||
height: 18px;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.account__disclaimer {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::InstanceAccountsDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
include LanguagesHelper
|
||||
|
||||
def self.with_params?
|
||||
|
@ -14,19 +15,23 @@ class Admin::Metrics::Dimension::InstanceAccountsDimension < Admin::Metrics::Dim
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['username'], human_key: row['username'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { domain: params[:domain], limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT accounts.username, count(follows.*) AS value
|
||||
FROM accounts
|
||||
LEFT JOIN follows ON follows.target_account_id = accounts.id
|
||||
WHERE accounts.domain = $1
|
||||
WHERE accounts.domain = :domain
|
||||
GROUP BY accounts.id, follows.target_account_id
|
||||
ORDER BY value DESC
|
||||
LIMIT $2
|
||||
LIMIT :limit
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['username'], human_key: row['username'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def params
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
include LanguagesHelper
|
||||
|
||||
def self.with_params?
|
||||
|
@ -14,21 +15,33 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
WHERE accounts.domain = $1
|
||||
AND statuses.id BETWEEN $2 AND $3
|
||||
WHERE accounts.domain = :domain
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
AND statuses.reblog_of_id IS NULL
|
||||
GROUP BY COALESCE(statuses.language, 'und')
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $4
|
||||
LIMIT :limit
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]])
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at, with_random: false)
|
||||
end
|
||||
|
||||
rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at, with_random: false)
|
||||
end
|
||||
|
||||
def params
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
include LanguagesHelper
|
||||
|
||||
def key
|
||||
|
@ -10,18 +11,22 @@ class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension:
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['locale'], human_key: standard_locale_name(row['locale']), value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT locale, count(*) AS value
|
||||
FROM users
|
||||
WHERE current_sign_in_at BETWEEN $1 AND $2
|
||||
WHERE current_sign_in_at BETWEEN :start_at AND :end_at
|
||||
AND locale IS NOT NULL
|
||||
GROUP BY locale
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
LIMIT :limit
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['locale'], human_key: standard_locale_name(row['locale']), value: row['value'].to_s } }
|
||||
end
|
||||
end
|
||||
|
|
13
app/lib/admin/metrics/dimension/query_helper.rb
Normal file
13
app/lib/admin/metrics/dimension/query_helper.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::Metrics::Dimension::QueryHelper
|
||||
protected
|
||||
|
||||
def dimension_data_rows
|
||||
ActiveRecord::Base.connection.select_all(sanitized_sql_string)
|
||||
end
|
||||
|
||||
def sanitized_sql_string
|
||||
ActiveRecord::Base.sanitize_sql_array(sql_array)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
|
||||
def key
|
||||
'servers'
|
||||
end
|
||||
|
@ -8,18 +10,30 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT accounts.domain, count(*) AS value
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
WHERE statuses.id BETWEEN $1 AND $2
|
||||
WHERE statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
GROUP BY accounts.domain
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
LIMIT :limit
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, Mastodon::Snowflake.id_at(@start_at)], [nil, Mastodon::Snowflake.id_at(@end_at)], [nil, @limit]])
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at)
|
||||
end
|
||||
|
||||
rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
|
||||
def key
|
||||
'sources'
|
||||
end
|
||||
|
@ -8,18 +10,22 @@ class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::B
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['name'] || 'web', human_key: row['name'] || I18n.t('admin.dashboard.website'), value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT oauth_applications.name, count(*) AS value
|
||||
FROM users
|
||||
LEFT JOIN oauth_applications ON oauth_applications.id = users.created_by_application_id
|
||||
WHERE users.created_at BETWEEN $1 AND $2
|
||||
WHERE users.created_at BETWEEN :start_at AND :end_at
|
||||
GROUP BY oauth_applications.name
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $3
|
||||
LIMIT :limit
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]])
|
||||
|
||||
rows.map { |row| { key: row['name'] || 'web', human_key: row['name'] || I18n.t('admin.dashboard.website'), value: row['value'].to_s } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
include LanguagesHelper
|
||||
|
||||
def self.with_params?
|
||||
|
@ -14,20 +15,36 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value
|
||||
FROM statuses
|
||||
INNER JOIN statuses_tags ON statuses_tags.status_id = statuses.id
|
||||
WHERE statuses_tags.tag_id = $1
|
||||
AND statuses.id BETWEEN $2 AND $3
|
||||
WHERE statuses_tags.tag_id = :tag_id
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
GROUP BY COALESCE(statuses.language, 'und')
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $4
|
||||
LIMIT :limit
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]])
|
||||
def tag_id
|
||||
params[:id]
|
||||
end
|
||||
|
||||
rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } }
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at, with_random: false)
|
||||
end
|
||||
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at, with_random: false)
|
||||
end
|
||||
|
||||
def params
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension::BaseDimension
|
||||
include Admin::Metrics::Dimension::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -12,21 +14,37 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
sql = <<-SQL.squish
|
||||
dimension_data_rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def sql_array
|
||||
[sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<-SQL.squish
|
||||
SELECT accounts.domain, count(*) AS value
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
INNER JOIN statuses_tags ON statuses_tags.status_id = statuses.id
|
||||
WHERE statuses_tags.tag_id = $1
|
||||
AND statuses.id BETWEEN $2 AND $3
|
||||
WHERE statuses_tags.tag_id = :tag_id
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
GROUP BY accounts.domain
|
||||
ORDER BY count(*) DESC
|
||||
LIMIT $4
|
||||
LIMIT :limit
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]])
|
||||
def tag_id
|
||||
params[:id]
|
||||
end
|
||||
|
||||
rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } }
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at, with_random: false)
|
||||
end
|
||||
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at, with_random: false)
|
||||
end
|
||||
|
||||
def params
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -25,33 +27,25 @@ class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
|
||||
else
|
||||
'accounts.domain = $3::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_accounts AS (
|
||||
SELECT accounts.id
|
||||
FROM accounts
|
||||
WHERE date_trunc('day', accounts.created_at)::date = axis.period
|
||||
AND #{account_matching_sql}
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
)
|
||||
SELECT count(*) FROM new_accounts
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measur
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
|
||||
else
|
||||
'accounts.domain = $3::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_followers AS (
|
||||
SELECT follows.id
|
||||
FROM follows
|
||||
INNER JOIN accounts ON follows.account_id = accounts.id
|
||||
WHERE date_trunc('day', follows.created_at)::date = axis.period
|
||||
AND #{account_matching_sql}
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
)
|
||||
SELECT count(*) FROM new_followers
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure:
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
|
||||
else
|
||||
'accounts.domain = $3::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_follows AS (
|
||||
SELECT follows.id
|
||||
FROM follows
|
||||
INNER JOIN accounts ON follows.target_account_id = accounts.id
|
||||
WHERE date_trunc('day', follows.created_at)::date = axis.period
|
||||
AND #{account_matching_sql}
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
)
|
||||
SELECT count(*) FROM new_follows
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
def self.with_params?
|
||||
|
@ -35,34 +36,26 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics:
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
|
||||
else
|
||||
'accounts.domain = $3::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_media_attachments AS (
|
||||
SELECT COALESCE(media_attachments.file_file_size, 0) + COALESCE(media_attachments.thumbnail_file_size, 0) AS size
|
||||
FROM media_attachments
|
||||
INNER JOIN accounts ON accounts.id = media_attachments.account_id
|
||||
WHERE date_trunc('day', media_attachments.created_at)::date = axis.period
|
||||
AND #{account_matching_sql}
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
)
|
||||
SELECT SUM(size) FROM new_media_attachments
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -25,34 +27,26 @@ class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure:
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $3::text))"
|
||||
else
|
||||
'accounts.domain = $3::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain] }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_reports AS (
|
||||
SELECT reports.id
|
||||
FROM reports
|
||||
INNER JOIN accounts ON accounts.id = reports.target_account_id
|
||||
WHERE date_trunc('day', reports.created_at)::date = axis.period
|
||||
AND #{account_matching_sql}
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
)
|
||||
SELECT count(*) FROM new_reports
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, params[:domain]]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -25,35 +27,35 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure
|
|||
nil
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
account_matching_sql = begin
|
||||
if params[:include_subdomains]
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || $5::text))"
|
||||
else
|
||||
'accounts.domain = $5::text'
|
||||
end
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
|
||||
end
|
||||
|
||||
sql = <<-SQL.squish
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_statuses AS (
|
||||
SELECT statuses.id
|
||||
FROM statuses
|
||||
INNER JOIN accounts ON accounts.id = statuses.account_id
|
||||
WHERE statuses.id BETWEEN $3 AND $4
|
||||
AND #{account_matching_sql}
|
||||
WHERE statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
AND #{account_domain_sql(params[:include_subdomains])}
|
||||
AND date_trunc('day', statuses.created_at)::date = axis.period
|
||||
)
|
||||
SELECT count(*) FROM new_statuses
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, params[:domain]]])
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at, with_random: false)
|
||||
end
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at, with_random: false)
|
||||
end
|
||||
|
||||
def time_period
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def key
|
||||
'new_users'
|
||||
end
|
||||
|
@ -15,8 +17,12 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
|
|||
User.where(created_at: previous_time_period).count
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
sql = <<-SQL.squish
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_users AS (
|
||||
SELECT users.id
|
||||
|
@ -26,12 +32,8 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe
|
|||
SELECT count(*) FROM new_users
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def key
|
||||
'opened_reports'
|
||||
end
|
||||
|
@ -15,8 +17,12 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
|
|||
Report.where(created_at: previous_time_period).count
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
sql = <<-SQL.squish
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH new_reports AS (
|
||||
SELECT reports.id
|
||||
|
@ -26,12 +32,8 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B
|
|||
SELECT count(*) FROM new_reports
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
end
|
||||
|
|
25
app/lib/admin/metrics/measure/query_helper.rb
Normal file
25
app/lib/admin/metrics/measure/query_helper.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::Metrics::Measure::QueryHelper
|
||||
protected
|
||||
|
||||
def perform_data_query
|
||||
measurement_data_rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
|
||||
def measurement_data_rows
|
||||
ActiveRecord::Base.connection.select_all(sanitized_sql_string)
|
||||
end
|
||||
|
||||
def sanitized_sql_string
|
||||
ActiveRecord::Base.sanitize_sql_array(sql_array)
|
||||
end
|
||||
|
||||
def account_domain_sql(include_subdomains)
|
||||
if include_subdomains
|
||||
"accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || :domain::text))"
|
||||
else
|
||||
'accounts.domain = :domain::text'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def key
|
||||
'resolved_reports'
|
||||
end
|
||||
|
@ -15,8 +17,12 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
|
|||
Report.resolved.where(action_taken_at: previous_time_period).count
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
sql = <<-SQL.squish
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
WITH resolved_reports AS (
|
||||
SELECT reports.id
|
||||
|
@ -26,12 +32,8 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
|
|||
SELECT count(*) FROM resolved_reports
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $1::timestamp)::date, date_trunc('day', $2::timestamp)::date, interval '1 day') AS period
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
) AS axis
|
||||
SQL
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at]])
|
||||
|
||||
rows.map { |row| { date: row['period'], value: row['value'].to_s } }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::BaseMeasure
|
||||
include Admin::Metrics::Measure::QueryHelper
|
||||
|
||||
def self.with_params?
|
||||
true
|
||||
end
|
||||
|
@ -19,25 +21,33 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base
|
|||
tag.statuses.where('statuses.id BETWEEN ? AND ?', Mastodon::Snowflake.id_at(@start_at - length_of_period, with_random: false), Mastodon::Snowflake.id_at(@end_at - length_of_period, with_random: false)).joins(:account).count('distinct accounts.domain')
|
||||
end
|
||||
|
||||
def perform_data_query
|
||||
sql = <<-SQL.squish
|
||||
def sql_array
|
||||
[sql_query_string, { start_at: @start_at, end_at: @end_at, tag_id: tag.id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id }]
|
||||
end
|
||||
|
||||
def sql_query_string
|
||||
<<~SQL.squish
|
||||
SELECT axis.*, (
|
||||
SELECT count(distinct accounts.domain) AS value
|
||||
FROM statuses
|
||||
INNER JOIN statuses_tags ON statuses.id = statuses_tags.status_id
|
||||
INNER JOIN accounts ON statuses.account_id = accounts.id
|
||||
WHERE statuses_tags.tag_id = $1
|
||||
AND statuses.id BETWEEN $2 AND $3
|
||||
WHERE statuses_tags.tag_id = :tag_id
|
||||
AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id
|
||||
AND date_trunc('day', statuses.created_at)::date = axis.day
|
||||
)
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', $4::timestamp)::date, date_trunc('day', $5::timestamp)::date, ('1 day')::interval) AS day
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, ('1 day')::interval) AS day
|
||||
) as axis
|
||||
SQL
|
||||
end
|
||||
|
||||
rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id].to_i], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @start_at], [nil, @end_at]])
|
||||
def earliest_status_id
|
||||
Mastodon::Snowflake.id_at(@start_at, with_random: false)
|
||||
end
|
||||
|
||||
rows.map { |row| { date: row['day'], value: row['value'].to_s } }
|
||||
def latest_status_id
|
||||
Mastodon::Snowflake.id_at(@end_at, with_random: false)
|
||||
end
|
||||
|
||||
def tag
|
||||
|
|
|
@ -40,9 +40,9 @@ class FeedManager
|
|||
def filter?(timeline_type, status, receiver)
|
||||
case timeline_type
|
||||
when :home
|
||||
filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status]))
|
||||
filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status]), :home)
|
||||
when :list
|
||||
filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status]))
|
||||
filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status]), :list)
|
||||
when :mentions
|
||||
filter_from_mentions?(status, receiver.id)
|
||||
when :tags
|
||||
|
@ -351,9 +351,10 @@ class FeedManager
|
|||
# @param [Integer] receiver_id
|
||||
# @param [Hash] crutches
|
||||
# @return [Boolean]
|
||||
def filter_from_home?(status, receiver_id, crutches)
|
||||
def filter_from_home?(status, receiver_id, crutches, timeline_type = :home)
|
||||
return false if receiver_id == status.account_id
|
||||
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
|
||||
return true if timeline_type != :list && crutches[:exclusive_list_users][status.account_id].present?
|
||||
return true if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language)
|
||||
|
||||
check_for_blocks = crutches[:active_mentions][status.id] || []
|
||||
|
@ -543,6 +544,8 @@ class FeedManager
|
|||
arr
|
||||
end
|
||||
|
||||
lists = List.where(account_id: receiver_id, exclusive: true)
|
||||
|
||||
crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map(&:in_reply_to_account_id)).pluck(:target_account_id).index_with(true)
|
||||
crutches[:languages] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id)).pluck(:target_account_id, :languages).to_h
|
||||
crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map { |s| s.account_id if s.reblog? }, show_reblogs: false).pluck(:target_account_id).index_with(true)
|
||||
|
@ -550,6 +553,7 @@ class FeedManager
|
|||
crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true)
|
||||
crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.flat_map { |s| [s.account.domain, s.reblog&.account&.domain] }.compact).pluck(:domain).index_with(true)
|
||||
crutches[:blocked_by] = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| [s.account_id, s.reblog&.account_id] }.flatten.compact).pluck(:account_id).index_with(true)
|
||||
crutches[:exclusive_list_users] = ListAccount.where(list: lists, account_id: statuses.map(&:account_id)).pluck(:account_id).index_with(true)
|
||||
|
||||
crutches
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# replies_policy :integer default("list"), not null
|
||||
# exclusive :boolean default(FALSE)
|
||||
#
|
||||
|
||||
class List < ApplicationRecord
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class REST::ListSerializer < ActiveModel::Serializer
|
||||
attributes :id, :title, :replies_policy
|
||||
attributes :id, :title, :replies_policy, :exclusive
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
|
||||
def host_to_url(str)
|
||||
"http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}" if str.present?
|
||||
"http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}".split('/').first if str.present?
|
||||
end
|
||||
|
||||
base_host = Rails.configuration.x.web_domain
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
if ENV['STATSD_ADDR'].present?
|
||||
host, port = ENV['STATSD_ADDR'].split(':')
|
||||
|
||||
$statsd = ::Statsd.new(host, port)
|
||||
$statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
|
||||
|
||||
::NSA.inform_statsd($statsd) do |informant|
|
||||
informant.collect(:action_controller, :web)
|
||||
informant.collect(:active_record, :db)
|
||||
informant.collect(:active_support_cache, :cache)
|
||||
informant.collect(:sidekiq, :sidekiq)
|
||||
end
|
||||
end
|
7
db/migrate/20230605085710_add_exclusive_to_lists.rb
Normal file
7
db/migrate/20230605085710_add_exclusive_to_lists.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddExclusiveToLists < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :lists, :exclusive, :boolean, null: false, default: false
|
||||
end
|
||||
end
|
|
@ -12,7 +12,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2023_05_31_154811) do
|
||||
ActiveRecord::Schema.define(version: 2023_06_05_085710) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -672,6 +672,7 @@ ActiveRecord::Schema.define(version: 2023_05_31_154811) do
|
|||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "replies_policy", default: 0, null: false
|
||||
t.boolean "exclusive", default: false
|
||||
t.index ["account_id"], name: "index_lists_on_account_id"
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::InstanceAccountsDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::InstanceLanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
18
spec/lib/admin/metrics/dimension/languages_dimension_spec.rb
Normal file
18
spec/lib/admin/metrics/dimension/languages_dimension_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::LanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
18
spec/lib/admin/metrics/dimension/servers_dimension_spec.rb
Normal file
18
spec/lib/admin/metrics/dimension/servers_dimension_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::ServersDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SoftwareVersionsDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
18
spec/lib/admin/metrics/dimension/sources_dimension_spec.rb
Normal file
18
spec/lib/admin/metrics/dimension/sources_dimension_spec.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SourcesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::SpaceUsageDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::TagLanguagesDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Dimension::TagServersDimension do
|
||||
subject(:dimension) { described_class.new(start_at, end_at, limit, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:limit) { 10 }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { dimension.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
17
spec/lib/admin/metrics/measure/active_users_measure_spec.rb
Normal file
17
spec/lib/admin/metrics/measure/active_users_measure_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::ActiveUsersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,4 +37,10 @@ describe Admin::Metrics::Measure::InstanceAccountsMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,4 +39,10 @@ describe Admin::Metrics::Measure::InstanceFollowersMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,4 +39,10 @@ describe Admin::Metrics::Measure::InstanceFollowsMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,4 +40,10 @@ describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,4 +36,10 @@ describe Admin::Metrics::Measure::InstanceReportsMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,4 +36,10 @@ describe Admin::Metrics::Measure::InstanceStatusesMeasure do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
17
spec/lib/admin/metrics/measure/interactions_measure_spec.rb
Normal file
17
spec/lib/admin/metrics/measure/interactions_measure_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::InteractionsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
17
spec/lib/admin/metrics/measure/new_users_measure_spec.rb
Normal file
17
spec/lib/admin/metrics/measure/new_users_measure_spec.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::NewUsersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::OpenedReportsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::ResolvedReportsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
19
spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb
Normal file
19
spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagAccountsMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
19
spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb
Normal file
19
spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagServersMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
19
spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb
Normal file
19
spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Admin::Metrics::Measure::TagUsesMeasure do
|
||||
subject(:measure) { described_class.new(start_at, end_at, params) }
|
||||
|
||||
let!(:tag) { Fabricate(:tag) }
|
||||
|
||||
let(:start_at) { 2.days.ago }
|
||||
let(:end_at) { Time.now.utc }
|
||||
let(:params) { ActionController::Parameters.new(id: tag.id) }
|
||||
|
||||
describe '#data' do
|
||||
it 'runs data query without error' do
|
||||
expect { measure.data }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,6 +26,7 @@ RSpec.describe FeedManager do
|
|||
let(:alice) { Fabricate(:account, username: 'alice') }
|
||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') }
|
||||
let(:jeff) { Fabricate(:account, username: 'jeff') }
|
||||
let(:list) { Fabricate(:list, account: alice) }
|
||||
|
||||
context 'with home feed' do
|
||||
it 'returns false for followee\'s status' do
|
||||
|
@ -153,6 +154,42 @@ RSpec.describe FeedManager do
|
|||
status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de')
|
||||
expect(FeedManager.instance.filter?(:home, status, alice)).to be false
|
||||
end
|
||||
|
||||
it 'returns true for post from followee on exclusive list' do
|
||||
list.exclusive = true
|
||||
alice.follow!(bob)
|
||||
list.accounts << bob
|
||||
allow(List).to receive(:where).and_return(list)
|
||||
status = Fabricate(:status, text: 'I post a lot', account: bob)
|
||||
expect(FeedManager.instance.filter?(:home, status, alice)).to be true
|
||||
end
|
||||
|
||||
it 'returns true for reblog from followee on exclusive list' do
|
||||
list.exclusive = true
|
||||
alice.follow!(jeff)
|
||||
list.accounts << jeff
|
||||
allow(List).to receive(:where).and_return(list)
|
||||
status = Fabricate(:status, text: 'I post a lot', account: bob)
|
||||
reblog = Fabricate(:status, reblog: status, account: jeff)
|
||||
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true
|
||||
end
|
||||
|
||||
it 'returns false for post from followee on non-exclusive list' do
|
||||
list.exclusive = false
|
||||
alice.follow!(bob)
|
||||
list.accounts << bob
|
||||
status = Fabricate(:status, text: 'I post a lot', account: bob)
|
||||
expect(FeedManager.instance.filter?(:home, status, alice)).to be false
|
||||
end
|
||||
|
||||
it 'returns false for reblog from followee on non-exclusive list' do
|
||||
list.exclusive = false
|
||||
alice.follow!(jeff)
|
||||
list.accounts << jeff
|
||||
status = Fabricate(:status, text: 'I post a lot', account: bob)
|
||||
reblog = Fabricate(:status, reblog: status, account: jeff)
|
||||
expect(FeedManager.instance.filter?(:home, reblog, alice)).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with mentions feed' do
|
||||
|
|
|
@ -998,4 +998,254 @@ describe Mastodon::CLI::Accounts do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#merge' do
|
||||
shared_examples 'an account not found' do |acct|
|
||||
it 'exits with an error message indicating that there is no such account' do
|
||||
expect { cli.invoke(:merge, arguments) }.to output(
|
||||
a_string_including("No such account (#{acct})")
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when "from_account" is not found' do
|
||||
let(:to_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:arguments) { ['non_existent_username@domain.com', "#{to_account.username}@#{to_account.domain}"] }
|
||||
|
||||
it_behaves_like 'an account not found', 'non_existent_username@domain.com'
|
||||
end
|
||||
|
||||
context 'when "from_account" is a local account' do
|
||||
let(:from_account) { Fabricate(:account, domain: nil, username: 'bob') }
|
||||
let(:to_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:arguments) { [from_account.username, "#{to_account.username}@#{to_account.domain}"] }
|
||||
|
||||
it_behaves_like 'an account not found', 'bob'
|
||||
end
|
||||
|
||||
context 'when "to_account" is not found' do
|
||||
let(:from_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:arguments) { ["#{from_account.username}@#{from_account.domain}", 'non_existent_username'] }
|
||||
|
||||
it_behaves_like 'an account not found', 'non_existent_username'
|
||||
end
|
||||
|
||||
context 'when "to_account" is local' do
|
||||
let(:from_account) { Fabricate(:account, domain: 'example.com') }
|
||||
let(:to_account) { Fabricate(:account, domain: nil, username: 'bob') }
|
||||
let(:arguments) do
|
||||
["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"]
|
||||
end
|
||||
|
||||
it_behaves_like 'an account not found', 'bob@'
|
||||
end
|
||||
|
||||
context 'when "from_account" and "to_account" public keys do not match' do
|
||||
let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'from_account') }
|
||||
let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'to_account') }
|
||||
let(:arguments) do
|
||||
["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"]
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account)
|
||||
allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account)
|
||||
end
|
||||
|
||||
it 'exits with an error message indicating that the accounts do not have the same pub key' do
|
||||
expect { cli.invoke(:merge, arguments) }.to output(
|
||||
a_string_including("Accounts don't have the same public key, might not be duplicates!\nOverride with --force")
|
||||
).to_stdout
|
||||
.and raise_error(SystemExit)
|
||||
end
|
||||
|
||||
context 'with --force option' do
|
||||
let(:options) { { force: true } }
|
||||
|
||||
before do
|
||||
allow(to_account).to receive(:merge_with!)
|
||||
allow(from_account).to receive(:destroy)
|
||||
end
|
||||
|
||||
it 'merges "from_account" into "to_account"' do
|
||||
cli.invoke(:merge, arguments, options)
|
||||
|
||||
expect(to_account).to have_received(:merge_with!).with(from_account).once
|
||||
end
|
||||
|
||||
it 'deletes "from_account"' do
|
||||
cli.invoke(:merge, arguments, options)
|
||||
|
||||
expect(from_account).to have_received(:destroy).once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when "from_account" and "to_account" public keys match' do
|
||||
let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'pub_key') }
|
||||
let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'pub_key') }
|
||||
let(:arguments) do
|
||||
["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"]
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account)
|
||||
allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account)
|
||||
allow(to_account).to receive(:merge_with!)
|
||||
allow(from_account).to receive(:destroy)
|
||||
end
|
||||
|
||||
it 'merges "from_account" into "to_account"' do
|
||||
cli.invoke(:merge, arguments)
|
||||
|
||||
expect(to_account).to have_received(:merge_with!).with(from_account).once
|
||||
end
|
||||
|
||||
it 'deletes "from_account"' do
|
||||
cli.invoke(:merge, arguments)
|
||||
|
||||
expect(from_account).to have_received(:destroy)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cull' do
|
||||
let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) }
|
||||
let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com') }
|
||||
let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org') }
|
||||
let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net') }
|
||||
let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com') }
|
||||
let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net') }
|
||||
|
||||
before do
|
||||
allow(DeleteAccountService).to receive(:new).and_return(delete_account_service)
|
||||
end
|
||||
|
||||
context 'when no domain is specified' do
|
||||
let(:scope) { Account.remote.where(protocol: :activitypub).partitioned }
|
||||
|
||||
before do
|
||||
allow(cli).to receive(:parallelize_with_progress).and_yield(tom)
|
||||
.and_yield(bob)
|
||||
.and_yield(gon)
|
||||
.and_yield(ana)
|
||||
.and_yield(tales)
|
||||
.and_return([5, 3])
|
||||
stub_request(:head, 'https://example.org/users/bob').to_return(status: 404)
|
||||
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
|
||||
stub_request(:head, 'https://example.net/users/tales').to_return(status: 200)
|
||||
end
|
||||
|
||||
it 'deletes all inactive remote accounts that longer exist in the origin server' do
|
||||
cli.cull
|
||||
|
||||
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
|
||||
expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once
|
||||
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
|
||||
end
|
||||
|
||||
it 'does not delete any active remote account that still exists in the origin server' do
|
||||
cli.cull
|
||||
|
||||
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
|
||||
expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false)
|
||||
expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false)
|
||||
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
|
||||
end
|
||||
|
||||
it 'touches inactive remote accounts that have not been deleted' do
|
||||
allow(tales).to receive(:touch)
|
||||
|
||||
cli.cull
|
||||
|
||||
expect(tales).to have_received(:touch).once
|
||||
end
|
||||
|
||||
it 'displays the summary correctly' do
|
||||
expect { cli.cull }.to output(
|
||||
a_string_including('Visited 5 accounts, removed 3')
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a domain is specified' do
|
||||
let(:domain) { 'example.net' }
|
||||
let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned }
|
||||
|
||||
before do
|
||||
allow(cli).to receive(:parallelize_with_progress).and_yield(gon)
|
||||
.and_yield(tales)
|
||||
.and_return([2, 2])
|
||||
stub_request(:head, 'https://example.net/users/gon').to_return(status: 410)
|
||||
stub_request(:head, 'https://example.net/users/tales').to_return(status: 404)
|
||||
end
|
||||
|
||||
it 'deletes inactive remote accounts that longer exist in the specified domain' do
|
||||
cli.cull(domain)
|
||||
|
||||
expect(cli).to have_received(:parallelize_with_progress).with(scope).once
|
||||
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
|
||||
expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once
|
||||
end
|
||||
|
||||
it 'displays the summary correctly' do
|
||||
expect { cli.cull }.to output(
|
||||
a_string_including('Visited 2 accounts, removed 2')
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a domain is unavailable' do
|
||||
shared_examples 'an unavailable domain' do
|
||||
before do
|
||||
allow(cli).to receive(:parallelize_with_progress).and_yield(tales).and_return([1, 0])
|
||||
end
|
||||
|
||||
it 'skips accounts from the unavailable domain' do
|
||||
cli.cull
|
||||
|
||||
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
|
||||
end
|
||||
|
||||
it 'displays the summary correctly' do
|
||||
expect { cli.cull }.to output(
|
||||
a_string_including("Visited 1 accounts, removed 0\nThe following domains were not available during the check:\n example.net")
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a connection timeout occurs' do
|
||||
before do
|
||||
stub_request(:head, 'https://example.net/users/tales').to_timeout
|
||||
end
|
||||
|
||||
it_behaves_like 'an unavailable domain'
|
||||
end
|
||||
|
||||
context 'when a connection error occurs' do
|
||||
before do
|
||||
stub_request(:head, 'https://example.net/users/tales').to_raise(HTTP::ConnectionError)
|
||||
end
|
||||
|
||||
it_behaves_like 'an unavailable domain'
|
||||
end
|
||||
|
||||
context 'when an ssl error occurs' do
|
||||
before do
|
||||
stub_request(:head, 'https://example.net/users/tales').to_raise(OpenSSL::SSL::SSLError)
|
||||
end
|
||||
|
||||
it_behaves_like 'an unavailable domain'
|
||||
end
|
||||
|
||||
context 'when a private network address error occurs' do
|
||||
before do
|
||||
stub_request(:head, 'https://example.net/users/tales').to_raise(Mastodon::PrivateNetworkAddressError)
|
||||
end
|
||||
|
||||
it_behaves_like 'an unavailable domain'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,57 @@ require 'rails_helper'
|
|||
require 'mastodon/cli/canonical_email_blocks'
|
||||
|
||||
describe Mastodon::CLI::CanonicalEmailBlocks do
|
||||
let(:cli) { described_class.new }
|
||||
|
||||
describe '.exit_on_failure?' do
|
||||
it 'returns true' do
|
||||
expect(described_class.exit_on_failure?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find' do
|
||||
let(:arguments) { ['user@example.com'] }
|
||||
|
||||
context 'when a block is present' do
|
||||
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
|
||||
|
||||
it 'announces the presence of the block' do
|
||||
expect { cli.invoke(:find, arguments) }.to output(
|
||||
a_string_including('user@example.com is blocked')
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a block is not present' do
|
||||
it 'announces the absence of the block' do
|
||||
expect { cli.invoke(:find, arguments) }.to output(
|
||||
a_string_including('user@example.com is not blocked')
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#remove' do
|
||||
let(:arguments) { ['user@example.com'] }
|
||||
|
||||
context 'when a block is present' do
|
||||
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
|
||||
|
||||
it 'removes the block' do
|
||||
expect { cli.invoke(:remove, arguments) }.to output(
|
||||
a_string_including('Unblocked user@example.com')
|
||||
).to_stdout
|
||||
|
||||
expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a block is not present' do
|
||||
it 'announces the absence of the block' do
|
||||
expect { cli.invoke(:remove, arguments) }.to output(
|
||||
a_string_including('user@example.com is not blocked')
|
||||
).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue