From 1947835c5152b7cc9a23bb1a5da0caa953e9d660 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Mar 2020 01:04:36 +0900 Subject: [PATCH] Resolve #6137 --- src/prelude/time.ts | 4 +- .../api/endpoints/charts/active-users.ts | 7 +- src/server/api/endpoints/charts/drive.ts | 7 +- src/server/api/endpoints/charts/federation.ts | 7 +- src/server/api/endpoints/charts/hashtag.ts | 7 +- src/server/api/endpoints/charts/instance.ts | 7 +- src/server/api/endpoints/charts/network.ts | 7 +- src/server/api/endpoints/charts/notes.ts | 7 +- src/server/api/endpoints/charts/user/drive.ts | 7 +- .../api/endpoints/charts/user/following.ts | 7 +- src/server/api/endpoints/charts/user/notes.ts | 7 +- .../api/endpoints/charts/user/reactions.ts | 7 +- src/server/api/endpoints/charts/users.ts | 7 +- src/server/api/endpoints/stats.ts | 6 +- src/services/chart/core.ts | 58 +++++++++----- test/chart.ts | 79 +++++++++++++------ 16 files changed, 167 insertions(+), 64 deletions(-) diff --git a/src/prelude/time.ts b/src/prelude/time.ts index 0c75d96fe..77a5fc1af 100644 --- a/src/prelude/time.ts +++ b/src/prelude/time.ts @@ -1,6 +1,6 @@ const dateTimeIntervals = { - 'days': 86400000, - 'hours': 3600000, + 'day': 86400000, + 'hour': 3600000, }; export function DateUTC(time: number[]): Date { diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 59bb1db10..327dd4de3 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(activeUsersChart.schema), }; export default define(meta, async (ps) => { - return await activeUsersChart.getChart(ps.span as any, ps.limit!); + return await activeUsersChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index 5c26fe719..752cb6f03 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(driveChart.schema), }; export default define(meta, async (ps) => { - return await driveChart.getChart(ps.span as any, ps.limit!); + return await driveChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index ebd60cc24..1701f9bde 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(federationChart.schema), }; export default define(meta, async (ps) => { - return await federationChart.getChart(ps.span as any, ps.limit!); + return await federationChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index 8d1443013..bb353e703 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -26,6 +26,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + tag: { validator: $.str, desc: { @@ -38,5 +43,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.tag); + return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.tag); }); diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts index 4c26b7614..3ccb2ba12 100644 --- a/src/server/api/endpoints/charts/instance.ts +++ b/src/server/api/endpoints/charts/instance.ts @@ -26,6 +26,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + host: { validator: $.str, desc: { @@ -39,5 +44,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await instanceChart.getChart(ps.span as any, ps.limit!, ps.host); + return await instanceChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.host); }); diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index 162c0c9ec..20f5977ba 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(networkChart.schema), }; export default define(meta, async (ps) => { - return await networkChart.getChart(ps.span as any, ps.limit!); + return await networkChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index c25f46f54..5111e299e 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(notesChart.schema), }; export default define(meta, async (ps) => { - return await notesChart.getChart(ps.span as any, ps.limit!); + return await notesChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index 6bfa42740..576bc7be6 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -27,6 +27,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + userId: { validator: $.type(ID), desc: { @@ -40,5 +45,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.userId); + return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index 0da995e2e..dcdf15b41 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -27,6 +27,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + userId: { validator: $.type(ID), desc: { @@ -40,5 +45,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.userId); + return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 754ade122..65c12d6be 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -27,6 +27,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + userId: { validator: $.type(ID), desc: { @@ -40,5 +45,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.userId); + return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index f3344c664..c83a203b9 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -27,6 +27,11 @@ export const meta = { } }, + offset: { + validator: $.optional.num, + default: 0, + }, + userId: { validator: $.type(ID), desc: { @@ -40,5 +45,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.userId); + return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.offset!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 0d7fb7b95..7f52184f1 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -25,11 +25,16 @@ export const meta = { 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' } }, + + offset: { + validator: $.optional.num, + default: 0, + }, }, res: convertLog(usersChart.schema), }; export default define(meta, async (ps) => { - return await usersChart.getChart(ps.span as any, ps.limit!); + return await usersChart.getChart(ps.span as any, ps.limit!, ps.offset!); }); diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 5bc224450..a6ee240a8 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -60,9 +60,9 @@ export default define(meta, async () => { Notes.count({ where: { userHost: null }, cache: 3600000 }), Users.count({ cache: 3600000 }), Users.count({ where: { host: null }, cache: 3600000 }), - federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]), - driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]), - driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]), + federationChart.getChart('hour', 1, 0).then(chart => chart.instance.total[0]), + driveChart.getChart('hour', 1, 0).then(chart => chart.local.totalSize[0]), + driveChart.getChart('hour', 1, 0).then(chart => chart.remote.totalSize[0]), ]); return { diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index d62149c9a..5a2508b55 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -8,7 +8,7 @@ import * as nestedProperty from 'nested-property'; import autobind from 'autobind-decorator'; import Logger from '../logger'; import { Schema } from '../../misc/schema'; -import { EntitySchema, getRepository, Repository, LessThan, MoreThanOrEqual } from 'typeorm'; +import { EntitySchema, getRepository, Repository, LessThan, MoreThanOrEqual, Between } from 'typeorm'; import { DateUTC, isTimeSame, isTimeBefore, subtractTimespan } from '../../prelude/time'; import { getChartInsertLock } from '../../misc/app-lock'; @@ -133,6 +133,21 @@ export default abstract class Chart> { return Math.floor(x.getTime() / 1000); } + @autobind + private static dateToYMDH(date: Date): [number, number, number, number] { + const y = date.getUTCFullYear(); + const m = date.getUTCMonth(); + const d = date.getUTCDate(); + const h = date.getUTCHours(); + + return [y, m, d, h]; + } + + @autobind + private static getCurrentDate(): [number, number, number, number] { + return Chart.dateToYMDH(new Date()); + } + @autobind public static schemaToEntity(name: string, schema: Schema): EntitySchema { return new EntitySchema({ @@ -211,18 +226,6 @@ export default abstract class Chart> { return log as T; } - @autobind - private getCurrentDate(): [number, number, number, number] { - const now = new Date(); - - const y = now.getUTCFullYear(); - const m = now.getUTCMonth(); - const d = now.getUTCDate(); - const h = now.getUTCHours(); - - return [y, m, d, h]; - } - @autobind private getLatestLog(span: Span, group: string | null = null): Promise { return this.repository.findOne({ @@ -237,7 +240,7 @@ export default abstract class Chart> { @autobind private async getCurrentLog(span: Span, group: string | null = null): Promise { - const [y, m, d, h] = this.getCurrentDate(); + const [y, m, d, h] = Chart.getCurrentDate(); const current = span == 'day' ? DateUTC([y, m, d]) : @@ -378,12 +381,23 @@ export default abstract class Chart> { } @autobind - public async getChart(span: Span, range: number, group: string | null = null): Promise> { - const [y, m, d, h] = this.getCurrentDate(); + public async getChart(span: Span, range: number, offset: number, group: string | null = null): Promise> { + let [y, m, d, h] = Chart.getCurrentDate(); + + let lt: Date = null as never; + + if (offset > 0) { + [y, m, d, h] = Chart.dateToYMDH(subtractTimespan(DateUTC([y, m, d, h]), offset, span)); + + lt = + span === 'day' ? DateUTC([y, m, d]) : + span === 'hour' ? DateUTC([y, m, d, h]) : + null as never; + } const gt = - span === 'day' ? subtractTimespan(DateUTC([y, m, d]), range - 1, 'days') : - span === 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), range - 1, 'hours') : + span === 'day' ? subtractTimespan(DateUTC([y, m, d]), range - 1, 'day') : + span === 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), range - 1, 'hour') : null as never; // ログ取得 @@ -391,7 +405,9 @@ export default abstract class Chart> { where: { group: group, span: span, - date: MoreThanOrEqual(Chart.dateToTimestamp(gt)) + date: offset === 0 + ? MoreThanOrEqual(Chart.dateToTimestamp(gt)) + : Between(Chart.dateToTimestamp(gt), Chart.dateToTimestamp(lt)) }, order: { date: -1 @@ -439,8 +455,8 @@ export default abstract class Chart> { // 整形 for (let i = (range - 1); i >= 0; i--) { const current = - span == 'day' ? subtractTimespan(DateUTC([y, m, d]), i, 'days') : - span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), i, 'hours') : + span == 'day' ? subtractTimespan(DateUTC([y, m, d]), i, 'day') : + span == 'hour' ? subtractTimespan(DateUTC([y, m, d, h]), i, 'hour') : null as never; const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current)); diff --git a/test/chart.ts b/test/chart.ts index 13f039263..27cf2833e 100644 --- a/test/chart.ts +++ b/test/chart.ts @@ -86,8 +86,8 @@ describe('Chart', () => { it('Can updates', async(async () => { await testChart.increment(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -109,8 +109,8 @@ describe('Chart', () => { it('Can updates (dec)', async(async () => { await testChart.decrement(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -130,8 +130,8 @@ describe('Chart', () => { })); it('Empty chart', async(async () => { - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -155,8 +155,8 @@ describe('Chart', () => { await testChart.increment(); await testChart.increment(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -182,8 +182,8 @@ describe('Chart', () => { await testChart.increment(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -209,8 +209,8 @@ describe('Chart', () => { await testChart.increment(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -235,8 +235,8 @@ describe('Chart', () => { clock.tick('05:00:00'); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -262,8 +262,8 @@ describe('Chart', () => { clock.tick('05:00:00'); await testChart.increment(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -282,14 +282,41 @@ describe('Chart', () => { }); })); + it('Can specify offset', async(async () => { + await testChart.increment(); + + clock.tick('01:00:00'); + + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3, 1); + const chartDays = await testChart.getChart('day', 3, 1); + + assert.deepStrictEqual(chartHours, { + foo: { + dec: [0, 0, 0], + inc: [1, 0, 0], + total: [1, 0, 0] + }, + }); + + assert.deepStrictEqual(chartDays, { + foo: { + dec: [0, 0, 0], + inc: [0, 0, 0], + total: [0, 0, 0] + }, + }); + })); + describe('Grouped', () => { it('Can updates', async(async () => { await testGroupedChart.increment('alice'); - const aliceChartHours = await testGroupedChart.getChart('hour', 3, 'alice'); - const aliceChartDays = await testGroupedChart.getChart('day', 3, 'alice'); - const bobChartHours = await testGroupedChart.getChart('hour', 3, 'bob'); - const bobChartDays = await testGroupedChart.getChart('day', 3, 'bob'); + const aliceChartHours = await testGroupedChart.getChart('hour', 3, 0, 'alice'); + const aliceChartDays = await testGroupedChart.getChart('day', 3, 0, 'alice'); + const bobChartHours = await testGroupedChart.getChart('hour', 3, 0, 'bob'); + const bobChartDays = await testGroupedChart.getChart('day', 3, 0, 'bob'); assert.deepStrictEqual(aliceChartHours, { foo: { @@ -331,8 +358,8 @@ describe('Chart', () => { await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('bob'); - const chartHours = await testUniqueChart.getChart('hour', 3); - const chartDays = await testUniqueChart.getChart('day', 3); + const chartHours = await testUniqueChart.getChart('hour', 3, 0); + const chartDays = await testUniqueChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: [2, 0, 0], @@ -350,8 +377,8 @@ describe('Chart', () => { await testChart.resync(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: { @@ -379,8 +406,8 @@ describe('Chart', () => { await testChart.resync(); - const chartHours = await testChart.getChart('hour', 3); - const chartDays = await testChart.getChart('day', 3); + const chartHours = await testChart.getChart('hour', 3, 0); + const chartDays = await testChart.getChart('day', 3, 0); assert.deepStrictEqual(chartHours, { foo: {