+
{formatMessage(messages.error)}
diff --git a/src/components/common/FilterLink.tsx b/src/components/common/FilterLink.tsx
index f91e1459..a9030227 100644
--- a/src/components/common/FilterLink.tsx
+++ b/src/components/common/FilterLink.tsx
@@ -10,9 +10,9 @@ import styles from './FilterLink.module.css';
export interface FilterLinkProps {
id: string;
value: string;
- label: string;
- externalUrl: string;
- className: string;
+ label?: string;
+ externalUrl?: string;
+ className?: string;
children: ReactNode;
}
diff --git a/src/components/common/HamburgerButton.tsx b/src/components/common/HamburgerButton.tsx
index 380392c8..5a81f3a3 100644
--- a/src/components/common/HamburgerButton.tsx
+++ b/src/components/common/HamburgerButton.tsx
@@ -1,7 +1,6 @@
-import { Button, Icon } from 'react-basics';
+import { Button, Icon, Icons } from 'react-basics';
import { useState } from 'react';
import MobileMenu from './MobileMenu';
-import Icons from 'components/icons';
export function HamburgerButton({ menuItems }: { menuItems: any[] }) {
const [active, setActive] = useState(false);
diff --git a/src/components/icons.ts b/src/components/icons.ts
index 8eb1f8b0..01d7caf5 100644
--- a/src/components/icons.ts
+++ b/src/components/icons.ts
@@ -22,7 +22,7 @@ import User from 'assets/user.svg';
import Users from 'assets/users.svg';
import Visitor from 'assets/visitor.svg';
-const icons: any = {
+const icons = {
...Icons,
AddUser,
Bars,
diff --git a/src/components/layout/PageHeader.module.css b/src/components/layout/PageHeader.module.css
index 8e615b93..a4eeb4c6 100644
--- a/src/components/layout/PageHeader.module.css
+++ b/src/components/layout/PageHeader.module.css
@@ -36,9 +36,4 @@
.header {
margin-bottom: 10px;
}
-
- .actions {
- flex-basis: 100%;
- order: -1;
- }
}
diff --git a/src/components/metrics/FilterTags.js b/src/components/metrics/FilterTags.js
index 554c223a..db8fdcbd 100644
--- a/src/components/metrics/FilterTags.js
+++ b/src/components/metrics/FilterTags.js
@@ -2,10 +2,12 @@ import { safeDecodeURI } from 'next-basics';
import { Button, Icon, Icons, Text } from 'react-basics';
import useNavigation from 'components/hooks/useNavigation';
import useMessages from 'components/hooks/useMessages';
+import useFormat from 'components/hooks/useFormat';
import styles from './FilterTags.module.css';
export function FilterTags({ params }) {
const { formatMessage, labels } = useMessages();
+ const { formatValue } = useFormat();
const {
router,
makeUrl,
@@ -34,7 +36,7 @@ export function FilterTags({ params }) {
return (
handleCloseFilter(key)}>
- {`${key}`} = {`${safeDecodeURI(params[key])}`}
+ {formatMessage(labels[key])} = {formatValue(safeDecodeURI(params[key]), key)}
diff --git a/src/components/metrics/FilterTags.module.css b/src/components/metrics/FilterTags.module.css
index c228dc4e..32bc2f6f 100644
--- a/src/components/metrics/FilterTags.module.css
+++ b/src/components/metrics/FilterTags.module.css
@@ -24,3 +24,7 @@
.tag:hover {
background: var(--blue200);
}
+
+.tag b {
+ text-transform: lowercase;
+}
diff --git a/src/lib/date.ts b/src/lib/date.ts
index a6c2b17b..51057309 100644
--- a/src/lib/date.ts
+++ b/src/lib/date.ts
@@ -194,7 +194,7 @@ export function incrementDateRange(value, increment) {
const { num, unit } = selectedUnit;
- const sub = num * increment;
+ const sub = Math.abs(num) * increment;
switch (unit) {
case 'hour':
diff --git a/src/lib/detect.ts b/src/lib/detect.ts
index 3b2f9021..dab08312 100644
--- a/src/lib/detect.ts
+++ b/src/lib/detect.ts
@@ -107,11 +107,16 @@ export async function getLocation(ip, req) {
const result = lookup.get(ip);
if (result) {
+ const country = result.country?.iso_code ?? result?.registered_country?.iso_code;
+ const subdivision1 = result.subdivisions?.[0]?.iso_code;
+ const subdivision2 = result.subdivisions?.[1]?.names?.en;
+ const city = result.city?.names?.en;
+
return {
- country: result.country?.iso_code ?? result?.registered_country?.iso_code,
- subdivision1: result.subdivisions?.[0]?.iso_code,
- subdivision2: result.subdivisions?.[1]?.names?.en,
- city: result.city?.names?.en,
+ country,
+ subdivision1: getRegionCode(country, subdivision1),
+ subdivision2,
+ city,
};
}
}
diff --git a/src/lib/middleware.ts b/src/lib/middleware.ts
index 5796009b..91fb6c7c 100644
--- a/src/lib/middleware.ts
+++ b/src/lib/middleware.ts
@@ -61,7 +61,9 @@ export const useAuth = createMiddleware(async (req, res, next) => {
} else if (redis.enabled && authKey) {
const key = await redis.client.get(authKey);
- user = await getUserById(key?.userId);
+ if (key?.userId) {
+ user = await getUserById(key.userId);
+ }
}
if (process.env.NODE_ENV === 'development') {
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
index 4b910f00..2abf230c 100644
--- a/src/lib/prisma.ts
+++ b/src/lib/prisma.ts
@@ -23,15 +23,15 @@ const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01',
};
-function getAddMinutesQuery(field: string, minutes: number): string {
+function getAddIntervalQuery(field: string, interval: string): string {
const db = getDatabaseType(process.env.DATABASE_URL);
if (db === POSTGRESQL) {
- return `${field} + interval '${minutes} minute'`;
+ return `${field} + interval '${interval}'`;
}
if (db === MYSQL) {
- return `DATE_ADD(${field}, interval ${minutes} minute)`;
+ return `DATE_ADD(${field}, interval ${interval})`;
}
}
@@ -80,15 +80,15 @@ function getDateQuery(field: string, unit: string, timezone?: string): string {
}
}
-function getTimestampIntervalQuery(field: string): string {
+function getTimestampDiffQuery(field1: string, field2: string): string {
const db = getDatabaseType();
if (db === POSTGRESQL) {
- return `floor(extract(epoch from max(${field}) - min(${field})))`;
+ return `floor(extract(epoch from (${field2} - ${field1})))`;
}
if (db === MYSQL) {
- return `floor(unix_timestamp(max(${field})) - unix_timestamp(min(${field})))`;
+ return `timestampdiff(second, ${field1}, ${field2})`;
}
}
@@ -216,11 +216,11 @@ function getSearchMode(): { mode?: Prisma.QueryMode } {
export default {
...prisma,
- getAddMinutesQuery,
+ getAddIntervalQuery,
getDayDiffQuery,
getCastColumnQuery,
getDateQuery,
- getTimestampIntervalQuery,
+ getTimestampDiffQuery,
getFilterQuery,
parseFilters,
getPageFilters,
diff --git a/src/lib/yup.ts b/src/lib/yup.ts
index 5bd0aa18..4008e44f 100644
--- a/src/lib/yup.ts
+++ b/src/lib/yup.ts
@@ -1,4 +1,4 @@
-import moment from 'moment';
+import moment from 'moment-timezone';
import * as yup from 'yup';
import { UNIT_TYPES } from './constants';
diff --git a/src/queries/analytics/eventData/getEventDataStats.ts b/src/queries/analytics/eventData/getEventDataStats.ts
index b940e9c4..39afa1ae 100644
--- a/src/queries/analytics/eventData/getEventDataStats.ts
+++ b/src/queries/analytics/eventData/getEventDataStats.ts
@@ -45,7 +45,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
async function clickhouseQuery(
websiteId: string,
filters: QueryFilters,
-): Promise<{ events: number; fields: number; records: number }> {
+): Promise<{ events: number; fields: number; records: number }[]> {
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, params } = await parseFilters(websiteId, filters);
diff --git a/src/queries/analytics/getRealtimeData.ts b/src/queries/analytics/getRealtimeData.ts
index 8786ab13..337e7475 100644
--- a/src/queries/analytics/getRealtimeData.ts
+++ b/src/queries/analytics/getRealtimeData.ts
@@ -2,15 +2,15 @@ import { md5 } from 'next-basics';
import { getSessions, getEvents } from 'queries/index';
import { EVENT_TYPE } from 'lib/constants';
-export async function getRealtimeData(websiteId, time) {
+export async function getRealtimeData(websiteId: string, startDate: Date) {
const [pageviews, sessions, events] = await Promise.all([
- getEvents(websiteId, time, EVENT_TYPE.pageView),
- getSessions(websiteId, time),
- getEvents(websiteId, time, EVENT_TYPE.customEvent),
+ getEvents(websiteId, startDate, EVENT_TYPE.pageView),
+ getSessions(websiteId, startDate),
+ getEvents(websiteId, startDate, EVENT_TYPE.customEvent),
]);
- const decorate = (id, data) => {
- return data.map(props => ({
+ const decorate = (id: string, data: any[]) => {
+ return data.map((props: { [key: string]: any }) => ({
...props,
__id: md5(id, ...Object.values(props)),
__type: id,
diff --git a/src/queries/analytics/getWebsiteStats.ts b/src/queries/analytics/getWebsiteStats.ts
index 654a09a9..4dbdb462 100644
--- a/src/queries/analytics/getWebsiteStats.ts
+++ b/src/queries/analytics/getWebsiteStats.ts
@@ -12,7 +12,8 @@ export async function getWebsiteStats(...args: [websiteId: string, filters: Quer
}
async function relationalQuery(websiteId: string, filters: QueryFilters) {
- const { getDateQuery, getTimestampIntervalQuery, parseFilters, rawQuery } = prisma;
+ const { getDateQuery, getAddIntervalQuery, getTimestampDiffQuery, parseFilters, rawQuery } =
+ prisma;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
@@ -24,13 +25,16 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) {
sum(t.c) as "pageviews",
count(distinct t.session_id) as "uniques",
sum(case when t.c = 1 then 1 else 0 end) as "bounces",
- sum(t.time) as "totaltime"
+ sum(case when t.max_time < ${getAddIntervalQuery('t.min_time', '1 hour')}
+ then ${getTimestampDiffQuery('t.min_time', 't.max_time')}
+ else 0 end) as "totaltime"
from (
select
website_event.session_id,
- ${getDateQuery('website_event.created_at', 'hour')},
- count(*) as c,
- ${getTimestampIntervalQuery('website_event.created_at')} as "time"
+ ${getDateQuery('website_event.created_at', 'day')},
+ count(*) as "c",
+ min(website_event.created_at) as "min_time",
+ max(website_event.created_at) as "max_time"
from website_event
join website
on website_event.website_id = website.website_id
diff --git a/src/queries/analytics/reports/getFunnel.ts b/src/queries/analytics/reports/getFunnel.ts
index 8dbd8d45..4387cf09 100644
--- a/src/queries/analytics/reports/getFunnel.ts
+++ b/src/queries/analytics/reports/getFunnel.ts
@@ -35,7 +35,7 @@ async function relationalQuery(
}[]
> {
const { windowMinutes, startDate, endDate, urls } = criteria;
- const { rawQuery, getAddMinutesQuery } = prisma;
+ const { rawQuery, getAddIntervalQuery } = prisma;
const { levelQuery, sumQuery } = getFunnelQuery(urls, windowMinutes);
function getFunnelQuery(
@@ -58,9 +58,9 @@ async function relationalQuery(
join website_event we
on l.session_id = we.session_id
where we.website_id = {{websiteId::uuid}}
- and we.created_at between l.created_at and ${getAddMinutesQuery(
+ and we.created_at between l.created_at and ${getAddIntervalQuery(
`l.created_at `,
- windowMinutes,
+ `${windowMinutes} minute`,
)}
and we.referrer_path = {{${i - 1}}}
and we.url_path = {{${i}}}
diff --git a/src/queries/analytics/reports/getInsights.ts b/src/queries/analytics/reports/getInsights.ts
index c5fac48b..a5b1b773 100644
--- a/src/queries/analytics/reports/getInsights.ts
+++ b/src/queries/analytics/reports/getInsights.ts
@@ -83,10 +83,17 @@ async function clickhouseQuery(
limit 500
`,
params,
- );
+ ).then(a => {
+ return Object.values(a).map(a => {
+ return {
+ x: a.x,
+ y: Number(a.y),
+ };
+ });
+ });
}
-function parseFields(fields) {
+function parseFields(fields: any[]) {
const query = fields.reduce(
(arr, field) => {
const { name } = field;
@@ -99,7 +106,7 @@ function parseFields(fields) {
return query.join(',\n');
}
-function parseGroupBy(fields) {
+function parseGroupBy(fields: { name: any }[]) {
if (!fields.length) {
return '';
}
diff --git a/yarn.lock b/yarn.lock
index 2c95f0d3..1e405135 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1941,22 +1941,22 @@
"@parcel/watcher-win32-ia32" "2.3.0"
"@parcel/watcher-win32-x64" "2.3.0"
-"@prisma/client@5.4.2":
- version "5.4.2"
- resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.4.2.tgz#786f9c1d8f06d955933004ac638d14da4bf14025"
- integrity sha512-2xsPaz4EaMKj1WS9iW6MlPhmbqtBsXAOeVttSePp8vTFTtvzh2hZbDgswwBdSCgPzmmwF+tLB259QzggvCmJqA==
+"@prisma/client@5.6.0":
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.6.0.tgz#1c15932250d5658fe0127e62faf4ecd96a877259"
+ integrity sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug==
dependencies:
- "@prisma/engines-version" "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574"
+ "@prisma/engines-version" "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee"
-"@prisma/engines-version@5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574":
- version "5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574"
- resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.4.1-2.ac9d7041ed77bcc8a8dbd2ab6616b39013829574.tgz#ff14f2926890edee47e8f1d08df7b4f392ee34bf"
- integrity sha512-wvupDL4AA1vf4TQNANg7kR7y98ITqPsk6aacfBxZKtrJKRIsWjURHkZCGcQliHdqCiW/hGreO6d6ZuSv9MhdAA==
+"@prisma/engines-version@5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee":
+ version "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee"
+ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz#57b003ab5e1ea1523b5cdd7f06b24ebcf5c7fd8c"
+ integrity sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw==
-"@prisma/engines@5.4.2":
- version "5.4.2"
- resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.4.2.tgz#ba2b7faeb227c76e423e88f962afe6a031319f3f"
- integrity sha512-fqeucJ3LH0e1eyFdT0zRx+oETLancu5+n4lhiYECyEz6H2RDskPJHJYHkVc0LhkU4Uv7fuEnppKU3nVKNzMh8g==
+"@prisma/engines@5.6.0":
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.6.0.tgz#82c445aa10633bbc0388aa2d6e411a0bd94c9439"
+ integrity sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==
"@prisma/extension-read-replicas@^0.3.0":
version "0.3.0"
@@ -2601,10 +2601,10 @@
"@typescript-eslint/types" "6.8.0"
eslint-visitor-keys "^3.4.1"
-"@umami/prisma-client@^0.5.0":
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.5.0.tgz#e2287debbf21f9344c989b9e7192491df88513bf"
- integrity sha512-BkStMrvxYZQPwEIyy30JJPucTTsmQqb4jD8+ciSHxcBc7039cW0XyX3TL/u9ebZmANzIuNO0XiBArwjWulGIjg==
+"@umami/prisma-client@^0.7.0":
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/@umami/prisma-client/-/prisma-client-0.7.0.tgz#f9de0dfc861c9ba6379c0789e012d4effa65f1ef"
+ integrity sha512-70Azr4aAYMU6c+Lx69bjumNnKWgOFoq2PuYmp+T2kfCDhMyMLXTLDVD5ArDrwJMl1gWsgvpnumxCirYy+6KhGg==
dependencies:
chalk "^4.1.2"
debug "^4.3.4"
@@ -3209,11 +3209,16 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
version "1.0.30001551"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz#1f2cfa8820bd97c971a57349d7fd8f6e08664a3e"
integrity sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==
+caniuse-lite@^1.0.30001406:
+ version "1.0.30001558"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz#d2c6e21fdbfe83817f70feab902421a19b7983ee"
+ integrity sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==
+
chalk@5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
@@ -7372,12 +7377,12 @@ pretty-bytes@^5.6.0:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
-prisma@5.4.2:
- version "5.4.2"
- resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.4.2.tgz#7eac9276439ec7073ec697c6c0dfa259d96e955e"
- integrity sha512-GDMZwZy7mysB2oXU+angQqJ90iaPFdD0rHaZNkn+dio5NRkGLmMqmXs31//tg/qXT3iB0cTQwnGGQNuirhSTZg==
+prisma@5.6.0:
+ version "5.6.0"
+ resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.6.0.tgz#ae2c27fdfb4d53be7f7dafb50d6b8b7f55c93aa5"
+ integrity sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==
dependencies:
- "@prisma/engines" "5.4.2"
+ "@prisma/engines" "5.6.0"
promise.series@^0.2.0:
version "0.2.0"
@@ -7471,10 +7476,10 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
-react-basics@^0.107.0:
- version "0.107.0"
- resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.107.0.tgz#e5615792cbb3e4707ba5c8f438b29d6a88cf38b3"
- integrity sha512-jYnP1z2LTotxXWYwxOBvF26vXxSUBJB0x62YPKkEr1vmJGeg8iOLr8JGF8KE3R6E+NTqzRt6Bmdtt93mjaog4A==
+react-basics@^0.109.0:
+ version "0.109.0"
+ resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.109.0.tgz#9c1f41ebf6abbcf67f7dd11a16a7fb5e3aedd38d"
+ integrity sha512-n955CwqIeQ/sTMxxvbtYpWtBWS07Rg39zrNKeUYN/JoCIq0YbbZiZDALAhh1Out+qsIe62NoOFA7JtHzk1EkHQ==
dependencies:
"@react-spring/web" "^9.7.3"
classnames "^2.3.1"