Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage data on stats page #1343

Draft
wants to merge 14 commits into
base: develop
Choose a base branch
from
Draft
2 changes: 2 additions & 0 deletions apps/sensenet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@
"clsx": "^1.1.1",
"date-fns": "^2.22.1",
"filesize": "^6.3.0",
"frappe-charts": "^1.6.1",
"react": "^16.13.0",
"react-autosuggest": "^10.1.0",
"react-day-picker": "^7.4.10",
"react-dom": "^16.13.0",
"react-frappe-charts": "^4.0.0",
"react-markdown": "^6.0.2",
"react-monaco-editor": "0.43.0",
"react-responsive": "^8.2.0",
Expand Down
134 changes: 134 additions & 0 deletions apps/sensenet/src/components/settings/stats-usage-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { formatSize } from '@sensenet/controls-react'
import { useRepository } from '@sensenet/hooks-react'
import { createStyles, ListItemText, makeStyles, MenuItem, Paper, Select } from '@material-ui/core'
import React, { useEffect, useState } from 'react'
import ReactFrappeChart from 'react-frappe-charts'
import { widgetStyles } from '../../globalStyles'
import { useLocalization } from '../../hooks'
import { useDateUtils } from '../../hooks/use-date-utils'
import { FullScreenLoader } from '../full-screen-loader'

export type PeriodData = {
PeriodStartDate: Date
PeriodEndDate: Date
}

export type ApiPeriod = {
DataType: String
Start: String
End: String
TimeWindow: 'Hour' | 'Day' | 'Month' | 'Year'
Resolution: 'Minute' | 'Hour' | 'Day' | 'Month'
CallCount: number[]
RequestLengths: number[]
ResponseLengths: number[]
}

const useWidgetStyles = makeStyles(widgetStyles)

const useStyles = makeStyles(() => {
return createStyles({
rowContainer: {
padding: '16px 0',
},
usageContainer: {
display: 'flex',
flexFlow: 'row',
},
leftContent: {
width: '60%',
backgroundColor: '#252525',
},
rightContent: {
width: '40%',
paddingLeft: '40px',
},
})
})

export interface UsageWidgetProps {
periodData: PeriodData[]
}

export const UsageWidget: React.FunctionComponent<UsageWidgetProps> = (props) => {
const classes = useStyles()
const widgetClasses = useWidgetStyles()
const localization = useLocalization().settings
const dateUtils = useDateUtils()
const repository = useRepository()
const [currentPeriod, setCurrentPeriod] = useState<PeriodData>(props.periodData[props.periodData.length - 1])
const [currentData, setCurrentData] = useState<ApiPeriod>()

const dataTraffic = currentData?.RequestLengths.map((request, index) => request + currentData.ResponseLengths[index])

useEffect(() => {
;(async () => {
const response = await repository.executeAction<any, ApiPeriod>({
idOrPath: '/Root',
name: 'GetApiUsagePeriod',
method: 'POST',
body: {
timeWindow: 'Month',
},
})

setCurrentData(response)
})()
}, [repository])

if (!dataTraffic) return <FullScreenLoader />
if (!currentPeriod) return null

return (
<div className={widgetClasses.root}>
<Paper elevation={0} className={widgetClasses.container}>
<div className={classes.rowContainer} style={{ fontSize: '16px' }}>
<span>{localization.traffic}</span>
</div>
<div className={classes.usageContainer}>
<div className={classes.leftContent}>
<ReactFrappeChart
type="line"
colors={['#26a69a']}
axisOptions={{ xAxisMode: 'span', yAxisMode: 'span', xIsSeries: 1 }}
height={400}
lineOptions={{ hideDots: 1 }}
barOptions={{ spaceRatio: 10 }}
data={{
labels: Array(dataTraffic.length)
.fill('')
.map((_item, index) => (index % 5 ? '' : index.toString())),
datasets: [{ values: dataTraffic }],
}}
/>
</div>
<div className={classes.rightContent}>
<Select
value={currentPeriod.PeriodStartDate}
onChange={(event) => {
setCurrentPeriod(
props.periodData.find((usageItem) => event.target.value === usageItem.PeriodStartDate.toString()) ||
props.periodData[props.periodData.length - 1],
)
}}>
{props.periodData.map((item, index) => {
return (
<MenuItem key={index} value={item.PeriodStartDate.toString()}>
<ListItemText>
{dateUtils.formatDate(item.PeriodStartDate, 'LLL dd yyyy')} -{' '}
{dateUtils.formatDate(item.PeriodEndDate, 'LLL dd yyyy')}
</ListItemText>
</MenuItem>
)
})}
</Select>
<div className={classes.rowContainer}>{localization.dataTraffic}</div>
<div style={{ fontSize: '30px' }}>{formatSize(dataTraffic.reduce((a, b) => a + b, 0))}</div>
<div className={classes.rowContainer}>{localization.apiCalls}</div>
<div style={{ fontSize: '30px' }}>{currentData?.CallCount.reduce((a, b) => a + b, 0)}</div>
</div>
</div>
</Paper>
</div>
)
}
54 changes: 53 additions & 1 deletion apps/sensenet/src/components/settings/stats.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useRepository, VersionInfo } from '@sensenet/hooks-react'
import { Container } from '@material-ui/core'
import clsx from 'clsx'
import { addMonths, isSameDay, parseISO } from 'date-fns'
import React, { useEffect, useState } from 'react'
import { useGlobalStyles } from '../../globalStyles'
import { useLocalization } from '../../hooks'
Expand All @@ -9,13 +10,63 @@ import { FullScreenLoader } from '../full-screen-loader'
import { ComponentsWidget } from './stats-components-widget'
import { InstalledPackagesWidget } from './stats-installed-packages-widget'
import { StorageWidget } from './stats-storage-widget'
import { UsageWidget } from './stats-usage-widget'

const makePeriodArrayFromRawData = (periodData: RawPeriodData) => {
const periodArray = []
const rawData = periodData

let currentDate = parseISO(rawData.First)
const lastDate = parseISO(rawData.Last)

while (!isSameDay(currentDate, lastDate)) {
const endDate = addMonths(currentDate, 1)
periodArray.push({
PeriodStartDate: currentDate,
PeriodEndDate: endDate,
})
currentDate = endDate
}
if (periodArray.length < rawData.Count) {
periodArray.push({
PeriodStartDate: lastDate,
PeriodEndDate: new Date(Date.now()),
})
}

return periodArray
}

export type RawPeriodData = {
Window: 'Hour' | 'Day' | 'Month' | 'Year'
Resolution: 'Minute' | 'Hour' | 'Day' | 'Month'
First: string
Last: string
Count: number
}

export const Stats: React.FunctionComponent = () => {
const globalClasses = useGlobalStyles()
const localization = useLocalization()
const repository = useRepository()
const [versionInfo, setVersionInfo] = useState<VersionInfo>()
const [dashboardData, setDashboardData] = useState<DashboardData>()
const [periodData, setPeriodData] = useState<RawPeriodData>()

useEffect(() => {
;(async () => {
const response = await repository.executeAction<any, RawPeriodData>({
idOrPath: '/Root',
name: 'GetApiUsagePeriods',
method: 'POST',
body: {
timeWindow: 'Month',
},
})

setPeriodData(response)
})()
}, [repository])

useEffect(() => {
;(async () => {
Expand Down Expand Up @@ -44,7 +95,7 @@ export const Stats: React.FunctionComponent = () => {
})()
}, [repository])

if (!versionInfo || !dashboardData) return <FullScreenLoader />
if (!versionInfo || !dashboardData || !periodData) return <FullScreenLoader />

return (
<div style={{ overflow: 'auto' }}>
Expand All @@ -53,6 +104,7 @@ export const Stats: React.FunctionComponent = () => {
</div>
<Container fixed>
<StorageWidget data={dashboardData} />
<UsageWidget periodData={periodData && makePeriodArrayFromRawData(periodData)} />
<ComponentsWidget data={versionInfo} />
<InstalledPackagesWidget data={versionInfo} />
</Container>
Expand Down
4 changes: 1 addition & 3 deletions apps/sensenet/src/hooks/use-date-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import format from 'date-fns/format'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import parseISO from 'date-fns/parseISO'
import { format, formatDistanceToNow, parseISO } from 'date-fns'
import { useCallback } from 'react'
import { LocalizationObject } from '../context'
import { usePersonalSettings } from '.'
Expand Down
3 changes: 3 additions & 0 deletions apps/sensenet/src/localization/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ const values = {
installedPackagesInfo:
'These packages are mainly the building bricks of sensenet components. There are tool-like packages that are not part of the component structure, they were made to run multiple times, for example delete or index content.',
notAvailable: 'Not available',
traffic: 'Traffic',
dataTraffic: 'Data traffic (request and response)',
apiCalls: 'Number of requests (API calls)',
},
customActions: {
executeCustomActionDialog: {
Expand Down
3 changes: 3 additions & 0 deletions apps/sensenet/src/localization/hungarian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ const values: Localization = {
installedPackagesInfo:
'Csomagok, melyekből a sensenet komponensek felépülnek. Léteznek olyan csomagok is, melyek nem felelősek a komponensekért, többszöri futtatásra lettek létrehozva - ilyenek a tool-típusú csomagok. Ezek például kontent törlésre vagy indexelésre használatosak.',
notAvailable: 'Nem elérhető',
traffic: 'Adatforgalom',
dataTraffic: 'Adatforgalom (kérések és válaszok)',
apiCalls: 'Kérések száma (API hívások)',
},
forms: {
referencePicker: 'Referencia választó',
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11907,6 +11907,11 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"

frappe-charts@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-1.6.1.tgz#2162cec05f4524b10cf232df8787a3ac20218384"
integrity sha512-Fteae/oqv4XdxP4ALoqTmUBBvXt8ECtghziqabp1ZA4yLqY8a3haafNqluwUCxnBOvrV+EdpvhZu0H+VEebX1Q==

[email protected]:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
Expand Down Expand Up @@ -20749,6 +20754,11 @@ react-focus-lock@^2.1.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"

react-frappe-charts@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/react-frappe-charts/-/react-frappe-charts-4.0.0.tgz#5a9958641ac0e74ce4c44a23788a5e879ac716a7"
integrity sha512-QmYY1ExlPidE8DK3jtjGWakP0YptHOlFMoYF4/Qxsbrw5hYMCwocPoco4b3e2qk6mI0trdpzILzpjmFWutO/MA==

react-helmet-async@^1.0.2, react-helmet-async@^1.0.7:
version "1.0.9"
resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.9.tgz#5b9ed2059de6b4aab47f769532f9fbcbce16c5ca"
Expand Down