feat: お知らせを特定日時で自動アーカイブできるようにする#17704
Conversation
|
このPRによるapi.jsonの差分 差分はこちら--- base
+++ head
@@ -2481,6 +2481,13 @@
],
"format": "misskey:id",
"default": null
+ },
+ "autoArchiveAt": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "default": null
}
},
"required": [
@@ -2549,6 +2556,15 @@
"$ref": "#/components/schemas/Error"
},
"examples": {
+ "INVALID_AUTO_ARCHIVE_AT": {
+ "value": {
+ "error": {
+ "message": "Invalid auto archive date.",
+ "code": "INVALID_AUTO_ARCHIVE_AT",
+ "id": "2a892bd5-487d-46a2-a5fe-3d85ad51defe"
+ }
+ }
+ },
"INVALID_PARAM": {
"value": {
"error": {
@@ -2923,6 +2939,13 @@
"isActive": {
"type": "boolean"
},
+ "autoArchiveAt": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
"forExistingUsers": {
"type": "boolean"
},
@@ -2957,6 +2980,7 @@
"icon",
"display",
"isActive",
+ "autoArchiveAt",
"forExistingUsers",
"silence",
"needConfirmationToRead",
@@ -3148,6 +3172,12 @@
},
"isActive": {
"type": "boolean"
+ },
+ "autoArchiveAt": {
+ "type": [
+ "integer",
+ "null"
+ ]
}
},
"required": [
@@ -3178,6 +3208,15 @@
}
}
},
+ "INVALID_AUTO_ARCHIVE_AT": {
+ "value": {
+ "error": {
+ "message": "Invalid auto archive date.",
+ "code": "INVALID_AUTO_ARCHIVE_AT",
+ "id": "01b83d7b-2fd5-4d7c-86c4-d03144d16355"
+ }
+ }
+ },
"INVALID_PARAM": {
"value": {
"error": { |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #17704 +/- ##
===========================================
+ Coverage 25.90% 26.26% +0.36%
===========================================
Files 1178 1181 +3
Lines 39984 40275 +291
Branches 11071 11133 +62
===========================================
+ Hits 10358 10580 +222
- Misses 23755 23815 +60
- Partials 5871 5880 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
📦 Frontend Bundle ReportChunk size diff (3 updated, 0 added, 0 removed)
Startup chunk size (1 updated, 0 added, 0 removed)
Startup chunks are the Vite entry for Bundle Stats
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⚙️ Backend Memory Usage ReportAfter GC
V8 Heap Snapshot Statistics
Head heap snapshot composition%%{init: {"sankey":{"showValues":false,"linkColor":"target","labelStyle":"outlined","nodeAlignment":"center","nodePadding":10,"nodeColors":{"Head":"#888888","code":"#f28e2c","bytecode":"#f28e2c","Other":"#888888","strings":"#e15759","string":"#e15759","jsArrays":"#76b7b2","typedArrays":"#59a14f","systemObjects":"#edc949","otherJsObjects":"#af7aa1","otherNonJsObjects":"#ff9da7"}}}}%%
sankey-beta
"Head","Code",28.8
"Code","bytecode",9.58
"Code","Other",19.22
"Head","Strings",34.97
"Strings","string",34.23
"Strings","Other",0.74
"Head","JS arrays",4.85
"Head","Typed arrays",0.62
"Head","System objects",2.58
"Head","Other JS objs",28.13
"Head","Other non-JS objs",0.05
Download representative V8 heap snapshot (head) Runtime Loaded JS FootprintClick to show
Largest Newly Loaded Modules
|
| pattern: '*/5 * * * *', | ||
| }, { | ||
| name: 'checkExpiredAnnouncements', | ||
| pattern: '* * * * *', |
There was a problem hiding this comment.
1分ごとに実行って結構高頻度な気がするけどそんなもんかしら
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughお知らせに任意の自動アーカイブ日時を設定できる機能を追加しました。日時に基づく状態判定、キューによる自動アーカイブ、API、管理画面、型定義、ローカライズ、テストが更新されています。 ChangesAnnouncement auto-archive
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant AdminAnnouncementsAPI
participant AnnouncementService
participant QueueService
participant ArchiveAnnouncementProcessorService
Admin->>AdminAnnouncementsAPI: create or update autoArchiveAt
AdminAnnouncementsAPI->>AnnouncementService: save announcement
AnnouncementService->>QueueService: scheduleAnnouncementArchive
QueueService->>ArchiveAnnouncementProcessorService: process archiveAnnouncement job
ArchiveAnnouncementProcessorService->>AnnouncementService: archiveAnnouncement
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/backend/src/server/api/endpoints/admin/announcements/create.ts (1)
26-59: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winResponse schema should include
autoArchiveAt.
paramDefacceptsautoArchiveAt, and the handler returns the packed announcement, butres.propertiesstill omits the field. Add it here soAdminAnnouncementsCreateResponsestays aligned with the API payload.🔧 Suggested fix
imageUrl: { type: 'string', optional: false, nullable: true, }, + autoArchiveAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, }, },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/backend/src/server/api/endpoints/admin/announcements/create.ts` around lines 26 - 59, Update the response schema’s res.properties in the admin announcements create endpoint to include autoArchiveAt, matching the field accepted by paramDef and returned by the packed announcement so AdminAnnouncementsCreateResponse reflects the complete API payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/backend/src/core/AnnouncementService.ts`:
- Around line 35-51: Update onModuleInit so a failure from scheduling one
announcement does not reject the entire initialization flow: isolate each
scheduleAnnouncementArchive call and handle its rejection per announcement,
while allowing the remaining announcements to be scheduled and bootstrap to
complete.
In `@packages/backend/src/core/QueueService.ts`:
- Around line 256-282: Update scheduleAnnouncementArchive to configure retries
in the options passed to systemQueue.add for archiveAnnouncement jobs. Set an
attempts value greater than one, preserving the existing job ID, delay, and
cleanup behavior.
---
Outside diff comments:
In `@packages/backend/src/server/api/endpoints/admin/announcements/create.ts`:
- Around line 26-59: Update the response schema’s res.properties in the admin
announcements create endpoint to include autoArchiveAt, matching the field
accepted by paramDef and returned by the packed announcement so
AdminAnnouncementsCreateResponse reflects the complete API payload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c49b0d7-625d-4288-a1a1-f00885fc2a46
📒 Files selected for processing (20)
CHANGELOG.mdlocales/ja-JP.ymlpackages/backend/migration/1783913268595-AutoArchiveAnnouncements.jspackages/backend/src/core/AnnouncementService.tspackages/backend/src/core/QueueService.tspackages/backend/src/models/Announcement.tspackages/backend/src/queue/QueueProcessorModule.tspackages/backend/src/queue/QueueProcessorService.tspackages/backend/src/queue/processors/ArchiveAnnouncementProcessorService.tspackages/backend/src/queue/types.tspackages/backend/src/server/api/endpoints/admin/announcements/create.tspackages/backend/src/server/api/endpoints/admin/announcements/list.tspackages/backend/src/server/api/endpoints/admin/announcements/update.tspackages/backend/src/server/api/endpoints/announcements.tspackages/backend/test/e2e/announcements.tspackages/backend/test/unit/AnnouncementService.tspackages/frontend/src/components/MkUserAnnouncementEditDialog.vuepackages/frontend/src/pages/admin/announcements.vuepackages/i18n/src/autogen/locale.tspackages/misskey-js/src/autogen/types.ts
| private queueService: QueueService, | ||
| ) { | ||
| } | ||
|
|
||
| @bindThis | ||
| public async onModuleInit(): Promise<void> { | ||
| // アップデート前に作成されたお知らせやRedisの再構築後にも予約を復元する | ||
| const announcements = await this.announcementsRepository.findBy({ | ||
| isActive: true, | ||
| autoArchiveAt: Not(IsNull()), | ||
| }); | ||
|
|
||
| await Promise.all(announcements.map(announcement => | ||
| this.queueService.scheduleAnnouncementArchive(announcement.id, announcement.autoArchiveAt!), | ||
| )); | ||
| } | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Promise.all in onModuleInit can abort bootstrap on a single failure.
If scheduleAnnouncementArchive rejects for any one announcement (e.g. transient Redis issue), Promise.all rejects the whole batch, and since this runs in OnModuleInit, it can fail application bootstrap for an unrelated transient error, risking a crash-loop/outage on deploy or restart.
🔧 Suggested fix: isolate per-item failures
await Promise.all(announcements.map(announcement =>
- this.queueService.scheduleAnnouncementArchive(announcement.id, announcement.autoArchiveAt!),
+ this.queueService.scheduleAnnouncementArchive(announcement.id, announcement.autoArchiveAt!).catch(err => {
+ // ログを出し、他の予約処理をブロックしないようにする
+ console.error(`Failed to reschedule archive for announcement ${announcement.id}`, err);
+ }),
));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private queueService: QueueService, | |
| ) { | |
| } | |
| @bindThis | |
| public async onModuleInit(): Promise<void> { | |
| // アップデート前に作成されたお知らせやRedisの再構築後にも予約を復元する | |
| const announcements = await this.announcementsRepository.findBy({ | |
| isActive: true, | |
| autoArchiveAt: Not(IsNull()), | |
| }); | |
| await Promise.all(announcements.map(announcement => | |
| this.queueService.scheduleAnnouncementArchive(announcement.id, announcement.autoArchiveAt!), | |
| )); | |
| } | |
| private queueService: QueueService, | |
| ) { | |
| } | |
| `@bindThis` | |
| public async onModuleInit(): Promise<void> { | |
| // アップデート前に作成されたお知らせやRedisの再構築後にも予約を復元する | |
| const announcements = await this.announcementsRepository.findBy({ | |
| isActive: true, | |
| autoArchiveAt: Not(IsNull()), | |
| }); | |
| await Promise.all(announcements.map(announcement => | |
| this.queueService.scheduleAnnouncementArchive(announcement.id, announcement.autoArchiveAt!).catch(err => { | |
| // ログを出し、他の予約処理をブロックしないようにする | |
| console.error(`Failed to reschedule archive for announcement ${announcement.id}`, err); | |
| }), | |
| )); | |
| } | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/backend/src/core/AnnouncementService.ts` around lines 35 - 51,
Update onModuleInit so a failure from scheduling one announcement does not
reject the entire initialization flow: isolate each scheduleAnnouncementArchive
call and handle its rejection per announcement, while allowing the remaining
announcements to be scheduled and bootstrap to complete.
| @bindThis | ||
| public async scheduleAnnouncementArchive(announcementId: MiAnnouncement['id'], autoArchiveAt: Date): Promise<void> { | ||
| const jobId = this.announcementArchiveJobId(announcementId, autoArchiveAt); | ||
| const existingJob = await this.systemQueue.getJob(jobId); | ||
| if (existingJob != null) { | ||
| const state = await existingJob.getState(); | ||
| if (state !== 'completed' && state !== 'failed') return; | ||
| await this.systemQueue.remove(jobId); | ||
| } | ||
|
|
||
| const data: ArchiveAnnouncementJobData = { | ||
| announcementId, | ||
| autoArchiveAt: autoArchiveAt.getTime(), | ||
| }; | ||
|
|
||
| await this.systemQueue.add('archiveAnnouncement', data, { | ||
| jobId, | ||
| delay: Math.max(0, autoArchiveAt.getTime() - Date.now()), | ||
| removeOnComplete: { | ||
| age: 3600 * 24 * 7, // keep up to 7 days | ||
| count: 30, | ||
| }, | ||
| removeOnFail: { | ||
| age: 3600 * 24 * 7, // keep up to 7 days | ||
| count: 100, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate system-queue defaults and archive processor behavior.
rg -n -C 4 'queue:system|defaultJobOptions|attempts:|archiveAnnouncement' packages/backend
# Locate coverage for scheduling and failed-job recovery.
rg -n -C 3 'scheduleAnnouncementArchive|ArchiveAnnouncement|autoArchiveAt' \
packages/backend/test packages/backend/srcRepository: misskey-dev/misskey
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== QueueModule.ts outline ==\n'
ast-grep outline packages/backend/src/core/QueueModule.ts --view expanded || true
printf '\n== QueueModule.ts relevant lines ==\n'
sed -n '1,140p' packages/backend/src/core/QueueModule.ts | cat -n
printf '\n== search baseQueueOptions / defaultJobOptions ==\n'
rg -n -C 4 'function baseQueueOptions|const baseQueueOptions|defaultJobOptions|attempts' packages/backend/src/core packages/backend/src/queue
printf '\n== base queue helper file candidates ==\n'
fd -a 'QueueModule.ts|queue.*ts|baseQueueOptions' packages/backend/src | head -n 50Repository: misskey-dev/misskey
Length of output: 17366
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== packages/backend/src/queue/const.ts ==\n'
sed -n '1,220p' packages/backend/src/queue/const.ts | cat -n
printf '\n== search for defaultJobOptions in repository ==\n'
rg -n -C 3 'defaultJobOptions|attempts\s*[:=]' packages/backend/src packages/backend/test
printf '\n== search for system queue add sites ==\n'
rg -n -C 2 "systemQueue\.add\(" packages/backend/srcRepository: misskey-dev/misskey
Length of output: 7389
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== QueueProcessorService worker setup ==\n'
sed -n '1,260p' packages/backend/src/queue/QueueProcessorService.ts | cat -n
printf '\n== search for Worker creation / retry-related options ==\n'
rg -n -C 4 'new Bull\.Worker|Worker\(|attempts|backoff|autorun|concurrency|useWorker' packages/backend/src/queue packages/backend/src/core
printf '\n== bullmq version and docs references in repo ==\n'
rg -n -C 2 'bullmq|retrying-failing-jobs|defaultJobOptions' package.json pnpm-lock.yaml packages/backend/package.json README.md packages/backend/srcRepository: misskey-dev/misskey
Length of output: 50375
🌐 Web query:
BullMQ default attempts value if attempts option is omitted retry failing jobs documentation
💡 Result:
In BullMQ, the default value for the attempts option is 1 [1][2][3]. This means that if the attempts option is omitted, the job will be executed once and will not be retried upon failure [4][3]. To enable automatic retries, you must explicitly set the attempts option to a value greater than 1 [4].
Citations:
- 1: https://api.docs.bullmq.io/interfaces/v5.DefaultJobOptions.html
- 2: https://api.docs.bullmq.io/interfaces/v5.BaseJobOptions.html
- 3: https://www.queuehub.tech/blog/2026-06-30-bullmq-job-retry-strategies/
- 4: https://docs.bullmq.io/guide/retrying-failing-jobs
Add retries for announcement archive jobs. BullMQ defaults attempts to 1, so a transient failure here won’t retry and the announcement can stay active until something else reschedules it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/backend/src/core/QueueService.ts` around lines 256 - 282, Update
scheduleAnnouncementArchive to configure retries in the options passed to
systemQueue.add for archiveAnnouncement jobs. Set an attempts value greater than
one, preserving the existing job ID, delay, and cleanup behavior.
🖥 Frontend Browser MetricsOnly metrics showing significant changes are displayed.
Requests by resource type
V8 heap snapshot statistics
Head heap snapshot composition%%{init: {"sankey":{"showValues":false,"linkColor":"target","labelStyle":"outlined","nodeAlignment":"center","nodePadding":10,"nodeColors":{"Head":"#888888","code":"#f28e2c","system / InstructionStream":"#f28e2c","Other":"#888888","strings":"#e15759","string":"#e15759","jsArrays":"#76b7b2","otherJsObjects":"#af7aa1","object shape":"#af7aa1","otherNonJsObjects":"#ff9da7","Extra native bytes":"#ff9da7","system / ExternalStringData":"#ff9da7"}}}}%%
sankey-beta
"Head","Code",25.04
"Code","system / InstructionStream",13.45
"Code","Other",11.59
"Head","Strings",10.71
"Strings","string",10.44
"Strings","Other",0.27
"Head","JS arrays",1.11
"Head","Other JS objs",18.47
"Other JS objs","object shape",5.73
"Other JS objs","Other",12.73
"Head","Other non-JS objs",44.68
"Other non-JS objs","Extra native bytes",18.17
"Other non-JS objs","system / ExternalStringData",14.24
"Other non-JS objs","Other",12.27
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
/preview |
What
アーカイブする日時を書き込むためのテキストボックスを生やしました
畳んだ時のリスト表示はこんな感じ
Why
fix #17688
Additional info (optional)
Checklist