diff --git a/.env b/.env index a107ed3..d3ebf86 100644 --- a/.env +++ b/.env @@ -1,4 +1,6 @@ -VITE_APP_APP_VERSION=10.0.12 +VITE_APP_APP_VERSION=10.0.14 +VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT=60 +VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT=5000 VITE_APP_TOKEN_CREATION_LINK=https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC VITE_APP_APPLICATION_REPO=https://github.com/kas-elvirov/gloc VITE_APP_DEVELOPER_WEBSITE=https://kas-elvirov.com diff --git a/.env.development b/.env.development index a107ed3..d3ebf86 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,6 @@ -VITE_APP_APP_VERSION=10.0.12 +VITE_APP_APP_VERSION=10.0.14 +VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT=60 +VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT=5000 VITE_APP_TOKEN_CREATION_LINK=https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC VITE_APP_APPLICATION_REPO=https://github.com/kas-elvirov/gloc VITE_APP_DEVELOPER_WEBSITE=https://kas-elvirov.com diff --git a/.env.production b/.env.production index a107ed3..d3ebf86 100644 --- a/.env.production +++ b/.env.production @@ -1,4 +1,6 @@ -VITE_APP_APP_VERSION=10.0.12 +VITE_APP_APP_VERSION=10.0.14 +VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT=60 +VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT=5000 VITE_APP_TOKEN_CREATION_LINK=https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC VITE_APP_APPLICATION_REPO=https://github.com/kas-elvirov/gloc VITE_APP_DEVELOPER_WEBSITE=https://kas-elvirov.com diff --git a/manifest.json b/manifest.json index f1c9025..50c99fc 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "short_name": "Gloc", "author": "Kas Elvirov", "description": "Github Gloc - counts locs on GitHub pages", - "version": "10.0.12", + "version": "10.0.14", "action": { "default_icon": { "16": "img/icon16.png", diff --git a/package-lock.json b/package-lock.json index ecaf611..d1d3735 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gloc", - "version": "10.0.12", + "version": "10.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gloc", - "version": "10.0.12", + "version": "10.0.14", "license": "GPL-2.0", "dependencies": { "@crxjs/vite-plugin": "^2.0.0-beta.26", diff --git a/package.json b/package.json index 100aeee..41c5b15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gloc", - "version": "10.0.12", + "version": "10.0.14", "engines": { "node": "16.19.0", "npm": "0.39.3" diff --git a/scripts/upAppVersion.js b/scripts/upAppVersion.js index 462e185..0a95cd9 100644 --- a/scripts/upAppVersion.js +++ b/scripts/upAppVersion.js @@ -10,6 +10,8 @@ const manifest = require('../manifest.json'); const env = '.env'; let parsedEnv = envfile.parse(env); parsedEnv.VITE_APP_APP_VERSION = package.version; +parsedEnv.VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT = 60; +parsedEnv.VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT = 5000; parsedEnv.VITE_APP_TOKEN_CREATION_LINK = 'https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC'; parsedEnv.VITE_APP_APPLICATION_REPO = 'https://github.com/kas-elvirov/gloc'; parsedEnv.VITE_APP_DEVELOPER_WEBSITE = 'https://kas-elvirov.com'; @@ -22,6 +24,8 @@ fs.writeFileSync('./.env', envfile.stringify(parsedEnv)); const envDevelopment = '.env.development'; let parsedEnvDevelopment = envfile.parse(envDevelopment); parsedEnvDevelopment.VITE_APP_APP_VERSION = package.version; +parsedEnvDevelopment.VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT = 60; +parsedEnvDevelopment.VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT = 5000; parsedEnvDevelopment.VITE_APP_TOKEN_CREATION_LINK = 'https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC'; parsedEnvDevelopment.VITE_APP_APPLICATION_REPO = 'https://github.com/kas-elvirov/gloc'; @@ -35,6 +39,8 @@ fs.writeFileSync('./.env.development', envfile.stringify(parsedEnvDevelopment)); const envProduction = '.env.production'; let parsedEnvProduction = envfile.parse(envProduction); parsedEnvProduction.VITE_APP_APP_VERSION = package.version; +parsedEnvProduction.VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT = 60; +parsedEnvProduction.VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT = 5000; parsedEnvProduction.VITE_APP_TOKEN_CREATION_LINK = 'https://github.com/settings/tokens/new?scopes=repo&description=Github%20GLOC'; parsedEnvProduction.VITE_APP_APPLICATION_REPO = 'https://github.com/kas-elvirov/gloc'; diff --git a/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.tsx b/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.tsx new file mode 100644 index 0000000..1766cd1 --- /dev/null +++ b/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react'; + +import { Box, LinearProgress, Typography } from '@mui/material'; + +import { LinearProgressWithLabelProps } from './LinearProgressWithLabel.types'; + +export const LinearProgressWithLabel: FC< + LinearProgressWithLabelProps +> = props => { + return ( + + + + + + {`${Math.round(props.value)}%`} + + + ); +}; diff --git a/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.types.ts b/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.types.ts new file mode 100644 index 0000000..df47aac --- /dev/null +++ b/src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel.types.ts @@ -0,0 +1,5 @@ +import { LinearProgressProps } from '@mui/material'; + +export type LinearProgressWithLabelProps = LinearProgressProps & { + value: number; +}; diff --git a/src/_modules/Content/containers/LocIndicator/LocIndicator.utils.ts b/src/_modules/Content/containers/LocIndicator/LocIndicator.utils.ts index 8756c0c..76a2712 100644 --- a/src/_modules/Content/containers/LocIndicator/LocIndicator.utils.ts +++ b/src/_modules/Content/containers/LocIndicator/LocIndicator.utils.ts @@ -25,12 +25,6 @@ const getErrorMessage = ({ let isError = false; const castedError = error as ErrorType; - console.group('getErrorMessage'); - console.log('data', data); - console.log('error', error); - console.log('isObjectValid(data)', isObjectValid(data)); - console.groupEnd(); - if (castedError?.status === 202 && !isObjectValid(data)) { return { errorMessage: 'Needs to retry', diff --git a/src/_modules/Popup/components/PopupPage/PopupPage.tsx b/src/_modules/Popup/components/PopupPage/PopupPage.tsx index 5876d99..7d483c3 100644 --- a/src/_modules/Popup/components/PopupPage/PopupPage.tsx +++ b/src/_modules/Popup/components/PopupPage/PopupPage.tsx @@ -1,4 +1,10 @@ +/* eslint-disable max-len */ +import { LinearProgressWithLabel } from 'src/_lib/components/LinearProgressWithLabel/LinearProgressWithLabel'; import { SYSTEM_DEFAULTS } from 'src/_shared/consts/defaults'; +import { + TrackEventsService, + TrackEventsState, +} from 'src/_shared/services/TrackEvent/TrackEvent'; import React, { FC, useEffect } from 'react'; @@ -31,14 +37,50 @@ export const PopupPage: FC = () => { SYSTEM_DEFAULTS.STORAGE.APP_MODE.DEFAULT_VALUE, ); + const [freeHourlyApiUsage, setFreeHourlyApiStatPercent] = React.useState(0); + const [tokenizedHourlyApiUsage, setTokenizedHourlyApiStatPercent] = + React.useState(0); + useEffect(() => { chrome?.storage?.sync?.get( { [SYSTEM_DEFAULTS.STORAGE.APP_MODE.KEY]: SYSTEM_DEFAULTS.STORAGE.APP_MODE.DEFAULT_VALUE, + [SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY]: + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.DEFAULT_VALUE, }, result => { setAppStatus(result[SYSTEM_DEFAULTS.STORAGE.APP_MODE.KEY]); + + if ( + (result[SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY] as TrackEventsState)[ + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.REQUESTS_STAT + ] + ) { + setFreeHourlyApiStatPercent( + TrackEventsService.calculatePercentOfHourlyEventUsage({ + state: result[ + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY + ] as TrackEventsState, + eventName: SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.REQUESTS_STAT, + limit: Number( + import.meta.env.VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT, + ), + }), + ); + + setTokenizedHourlyApiStatPercent( + TrackEventsService.calculatePercentOfHourlyEventUsage({ + state: result[ + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY + ] as TrackEventsState, + eventName: SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.REQUESTS_STAT, + limit: Number( + import.meta.env.VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT, + ), + }), + ); + } }, ); }, []); @@ -102,6 +144,26 @@ export const PopupPage: FC = () => { title={`App is ${isAppEnabled ? 'enabled' : 'disabled'}`} /> + + + Free requests (GitHub limit -{' '} + {Number(import.meta.env.VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT)} per + hour) + + + + + + + + Tokenised requests (GitHub limit -{' '} + {Number(import.meta.env.VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT)}{' '} + per hour) + + + + + diff --git a/src/_shared/api/github/endpoints.ts b/src/_shared/api/github/endpoints.ts index 37b9e03..22058b1 100644 --- a/src/_shared/api/github/endpoints.ts +++ b/src/_shared/api/github/endpoints.ts @@ -1,3 +1,6 @@ +import { SYSTEM_DEFAULTS } from 'src/_shared/consts/defaults'; +import { TrackEventsService } from 'src/_shared/services/TrackEvent/TrackEvent'; + import { BaseQueryFn, FetchBaseQueryError, @@ -76,6 +79,11 @@ export const githubApi = createApi({ Accept: 'application/vnd.github.v3+json', }, }), + onQueryStarted: () => { + TrackEventsService.trackEvent( + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.REQUESTS_STAT, + ); + }, extraOptions: { maxRetries: 5 }, }), getAllUserRepos: builder.query< diff --git a/src/_shared/consts/defaults.ts b/src/_shared/consts/defaults.ts index 176b00e..e556aab 100644 --- a/src/_shared/consts/defaults.ts +++ b/src/_shared/consts/defaults.ts @@ -13,6 +13,11 @@ export const SYSTEM_DEFAULTS = { KEY: 'x-github-token', DEFAULT_VALUE: '', }, + EVENTS_STAT: { + REQUESTS_STAT: 'getRepoCodeFrequency', + KEY: 'x-events-stat', + DEFAULT_VALUE: {}, + }, }, DEBOUNCE: { 300: 300, diff --git a/src/_shared/services/TrackEvent/TrackEvent.ts b/src/_shared/services/TrackEvent/TrackEvent.ts new file mode 100644 index 0000000..8746e97 --- /dev/null +++ b/src/_shared/services/TrackEvent/TrackEvent.ts @@ -0,0 +1,106 @@ +import { SYSTEM_DEFAULTS } from '../../consts/defaults'; + +export type TrackEventsEventName = string; +/** + * YYYY-MM-DD format + */ +export type TrackEventsCurrentDate = string; + +export type TrackEventsState = Record< + TrackEventsEventName, + { + hourly: [ + number, + number, + number, + number, + number, + + number, + number, + number, + number, + number, + + number, + number, + number, + number, + number, + + number, + number, + number, + number, + number, + + number, + number, + number, + number, + ]; + daily: Record; + } +>; + +class TrackEvents { + state: TrackEventsState = {}; + + trackEvent = (eventName: TrackEventsEventName) => { + chrome?.storage?.sync?.get( + { + [SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY]: + SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.DEFAULT_VALUE, + }, + result => { + const now = new Date(); + + const currentHour = now.getHours(); + const currentDate = now.toISOString().split('T')[0]; + + const state = { + ...result[SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY], + } as TrackEventsState; + + if (!state[eventName]) { + state[eventName] = { + // @ts-expect-error Everything is okay (array sizes are compatible. I think) + hourly: Array(25).fill(0), + daily: {}, + }; + } + + state[eventName].hourly[currentHour]++; + + if (!state[eventName].daily[currentDate]) { + state[eventName].daily[currentDate] = 0; + } + + state[eventName].daily[currentDate]++; + + chrome.storage?.sync?.set?.( + { [SYSTEM_DEFAULTS.STORAGE.EVENTS_STAT.KEY]: state }, + () => {}, + ); + }, + ); + }; + + calculatePercentOfHourlyEventUsage = ({ + state, + eventName, + limit, + }: { + state: TrackEventsState; + eventName: TrackEventsEventName; + limit: number; + }): number => { + const now = new Date(); + + const currentHour = now.getHours(); + + return state[eventName].hourly[currentHour] / (limit / 100); + }; +} + +export const TrackEventsService = new TrackEvents(); diff --git a/src/environment.d.ts b/src/environment.d.ts index f951f13..100b5dd 100644 --- a/src/environment.d.ts +++ b/src/environment.d.ts @@ -5,6 +5,10 @@ declare global { */ VITE_APP_APP_VERSION: string; + VITE_APP_GITHUB_API_FREE_HOURLY_LIMIT: string; + + VITE_APP_GITHUB_API_TOKENIZED_HOURLY_LIMIT: string; + /** * # Base of chrome extension settings link */