diff --git a/components/messages.js b/components/messages.js
index 28f5bf18..cc1d0e86 100644
--- a/components/messages.js
+++ b/components/messages.js
@@ -97,6 +97,7 @@ export const labels = defineMessages({
all: { id: 'label.all', defaultMessage: 'All' },
sessions: { id: 'label.sessions', defaultMessage: 'Sessions' },
pageNotFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
+ logs: { id: 'label.activity-log', defaultMessage: 'Activity log' },
});
export const messages = defineMessages({
@@ -168,6 +169,14 @@ export const messages = defineMessages({
id: 'message.team-not-found',
defaultMessage: 'Team not found.',
},
+ visitorLog: {
+ id: 'message.visitor-log',
+ defaultMessage: 'Visitor from {country} using {browser} on {os} {device}',
+ },
+ eventLog: {
+ id: 'message.event-log',
+ defaultMessage: '{event} on {url}',
+ },
});
export const devices = defineMessages({
diff --git a/components/metrics/RealtimeChart.js b/components/metrics/RealtimeChart.js
index 6ead67bf..00bd0051 100644
--- a/components/metrics/RealtimeChart.js
+++ b/components/metrics/RealtimeChart.js
@@ -8,7 +8,7 @@ function mapData(data) {
let last = 0;
const arr = [];
- data.reduce((obj, { timestamp }) => {
+ data?.reduce((obj, { timestamp }) => {
const t = startOfMinute(new Date(timestamp));
if (t.getTime() > last) {
obj = { t: format(t, 'yyyy-LL-dd HH:mm:00'), y: 1 };
@@ -33,16 +33,9 @@ export default function RealtimeChart({ data, unit, ...props }) {
return { pageviews: [], sessions: [] };
}
- const visitors = data.sessions?.reduce((arr, val) => {
- if (!arr.find(({ sessionId }) => sessionId === val.sessionId)) {
- return arr.concat(val);
- }
- return arr;
- }, []);
-
return {
pageviews: getDateArray(mapData(data.pageviews), startDate, endDate, unit),
- sessions: getDateArray(mapData(visitors), startDate, endDate, unit),
+ sessions: getDateArray(mapData(data.visitors), startDate, endDate, unit),
};
}, [data, startDate, endDate, unit]);
diff --git a/components/pages/realtime/RealtimeDashboard.js b/components/pages/realtime/RealtimeDashboard.js
index a59a6d07..9ac6a863 100644
--- a/components/pages/realtime/RealtimeDashboard.js
+++ b/components/pages/realtime/RealtimeDashboard.js
@@ -58,7 +58,7 @@ export default function RealtimeDashboard({ websiteId }) {
const realtimeData = useMemo(() => {
if (!currentData) {
- return { pageviews: [], sessions: [], events: [], countries: [] };
+ return { pageviews: [], sessions: [], events: [], countries: [], visitors: [] };
}
currentData.countries = percentFilter(
@@ -84,6 +84,13 @@ export default function RealtimeDashboard({ websiteId }) {
.sort(firstBy('y', -1)),
);
+ currentData.visitors = currentData.sessions.reduce((arr, val) => {
+ if (!arr.find(({ sessionId }) => sessionId === val.sessionId)) {
+ return arr.concat(val);
+ }
+ return arr;
+ }, []);
+
return currentData;
}, [currentData]);
diff --git a/components/pages/realtime/RealtimeHeader.js b/components/pages/realtime/RealtimeHeader.js
index bfcce6e9..15730d5d 100644
--- a/components/pages/realtime/RealtimeHeader.js
+++ b/components/pages/realtime/RealtimeHeader.js
@@ -5,14 +5,7 @@ import styles from './RealtimeHeader.module.css';
export default function RealtimeHeader({ data = {} }) {
const { formatMessage } = useIntl();
- const { pageviews, sessions, events, countries } = data;
-
- const visitors = sessions?.reduce((arr, { sessionId }) => {
- if (sessionId && !arr.includes(sessionId)) {
- return arr.concat(sessionId);
- }
- return arr;
- }, []);
+ const { pageviews, visitors, events, countries } = data;
return (
diff --git a/components/pages/realtime/RealtimeLog.js b/components/pages/realtime/RealtimeLog.js
index 25fba487..06b7537e 100644
--- a/components/pages/realtime/RealtimeLog.js
+++ b/components/pages/realtime/RealtimeLog.js
@@ -1,11 +1,11 @@
import { useMemo, useState } from 'react';
-import { StatusLight, Icon } from 'react-basics';
+import { StatusLight, Icon, Text } from 'react-basics';
import { useIntl, FormattedMessage } from 'react-intl';
import { FixedSizeList } from 'react-window';
import firstBy from 'thenby';
import FilterButtons from 'components/common/FilterButtons';
import NoData from 'components/common/NoData';
-import { getDeviceMessage, labels } from 'components/messages';
+import { getDeviceMessage, labels, messages } from 'components/messages';
import useLocale from 'hooks/useLocale';
import useCountryNames from 'hooks/useCountryNames';
import { BROWSERS } from 'lib/constants';
@@ -15,12 +15,12 @@ import { safeDecodeURI } from 'next-basics';
import Icons from 'components/icons';
import styles from './RealtimeLog.module.css';
-const TYPE_ALL = 'type-all';
-const TYPE_PAGEVIEW = 'type-pageview';
-const TYPE_SESSION = 'type-session';
-const TYPE_EVENT = 'type-event';
+const TYPE_ALL = 'all';
+const TYPE_PAGEVIEW = 'pageview';
+const TYPE_SESSION = 'session';
+const TYPE_EVENT = 'event';
-const TYPE_ICONS = {
+const icons = {
[TYPE_PAGEVIEW]:
,
[TYPE_SESSION]:
,
[TYPE_EVENT]:
,
@@ -32,30 +32,6 @@ export default function RealtimeLog({ data, websiteDomain }) {
const countryNames = useCountryNames(locale);
const [filter, setFilter] = useState(TYPE_ALL);
- const logs = useMemo(() => {
- if (!data) {
- return [];
- }
-
- const { pageviews, sessions, events } = data;
- const logs = [...pageviews, ...sessions, ...events].sort(firstBy('createdAt', -1));
- if (filter) {
- return logs.filter(row => getType(row) === filter);
- }
- return logs;
- }, [data, filter]);
-
- const uuids = useMemo(() => {
- if (!data) {
- return [];
- }
-
- return data.sessions.reduce((obj, { sessionId, sessionUuid }) => {
- obj[sessionId] = sessionUuid;
- return obj;
- }, {});
- }, [data]);
-
const buttons = [
{
label: formatMessage(labels.all),
@@ -66,7 +42,7 @@ export default function RealtimeLog({ data, websiteDomain }) {
key: TYPE_PAGEVIEW,
},
{
- label: formatMessage(labels.sessions),
+ label: formatMessage(labels.visitors),
key: TYPE_SESSION,
},
{
@@ -75,42 +51,41 @@ export default function RealtimeLog({ data, websiteDomain }) {
},
];
- function getType({ pageviewId, sessionId, eventId }) {
- if (eventId) {
- return TYPE_EVENT;
- }
- if (pageviewId) {
- return TYPE_PAGEVIEW;
- }
- if (sessionId) {
- return TYPE_SESSION;
- }
- return null;
- }
+ const getTime = ({ createdAt }) => dateFormat(new Date(createdAt), 'pp', locale);
- function getIcon(row) {
- return TYPE_ICONS[getType(row)];
- }
+ const getColor = ({ sessionId }) => stringToColor(sessionId);
- function getDetail({
- eventName,
- pageviewId,
- sessionId,
- url,
- browser,
- os,
- country,
- device,
- websiteId,
- }) {
- if (eventName) {
- return
{eventName}
;
+ const getIcon = ({ __type }) => icons[__type];
+
+ const getDetail = log => {
+ const { __type, eventName, url, browser, os, country, device } = log;
+
+ if (__type === TYPE_EVENT) {
+ return (
+
{eventName || formatMessage(labels.unknown)},
+ url: (
+
+ {url}
+
+ ),
+ }}
+ />
+ );
}
- if (pageviewId) {
+
+ if (__type === TYPE_PAGEVIEW) {
return (
@@ -118,11 +93,11 @@ export default function RealtimeLog({ data, websiteDomain }) {
);
}
- if (sessionId) {
+
+ if (__type === TYPE_SESSION) {
return (
{countryNames[country] || formatMessage(labels.unknown)},
browser: {BROWSERS[browser]},
@@ -132,17 +107,7 @@ export default function RealtimeLog({ data, websiteDomain }) {
/>
);
}
- }
-
- function getTime({ createdAt }) {
- return dateFormat(new Date(createdAt), 'pp', locale);
- }
-
- function getColor(row) {
- const { sessionId } = row;
-
- return stringToColor(uuids[sessionId] || `${sessionId}}`);
- }
+ };
const Row = ({ index, style }) => {
const row = logs[index];
@@ -153,19 +118,32 @@ export default function RealtimeLog({ data, websiteDomain }) {
{getTime(row)}
-
- {getDetail(row)}
+ {getIcon(row)}
+ {getDetail(row)}
);
};
+ const logs = useMemo(() => {
+ if (!data) {
+ return [];
+ }
+
+ const { pageviews, visitors, events } = data;
+ const logs = [...pageviews, ...visitors, ...events].sort(firstBy('createdAt', -1));
+
+ if (filter !== TYPE_ALL) {
+ return logs.filter(({ __type }) => __type === filter);
+ }
+
+ return logs;
+ }, [data, filter]);
+
return (
-
-
-
+
{formatMessage(labels.logs)}
{logs?.length === 0 && }
{logs?.length > 0 && (
diff --git a/components/pages/realtime/RealtimeLog.module.css b/components/pages/realtime/RealtimeLog.module.css
index 292bbd8c..7e54e34c 100644
--- a/components/pages/realtime/RealtimeLog.module.css
+++ b/components/pages/realtime/RealtimeLog.module.css
@@ -1,16 +1,14 @@
.table {
- font-size: var(--font-size-xs);
+ font-size: var(--font-size-sm);
overflow: hidden;
height: 100%;
- display: grid;
- grid-template-rows: fit-content(100%) fit-content(100%) auto;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
- font-size: 16px;
+ font-size: var(--font-size-md);
line-height: 40px;
font-weight: 600;
}
@@ -18,6 +16,7 @@
.row {
display: flex;
align-items: center;
+ gap: 10px;
height: 40px;
border-bottom: 1px solid var(--base300);
}
@@ -44,6 +43,7 @@
.detail {
display: flex;
flex: 1;
+ gap: 10px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/queries/analytics/stats/getRealtimeData.ts b/queries/analytics/stats/getRealtimeData.ts
index 052259fb..989194bf 100644
--- a/queries/analytics/stats/getRealtimeData.ts
+++ b/queries/analytics/stats/getRealtimeData.ts
@@ -14,15 +14,15 @@ export async function getRealtimeData(websiteId, time) {
return data.map(props => ({
...props,
__id: md5(id, ...Object.values(props)),
- timestamp: props.timestamp * 1000,
- timestampCompare: new Date(props.createdAt).getTime(),
+ __type: id,
+ timestamp: props.timestamp ? props.timestamp * 1000 : new Date(props.createdAt).getTime(),
}));
};
return {
- pageviews: decorate('pageviews', pageviews),
- sessions: decorate('sessions', sessions),
- events: decorate('events', events),
+ pageviews: decorate('pageview', pageviews),
+ sessions: decorate('session', sessions),
+ events: decorate('event', events),
timestamp: Date.now(),
};
}