diff --git a/.coderabbit.yaml b/.coderabbit.yaml
new file mode 100644
index 0000000..9637bac
--- /dev/null
+++ b/.coderabbit.yaml
@@ -0,0 +1,42 @@
+language: "jp"
+early_access: false
+reviews:
+ request_changes_workflow: false
+ high_level_summary: true
+ poem: true
+ review_status: true
+ collapse_walkthrough: false
+ path_filters:
+ - "!**/.toml"
+ - "!**/.yml"
+ path_instructions:
+ - path: "**/*.py"
+ instructions: |
+ あなたは @coderabbitai(別名 github-actions[bot])で、OpenAIによって訓練された言語モデルです。
+ あなたの目的は、非常に経験豊富なソフトウェアエンジニアとして機能し、コードの一部を徹底的にレビューし、
+ 以下のようなキーエリアを改善するためのコードスニペットを提案することです:
+ - ロジック
+ - セキュリティ
+ - パフォーマンス
+ - データ競合
+ - 一貫性
+ - エラー処理
+ - 保守性
+ - モジュール性
+ - 複雑性
+ - 最適化
+ - ベストプラクティス: DRY, SOLID, KISS
+
+ 些細なコードスタイルの問題や、コメント・ドキュメントの欠落についてはコメントしないでください。
+ 重要な問題を特定し、解決して全体的なコード品質を向上させることを目指してくださいが、細かい問題は意図的に無視してください。
+ auto_review:
+ enabled: true
+ ignore_title_keywords:
+ - "WIP"
+ - "DO NOT MERGE"
+ drafts: false
+ base_branches:
+ - "develop"
+ - "feature/*"
+chat:
+ auto_reply: true
diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml
new file mode 100644
index 0000000..7d0b586
--- /dev/null
+++ b/.github/pr-labeler.yml
@@ -0,0 +1,14 @@
+feature:
+ - 'feature/*'
+ - 'feat/*'
+refactor:
+ - 'refactor/*'
+bug:
+ - 'fix/*'
+minor:
+ - 'release/*'
+ - 'feature/*'
+ - 'feat/*'
+ - 'refactor/*'
+chore:
+ - 'chore/*'
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
new file mode 100644
index 0000000..e6789e0
--- /dev/null
+++ b/.github/release-drafter.yml
@@ -0,0 +1,31 @@
+name-template: '$RESOLVED_VERSION'
+tag-template: '$RESOLVED_VERSION'
+categories:
+ - title: '🚀 Features'
+ labels:
+ - 'feature'
+ - 'enhancement'
+ - title: '🐛 Bug Fixes'
+ labels:
+ - 'fix'
+ - 'bugfix'
+ - 'bug'
+ - title: '🧰 Maintenance'
+ label: 'chore'
+change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
+change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+version-resolver:
+ major:
+ labels:
+ - 'major'
+ minor:
+ labels:
+ - 'minor'
+ patch:
+ labels:
+ - 'patch'
+ default: minor
+template: |
+ ## Changes
+
+ $CHANGES
diff --git a/.github/workflows/pr_label.yml b/.github/workflows/pr_label.yml
new file mode 100644
index 0000000..1bf4a71
--- /dev/null
+++ b/.github/workflows/pr_label.yml
@@ -0,0 +1,15 @@
+name: pull request label
+on:
+ pull_request:
+ types: [opened]
+permissions:
+ pull-requests: write
+ contents: read
+jobs:
+ pr-labeler:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: TimonVS/pr-labeler-action@v4
+ name: make label
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..7c3cf84
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+name: make release
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - main
+ push:
+ tags:
+ - '*'
+ workflow_dispatch: {}
+env:
+ cache-unique-key: mahjong-rust-ai
+permissions:
+ id-token: write
+ contents: read
+jobs:
+ release:
+ permissions:
+ # write permission is required to create a github release
+ contents: write
+ # write permission is required for autolabeler
+ # otherwise, read permission is required at least
+ pull-requests: read
+ runs-on: ubuntu-latest
+ if: ${{ github.event.pull_request.merged == true }}
+ steps:
+ - name: Create Release
+ uses: release-drafter/release-drafter@v5
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ publish: true
diff --git a/src/App.tsx b/src/App.tsx
index 0013db5..e49677b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,7 +10,7 @@ import { DefaultLayout } from './layouts/default'
import { RequireAuth } from './components/atoms/RequireAuth'
import { SignIn } from './pages/signin'
import { OpenAPI } from './apis/analysis'
-import { GamesIndex } from './pages/games'
+import { StatisticsIndex } from './pages/statistics'
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes)
@@ -33,7 +33,7 @@ function App() {
index
element={
-
+
}
/>
diff --git a/src/apis/analysis/index.ts b/src/apis/analysis/index.ts
index 64f3127..a2339f0 100644
--- a/src/apis/analysis/index.ts
+++ b/src/apis/analysis/index.ts
@@ -7,13 +7,19 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI';
+export type { AverageScore } from './models/AverageScore';
+export type { Dataset } from './models/Dataset';
export type { Element_int_ } from './models/Element_int_';
export type { Game } from './models/Game';
export type { GenericList_int_ } from './models/GenericList_int_';
export type { HTTPValidationError } from './models/HTTPValidationError';
export type { Kyoku } from './models/Kyoku';
+export type { NagareCount } from './models/NagareCount';
export type { ValidationError } from './models/ValidationError';
+export type { YakuCount } from './models/YakuCount';
+export { DatasetsService } from './services/DatasetsService';
export { DefaultService } from './services/DefaultService';
export { GamesService } from './services/GamesService';
export { KyokusService } from './services/KyokusService';
+export { StatisticsService } from './services/StatisticsService';
diff --git a/src/apis/analysis/models/AverageScore.ts b/src/apis/analysis/models/AverageScore.ts
new file mode 100644
index 0000000..448d070
--- /dev/null
+++ b/src/apis/analysis/models/AverageScore.ts
@@ -0,0 +1,11 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type AverageScore = {
+ player_name: string;
+ score: number;
+ point: number;
+ game_count: number;
+};
+
diff --git a/src/apis/analysis/models/Dataset.ts b/src/apis/analysis/models/Dataset.ts
new file mode 100644
index 0000000..23e4571
--- /dev/null
+++ b/src/apis/analysis/models/Dataset.ts
@@ -0,0 +1,9 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type Dataset = {
+ id: string;
+ friendly_name: (string | null);
+};
+
diff --git a/src/apis/analysis/models/NagareCount.ts b/src/apis/analysis/models/NagareCount.ts
new file mode 100644
index 0000000..8800ed2
--- /dev/null
+++ b/src/apis/analysis/models/NagareCount.ts
@@ -0,0 +1,9 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type NagareCount = {
+ name: string;
+ count: number;
+};
+
diff --git a/src/apis/analysis/models/YakuCount.ts b/src/apis/analysis/models/YakuCount.ts
new file mode 100644
index 0000000..25f044f
--- /dev/null
+++ b/src/apis/analysis/models/YakuCount.ts
@@ -0,0 +1,10 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+export type YakuCount = {
+ name: string;
+ han_count: number;
+ count: number;
+};
+
diff --git a/src/apis/analysis/services/DatasetsService.ts b/src/apis/analysis/services/DatasetsService.ts
new file mode 100644
index 0000000..a53ffc5
--- /dev/null
+++ b/src/apis/analysis/services/DatasetsService.ts
@@ -0,0 +1,21 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { Dataset } from '../models/Dataset';
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+export class DatasetsService {
+ /**
+ * Get Datasets
+ * @returns Dataset Successful Response
+ * @throws ApiError
+ */
+ public static getDatasetsDatasetsGet(): CancelablePromise> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/datasets',
+ });
+ }
+}
diff --git a/src/apis/analysis/services/StatisticsService.ts b/src/apis/analysis/services/StatisticsService.ts
new file mode 100644
index 0000000..c8e27d5
--- /dev/null
+++ b/src/apis/analysis/services/StatisticsService.ts
@@ -0,0 +1,90 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+import type { AverageScore } from '../models/AverageScore';
+import type { NagareCount } from '../models/NagareCount';
+import type { YakuCount } from '../models/YakuCount';
+import type { CancelablePromise } from '../core/CancelablePromise';
+import { OpenAPI } from '../core/OpenAPI';
+import { request as __request } from '../core/request';
+export class StatisticsService {
+ /**
+ * Get Average Score By Player
+ * @param datasetId
+ * @param startDate
+ * @param endDate
+ * @returns AverageScore Successful Response
+ * @throws ApiError
+ */
+ public static getAverageScoreByPlayerStatisticsAverageScoreByPlayerGet(
+ datasetId: string,
+ startDate: string,
+ endDate: string,
+ ): CancelablePromise> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/statistics/average_score_by_player',
+ query: {
+ 'dataset_id': datasetId,
+ 'start_date': startDate,
+ 'end_date': endDate,
+ },
+ errors: {
+ 422: `Validation Error`,
+ },
+ });
+ }
+ /**
+ * Get Yaku Count
+ * @param datasetId
+ * @param startDate
+ * @param endDate
+ * @returns YakuCount Successful Response
+ * @throws ApiError
+ */
+ public static getYakuCountStatisticsYakuCountGet(
+ datasetId: string,
+ startDate: string,
+ endDate: string,
+ ): CancelablePromise> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/statistics/yaku_count',
+ query: {
+ 'dataset_id': datasetId,
+ 'start_date': startDate,
+ 'end_date': endDate,
+ },
+ errors: {
+ 422: `Validation Error`,
+ },
+ });
+ }
+ /**
+ * Get Nagare Count
+ * @param datasetId
+ * @param startDate
+ * @param endDate
+ * @returns NagareCount Successful Response
+ * @throws ApiError
+ */
+ public static getNagareCountStatisticsNagareCountGet(
+ datasetId: string,
+ startDate: string,
+ endDate: string,
+ ): CancelablePromise> {
+ return __request(OpenAPI, {
+ method: 'GET',
+ url: '/statistics/nagare_count',
+ query: {
+ 'dataset_id': datasetId,
+ 'start_date': startDate,
+ 'end_date': endDate,
+ },
+ errors: {
+ 422: `Validation Error`,
+ },
+ });
+ }
+}
diff --git a/src/components/atoms/RequireAuth/index.tsx b/src/components/atoms/RequireAuth/index.tsx
index ad283f1..42f5fb2 100644
--- a/src/components/atoms/RequireAuth/index.tsx
+++ b/src/components/atoms/RequireAuth/index.tsx
@@ -1,20 +1,40 @@
import { useAuth0 } from '@auth0/auth0-react'
-import { Navigate, useLocation } from 'react-router-dom'
+import { ErrorBoundary } from '@sentry/react'
+import { Suspense } from 'react'
+import { Navigate } from 'react-router-dom'
+import { fetchAPIToken, useAPIToken } from '../../../hooks/common/useToken'
type Props = {
children?: React.ReactNode
redirect: string
}
-export const RequireAuth: React.FC = ({ children, redirect }) => {
- const { isLoading, isAuthenticated } = useAuth0()
- const location = useLocation()
+const AsyncComponent = ({ children, redirect }: Props) => {
+ const { data } = useAPIToken()
+ const {
+ isLoading,
+ isAuthenticated,
+ getAccessTokenSilently,
+ getAccessTokenWithPopup,
+ } = useAuth0()
+
+ if (isLoading) throw new Promise((resolve) => setTimeout(resolve, 100)) // 待機
- if (isLoading) {
- return Please Wait
- } else if (isAuthenticated) {
- return <>{children}>
- } else {
- return
+ if (!isAuthenticated) {
+ return
+ } else if (!data) {
+ throw fetchAPIToken(getAccessTokenSilently, getAccessTokenWithPopup)
}
+
+ return <>{children}>
+}
+
+export const RequireAuth: React.FC = ({ children, redirect }) => {
+ return (
+
+ Please Wait}>
+ {children}
+
+
+ )
}
diff --git a/src/hooks/common/useToken.ts b/src/hooks/common/useToken.ts
index bef0064..da729f2 100644
--- a/src/hooks/common/useToken.ts
+++ b/src/hooks/common/useToken.ts
@@ -1,14 +1,12 @@
-import { GetTokenSilentlyOptions, LogoutOptions } from '@auth0/auth0-react'
+import {
+ GetTokenSilentlyOptions,
+ GetTokenWithPopupOptions,
+ LogoutOptions,
+ PopupConfigOptions,
+} from '@auth0/auth0-react'
import useSWR, { SWRResponse, mutate } from 'swr'
import { OpenAPI } from '../../apis/analysis'
-export const useAuth0Token = (): SWRResponse => {
- return useSWR('auth0/token', null, {
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- })
-}
-
export const useAPIToken = (): SWRResponse => {
return useSWR('auth0/api-token', null, {
revalidateOnFocus: false,
@@ -17,14 +15,29 @@ export const useAPIToken = (): SWRResponse => {
}
export const fetchAPIToken = async (
- getAccessTokenSilently: (options?: GetTokenSilentlyOptions) => Promise
+ getAccessTokenSilently: (
+ options?: GetTokenSilentlyOptions
+ ) => Promise,
+ getAccessTokenWithPopup: (
+ options?: GetTokenWithPopupOptions,
+ config?: PopupConfigOptions
+ ) => Promise
) => {
- const token = await getAccessTokenSilently({
- authorizationParams: {
- audience: import.meta.env.VITE_AUTH0_API_AUDIENCE,
- scope: import.meta.env.VITE_AUTH0_API_SCOPE,
- },
- })
+ const token =
+ import.meta.env.VITE_ENV === 'local'
+ ? await getAccessTokenWithPopup({
+ authorizationParams: {
+ audience: import.meta.env.VITE_AUTH0_API_AUDIENCE,
+ scope: import.meta.env.VITE_AUTH0_API_SCOPE,
+ },
+ })
+ : await getAccessTokenSilently({
+ authorizationParams: {
+ audience: import.meta.env.VITE_AUTH0_API_AUDIENCE,
+ scope: import.meta.env.VITE_AUTH0_API_SCOPE,
+ },
+ })
+
OpenAPI.TOKEN = token
mutate('auth0/api-token', token)
}
@@ -39,5 +52,5 @@ export const signOut = async (
})
mutate('auth0/idtoken', undefined)
- mutate('auth0/token', undefined)
+ mutate('auth0/api-token', undefined)
}
diff --git a/src/hooks/swr/dataset/index.ts b/src/hooks/swr/dataset/index.ts
new file mode 100644
index 0000000..7b1ca39
--- /dev/null
+++ b/src/hooks/swr/dataset/index.ts
@@ -0,0 +1,11 @@
+import useSWR, { SWRConfiguration } from 'swr'
+import { DatasetsService } from '../../../apis/analysis'
+import { useAPIToken } from '../../common/useToken'
+
+export const useDatasets = (config?: Partial) => {
+ const { data: token } = useAPIToken()
+
+ return useSWR(['datasets', token], DatasetsService.getDatasetsDatasetsGet, {
+ ...config,
+ })
+}
diff --git a/src/hooks/swr/statistics/index.ts b/src/hooks/swr/statistics/index.ts
new file mode 100644
index 0000000..d7bab72
--- /dev/null
+++ b/src/hooks/swr/statistics/index.ts
@@ -0,0 +1,38 @@
+import { StatisticsService } from '../../../apis/analysis'
+
+// hooksじゃないので本来ここに置くのはおかしいが、あとで移動する
+export const getAverageScoreByPlayer = async (
+ datasetId: string,
+ startDate: string,
+ endDate: string
+) => {
+ return await StatisticsService.getAverageScoreByPlayerStatisticsAverageScoreByPlayerGet(
+ datasetId,
+ startDate,
+ endDate
+ )
+}
+
+export const getYakuCount = async (
+ datasetId: string,
+ startDate: string,
+ endDate: string
+) => {
+ return await StatisticsService.getYakuCountStatisticsYakuCountGet(
+ datasetId,
+ startDate,
+ endDate
+ )
+}
+
+export const getNagareCount = async (
+ datasetId: string,
+ startDate: string,
+ endDate: string
+) => {
+ return await StatisticsService.getNagareCountStatisticsNagareCountGet(
+ datasetId,
+ startDate,
+ endDate
+ )
+}
diff --git a/src/pages/games/index.tsx b/src/pages/games/index.tsx
index 12db771..07c7dce 100644
--- a/src/pages/games/index.tsx
+++ b/src/pages/games/index.tsx
@@ -10,14 +10,15 @@ import {
Stack,
Table,
TableBody,
- TableCell,
TableContainer,
TableRow,
} from '@mui/material'
import { useGames } from '../../hooks/swr/games'
import { JsonViewer } from '@textea/json-viewer'
+import { useDatasets } from '../../hooks/swr/dataset'
export const GamesIndex = () => {
+ const { data: datasets } = useDatasets()
const [startDate, setStartDate] = useState(
dayjs().subtract(1, 'day')
)
@@ -47,8 +48,9 @@ export const GamesIndex = () => {
value={datasetId}
onChange={handleChangeDatasetId}
>
-
-
+ {datasets?.map((dataset) => (
+
+ ))}
{
+ const { data: datasets } = useDatasets()
+ const [startDate, setStartDate] = useState(
+ dayjs().subtract(1, 'day')
+ )
+ const [endDate, setEndDate] = useState(
+ dayjs().subtract(1, 'day')
+ )
+ const [datasetId, setDatasetId] = useState('')
+
+ const handleChangeDatasetId = (event: SelectChangeEvent) => {
+ setDatasetId(event.target.value as string)
+ }
+
+ const [result, setResult] = useState(null)
+
+ const handleGetAverageScoreByPlayer = async () => {
+ const response = await getAverageScoreByPlayer(
+ datasetId,
+ startDate?.format('YYYY-MM-DD') ?? '',
+ endDate?.format('YYYY-MM-DD') ?? ''
+ )
+ setResult(response)
+ }
+
+ const handleGetYakuCount = async () => {
+ const response = await getYakuCount(
+ datasetId,
+ startDate?.format('YYYY-MM-DD') ?? '',
+ endDate?.format('YYYY-MM-DD') ?? ''
+ )
+ setResult(response)
+ }
+
+ const handleGetNagareCount = async () => {
+ const response = await getNagareCount(
+ datasetId,
+ startDate?.format('YYYY-MM-DD') ?? '',
+ endDate?.format('YYYY-MM-DD') ?? ''
+ )
+ setResult(response)
+ }
+
+ return (
+
+
+ {/* フォームをいれる(のちのちcomponent化) */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* APIの結果表示 */}
+
+
+
+ {result?.map((r) => (
+
+
+
+ ))}
+
+
+
+
+
+ )
+}