feat: リモートサーバーのサーバー情報を収集しないオプション (#14634)

* wip

* wip

* Update FetchInstanceMetadataService.ts

* Update FetchInstanceMetadataService.ts

* Update types.ts
This commit is contained in:
syuilo 2024-10-13 20:22:16 +09:00 committed by GitHub
parent 45d42b8641
commit ff47fef572
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 185 additions and 86 deletions

4
locales/index.d.ts vendored
View File

@ -4366,6 +4366,10 @@ export interface Locale extends ILocale {
* *
*/ */
"enableChartsForFederatedInstances": string; "enableChartsForFederatedInstances": string;
/**
*
*/
"enableStatsForFederatedInstances": string;
/** /**
* *
*/ */

View File

@ -1087,6 +1087,7 @@ retryAllQueuesConfirmTitle: "今すぐ再試行しますか?"
retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。" retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。"
enableChartsForRemoteUser: "リモートユーザーのチャートを生成" enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
enableChartsForFederatedInstances: "リモートサーバーのチャートを生成" enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
enableStatsForFederatedInstances: "リモートサーバーの情報を取得"
showClipButtonInNoteFooter: "ノートのアクションにクリップを追加" showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
reactionsDisplaySize: "リアクションの表示サイズ" reactionsDisplaySize: "リアクションの表示サイズ"
limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する" limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class EnableStatsForFederatedInstances1727318020265 {
name = 'EnableStatsForFederatedInstances1727318020265'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableStatsForFederatedInstances" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableStatsForFederatedInstances"`);
}
}

View File

@ -274,13 +274,15 @@ export class AccountMoveService {
} }
// Update instance stats by decreasing remote followers count by the number of local followers who were following the old account. // Update instance stats by decreasing remote followers count by the number of local followers who were following the old account.
if (this.userEntityService.isRemoteUser(oldAccount)) { if (this.meta.enableStatsForFederatedInstances) {
this.federatedInstanceService.fetch(oldAccount.host).then(async i => { if (this.userEntityService.isRemoteUser(oldAccount)) {
this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length); this.federatedInstanceService.fetchOrRegister(oldAccount.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
this.instanceChart.updateFollowers(i.host, false); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateFollowers(i.host, false);
}); }
});
}
} }
// FIXME: expensive? // FIXME: expensive?

View File

@ -47,7 +47,7 @@ export class FederatedInstanceService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async fetch(host: string): Promise<MiInstance> { public async fetchOrRegister(host: string): Promise<MiInstance> {
host = this.utilityService.toPuny(host); host = this.utilityService.toPuny(host);
const cached = await this.federatedInstanceCache.get(host); const cached = await this.federatedInstanceCache.get(host);
@ -70,6 +70,24 @@ export class FederatedInstanceService implements OnApplicationShutdown {
} }
} }
@bindThis
public async fetch(host: string): Promise<MiInstance | null> {
host = this.utilityService.toPuny(host);
const cached = await this.federatedInstanceCache.get(host);
if (cached !== undefined) return cached;
const index = await this.instancesRepository.findOneBy({ host });
if (index == null) {
this.federatedInstanceCache.set(host, null);
return null;
} else {
this.federatedInstanceCache.set(host, index);
return index;
}
}
@bindThis @bindThis
public async update(id: MiInstance['id'], data: Partial<MiInstance>): Promise<void> { public async update(id: MiInstance['id'], data: Partial<MiInstance>): Promise<void> {
const result = await this.instancesRepository.createQueryBuilder().update() const result = await this.instancesRepository.createQueryBuilder().update()

View File

@ -82,7 +82,7 @@ export class FetchInstanceMetadataService {
try { try {
if (!force) { if (!force) {
const _instance = await this.federatedInstanceService.fetch(host); const _instance = await this.federatedInstanceService.fetchOrRegister(host);
const now = Date.now(); const now = Date.now();
if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) { if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) {
// unlock at the finally caluse // unlock at the finally caluse

View File

@ -511,13 +511,15 @@ export class NoteCreateService implements OnApplicationShutdown {
} }
// Register host // Register host
if (this.userEntityService.isRemoteUser(user)) { if (this.meta.enableStatsForFederatedInstances) {
this.federatedInstanceService.fetch(user.host).then(async i => { if (this.userEntityService.isRemoteUser(user)) {
this.updateNotesCountQueue.enqueue(i.id, 1); this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.updateNotesCountQueue.enqueue(i.id, 1);
this.instanceChart.updateNote(i.host, note, true); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateNote(i.host, note, true);
}); }
});
}
} }
// ハッシュタグ更新 // ハッシュタグ更新

View File

@ -106,13 +106,15 @@ export class NoteDeleteService {
this.perUserNotesChart.update(user, note, false); this.perUserNotesChart.update(user, note, false);
} }
if (this.userEntityService.isRemoteUser(user)) { if (this.meta.enableStatsForFederatedInstances) {
this.federatedInstanceService.fetch(user.host).then(async i => { if (this.userEntityService.isRemoteUser(user)) {
this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
this.instanceChart.updateNote(i.host, note, false); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateNote(i.host, note, false);
}); }
});
}
} }
} }

View File

@ -305,20 +305,22 @@ export class UserFollowingService implements OnModuleInit {
//#endregion //#endregion
//#region Update instance stats //#region Update instance stats
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.meta.enableStatsForFederatedInstances) {
this.federatedInstanceService.fetch(follower.host).then(async i => { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.instancesRepository.increment({ id: i.id }, 'followingCount', 1); this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
this.instanceChart.updateFollowing(i.host, true); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateFollowing(i.host, true);
}); }
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { });
this.federatedInstanceService.fetch(followee.host).then(async i => { } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
this.instancesRepository.increment({ id: i.id }, 'followersCount', 1); this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
this.instanceChart.updateFollowers(i.host, true); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateFollowers(i.host, true);
}); }
});
}
} }
//#endregion //#endregion
@ -437,20 +439,22 @@ export class UserFollowingService implements OnModuleInit {
//#endregion //#endregion
//#region Update instance stats //#region Update instance stats
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.meta.enableStatsForFederatedInstances) {
this.federatedInstanceService.fetch(follower.host).then(async i => { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1); this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
this.instanceChart.updateFollowing(i.host, false); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateFollowing(i.host, false);
}); }
} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { });
this.federatedInstanceService.fetch(followee.host).then(async i => { } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1); this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => {
if (this.meta.enableChartsForFederatedInstances) { this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
this.instanceChart.updateFollowers(i.host, false); if (this.meta.enableChartsForFederatedInstances) {
} this.instanceChart.updateFollowers(i.host, false);
}); }
});
}
} }
//#endregion //#endregion

View File

@ -408,13 +408,15 @@ export class ApPersonService implements OnModuleInit {
this.cacheService.uriPersonCache.set(user.uri, user); this.cacheService.uriPersonCache.set(user.uri, user);
// Register host // Register host
this.federatedInstanceService.fetch(host).then(i => { if (this.meta.enableStatsForFederatedInstances) {
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); this.federatedInstanceService.fetchOrRegister(host).then(i => {
this.fetchInstanceMetadataService.fetchInstanceMetadata(i); this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
if (this.meta.enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.newUser(i.host); this.instanceChart.newUser(i.host);
} }
}); this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
});
}
this.usersChart.update(user, true); this.usersChart.update(user, true);

View File

@ -529,6 +529,11 @@ export class MiMeta {
}) })
public enableChartsForFederatedInstances: boolean; public enableChartsForFederatedInstances: boolean;
@Column('boolean', {
default: true,
})
public enableStatsForFederatedInstances: boolean;
@Column('boolean', { @Column('boolean', {
default: false, default: false,
}) })

View File

@ -74,8 +74,17 @@ export class DeliverProcessorService {
try { try {
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest); await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
// Update stats this.apRequestChart.deliverSucc();
this.federatedInstanceService.fetch(host).then(i => { this.federationChart.deliverd(host, true);
// Update instance stats
process.nextTick(async () => {
const i = await (this.meta.enableStatsForFederatedInstances
? this.federatedInstanceService.fetchOrRegister(host)
: this.federatedInstanceService.fetch(host));
if (i == null) return;
if (i.isNotResponding) { if (i.isNotResponding) {
this.federatedInstanceService.update(i.id, { this.federatedInstanceService.update(i.id, {
isNotResponding: false, isNotResponding: false,
@ -83,9 +92,9 @@ export class DeliverProcessorService {
}); });
} }
this.fetchInstanceMetadataService.fetchInstanceMetadata(i); if (this.meta.enableStatsForFederatedInstances) {
this.apRequestChart.deliverSucc(); this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
this.federationChart.deliverd(i.host, true); }
if (this.meta.enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.requestSent(i.host, true); this.instanceChart.requestSent(i.host, true);
@ -94,8 +103,11 @@ export class DeliverProcessorService {
return 'Success'; return 'Success';
} catch (res) { } catch (res) {
// Update stats this.apRequestChart.deliverFail();
this.federatedInstanceService.fetch(host).then(i => { this.federationChart.deliverd(host, false);
// Update instance stats
this.federatedInstanceService.fetchOrRegister(host).then(i => {
if (!i.isNotResponding) { if (!i.isNotResponding) {
this.federatedInstanceService.update(i.id, { this.federatedInstanceService.update(i.id, {
isNotResponding: true, isNotResponding: true,
@ -116,9 +128,6 @@ export class DeliverProcessorService {
}); });
} }
this.apRequestChart.deliverFail();
this.federationChart.deliverd(i.host, false);
if (this.meta.enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.requestSent(i.host, false); this.instanceChart.requestSent(i.host, false);
} }
@ -129,7 +138,7 @@ export class DeliverProcessorService {
if (!res.isRetryable) { if (!res.isRetryable) {
// 相手が閉鎖していることを明示しているため、配送停止する // 相手が閉鎖していることを明示しているため、配送停止する
if (job.data.isSharedInbox && res.statusCode === 410) { if (job.data.isSharedInbox && res.statusCode === 410) {
this.federatedInstanceService.fetch(host).then(i => { this.federatedInstanceService.fetchOrRegister(host).then(i => {
this.federatedInstanceService.update(i.id, { this.federatedInstanceService.update(i.id, {
suspensionState: 'goneSuspended', suspensionState: 'goneSuspended',
}); });

View File

@ -192,21 +192,27 @@ export class InboxProcessorService implements OnApplicationShutdown {
} }
} }
// Update stats this.apRequestChart.inbox();
this.federatedInstanceService.fetch(authUser.user.host).then(i => { this.federationChart.inbox(authUser.user.host);
// Update instance stats
process.nextTick(async () => {
const i = await (this.meta.enableStatsForFederatedInstances
? this.federatedInstanceService.fetchOrRegister(authUser.user.host)
: this.federatedInstanceService.fetch(authUser.user.host));
if (i == null) return;
this.updateInstanceQueue.enqueue(i.id, { this.updateInstanceQueue.enqueue(i.id, {
latestRequestReceivedAt: new Date(), latestRequestReceivedAt: new Date(),
shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding',
}); });
this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
this.apRequestChart.inbox();
this.federationChart.inbox(i.host);
if (this.meta.enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.requestReceived(i.host); this.instanceChart.requestReceived(i.host);
} }
this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
}); });
// アクティビティを処理 // アクティビティを処理

View File

@ -348,6 +348,10 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
enableStatsForFederatedInstances: {
type: 'boolean',
optional: false, nullable: false,
},
enableServerMachineStats: { enableServerMachineStats: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
@ -635,6 +639,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
truemailAuthKey: instance.truemailAuthKey, truemailAuthKey: instance.truemailAuthKey,
enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances,
enableServerMachineStats: instance.enableServerMachineStats, enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration, enableIdenticonGeneration: instance.enableIdenticonGeneration,
bannedEmailDomains: instance.bannedEmailDomains, bannedEmailDomains: instance.bannedEmailDomains,

View File

@ -136,6 +136,7 @@ export const paramDef = {
truemailAuthKey: { type: 'string', nullable: true }, truemailAuthKey: { type: 'string', nullable: true },
enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForRemoteUser: { type: 'boolean' },
enableChartsForFederatedInstances: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' },
enableStatsForFederatedInstances: { type: 'boolean' },
enableServerMachineStats: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' },
enableIdenticonGeneration: { type: 'boolean' }, enableIdenticonGeneration: { type: 'boolean' },
serverRules: { type: 'array', items: { type: 'string' } }, serverRules: { type: 'array', items: { type: 'string' } },
@ -578,6 +579,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
} }
if (ps.enableStatsForFederatedInstances !== undefined) {
set.enableStatsForFederatedInstances = ps.enableStatsForFederatedInstances;
}
if (ps.enableServerMachineStats !== undefined) { if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats; set.enableServerMachineStats = ps.enableServerMachineStats;
} }

View File

@ -8,6 +8,7 @@ process.env.NODE_ENV = 'test';
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { Redis } from 'ioredis'; import { Redis } from 'ioredis';
import type { TestingModule } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js'; import { GlobalModule } from '@/GlobalModule.js';
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@ -16,7 +17,6 @@ import { LoggerService } from '@/core/LoggerService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { TestingModule } from '@nestjs/testing';
function mockRedis() { function mockRedis() {
const hash = {} as any; const hash = {} as any;
@ -52,7 +52,7 @@ describe('FetchInstanceMetadataService', () => {
if (token === HttpRequestService) { if (token === HttpRequestService) {
return { getJson: jest.fn(), getHtml: jest.fn(), send: jest.fn() }; return { getJson: jest.fn(), getHtml: jest.fn(), send: jest.fn() };
} else if (token === FederatedInstanceService) { } else if (token === FederatedInstanceService) {
return { fetch: jest.fn() }; return { fetchOrRegister: jest.fn() };
} else if (token === DI.redis) { } else if (token === DI.redis) {
return mockRedis; return mockRedis;
} }
@ -75,7 +75,7 @@ describe('FetchInstanceMetadataService', () => {
test('Lock and update', async () => { test('Lock and update', async () => {
redisClient.set = mockRedis(); redisClient.set = mockRedis();
const now = Date.now(); const now = Date.now();
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any); federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any);
httpRequestService.getJson.mockImplementation(() => { throw Error(); }); httpRequestService.getJson.mockImplementation(() => { throw Error(); });
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
@ -83,14 +83,14 @@ describe('FetchInstanceMetadataService', () => {
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(tryLockSpy).toHaveBeenCalledTimes(1);
expect(unlockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1);
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1);
expect(httpRequestService.getJson).toHaveBeenCalled(); expect(httpRequestService.getJson).toHaveBeenCalled();
}); });
test('Lock and don\'t update', async () => { test('Lock and don\'t update', async () => {
redisClient.set = mockRedis(); redisClient.set = mockRedis();
const now = Date.now(); const now = Date.now();
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any); federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any);
httpRequestService.getJson.mockImplementation(() => { throw Error(); }); httpRequestService.getJson.mockImplementation(() => { throw Error(); });
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
@ -98,14 +98,14 @@ describe('FetchInstanceMetadataService', () => {
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(tryLockSpy).toHaveBeenCalledTimes(1);
expect(unlockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1);
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1);
expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
}); });
test('Do nothing when lock not acquired', async () => { test('Do nothing when lock not acquired', async () => {
redisClient.set = mockRedis(); redisClient.set = mockRedis();
const now = Date.now(); const now = Date.now();
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
httpRequestService.getJson.mockImplementation(() => { throw Error(); }); httpRequestService.getJson.mockImplementation(() => { throw Error(); });
await fetchInstanceMetadataService.tryLock('example.com'); await fetchInstanceMetadataService.tryLock('example.com');
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
@ -114,14 +114,14 @@ describe('FetchInstanceMetadataService', () => {
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(tryLockSpy).toHaveBeenCalledTimes(1);
expect(unlockSpy).toHaveBeenCalledTimes(0); expect(unlockSpy).toHaveBeenCalledTimes(0);
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0);
expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
}); });
test('Do when lock not acquired but forced', async () => { test('Do when lock not acquired but forced', async () => {
redisClient.set = mockRedis(); redisClient.set = mockRedis();
const now = Date.now(); const now = Date.now();
federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
httpRequestService.getJson.mockImplementation(() => { throw Error(); }); httpRequestService.getJson.mockImplementation(() => { throw Error(); });
await fetchInstanceMetadataService.tryLock('example.com'); await fetchInstanceMetadataService.tryLock('example.com');
const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
@ -130,7 +130,7 @@ describe('FetchInstanceMetadataService', () => {
await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true); await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true);
expect(tryLockSpy).toHaveBeenCalledTimes(0); expect(tryLockSpy).toHaveBeenCalledTimes(0);
expect(unlockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1);
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0);
expect(httpRequestService.getJson).toHaveBeenCalled(); expect(httpRequestService.getJson).toHaveBeenCalled();
}); });
}); });

View File

@ -29,6 +29,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</div> </div>
<div class="_panel" style="padding: 16px;">
<MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
<template #label>{{ i18n.ts.enableStatsForFederatedInstances }}</template>
<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
</MkSwitch>
</div>
<div class="_panel" style="padding: 16px;"> <div class="_panel" style="padding: 16px;">
<MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances"> <MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
@ -120,6 +127,7 @@ const meta = await misskeyApi('admin/meta');
const enableServerMachineStats = ref(meta.enableServerMachineStats); const enableServerMachineStats = ref(meta.enableServerMachineStats);
const enableIdenticonGeneration = ref(meta.enableIdenticonGeneration); const enableIdenticonGeneration = ref(meta.enableIdenticonGeneration);
const enableChartsForRemoteUser = ref(meta.enableChartsForRemoteUser); const enableChartsForRemoteUser = ref(meta.enableChartsForRemoteUser);
const enableStatsForFederatedInstances = ref(meta.enableStatsForFederatedInstances);
const enableChartsForFederatedInstances = ref(meta.enableChartsForFederatedInstances); const enableChartsForFederatedInstances = ref(meta.enableChartsForFederatedInstances);
function onChange_enableServerMachineStats(value: boolean) { function onChange_enableServerMachineStats(value: boolean) {
@ -146,6 +154,14 @@ function onChange_enableChartsForRemoteUser(value: boolean) {
}); });
} }
function onChange_enableStatsForFederatedInstances(value: boolean) {
os.apiWithDialog('admin/update-meta', {
enableStatsForFederatedInstances: value,
}).then(() => {
fetchInstance(true);
});
}
function onChange_enableChartsForFederatedInstances(value: boolean) { function onChange_enableChartsForFederatedInstances(value: boolean) {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
enableChartsForFederatedInstances: value, enableChartsForFederatedInstances: value,

View File

@ -5165,6 +5165,7 @@ export type operations = {
truemailAuthKey: string | null; truemailAuthKey: string | null;
enableChartsForRemoteUser: boolean; enableChartsForRemoteUser: boolean;
enableChartsForFederatedInstances: boolean; enableChartsForFederatedInstances: boolean;
enableStatsForFederatedInstances: boolean;
enableServerMachineStats: boolean; enableServerMachineStats: boolean;
enableIdenticonGeneration: boolean; enableIdenticonGeneration: boolean;
manifestJsonOverride: string; manifestJsonOverride: string;
@ -9547,6 +9548,7 @@ export type operations = {
truemailAuthKey?: string | null; truemailAuthKey?: string | null;
enableChartsForRemoteUser?: boolean; enableChartsForRemoteUser?: boolean;
enableChartsForFederatedInstances?: boolean; enableChartsForFederatedInstances?: boolean;
enableStatsForFederatedInstances?: boolean;
enableServerMachineStats?: boolean; enableServerMachineStats?: boolean;
enableIdenticonGeneration?: boolean; enableIdenticonGeneration?: boolean;
serverRules?: string[]; serverRules?: string[];