Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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==

fresh@0.5.2:
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