From 3968597a7b8576c7b4873b0a091d5b6ef64a447a Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 30 Dec 2022 18:26:36 +0900
Subject: [PATCH] improve heatmap

---
 ...MkActiveUsersHeatmap.vue => MkHeatmap.vue} | 41 ++++++++++++++++---
 .../src/components/MkInstanceStats.vue        | 24 ++++++-----
 .../src/pages/admin/overview.heatmap.vue      | 14 ++++++-
 3 files changed, 61 insertions(+), 18 deletions(-)
 rename packages/frontend/src/components/{MkActiveUsersHeatmap.vue => MkHeatmap.vue} (77%)

diff --git a/packages/frontend/src/components/MkActiveUsersHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
similarity index 77%
rename from packages/frontend/src/components/MkActiveUsersHeatmap.vue
rename to packages/frontend/src/components/MkHeatmap.vue
index 744193dd5f..078d0721da 100644
--- a/packages/frontend/src/components/MkActiveUsersHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -8,7 +8,7 @@
 </template>
 
 <script lang="ts" setup>
-import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
 import {
 	Chart,
 	ArcElement,
@@ -54,6 +54,10 @@ Chart.register(
 	MatrixController, MatrixElement,
 );
 
+const props = defineProps<{
+	src: string;
+}>();
+
 const rootEl = $ref<HTMLDivElement>(null);
 const chartEl = $ref<HTMLCanvasElement>(null);
 const now = new Date();
@@ -96,7 +100,24 @@ async function renderChart() {
 		});
 	};
 
-	const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+	let values;
+
+	if (props.src === 'active-users') {
+		const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+		values = raw.readWrite;
+	} else if (props.src === 'notes') {
+		const raw = await os.api('charts/notes', { limit: chartLimit, span: 'day' });
+		values = raw.local.inc;
+	} else if (props.src === 'ap-requests-inbox-received') {
+		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		values = raw.inboxReceived;
+	} else if (props.src === 'ap-requests-deliver-succeeded') {
+		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		values = raw.deliverSucceeded;
+	} else if (props.src === 'ap-requests-deliver-failed') {
+		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		values = raw.deliverFailed;
+	}
 
 	fetching = false;
 
@@ -110,7 +131,9 @@ async function renderChart() {
 	const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
 
 	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
-	const max = raw.readWrite.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
+	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
+
+	const min = Math.max(0, Math.min(...values) - 1);
 
 	const marginEachCell = 4;
 
@@ -119,14 +142,17 @@ async function renderChart() {
 		data: {
 			datasets: [{
 				label: 'Read & Write',
-				data: format(raw.readWrite),
+				data: format(values),
 				pointRadius: 0,
 				borderWidth: 0,
 				borderJoinStyle: 'round',
 				borderRadius: 3,
 				backgroundColor(c) {
 					const value = c.dataset.data[c.dataIndex].v;
-					const a = value / max;
+					let a = (value - min) / max;
+					if (value !== 0) { // 0でない限りは完全に不可視にはしない
+						a = Math.max(a, 0.05);
+					}
 					return alpha(color, a);
 				},
 				fill: true,
@@ -222,6 +248,11 @@ async function renderChart() {
 	});
 }
 
+watch(() => props.src, () => {
+	fetching = true;
+	renderChart();
+});
+
 onMounted(async () => {
 	renderChart();
 });
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index f01bb7426c..3500a340a8 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -38,8 +38,15 @@
 
 	<MkFolder class="item">
 		<template #header>Active users heatmap</template>
+		<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;">
+			<option value="active-users">Active users</option>
+			<option value="notes">Notes</option>
+			<option value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
+			<option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
+			<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
+		</MkSelect>
 		<div class="_panel" :class="$style.heatmap">
-			<MkActiveUsersHeatmap/>
+			<MkHeatmap :src="heatmapSrc"/>
 		</div>
 	</MkFolder>
 
@@ -93,7 +100,7 @@ import MkChart from '@/components/MkChart.vue';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
-import MkActiveUsersHeatmap from '@/components/MkActiveUsersHeatmap.vue';
+import MkHeatmap from '@/components/MkHeatmap.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
 
@@ -115,15 +122,10 @@ Chart.register(
 	Filler,
 );
 
-const props = withDefaults(defineProps<{
-	chartLimit?: number;
-	detailed?: boolean;
-}>(), {
-	chartLimit: 90,
-});
-
-const chartSpan = $ref<'hour' | 'day'>('hour');
-const chartSrc = $ref('active-users');
+const chartLimit = 90;
+let chartSpan = $ref<'hour' | 'day'>('hour');
+let chartSrc = $ref('active-users');
+let heatmapSrc = $ref('active-users');
 let subDoughnutEl = $ref<HTMLCanvasElement>();
 let pubDoughnutEl = $ref<HTMLCanvasElement>();
 
diff --git a/packages/frontend/src/pages/admin/overview.heatmap.vue b/packages/frontend/src/pages/admin/overview.heatmap.vue
index 16d1c83b9f..4d56d2a51f 100644
--- a/packages/frontend/src/pages/admin/overview.heatmap.vue
+++ b/packages/frontend/src/pages/admin/overview.heatmap.vue
@@ -1,11 +1,21 @@
 <template>
 <div class="_panel" :class="$style.root">
-	<MkActiveUsersHeatmap/>
+	<MkSelect v-model="src" style="margin: 0 0 12px 0;" small>
+		<option value="active-users">Active users</option>
+		<option value="notes">Notes</option>
+		<option value="ap-requests-inbox-received">AP Requests: inboxReceived</option>
+		<option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option>
+		<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
+	</MkSelect>
+	<MkHeatmap :src="src"/>
 </div>
 </template>
 
 <script lang="ts" setup>
-import MkActiveUsersHeatmap from '@/components/MkActiveUsersHeatmap.vue';
+import MkHeatmap from '@/components/MkHeatmap.vue';
+import MkSelect from '@/components/form/select.vue';
+
+let src = $ref('active-users');
 </script>
 
 <style lang="scss" module>