@ossy/analytics 3.7.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/AnalyticsProductHome.jsx +86 -58
- package/src/en.translations.json +2 -0
- package/src/sv.translations.json +2 -0
- package/src/usePageViewStats.js +18 -13
- package/src/useWorkspaceKpis.js +16 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/analytics",
|
|
3
3
|
"description": "Analytics module — workspace insight KPIs",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.9.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"module": "./src/index.js",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ossy/event-store": "^3.
|
|
29
|
-
"@ossy/workspaces": "^3.
|
|
28
|
+
"@ossy/event-store": "^3.8.0",
|
|
29
|
+
"@ossy/workspaces": "^3.9.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@ossy/app": "*",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"@jest/globals": "^30.2.0",
|
|
43
43
|
"jest": "^30.2.0"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "f404be69becb27a1fd853a6ff1903554e76e7d17"
|
|
46
46
|
}
|
|
@@ -3,16 +3,37 @@ import { Text, View, Page, useLocale } from '@ossy/design-system'
|
|
|
3
3
|
import { usePageViewStats } from './usePageViewStats.js'
|
|
4
4
|
import { useWorkspaceKpis } from './useWorkspaceKpis.js'
|
|
5
5
|
|
|
6
|
+
function SectionPanel ({ title, children }) {
|
|
7
|
+
return (
|
|
8
|
+
<View surface="primary" roundness="xs" style={{ minWidth: 0, overflow: 'hidden' }}>
|
|
9
|
+
<View stack bordered>
|
|
10
|
+
<View.Item
|
|
11
|
+
data-surface="primary"
|
|
12
|
+
layout="row"
|
|
13
|
+
style={{
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
justifyContent: 'space-between',
|
|
16
|
+
padding: 'var(--space-s) var(--space-m)',
|
|
17
|
+
gap: 'var(--space-m)',
|
|
18
|
+
height: '48px',
|
|
19
|
+
flexShrink: 0,
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<Text variant="small" as="h2" style={{ fontWeight: 'bold' }} text={title} />
|
|
23
|
+
</View.Item>
|
|
24
|
+
<View.Item>
|
|
25
|
+
{children}
|
|
26
|
+
</View.Item>
|
|
27
|
+
</View>
|
|
28
|
+
</View>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
6
32
|
function SummaryCard ({ label, value }) {
|
|
7
33
|
return (
|
|
8
|
-
<View
|
|
9
|
-
surface="primary"
|
|
10
|
-
roundness="m"
|
|
11
|
-
inset="m"
|
|
12
|
-
style={{ border: '1px solid var(--separator)' }}
|
|
13
|
-
>
|
|
34
|
+
<View gap="xs" style={{ minWidth: 140, flex: '1 1 140px' }}>
|
|
14
35
|
<Text variant="small" style={{ opacity: 0.75 }}>{label}</Text>
|
|
15
|
-
<Text variant="heading-secondary" as="
|
|
36
|
+
<Text variant="heading-secondary" as="p">{value}</Text>
|
|
16
37
|
</View>
|
|
17
38
|
)
|
|
18
39
|
}
|
|
@@ -38,6 +59,9 @@ export default function AnalyticsProductHome ({ workspace }) {
|
|
|
38
59
|
const workspaceKpis = useWorkspaceKpis({ workspaceId: workspace?.id })
|
|
39
60
|
|
|
40
61
|
const { status, totalViews, dailyViews, topPaths } = pageViews
|
|
62
|
+
const kpisSettled = workspaceKpis.status === 'success' || workspaceKpis.status === 'error'
|
|
63
|
+
const viewsSettled = status === 'success' || status === 'error'
|
|
64
|
+
const pageLoading = !kpisSettled || !viewsSettled
|
|
41
65
|
|
|
42
66
|
return (
|
|
43
67
|
<Page
|
|
@@ -45,72 +69,76 @@ export default function AnalyticsProductHome ({ workspace }) {
|
|
|
45
69
|
description="analytics.home.description"
|
|
46
70
|
>
|
|
47
71
|
<View gap="m">
|
|
48
|
-
{
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
72
|
+
{pageLoading && (
|
|
73
|
+
<SectionPanel title="analytics.home.overview">
|
|
74
|
+
<View inset="m">
|
|
75
|
+
<Text>{t('analytics.home.loading')}</Text>
|
|
76
|
+
</View>
|
|
77
|
+
</SectionPanel>
|
|
52
78
|
)}
|
|
53
79
|
|
|
54
|
-
{workspaceKpis.status === 'error' && (
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
|
|
80
|
+
{!pageLoading && workspaceKpis.status === 'error' && (
|
|
81
|
+
<SectionPanel title="analytics.home.overview">
|
|
82
|
+
<View inset="m">
|
|
83
|
+
<Text>{t('analytics.home.workspaceKpisError')}</Text>
|
|
84
|
+
</View>
|
|
85
|
+
</SectionPanel>
|
|
58
86
|
)}
|
|
59
87
|
|
|
60
|
-
{workspaceKpis.status === 'success' && (
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
{!pageLoading && workspaceKpis.status === 'success' && (
|
|
89
|
+
<SectionPanel title="analytics.home.overview">
|
|
90
|
+
<View layout="row-wrap" gap="m" inset="m">
|
|
91
|
+
<SummaryCard label={t('analytics.home.members')} value={`${workspaceKpis.members}`} />
|
|
92
|
+
<SummaryCard label={t('analytics.home.resources')} value={`${workspaceKpis.resources}`} />
|
|
93
|
+
</View>
|
|
94
|
+
</SectionPanel>
|
|
65
95
|
)}
|
|
66
96
|
|
|
67
|
-
{status === 'error' && (
|
|
68
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
97
|
+
{!pageLoading && status === 'error' && (
|
|
98
|
+
<SectionPanel title="analytics.home.traffic">
|
|
99
|
+
<View inset="m">
|
|
100
|
+
<Text>{t('analytics.home.error')}</Text>
|
|
101
|
+
</View>
|
|
102
|
+
</SectionPanel>
|
|
71
103
|
)}
|
|
72
104
|
|
|
73
|
-
{status === 'success' && (
|
|
105
|
+
{!pageLoading && status === 'success' && (
|
|
74
106
|
<>
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
<SectionPanel title="analytics.home.traffic">
|
|
108
|
+
<View layout="row-wrap" gap="m" inset="m">
|
|
109
|
+
<SummaryCard label={t('analytics.home.totalPageViews')} value={`${totalViews}`} />
|
|
110
|
+
<SummaryCard label={t('analytics.home.trackedPages')} value={`${topPaths.length}`} />
|
|
111
|
+
<SummaryCard label={t('analytics.home.daysWithTraffic')} value={`${dailyViews.length}`} />
|
|
112
|
+
</View>
|
|
113
|
+
</SectionPanel>
|
|
80
114
|
|
|
81
115
|
<View layout="row" gap="m" style={{ alignItems: 'flex-start' }}>
|
|
82
|
-
<View
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<View key={item.path || 'unknown'} layout="row" justifyContent="space-between">
|
|
93
|
-
<Text variant="small">{item.path || t('analytics.home.unknownPath')}</Text>
|
|
94
|
-
<Text variant="small">{item.views}</Text>
|
|
116
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
117
|
+
<SectionPanel title="analytics.home.topPaths">
|
|
118
|
+
<View gap="s" inset="m">
|
|
119
|
+
{topPaths.length === 0 && <Text variant="small" text="analytics.home.noPageViews" />}
|
|
120
|
+
{topPaths.map((item) => (
|
|
121
|
+
<View key={item.path || 'unknown'} layout="row" justifyContent="space-between">
|
|
122
|
+
<Text variant="small">{item.path || t('analytics.home.unknownPath')}</Text>
|
|
123
|
+
<Text variant="small">{item.views}</Text>
|
|
124
|
+
</View>
|
|
125
|
+
))}
|
|
95
126
|
</View>
|
|
96
|
-
|
|
127
|
+
</SectionPanel>
|
|
97
128
|
</View>
|
|
98
129
|
|
|
99
|
-
<View
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<View key={item.date} layout="row" justifyContent="space-between">
|
|
110
|
-
<Text variant="small">{item.date}</Text>
|
|
111
|
-
<Text variant="small">{item.views}</Text>
|
|
130
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
131
|
+
<SectionPanel title="analytics.home.dailyPageViews">
|
|
132
|
+
<View gap="s" inset="m">
|
|
133
|
+
{dailyViews.length === 0 && <Text variant="small" text="analytics.home.noDailyData" />}
|
|
134
|
+
{dailyViews.map((item) => (
|
|
135
|
+
<View key={item.date} layout="row" justifyContent="space-between">
|
|
136
|
+
<Text variant="small">{item.date}</Text>
|
|
137
|
+
<Text variant="small">{item.views}</Text>
|
|
138
|
+
</View>
|
|
139
|
+
))}
|
|
112
140
|
</View>
|
|
113
|
-
|
|
141
|
+
</SectionPanel>
|
|
114
142
|
</View>
|
|
115
143
|
</View>
|
|
116
144
|
</>
|
package/src/en.translations.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"analytics/home.documentTitle": "Analytics",
|
|
3
3
|
"analytics.home.title": "Analytics",
|
|
4
4
|
"analytics.home.description": "Workspace insight KPIs for members, resources, and traffic.",
|
|
5
|
+
"analytics.home.overview": "Overview",
|
|
6
|
+
"analytics.home.traffic": "Traffic",
|
|
5
7
|
"analytics.home.loading": "Loading analytics...",
|
|
6
8
|
"analytics.home.error": "Could not load analytics data.",
|
|
7
9
|
"analytics.home.members": "Members",
|
package/src/sv.translations.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"analytics/home.documentTitle": "Analys",
|
|
3
3
|
"analytics.home.title": "Analys",
|
|
4
4
|
"analytics.home.description": "Workspace-KPIer för medlemmar, resurser och trafik.",
|
|
5
|
+
"analytics.home.overview": "Översikt",
|
|
6
|
+
"analytics.home.traffic": "Trafik",
|
|
5
7
|
"analytics.home.loading": "Laddar analys...",
|
|
6
8
|
"analytics.home.error": "Kunde inte ladda analysdata.",
|
|
7
9
|
"analytics.home.members": "Medlemmar",
|
package/src/usePageViewStats.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { GetPageViewStats } from '@ossy/resources'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
4
|
|
|
5
5
|
export function usePageViewStats({ workspaceId, from, to, limit = 10 }) {
|
|
6
6
|
const sdk = useSdk()
|
|
7
|
-
const
|
|
7
|
+
const invokeRef = useRef(sdk.invoke)
|
|
8
|
+
invokeRef.current = sdk.invoke
|
|
9
|
+
|
|
10
|
+
const [status, setStatus] = useState('idle')
|
|
8
11
|
const [data, setData] = useState({
|
|
9
12
|
totalViews: 0,
|
|
10
13
|
dailyViews: [],
|
|
11
14
|
topPaths: [],
|
|
12
15
|
})
|
|
13
16
|
|
|
14
|
-
const payload = useMemo(() => ({
|
|
15
|
-
from,
|
|
16
|
-
to,
|
|
17
|
-
limit,
|
|
18
|
-
}), [from, to, limit])
|
|
19
|
-
|
|
20
17
|
useEffect(() => {
|
|
21
|
-
if (!workspaceId) return
|
|
18
|
+
if (!workspaceId) return undefined
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
let cancelled = false
|
|
21
|
+
setStatus((current) => (current === 'success' ? current : 'loading'))
|
|
22
|
+
|
|
23
|
+
invokeRef.current(GetPageViewStats, { from, to, limit })
|
|
24
|
+
.then((json) => {
|
|
25
|
+
if (cancelled) return
|
|
26
26
|
setData({
|
|
27
27
|
totalViews: json?.totalViews || 0,
|
|
28
28
|
dailyViews: json?.dailyViews || [],
|
|
@@ -31,9 +31,14 @@ export function usePageViewStats({ workspaceId, from, to, limit = 10 }) {
|
|
|
31
31
|
setStatus('success')
|
|
32
32
|
})
|
|
33
33
|
.catch(() => {
|
|
34
|
+
if (cancelled) return
|
|
34
35
|
setStatus('error')
|
|
35
36
|
})
|
|
36
|
-
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true
|
|
40
|
+
}
|
|
41
|
+
}, [workspaceId, from, to, limit])
|
|
37
42
|
|
|
38
43
|
return {
|
|
39
44
|
status,
|
package/src/useWorkspaceKpis.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { useSdk } from '@ossy/sdk-react'
|
|
3
3
|
import { metadata as GetWorkspaceKpis } from './get-workspace-kpis.action.js'
|
|
4
4
|
|
|
5
5
|
export function useWorkspaceKpis({ workspaceId }) {
|
|
6
6
|
const sdk = useSdk()
|
|
7
|
+
const invokeRef = useRef(sdk.invoke)
|
|
8
|
+
invokeRef.current = sdk.invoke
|
|
9
|
+
|
|
7
10
|
const [status, setStatus] = useState('idle')
|
|
8
11
|
const [data, setData] = useState({
|
|
9
12
|
members: 0,
|
|
@@ -11,11 +14,14 @@ export function useWorkspaceKpis({ workspaceId }) {
|
|
|
11
14
|
})
|
|
12
15
|
|
|
13
16
|
useEffect(() => {
|
|
14
|
-
if (!workspaceId) return
|
|
17
|
+
if (!workspaceId) return undefined
|
|
18
|
+
|
|
19
|
+
let cancelled = false
|
|
20
|
+
setStatus((current) => (current === 'success' ? current : 'loading'))
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
sdk.invoke(GetWorkspaceKpis, {})
|
|
22
|
+
invokeRef.current(GetWorkspaceKpis, {})
|
|
18
23
|
.then((json) => {
|
|
24
|
+
if (cancelled) return
|
|
19
25
|
setData({
|
|
20
26
|
members: json?.members || 0,
|
|
21
27
|
resources: json?.resources || 0,
|
|
@@ -23,9 +29,14 @@ export function useWorkspaceKpis({ workspaceId }) {
|
|
|
23
29
|
setStatus('success')
|
|
24
30
|
})
|
|
25
31
|
.catch(() => {
|
|
32
|
+
if (cancelled) return
|
|
26
33
|
setStatus('error')
|
|
27
34
|
})
|
|
28
|
-
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true
|
|
38
|
+
}
|
|
39
|
+
}, [workspaceId])
|
|
29
40
|
|
|
30
41
|
return {
|
|
31
42
|
status,
|