From d907fd088df197477b4f961ef540b6e714e8b85d Mon Sep 17 00:00:00 2001 From: Sergey Konovalov Date: Fri, 5 Sep 2025 18:01:26 +0300 Subject: [PATCH] [refactor] Fix admin panel statistics rendering in some info.json responses --- .husky/pre-commit | 1 + .../components/Statistics/InfoTable/index.js | 81 +++--- .../Statistics/InfoTable/styles.module.css | 19 +- .../src/components/Statistics/ModeSwitcher.js | 28 +++ .../Statistics/MonthlyStatistics.js | 86 +++++++ .../client/src/components/Statistics/index.js | 232 +++++++++++++++--- .../components/Statistics/styles.module.css | 15 ++ tests/fixtures/README.md | 46 ++++ tests/fixtures/info/connections_basic.json | 42 ++++ .../info/connections_critical_remaining.json | 25 ++ .../info/connections_missing_periods.json | 32 +++ .../info/developer_edition_users.json | 35 +++ .../fixtures/info/open_source_connection.json | 27 ++ tests/fixtures/info/users_basic.json | 35 +++ .../info/users_critical_remaining.json | 24 ++ .../info/users_license_invalid_type.json | 24 ++ tests/fixtures/info/users_no_license.json | 24 ++ .../users_trial_limited_start_critical.json | 24 ++ .../info/users_updates_unavailable.json | 24 ++ 19 files changed, 753 insertions(+), 71 deletions(-) create mode 100644 AdminPanel/client/src/components/Statistics/ModeSwitcher.js create mode 100644 AdminPanel/client/src/components/Statistics/MonthlyStatistics.js create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/info/connections_basic.json create mode 100644 tests/fixtures/info/connections_critical_remaining.json create mode 100644 tests/fixtures/info/connections_missing_periods.json create mode 100644 tests/fixtures/info/developer_edition_users.json create mode 100644 tests/fixtures/info/open_source_connection.json create mode 100644 tests/fixtures/info/users_basic.json create mode 100644 tests/fixtures/info/users_critical_remaining.json create mode 100644 tests/fixtures/info/users_license_invalid_type.json create mode 100644 tests/fixtures/info/users_no_license.json create mode 100644 tests/fixtures/info/users_trial_limited_start_critical.json create mode 100644 tests/fixtures/info/users_updates_unavailable.json diff --git a/.husky/pre-commit b/.husky/pre-commit index e69de29b..2312dc58 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/AdminPanel/client/src/components/Statistics/InfoTable/index.js b/AdminPanel/client/src/components/Statistics/InfoTable/index.js index aad38b13..9137b602 100644 --- a/AdminPanel/client/src/components/Statistics/InfoTable/index.js +++ b/AdminPanel/client/src/components/Statistics/InfoTable/index.js @@ -1,41 +1,64 @@ import styles from './styles.module.css'; -export default function InfoTable({caption, editor, viewer, desc}) { +/** + * Renders a two-section info table for Editor and Live Viewer values. + * Values can optionally include a status class in v[1] like 'critical' or 'normal'. + * Sections can be toggled via the `mode` prop: 'all' | 'edit' | 'view'. + * + * @param {{ + * caption?: string, + * editor: Array<[number|string, ("critical"|"normal")?]>, + * viewer: Array<[number|string, ("critical"|"normal")?]>, + * desc: string[], + * mode?: 'all' | 'edit' | 'view' + * }} props + */ +export default function InfoTable({caption, editor, viewer, desc, mode = 'all'}) { return (
{caption &&
{caption}
} -
EDITORS
-
-
- {editor.map((v, i) => ( -
- {v[0]} + + {mode !== 'view' && ( + <> +
EDITORS
+
+
+ {[0, 1, 2, 3].map(i => ( +
+ {editor[i] && editor[i][0] !== undefined ? editor[i][0] : ''} +
+ ))}
- ))} -
-
- {desc.map((d, i) => ( -
- {d} +
+ {[0, 1, 2, 3].map(i => ( +
+ {desc[i] || ''} +
+ ))}
- ))} -
-
LIVE VIEWER
-
-
- {viewer.map((v, i) => ( -
- {v[0]} + + )} + + {mode !== 'edit' && ( + <> +
LIVE VIEWER
+
+
+ {[0, 1, 2, 3].map(i => ( +
+ {viewer[i] && viewer[i][0] !== undefined ? viewer[i][0] : ''} +
+ ))}
- ))} -
-
- {desc.map((d, i) => ( -
- {d} +
+ {[0, 1, 2, 3].map(i => ( +
+ {desc[i] || ''} +
+ ))}
- ))} -
+ + )}
); } diff --git a/AdminPanel/client/src/components/Statistics/InfoTable/styles.module.css b/AdminPanel/client/src/components/Statistics/InfoTable/styles.module.css index bfc37573..d187698d 100644 --- a/AdminPanel/client/src/components/Statistics/InfoTable/styles.module.css +++ b/AdminPanel/client/src/components/Statistics/InfoTable/styles.module.css @@ -2,23 +2,26 @@ margin-bottom: 25px; } .sectionHeader { - background: #f5f5f5; + background: #efefef; + font-size: 16px; font-weight: 400; padding: 6px 8px; margin-bottom: 8px; + border-radius: 1px; } .row { display: flex; justify-content: start; } .valueCell { - font-size: 22px; + font-size: 26px; + line-height: 28px; padding: 8px 0; width: 25%; padding-left: 10px; } .labelCell { - font-size: 13px; + font-size: 12px; color: #555; padding-bottom: 8px; width: 25%; @@ -36,10 +39,18 @@ } .divider { - border-bottom: 1px solid #ddd; + border-bottom: 1px solid #dadada; margin: 2px 10px; } .remainingValue { color: rgb(1, 125, 28); } + +/* match branding coloring */ +.critical { + color: #ff0000; +} +.normal { + color: #017d1c; +} diff --git a/AdminPanel/client/src/components/Statistics/ModeSwitcher.js b/AdminPanel/client/src/components/Statistics/ModeSwitcher.js new file mode 100644 index 00000000..de78f3df --- /dev/null +++ b/AdminPanel/client/src/components/Statistics/ModeSwitcher.js @@ -0,0 +1,28 @@ +import styles from './styles.module.css'; + +/** + * Mode switcher component for statistics view. + * Persists selected mode to localStorage via parent. + * + * @param {{ + * mode: 'all'|'edit'|'view', + * setMode: (mode: 'all'|'edit'|'view') => void + * }} props + */ +export default function ModeSwitcher({mode, setMode}) { + return ( +
+ setMode('all')}> + All + + | + setMode('edit')}> + Editors + + | + setMode('view')}> + Live Viewer + +
+ ); +} diff --git a/AdminPanel/client/src/components/Statistics/MonthlyStatistics.js b/AdminPanel/client/src/components/Statistics/MonthlyStatistics.js new file mode 100644 index 00000000..d77fab01 --- /dev/null +++ b/AdminPanel/client/src/components/Statistics/MonthlyStatistics.js @@ -0,0 +1,86 @@ +import {memo, useMemo} from 'react'; +import InfoTable from './InfoTable/index'; + +const MILLISECONDS_PER_DAY = 86400000; + +/** + * Count internal/external users. + * @param {Record} users + * @returns {{internal: number, external: number}} + */ +function countUsers(users = {}) { + let internal = 0; + let external = 0; + for (const uid in users) { + if (Object.prototype.hasOwnProperty.call(users, uid)) { + users[uid]?.anonym ? external++ : internal++; + } + } + return {internal, external}; +} + +/** + * MonthlyStatistics - renders usage statistics by month. + * Mirrors logic from branding/info/index.html fillStatistic(). + * + * @param {{ byMonth?: Array, mode: 'all'|'edit'|'view' }} props + */ +function MonthlyStatistics({byMonth, mode}) { + const periods = useMemo(() => { + if (!Array.isArray(byMonth) || byMonth.length < 1) return []; + + // Build periods in chronological order, then reverse for display. + const mapped = byMonth + .map((item, index) => { + const date = item?.date ? new Date(item.date) : null; + if (!date) return null; + + const editCounts = countUsers(item?.users); + const viewCounts = countUsers(item?.usersView); + + const nextDate = index + 1 < byMonth.length ? new Date(byMonth[index + 1].date) : null; + + return { + startDate: date, + endDate: nextDate ? new Date(nextDate.getTime() - MILLISECONDS_PER_DAY) : null, + internalEdit: editCounts.internal, + externalEdit: editCounts.external, + internalView: viewCounts.internal, + externalView: viewCounts.external + }; + }) + .filter(Boolean) + .reverse(); + + return mapped; + }, [byMonth]); + + if (periods.length < 1) return null; + + return ( + <> +
Usage statistics for the reporting period
+ {periods.map((p, idx) => { + const caption = p.endDate + ? `${p.startDate.toLocaleDateString()} - ${p.endDate.toLocaleDateString()}` + : `From ${p.startDate.toLocaleDateString()}`; + + const editor = [ + [p.internalEdit, ''], + [p.externalEdit, ''], + [p.internalEdit + p.externalEdit, ''] + ]; + const viewer = [ + [p.internalView, ''], + [p.externalView, ''], + [p.internalView + p.externalView, ''] + ]; + const desc = ['Internal', 'External', 'Active', '']; + + return ; + })} + + ); +} + +export default memo(MonthlyStatistics); diff --git a/AdminPanel/client/src/components/Statistics/index.js b/AdminPanel/client/src/components/Statistics/index.js index cc58c8e3..74a05c2e 100644 --- a/AdminPanel/client/src/components/Statistics/index.js +++ b/AdminPanel/client/src/components/Statistics/index.js @@ -1,75 +1,231 @@ +import {useEffect, useMemo, useState} from 'react'; import {useQuery} from '@tanstack/react-query'; import TopBlock from './TopBlock/index'; import InfoTable from './InfoTable/index'; +import ModeSwitcher from './ModeSwitcher'; +import MonthlyStatistics from './MonthlyStatistics'; import styles from './styles.module.css'; import {fetchStatistics} from '../../api'; +// Constants +const CRITICAL_COLOR = '#ff0000'; +const CRITICAL_THRESHOLD = 0.1; +const TIME_PERIODS = ['hour', 'day', 'week', 'month']; +const TIME_PERIOD_LABELS = ['Last Hour', '24 Hours', 'Week', 'Month']; +const SECONDS_PER_DAY = 86400; + +/** + * Calculate critical status for remaining values + * @param {number} remaining - Remaining count + * @param {number} limit - Total limit + * @returns {string} 'normal' | 'critical' + */ +const getCriticalStatus = (remaining, limit) => (remaining > limit * CRITICAL_THRESHOLD ? 'normal' : 'critical'); + +// ModeSwitcher moved to ./ModeSwitcher (kept behavior, simplified markup/styles) + +/** + * Statistics component - renders Document Server statistics + * Mirrors branding/info/index.html rendering logic with mode toggling + */ export default function Statistics() { const {data, isLoading, error} = useQuery({ queryKey: ['statistics'], queryFn: fetchStatistics }); - if (isLoading) return
Loading statistics...
; - if (error) return
Error: {error.message}
; - if (!data) return null; + const [mode, setMode] = useState(() => { + try { + const saved = window.localStorage?.getItem('server-info-display-mode'); + return saved || 'all'; + } catch { + return 'all'; + } + }); - const {licenseInfo, quota, connectionsStat, serverInfo} = data; + useEffect(() => { + try { + window.localStorage?.setItem('server-info-display-mode', mode); + } catch { + /* ignore */ + } + }, [mode]); + // Safe defaults to maintain hook order consistency (memoized to avoid dependency changes) + const licenseInfo = useMemo(() => data?.licenseInfo ?? {}, [data?.licenseInfo]); + const quota = useMemo(() => data?.quota ?? {}, [data?.quota]); + const connectionsStat = useMemo(() => data?.connectionsStat ?? {}, [data?.connectionsStat]); + const serverInfo = useMemo(() => data?.serverInfo ?? {}, [data?.serverInfo]); + + // Derived values used across multiple components + const isUsersModel = licenseInfo.usersCount > 0; + const limitEdit = isUsersModel ? licenseInfo.usersCount : licenseInfo.connections; + const limitView = isUsersModel ? licenseInfo.usersViewCount : licenseInfo.connectionsView; + + // Build block const buildDate = licenseInfo.buildDate ? new Date(licenseInfo.buildDate).toLocaleDateString() : ''; + const packageTypeLabel = licenseInfo.packageType === 0 ? 'Open source' : licenseInfo.packageType === 1 ? 'Enterprise Edition' : 'Developer Edition'; const buildBlock = ( -
Type: {licenseInfo.packageType === 0 ? 'Open source' : licenseInfo.packageType === 1 ? 'Enterprise Edition' : 'Developer Edition'}
+
Type: {packageTypeLabel}
Version: {serverInfo.buildVersion}.{serverInfo.buildNumber}
Release date: {buildDate}
); - const licenseBlock = ( - - {licenseInfo.startDate === null ? ( - 'No license' - ) : ( -
Start date: {licenseInfo.startDate ? new Date(licenseInfo.startDate).toLocaleDateString() : ''}
- )} -
- ); - const connectionsBlock = ( - -
Editors: {licenseInfo.connections}
-
Live Viewer: {licenseInfo.connectionsView}
+ + // License block (mirrors fillInfo license validity rendering) + const licenseBlock = (() => { + if (licenseInfo.endDate === null) { + return ( + +
No license
+
+ ); + } + const isLimited = licenseInfo.mode & 1 || licenseInfo.mode & 4; + const licEnd = new Date(licenseInfo.endDate); + const srvDate = new Date(serverInfo.date); + const licType = licenseInfo.type; + const isInvalid = licType === 2 || licType === 1 || licType === 6 || licType === 11; + const isUpdateUnavailable = !isLimited && srvDate > licEnd; + const licValidText = isLimited ? 'Valid: ' : 'Updates available: '; + const licValidColor = isInvalid || isUpdateUnavailable ? CRITICAL_COLOR : undefined; + + const startDateStr = licenseInfo.startDate ? new Date(licenseInfo.startDate).toLocaleDateString() : ''; + const isStartCritical = licType === 16 || (licenseInfo.startDate ? new Date(licenseInfo.startDate) > srvDate : false); + const trialText = licenseInfo.mode & 1 ? 'Trial' : ''; + + return ( + + {startDateStr &&
Start date: {startDateStr}
} +
+ {licValidText} + {licEnd.toLocaleDateString()} +
+ {trialText &&
{trialText}
} +
+ ); + })(); + + // Limits block + const limitTitle = isUsersModel ? 'Users limit' : 'Connections limit'; + const limitsBlock = ( + +
Editors: {limitEdit}
+
Live Viewer: {limitView}
); - const valueEdit = licenseInfo.connections - (quota.edit.connectionsCount || 0); - const valueView = licenseInfo.connectionsView - (quota.view.connectionsCount || 0); - const editor = [ - [quota.edit.connectionsCount || 0, ''], - [valueEdit, valueEdit > licenseInfo.connections * 0.1 ? 'normal' : 'critical'] - ]; - const viewer = [ - [quota.view.connectionsCount || 0, ''], - [valueView, valueView > licenseInfo.connectionsView * 0.1 ? 'normal' : 'critical'] - ]; - const desc = ['Active', 'Remaining']; + // Current activity/usage table + const currentTable = useMemo(() => { + if (isUsersModel) { + // Users model + const days = parseInt(licenseInfo.usersExpire / SECONDS_PER_DAY, 10) || 1; + const qEditUnique = quota?.edit?.usersCount?.unique || 0; + const qEditAnon = quota?.edit?.usersCount?.anonymous || 0; + const qViewUnique = quota?.view?.usersCount?.unique || 0; + const qViewAnon = quota?.view?.usersCount?.anonymous || 0; - const peaksDesc = ['Last Hour', '24 Hours', 'Week', 'Month']; - const peaksEditor = ['hour', 'day', 'week', 'month'].map(k => [connectionsStat?.[k]?.edit?.max || 0]); - const peaksViewer = ['hour', 'day', 'week', 'month'].map(k => [connectionsStat?.[k]?.liveview?.max || 0]); - const avrEditor = ['hour', 'day', 'week', 'month'].map(k => [connectionsStat?.[k]?.edit?.avr || 0]); - const avrViewer = ['hour', 'day', 'week', 'month'].map(k => [connectionsStat?.[k]?.liveview?.avr || 0]); + const remainingEdit = limitEdit - qEditUnique; + const remainingView = limitView - qViewUnique; + + const editor = [ + [qEditUnique, ''], + [qEditUnique - qEditAnon, ''], + [qEditAnon, ''], + [remainingEdit, getCriticalStatus(remainingEdit, limitEdit)] + ]; + const viewer = [ + [qViewUnique, ''], + [qViewUnique - qViewAnon, ''], + [qViewAnon, ''], + [remainingView, getCriticalStatus(remainingView, limitView)] + ]; + const desc = ['Active', 'Internal', 'External', 'Remaining']; + return ( + 1 ? 'days' : 'day'}`} + editor={editor} + viewer={viewer} + desc={desc} + /> + ); + } + + // Connections model + const activeEdit = quota?.edit?.connectionsCount || 0; + const activeView = quota?.view?.connectionsCount || 0; + const remainingEdit = limitEdit - activeEdit; + const remainingView = limitView - activeView; + const editor = [ + [activeEdit, ''], + [remainingEdit, getCriticalStatus(remainingEdit, limitEdit)] + ]; + const viewer = [ + [activeView, ''], + [remainingView, getCriticalStatus(remainingView, limitView)] + ]; + const desc = ['Active', 'Remaining']; + return ; + }, [isUsersModel, licenseInfo, quota, limitEdit, limitView, mode]); + + // Peaks and Averages (only for connections model) + const peaksAverage = useMemo(() => { + if (isUsersModel) return null; + + const editorPeaks = []; + const viewerPeaks = []; + const editorAvr = []; + const viewerAvr = []; + + TIME_PERIODS.forEach((k, index) => { + const item = connectionsStat?.[k]; + if (item?.edit) { + let value = item.edit.max || 0; + editorPeaks[index] = [value, value >= limitEdit ? 'critical' : '']; + value = item.edit.avr || 0; + editorAvr[index] = [value, value >= limitEdit ? 'critical' : '']; + } + if (item?.liveview) { + let value = item.liveview.max || 0; + viewerPeaks[index] = [value, value >= limitView ? 'critical' : '']; + value = item.liveview.avr || 0; + viewerAvr[index] = [value, value >= limitView ? 'critical' : '']; + } + }); + return ( + <> + + + + ); + }, [isUsersModel, connectionsStat, limitEdit, limitView, mode]); + // MonthlyStatistics moved to ./MonthlyStatistics for clarity and to keep this file concise + + // After hooks and memos: show loading/error states + if (error) { + return
Error: {error.message}
; + } + if (isLoading || !data) { + return
Please, wait...
; + } return (
{buildBlock} {licenseBlock} - {connectionsBlock} + {limitsBlock}
- - - + + + + {currentTable} + {peaksAverage} + {isUsersModel && }
); } diff --git a/AdminPanel/client/src/components/Statistics/styles.module.css b/AdminPanel/client/src/components/Statistics/styles.module.css index f12b5d7e..49e6994d 100644 --- a/AdminPanel/client/src/components/Statistics/styles.module.css +++ b/AdminPanel/client/src/components/Statistics/styles.module.css @@ -3,3 +3,18 @@ margin-bottom: 24px; gap: 24px; } + +.modeBar { + margin: 8px 0 16px; + font-size: 14px; +} +.modeLink { + cursor: pointer; + padding: 0 6px; +} +.modeSeparator { + color: #777; +} +.current { + font-weight: 700; +} diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 00000000..b1a4f0f9 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,46 @@ +# info.json Fixtures for Rendering Tests + +This directory contains sample `info.json` payloads that exercise different rendering paths in: + +- Static page: `branding/info/index.html` +- React AdminPanel: `AdminPanel/client/src/components/Statistics/` + +Each file is self-contained and adheres to the server `info.json` schema used by the UI. + +## Automatic Fixture Cycling + +To enable automatic cycling through fixtures on each request, add this code to your `licenseInfo` function: + +```javascript +const path = require('path'); +const fs = require('fs'); + +// Request counter for cycling through fixtures (persistent across calls) +licenseInfo.requestCounter = (licenseInfo.requestCounter || 0) + 1; +licenseInfo.fixtureFiles = licenseInfo.fixtureFiles || []; + +// Load fixture files list on first call +if (licenseInfo.fixtureFiles.length === 0) { + try { + const fixturesDir = path.join(__dirname, '../../../tests/fixtures/info'); + const files = fs.readdirSync(fixturesDir); + licenseInfo.fixtureFiles = files.filter(file => file.endsWith('.json')); + } catch (e) { + // If fixtures directory doesn't exist, continue with normal flow + } +} + +// Cycle through fixtures on every request +if (licenseInfo.fixtureFiles.length > 0) { + const fixtureIndex = (licenseInfo.requestCounter - 1) % licenseInfo.fixtureFiles.length; + const fixturePath = path.join(__dirname, '../../../tests/fixtures/info', licenseInfo.fixtureFiles[fixtureIndex]); + try { + const fixtureData = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); + return res.json(fixtureData); + } catch (e) { + // If fixture fails to load, continue with normal flow + } +} +``` + +## Files diff --git a/tests/fixtures/info/connections_basic.json b/tests/fixtures/info/connections_basic.json new file mode 100644 index 00000000..b99bf756 --- /dev/null +++ b/tests/fixtures/info/connections_basic.json @@ -0,0 +1,42 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-20T00:00:00Z", + "mode": 4, + "endDate": "2026-08-20T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "connections": 50, + "connectionsView": 30, + "usersCount": 0, + "usersViewCount": 0 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "1234", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"connectionsCount": 20}, + "view": {"connectionsCount": 28}, + "byMonth": [] + }, + "connectionsStat": { + "hour": { + "edit": {"max": 52, "avr": 40}, + "liveview": {"max": 29, "avr": 27} + }, + "day": { + "edit": {"max": 45, "avr": 30}, + "liveview": {"max": 30, "avr": 22} + }, + "week": { + "edit": {"max": 50, "avr": 35}, + "liveview": {"max": 31, "avr": 26} + }, + "month": { + "edit": {"max": 40, "avr": 28}, + "liveview": {"max": 25, "avr": 20} + } + } +} diff --git a/tests/fixtures/info/connections_critical_remaining.json b/tests/fixtures/info/connections_critical_remaining.json new file mode 100644 index 00000000..274c2d59 --- /dev/null +++ b/tests/fixtures/info/connections_critical_remaining.json @@ -0,0 +1,25 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-10T00:00:00Z", + "mode": 4, + "endDate": "2026-08-10T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "connections": 20, + "connectionsView": 10, + "usersCount": 0, + "usersViewCount": 0 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "1236", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"connectionsCount": 19}, + "view": {"connectionsCount": 9}, + "byMonth": [] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/connections_missing_periods.json b/tests/fixtures/info/connections_missing_periods.json new file mode 100644 index 00000000..f0baf817 --- /dev/null +++ b/tests/fixtures/info/connections_missing_periods.json @@ -0,0 +1,32 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-15T00:00:00Z", + "mode": 4, + "endDate": "2026-08-15T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "connections": 40, + "connectionsView": 25, + "usersCount": 0, + "usersViewCount": 0 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "1235", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"connectionsCount": 12}, + "view": {"connectionsCount": 9}, + "byMonth": [] + }, + "connectionsStat": { + "hour": { + "edit": {"max": 10, "avr": 8} + }, + "day": { + "liveview": {"max": 20, "avr": 12} + } + } +} diff --git a/tests/fixtures/info/developer_edition_users.json b/tests/fixtures/info/developer_edition_users.json new file mode 100644 index 00000000..8e018dc6 --- /dev/null +++ b/tests/fixtures/info/developer_edition_users.json @@ -0,0 +1,35 @@ +{ + "licenseInfo": { + "packageType": 2, + "buildDate": "2025-08-18T00:00:00Z", + "mode": 4, + "endDate": "2026-08-18T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "usersCount": 200, + "usersViewCount": 150, + "usersExpire": 2592000 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2007", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 120, "anonymous": 30}}, + "view": {"usersCount": {"unique": 100, "anonymous": 20}}, + "byMonth": [ + { + "date": "2025-06-01T00:00:00Z", + "users": {"a": {}, "b": {}, "c": {"anonym": true}}, + "usersView": {"d": {}, "e": {"anonym": true}} + }, + { + "date": "2025-07-01T00:00:00Z", + "users": {"f": {}, "g": {"anonym": true}}, + "usersView": {"h": {}, "i": {}, "j": {"anonym": true}} + } + ] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/open_source_connection.json b/tests/fixtures/info/open_source_connection.json new file mode 100644 index 00000000..3a1fd848 --- /dev/null +++ b/tests/fixtures/info/open_source_connection.json @@ -0,0 +1,27 @@ +{ + "licenseInfo": { + "packageType": 0, + "buildDate": "2025-08-12T00:00:00Z", + "mode": 4, + "endDate": "2026-08-12T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "connections": 15, + "connectionsView": 10, + "usersCount": 0, + "usersViewCount": 0 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "1237", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"connectionsCount": 5}, + "view": {"connectionsCount": 3}, + "byMonth": [] + }, + "connectionsStat": { + "hour": {"edit": {"max": 12, "avr": 7}, "liveview": {"max": 9, "avr": 5}} + } +} diff --git a/tests/fixtures/info/users_basic.json b/tests/fixtures/info/users_basic.json new file mode 100644 index 00000000..ce220e08 --- /dev/null +++ b/tests/fixtures/info/users_basic.json @@ -0,0 +1,35 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-01T00:00:00Z", + "mode": 4, + "endDate": "2026-08-01T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "usersCount": 100, + "usersViewCount": 60, + "usersExpire": 2592000 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2001", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 55, "anonymous": 5}}, + "view": {"usersCount": {"unique": 45, "anonymous": 2}}, + "byMonth": [ + { + "date": "2025-06-01T00:00:00Z", + "users": {"1": {}, "2": {"anonym": true}, "3": {}}, + "usersView": {"10": {}, "11": {"anonym": true}} + }, + { + "date": "2025-07-01T00:00:00Z", + "users": {"4": {}, "5": {}, "6": {"anonym": true}}, + "usersView": {"12": {}, "13": {"anonym": true}, "14": {"anonym": true}} + } + ] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/users_critical_remaining.json b/tests/fixtures/info/users_critical_remaining.json new file mode 100644 index 00000000..dcde48a2 --- /dev/null +++ b/tests/fixtures/info/users_critical_remaining.json @@ -0,0 +1,24 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-05T00:00:00Z", + "mode": 4, + "endDate": "2026-08-05T00:00:00Z", + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "usersCount": 10, + "usersViewCount": 5, + "usersExpire": 604800 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2002", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 10, "anonymous": 2}}, + "view": {"usersCount": {"unique": 5, "anonymous": 1}}, + "byMonth": [] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/users_license_invalid_type.json b/tests/fixtures/info/users_license_invalid_type.json new file mode 100644 index 00000000..43f8303b --- /dev/null +++ b/tests/fixtures/info/users_license_invalid_type.json @@ -0,0 +1,24 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-01T00:00:00Z", + "mode": 4, + "endDate": "2025-12-01T00:00:00Z", + "type": 2, + "startDate": "2025-08-01T00:00:00Z", + "usersCount": 100, + "usersViewCount": 80, + "usersExpire": 2592000 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2005", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 50, "anonymous": 10}}, + "view": {"usersCount": {"unique": 40, "anonymous": 5}}, + "byMonth": [] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/users_no_license.json b/tests/fixtures/info/users_no_license.json new file mode 100644 index 00000000..c9542578 --- /dev/null +++ b/tests/fixtures/info/users_no_license.json @@ -0,0 +1,24 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-08-01T00:00:00Z", + "mode": 0, + "endDate": null, + "type": 0, + "startDate": "2025-08-01T00:00:00Z", + "usersCount": 50, + "usersViewCount": 40, + "usersExpire": 1209600 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2003", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 5, "anonymous": 1}}, + "view": {"usersCount": {"unique": 2, "anonymous": 0}}, + "byMonth": [] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/users_trial_limited_start_critical.json b/tests/fixtures/info/users_trial_limited_start_critical.json new file mode 100644 index 00000000..4abe8163 --- /dev/null +++ b/tests/fixtures/info/users_trial_limited_start_critical.json @@ -0,0 +1,24 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-09-01T00:00:00Z", + "mode": 5, + "endDate": "2026-09-01T00:00:00Z", + "type": 16, + "startDate": "2025-09-10T00:00:00Z", + "usersCount": 100, + "usersViewCount": 100, + "usersExpire": 2592000 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2004", + "date": "2025-09-05T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 1, "anonymous": 0}}, + "view": {"usersCount": {"unique": 1, "anonymous": 0}}, + "byMonth": [] + }, + "connectionsStat": {} +} diff --git a/tests/fixtures/info/users_updates_unavailable.json b/tests/fixtures/info/users_updates_unavailable.json new file mode 100644 index 00000000..c2d62324 --- /dev/null +++ b/tests/fixtures/info/users_updates_unavailable.json @@ -0,0 +1,24 @@ +{ + "licenseInfo": { + "packageType": 1, + "buildDate": "2025-07-01T00:00:00Z", + "mode": 0, + "endDate": "2025-09-01T00:00:00Z", + "type": 0, + "startDate": "2025-07-01T00:00:00Z", + "usersCount": 100, + "usersViewCount": 80, + "usersExpire": 2592000 + }, + "serverInfo": { + "buildVersion": "8.2", + "buildNumber": "2006", + "date": "2025-09-10T12:00:00Z" + }, + "quota": { + "edit": {"usersCount": {"unique": 50, "anonymous": 5}}, + "view": {"usersCount": {"unique": 40, "anonymous": 4}}, + "byMonth": [] + }, + "connectionsStat": {} +}