mirror of
https://github.com/mastodon/mastodon.git
synced 2025-12-16 04:12:55 +00:00
Compare commits
4 Commits
022ca24f2c
...
c836c4829c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c836c4829c | ||
|
|
e0912c1729 | ||
|
|
945ef5a8e1 | ||
|
|
27e1270ed6 |
|
|
@ -1,11 +1,11 @@
|
|||
import punycode from 'node:punycode';
|
||||
|
||||
import { useCallback, useId, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import punycode from 'punycode/';
|
||||
|
||||
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
|
||||
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
|
||||
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ class AnnualReport
|
|||
AnnualReport::TypeDistribution,
|
||||
AnnualReport::TopStatuses,
|
||||
AnnualReport::MostUsedApps,
|
||||
AnnualReport::CommonlyInteractedWithAccounts,
|
||||
AnnualReport::TimeSeries,
|
||||
AnnualReport::TopHashtags,
|
||||
AnnualReport::MostRebloggedAccounts,
|
||||
AnnualReport::Percentiles,
|
||||
].freeze
|
||||
|
||||
SCHEMA = 1
|
||||
SCHEMA = 2
|
||||
|
||||
def self.table_name_prefix
|
||||
'annual_report_'
|
||||
|
|
@ -26,12 +23,6 @@ class AnnualReport
|
|||
@year = year
|
||||
end
|
||||
|
||||
def self.prepare(year)
|
||||
SOURCES.each do |klass|
|
||||
klass.prepare(year)
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
return if GeneratedAnnualReport.exists?(account: @account, year: @year)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source
|
||||
MINIMUM_INTERACTIONS = 1
|
||||
SET_SIZE = 40
|
||||
|
||||
def generate
|
||||
{
|
||||
commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)|
|
||||
{
|
||||
account_id: account_id.to_s,
|
||||
count: count,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commonly_interacted_with_accounts
|
||||
report_statuses.not_replying_to_account(@account).group(:in_reply_to_account_id).having(minimum_interaction_count).order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
|
||||
def minimum_interaction_count
|
||||
Arel.star.count.gt(MINIMUM_INTERACTIONS)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AnnualReport::MostRebloggedAccounts < AnnualReport::Source
|
||||
MINIMUM_REBLOGS = 1
|
||||
SET_SIZE = 10
|
||||
|
||||
def generate
|
||||
{
|
||||
most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)|
|
||||
{
|
||||
account_id: account_id.to_s,
|
||||
count: count,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def most_reblogged_accounts
|
||||
report_statuses.only_reblogs.joins(reblog: :account).group(accounts: [:id]).having(minimum_reblog_count).order(count_all: :desc).limit(SET_SIZE).count
|
||||
end
|
||||
|
||||
def minimum_reblog_count
|
||||
Arel.star.count.gt(MINIMUM_REBLOGS)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AnnualReport::Percentiles < AnnualReport::Source
|
||||
def self.prepare(year)
|
||||
AnnualReport::StatusesPerAccountCount.connection.exec_query(<<~SQL.squish, nil, [year, Mastodon::Snowflake.id_at(DateTime.new(year).beginning_of_year), Mastodon::Snowflake.id_at(DateTime.new(year).end_of_year)])
|
||||
INSERT INTO annual_report_statuses_per_account_counts (year, account_id, statuses_count)
|
||||
SELECT $1, account_id, count(*)
|
||||
FROM statuses
|
||||
WHERE id BETWEEN $2 AND $3
|
||||
AND (local OR uri IS NULL)
|
||||
GROUP BY account_id
|
||||
ON CONFLICT (year, account_id) DO NOTHING
|
||||
SQL
|
||||
end
|
||||
|
||||
def generate
|
||||
{
|
||||
percentiles: {
|
||||
statuses: 100.0 - ((total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100),
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def statuses_created
|
||||
@statuses_created ||= report_statuses.count
|
||||
end
|
||||
|
||||
def total_with_fewer_statuses
|
||||
@total_with_fewer_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).where(statuses_count: ...statuses_created).count
|
||||
end
|
||||
|
||||
def total_with_any_statuses
|
||||
@total_with_any_statuses ||= AnnualReport::StatusesPerAccountCount.where(year: year).count
|
||||
end
|
||||
end
|
||||
|
|
@ -8,10 +8,6 @@ class AnnualReport::Source
|
|||
@year = year
|
||||
end
|
||||
|
||||
def self.prepare(_year)
|
||||
# Use this method if any pre-calculations must be made before individual annual reports are generated
|
||||
end
|
||||
|
||||
def generate
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
class AnnualReport::TopHashtags < AnnualReport::Source
|
||||
MINIMUM_TAGGINGS = 1
|
||||
SET_SIZE = 40
|
||||
SET_SIZE = 5
|
||||
|
||||
def generate
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::CommonlyInteractedWithAccounts do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
commonly_interacted_with_accounts: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:other_account) { Fabricate :account }
|
||||
let(:most_other_account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: other_account).id
|
||||
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
|
||||
Fabricate :status, account: account, reply: true, in_reply_to_id: Fabricate(:status, account: most_other_account).id
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
commonly_interacted_with_accounts: eq(
|
||||
[
|
||||
{ account_id: most_other_account.id.to_s, count: 3 },
|
||||
{ account_id: other_account.id.to_s, count: 2 },
|
||||
]
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::MostRebloggedAccounts do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, Time.zone.now.year) }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_reblogged_accounts: be_an(Array).and(be_empty)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
let(:other_account) { Fabricate :account }
|
||||
let(:most_other_account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
_other = Fabricate :status
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: other_account)
|
||||
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
|
||||
Fabricate :status, account: account, reblog: Fabricate(:status, account: most_other_account)
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
most_reblogged_accounts: eq(
|
||||
[
|
||||
{ account_id: most_other_account.id.to_s, count: 3 },
|
||||
{ account_id: other_account.id.to_s, count: 2 },
|
||||
]
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnualReport::Percentiles do
|
||||
describe '#generate' do
|
||||
subject { described_class.new(account, year) }
|
||||
|
||||
let(:year) { Time.zone.now.year }
|
||||
|
||||
context 'with an inactive account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
it 'builds a report for an account' do
|
||||
described_class.prepare(year)
|
||||
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
statuses: 100
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an active account' do
|
||||
let(:account) { Fabricate :account }
|
||||
|
||||
before do
|
||||
Fabricate.times 2, :status # Others as `account`
|
||||
Fabricate.times 2, :status, account: account
|
||||
end
|
||||
|
||||
it 'builds a report for an account' do
|
||||
described_class.prepare(year)
|
||||
|
||||
expect(subject.generate)
|
||||
.to include(
|
||||
percentiles: include(
|
||||
statuses: 50
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -13,13 +13,4 @@ RSpec.describe AnnualReport do
|
|||
.to change(GeneratedAnnualReport, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.prepare' do
|
||||
before { Fabricate :status }
|
||||
|
||||
it 'generates records from source class which prepare data' do
|
||||
expect { described_class.prepare(Time.current.year) }
|
||||
.to change(AnnualReport::StatusesPerAccountCount, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"dotenv": "^17.0.0",
|
||||
"express": "^5.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"jsdom": "^27.0.0",
|
||||
|
|
|
|||
11
yarn.lock
11
yarn.lock
|
|
@ -2868,7 +2868,7 @@ __metadata:
|
|||
"@types/ws": "npm:^8.5.9"
|
||||
bufferutil: "npm:^4.0.7"
|
||||
cors: "npm:^2.8.5"
|
||||
dotenv: "npm:^16.0.3"
|
||||
dotenv: "npm:^17.0.0"
|
||||
express: "npm:^5.1.0"
|
||||
globals: "npm:^16.0.0"
|
||||
ioredis: "npm:^5.3.2"
|
||||
|
|
@ -6485,13 +6485,20 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv@npm:^16.0.3, dotenv@npm:^16.4.2":
|
||||
"dotenv@npm:^16.4.2":
|
||||
version: 16.6.1
|
||||
resolution: "dotenv@npm:16.6.1"
|
||||
checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dotenv@npm:^17.0.0":
|
||||
version: 17.2.3
|
||||
resolution: "dotenv@npm:17.2.3"
|
||||
checksum: 10c0/c884403209f713214a1b64d4d1defa4934c2aa5b0002f5a670ae298a51e3c3ad3ba79dfee2f8df49f01ae74290fcd9acdb1ab1d09c7bfb42b539036108bb2ba0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "dunder-proto@npm:1.0.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user