From 804cef2c2e0f065375323d8beda8ef9337dd567e Mon Sep 17 00:00:00 2001 From: Yoichiro Tanaka Date: Mon, 18 Dec 2023 17:00:04 +0900 Subject: [PATCH] Fetch keyboard statistics and show them. --- package.json | 1 + src/actions/keyboards.actions.ts | 8 ++ src/actions/storage.action.ts | 17 +++++ .../statistics/Statistics.container.ts | 5 +- .../editdefinition/statistics/Statistics.scss | 2 +- .../editdefinition/statistics/Statistics.tsx | 73 ++++++++++++++++++- src/services/provider/Firebase.ts | 27 +++++++ src/services/storage/Storage.ts | 16 ++++ src/store/reducers.ts | 4 + src/store/state.ts | 3 + yarn.lock | 5 ++ 11 files changed, 156 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1ea761c6..8155f100 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-draggable": "^4.4.3", + "react-google-charts": "^4.0.1", "react-helmet-async": "^1.0.9", "react-image-gallery": "^1.2.7", "react-redux": "^7.2.2", diff --git a/src/actions/keyboards.actions.ts b/src/actions/keyboards.actions.ts index 333f2a69..a6c36db9 100644 --- a/src/actions/keyboards.actions.ts +++ b/src/actions/keyboards.actions.ts @@ -14,6 +14,7 @@ import { IBuildableFirmwareFileType, IKeyboardDefinitionAuthorType, IKeyboardDefinitionDocument, + IKeyboardStatistics, IStore, } from '../services/storage/Storage'; import { ThunkAction, ThunkDispatch } from 'redux-thunk'; @@ -196,6 +197,7 @@ export const KEYBOARDS_EDIT_DEFINITION_UPDATE_ORGANIZATION_ID = `${KEYBOARDS_EDI export const KEYBOARDS_EDIT_DEFINITION_UPDATE_AUTHOR_TYPE = `${KEYBOARDS_EDIT_DEFINITION_ACTIONS}/UpdateAuthorType`; export const KEYBOARDS_EDIT_DEFINITION_UPDATE_BUILDABLE_FIRMWARE_FILE = `${KEYBOARDS_EDIT_DEFINITION_ACTIONS}/UpdateBuildableFirmwareFile`; export const KEYBOARDS_EDIT_DEFINITION_UPDATE_BUILDABLE_FIRMWARE_CODE_PARAMETERS = `${KEYBOARDS_EDIT_DEFINITION_ACTIONS}/UpdateBuildableFirmwareCodeParameters`; +export const KEYBOARDS_EDIT_DEFINITION_UPDATE_KEYBOARD_STATISTICS = `${KEYBOARDS_EDIT_DEFINITION_ACTIONS}/UpdateKeyboardStatistics`; export const KeyboardsEditDefinitionActions = { clear: () => { @@ -447,6 +449,12 @@ export const KeyboardsEditDefinitionActions = { value: parameters, }; }, + updateKeyboardStatistics: (statistics: IKeyboardStatistics | undefined) => { + return { + type: KEYBOARDS_EDIT_DEFINITION_UPDATE_KEYBOARD_STATISTICS, + value: statistics, + }; + }, }; type ActionTypes = ReturnType< diff --git a/src/actions/storage.action.ts b/src/actions/storage.action.ts index 118f046c..04f485de 100644 --- a/src/actions/storage.action.ts +++ b/src/actions/storage.action.ts @@ -335,6 +335,23 @@ export const storageActionsThunk = { ); return; } + const fetchKeyboardStatisticsResult = + await storage.instance!.fetchKeyboardStatistics(definitionId); + if (isError(fetchKeyboardStatisticsResult)) { + console.error(fetchKeyboardStatisticsResult.cause); + dispatch( + NotificationActions.addError( + fetchKeyboardStatisticsResult.error, + fetchKeyboardStatisticsResult.cause + ) + ); + return; + } + dispatch( + KeyboardsEditDefinitionActions.updateKeyboardStatistics( + fetchKeyboardStatisticsResult.value + ) + ); dispatch( StorageActions.updateBuildableFirmware( fetchBuildableFirmwareResult.value diff --git a/src/components/keyboards/editdefinition/statistics/Statistics.container.ts b/src/components/keyboards/editdefinition/statistics/Statistics.container.ts index 36b377c9..2deaad45 100644 --- a/src/components/keyboards/editdefinition/statistics/Statistics.container.ts +++ b/src/components/keyboards/editdefinition/statistics/Statistics.container.ts @@ -3,10 +3,13 @@ import { connect } from 'react-redux'; import Statistics from './Statistics'; const mapStateToProps = (state: RootState) => { - return {}; + return { + statistics: state.keyboards.editdefinition.keyboardStatistics, + }; }; export type StatisticsStateType = ReturnType; +// eslint-disable-next-line no-unused-vars const mapDispatchToProps = (_dispatch: any) => { return {}; }; diff --git a/src/components/keyboards/editdefinition/statistics/Statistics.scss b/src/components/keyboards/editdefinition/statistics/Statistics.scss index 4abdbc5c..e28e4c6f 100644 --- a/src/components/keyboards/editdefinition/statistics/Statistics.scss +++ b/src/components/keyboards/editdefinition/statistics/Statistics.scss @@ -3,7 +3,7 @@ .edit-definition-statistics { &-container { display: flex; - flex-direction: row; + flex-direction: column; width: 100%; } } diff --git a/src/components/keyboards/editdefinition/statistics/Statistics.tsx b/src/components/keyboards/editdefinition/statistics/Statistics.tsx index f2c5522b..e0c842b1 100644 --- a/src/components/keyboards/editdefinition/statistics/Statistics.tsx +++ b/src/components/keyboards/editdefinition/statistics/Statistics.tsx @@ -4,7 +4,8 @@ import { StatisticsStateType, } from './Statistics.container'; import './Statistics.scss'; -import { Card } from '@mui/material'; +import { Card, CardContent, Typography } from '@mui/material'; +import { Chart } from 'react-google-charts'; type OwnProps = {}; type StatisticsProps = OwnProps & @@ -12,10 +13,76 @@ type StatisticsProps = OwnProps & Partial; export default function Statistics(props: StatisticsProps) { + const data = props.statistics; + return (
- - + + + + Counts of opening a keyboard per day + + {data !== undefined && ( + [ + label, + data.statistics.counts_of_opening_keyboard.values[index], + ] + ), + ]} + width="100%" + height="300px" + options={{ + legend: 'none', + }} + /> + )} + + * This statistics will be shown after logs are collected by enough + users because of avoiding a privacy issue. + + + + + + + Counts of flashing a keymap to MCU + + {data !== undefined && ( + [ + label, + data.statistics.counts_of_flashing_keymap.values[index], + ] + ), + ]} + width="100%" + height="300px" + options={{ + legend: 'none', + }} + /> + )} + + * This statistics will be shown after logs are collected by enough + users because of avoiding a privacy issue. + + +
); } diff --git a/src/services/provider/Firebase.ts b/src/services/provider/Firebase.ts index 4ab427ba..d639fb78 100644 --- a/src/services/provider/Firebase.ts +++ b/src/services/provider/Firebase.ts @@ -44,6 +44,7 @@ import { BUILDABLE_FIRMWARE_QMK_FIRMWARE_VERSION, IBuildableFirmwareQmkFirmwareVersion, IOperationLogType, + IKeyboardStatistics, } from '../storage/Storage'; import { IAuth, IAuthenticationResult } from '../auth/Auth'; import { IFirmwareCodePlace, IKeyboardFeatures } from '../../store/state'; @@ -2026,4 +2027,30 @@ export class FirebaseProvider implements IStorage, IAuth { // Ignore error. } } + + async fetchKeyboardStatistics( + keyboardDefinitionId: string + ): Promise> { + try { + const createKeyboardStatistics = this.functions.httpsCallable( + 'createKeyboardStatistics' + ); + const createKeyboardStatisticsResult = await createKeyboardStatistics({ + keyboardDefinitionId, + }); + const data = createKeyboardStatisticsResult.data; + if (data.success) { + return successResultOf(data); + } else { + console.error(data.errorMessage); + return errorResultOf(data.errorMessage); + } + } catch (error) { + console.error(error); + return errorResultOf( + `Fetching keyboard statistics failed: ${error}`, + error + ); + } + } } diff --git a/src/services/storage/Storage.ts b/src/services/storage/Storage.ts index ce0aaad0..fdde8148 100644 --- a/src/services/storage/Storage.ts +++ b/src/services/storage/Storage.ts @@ -318,6 +318,19 @@ export type IFirmwareBuildingTask = { export type IOperationLogType = 'configure/flash' | 'configure/open'; +export type IKeyboardStatistics = { + statistics: { + counts_of_opening_keyboard: { + labels: string[]; + values: number[]; + }; + counts_of_flashing_keymap: { + labels: string[]; + values: number[]; + }; + }; +}; + /* eslint-disable no-unused-vars */ export interface IStorage { fetchKeyboardDefinitionDocumentByDeviceInfo( @@ -539,5 +552,8 @@ export interface IStorage { keyboardDefinitionId: string, operation: IOperationLogType ): Promise; + fetchKeyboardStatistics( + keyboardDefinitionId: string + ): Promise>; } /* eslint-enable no-unused-vars */ diff --git a/src/store/reducers.ts b/src/store/reducers.ts index 1f7c7c7f..4248cfc4 100644 --- a/src/store/reducers.ts +++ b/src/store/reducers.ts @@ -143,6 +143,7 @@ import { KEYBOARDS_EDIT_DEFINITION_UPDATE_JSON_FILENAME, KEYBOARDS_EDIT_DEFINITION_UPDATE_JSON_STRING, KEYBOARDS_EDIT_DEFINITION_UPDATE_KEYBOARD_DEFINITION, + KEYBOARDS_EDIT_DEFINITION_UPDATE_KEYBOARD_STATISTICS, KEYBOARDS_EDIT_DEFINITION_UPDATE_MAIN_IMAGE_UPLOADED_RATE, KEYBOARDS_EDIT_DEFINITION_UPDATE_MAIN_IMAGE_UPLOADING, KEYBOARDS_EDIT_DEFINITION_UPDATE_ORGANIZATION_EVIDENCE, @@ -455,6 +456,9 @@ const keyboardsEditKeyboardReducer = ( draft.keyboards.editdefinition.buildableFirmwareCodeParameters = action.value; break; + case KEYBOARDS_EDIT_DEFINITION_UPDATE_KEYBOARD_STATISTICS: + draft.keyboards.editdefinition.keyboardStatistics = action.value; + break; } }; diff --git a/src/store/state.ts b/src/store/state.ts index a323a604..adea8506 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -14,6 +14,7 @@ import { IFirmwareBuildingTask, IKeyboardDefinitionAuthorType, IKeyboardDefinitionDocument, + IKeyboardStatistics, IOrganization, IOrganizationMember, IStorage, @@ -411,6 +412,7 @@ export type RootState = { buildableFirmwareFile: IBuildableFirmwareFile | null; buildableFirmwareFileType: IBuildableFirmwareFileType | null; buildableFirmwareCodeParameters: IBuildableFirmwareCodeParameter[]; + keyboardStatistics: IKeyboardStatistics | undefined; }; }; catalog: { @@ -671,6 +673,7 @@ export const INIT_STATE: RootState = { buildableFirmwareFile: null, buildableFirmwareFileType: null, buildableFirmwareCodeParameters: [], + keyboardStatistics: undefined, }, }, catalog: { diff --git a/yarn.lock b/yarn.lock index a51a5bb9..754d83e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12926,6 +12926,11 @@ react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-google-charts@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-google-charts/-/react-google-charts-4.0.1.tgz#b7713856a48009b77318f8951ddf2c3ba39f991b" + integrity sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ== + react-helmet-async@*, react-helmet-async@^1.0.7, react-helmet-async@^1.0.9: version "1.2.3" resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz#57326a69304ea3293036eafb49475e9ba454cb37"