diff --git a/components/messages.js b/components/messages.js index 68e3b3d5..8263677a 100644 --- a/components/messages.js +++ b/components/messages.js @@ -163,6 +163,13 @@ export const labels = defineMessages({ insights: { id: 'label.insights', defaultMessage: 'Insights' }, retention: { id: 'label.retention', defaultMessage: 'Retention' }, dropoff: { id: 'label.dropoff', defaultMessage: 'Dropoff' }, + referrer: { id: 'label.referrer', defaultMessage: 'Referrer' }, + country: { id: 'label.country', defaultMessage: 'Country' }, + region: { id: 'label.region', defaultMessage: 'Region' }, + city: { id: 'label.city', defaultMessage: 'City' }, + browser: { id: 'label.browser', defaultMessage: 'Browser' }, + device: { id: 'label.device', defaultMessage: 'Device' }, + pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' }, }); export const messages = defineMessages({ diff --git a/components/pages/event-data/EventDataMetricsBar.js b/components/pages/event-data/EventDataMetricsBar.js index 48843287..90f065d5 100644 --- a/components/pages/event-data/EventDataMetricsBar.js +++ b/components/pages/event-data/EventDataMetricsBar.js @@ -28,6 +28,11 @@ export function EventDataMetricsBar({ websiteId }) { {!error && isFetched && ( <> + {row => ( - + {row.eventName} )} diff --git a/components/pages/event-data/EventDataValueTable.js b/components/pages/event-data/EventDataValueTable.js index b52c46d3..3688ad09 100644 --- a/components/pages/event-data/EventDataValueTable.js +++ b/components/pages/event-data/EventDataValueTable.js @@ -6,14 +6,14 @@ import PageHeader from 'components/layout/PageHeader'; import Empty from 'components/common/Empty'; import { DATA_TYPES } from 'lib/constants'; -export function EventDataValueTable({ data = [], eventName }) { +export function EventDataValueTable({ data = [], event }) { const { formatMessage, labels } = useMessages(); const { resolveUrl } = usePageQuery(); const Title = () => { return ( <> - + - {eventName} + {event} ); }; diff --git a/components/pages/reports/FilterSelectForm.js b/components/pages/reports/FilterSelectForm.js new file mode 100644 index 00000000..0dc107b0 --- /dev/null +++ b/components/pages/reports/FilterSelectForm.js @@ -0,0 +1,13 @@ +import { useState } from 'react'; +import FieldSelectForm from './FieldSelectForm'; +import FieldFilterForm from './FieldFilterForm'; + +export default function FilterSelectForm({ fields, onSelect }) { + const [field, setField] = useState(); + + if (!field) { + return ; + } + + return ; +} diff --git a/components/pages/reports/ReportDetails.js b/components/pages/reports/ReportDetails.js index 39cd285d..df1589af 100644 --- a/components/pages/reports/ReportDetails.js +++ b/components/pages/reports/ReportDetails.js @@ -1,10 +1,12 @@ import FunnelReport from './funnel/FunnelReport'; import EventDataReport from './event-data/EventDataReport'; +import InsightsReport from './insights/InsightsReport'; import RetentionReport from './retention/RetentionReport'; const reports = { funnel: FunnelReport, 'event-data': EventDataReport, + insights: InsightsReport, retention: RetentionReport, }; diff --git a/components/pages/reports/insights/InsightsParameters.js b/components/pages/reports/insights/InsightsParameters.js index 692c5ead..5d7e1fca 100644 --- a/components/pages/reports/insights/InsightsParameters.js +++ b/components/pages/reports/insights/InsightsParameters.js @@ -7,40 +7,38 @@ import Icons from 'components/icons'; import BaseParameters from '../BaseParameters'; import ParameterList from '../ParameterList'; import styles from './InsightsParameters.module.css'; -import FieldSelectForm from '../FieldSelectForm'; import PopupForm from '../PopupForm'; -import FieldFilterForm from '../FieldFilterForm'; - -const fieldOptions = [ - { name: 'url', type: 'string' }, - { name: 'title', type: 'string' }, - { name: 'referrer', type: 'string' }, - { name: 'query', type: 'string' }, - { name: 'browser', type: 'string' }, - { name: 'os', type: 'string' }, - { name: 'device', type: 'string' }, - { name: 'country', type: 'string' }, - { name: 'region', type: 'string' }, - { name: 'city', type: 'string' }, - { name: 'language', type: 'string' }, -]; +import FilterSelectForm from '../FilterSelectForm'; +import FieldSelectForm from '../FieldSelectForm'; export function InsightsParameters() { const { report, runReport, updateReport, isRunning } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); const ref = useRef(null); const { parameters } = report || {}; - const { websiteId, dateRange, fields, filters, groups } = parameters || {}; - const queryEnabled = websiteId && dateRange && fields?.length; + const { websiteId, dateRange, filters, groups } = parameters || {}; + const queryEnabled = websiteId && dateRange && (filters?.length || groups?.length); + + const fieldOptions = [ + { name: 'url_path', type: 'string', label: formatMessage(labels.url) }, + { name: 'page_title', type: 'string', label: formatMessage(labels.pageTitle) }, + { name: 'referrer_domain', type: 'string', label: formatMessage(labels.referrer) }, + { name: 'url_query', type: 'string', label: formatMessage(labels.query) }, + { name: 'browser', type: 'string', label: formatMessage(labels.browser) }, + { name: 'os', type: 'string', label: formatMessage(labels.os) }, + { name: 'device', type: 'string', label: formatMessage(labels.device) }, + { name: 'country', type: 'string', label: formatMessage(labels.country) }, + { name: 'region', type: 'string', label: formatMessage(labels.region) }, + { name: 'city', type: 'string', label: formatMessage(labels.city) }, + { name: 'language', type: 'string', label: formatMessage(labels.language) }, + ]; const parameterGroups = [ - { label: formatMessage(labels.fields), group: REPORT_PARAMETERS.fields }, - { label: formatMessage(labels.filters), group: REPORT_PARAMETERS.filters }, { label: formatMessage(labels.breakdown), group: REPORT_PARAMETERS.groups }, + { label: formatMessage(labels.filters), group: REPORT_PARAMETERS.filters }, ]; const parameterData = { - fields, filters, groups, }; @@ -73,11 +71,11 @@ export function InsightsParameters() { {(close, element) => { return ( - {group === REPORT_PARAMETERS.fields && ( + {group === REPORT_PARAMETERS.groups && ( )} {group === REPORT_PARAMETERS.filters && ( - + )} ); @@ -97,27 +95,21 @@ export function InsightsParameters() { items={parameterData[group]} onRemove={index => handleRemove(group, index)} > - {({ name, value }) => { + {({ value, label }) => { return (
- {group === REPORT_PARAMETERS.fields && ( + {group === REPORT_PARAMETERS.groups && ( <> -
{name}
-
{value}
+
{label}
)} {group === REPORT_PARAMETERS.filters && ( <> -
{name}
+
{label}
{value[0]}
{value[1]}
)} - {group === REPORT_PARAMETERS.groups && ( - <> -
{name}
- - )}
); }} diff --git a/components/pages/reports/insights/InsightsTable.js b/components/pages/reports/insights/InsightsTable.js index d751445b..7832a899 100644 --- a/components/pages/reports/insights/InsightsTable.js +++ b/components/pages/reports/insights/InsightsTable.js @@ -6,14 +6,15 @@ import { ReportContext } from '../Report'; export function InsightsTable() { const { report } = useContext(ReportContext); const { formatMessage, labels } = useMessages(); - const { fields = [] } = report?.parameters || {}; + const { groups = [] } = report?.parameters || {}; return ( - {fields.map(({ name }) => { - return ; + {groups.map(({ name, label }) => { + return ; })} - + + ); } diff --git a/components/pages/websites/WebsiteEventData.js b/components/pages/websites/WebsiteEventData.js index d6cb2639..5e208355 100644 --- a/components/pages/websites/WebsiteEventData.js +++ b/components/pages/websites/WebsiteEventData.js @@ -26,15 +26,15 @@ function useData(websiteId, eventName) { export default function WebsiteEventData({ websiteId }) { const { - query: { eventName }, + query: { event }, } = usePageQuery(); - const { data } = useData(websiteId, eventName); + const { data } = useData(websiteId, event); return ( - {!eventName && } - {eventName && } + {!event && } + {event && } ); } diff --git a/lib/clickhouse.ts b/lib/clickhouse.ts index 6d5bcf42..f7abd94f 100644 --- a/lib/clickhouse.ts +++ b/lib/clickhouse.ts @@ -2,8 +2,8 @@ import { ClickHouse } from 'clickhouse'; import dateFormat from 'dateformat'; import debug from 'debug'; import { CLICKHOUSE } from 'lib/db'; -import { QueryFilters } from './types'; -import { FILTER_COLUMNS, IGNORED_FILTERS } from './constants'; +import { QueryFilters, QueryOptions } from './types'; +import { FILTER_COLUMNS } from './constants'; import { loadWebsite } from './load'; import { maxDate } from './date'; @@ -63,12 +63,12 @@ function getDateFormat(date) { return `'${dateFormat(date, 'UTC:yyyy-mm-dd HH:MM:ss')}'`; } -function getFilterQuery(filters = {}) { +function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}) { const query = Object.keys(filters).reduce((arr, key) => { const filter = filters[key]; + const column = FILTER_COLUMNS[key] ?? options?.columns?.[key]; - if (filter !== undefined && !IGNORED_FILTERS.includes(key)) { - const column = FILTER_COLUMNS[key] || key; + if (filter !== undefined && column) { arr.push(`and ${column} = {${key}:String}`); } @@ -85,11 +85,12 @@ function getFilterQuery(filters = {}) { async function parseFilters( websiteId: string, filters: QueryFilters & { [key: string]: any } = {}, + options?: QueryOptions, ) { const website = await loadWebsite(websiteId); return { - filterQuery: getFilterQuery(filters), + filterQuery: getFilterQuery(filters, options), params: { ...filters, websiteId, diff --git a/lib/constants.ts b/lib/constants.ts index dcb64143..887f90a9 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -48,13 +48,16 @@ export const FILTER_COLUMNS = { referrer: 'referrer_domain', title: 'page_title', query: 'url_query', + os: 'os', + browser: 'browser', + device: 'device', + country: 'country', region: 'subdivision1', - eventType: 'event_type', - eventName: 'event_name', + city: 'city', + language: 'language', + event: 'event_name', }; -export const IGNORED_FILTERS = ['startDate', 'endDate', 'timezone', 'unit']; - export const COLLECTION_TYPE = { event: 'event', identify: 'identify', diff --git a/lib/detect.ts b/lib/detect.ts index 9c1e1fa4..43dac649 100644 --- a/lib/detect.ts +++ b/lib/detect.ts @@ -3,6 +3,7 @@ import { getClientIp } from 'request-ip'; import { browserName, detectOS } from 'detect-browser'; import isLocalhost from 'is-localhost-ip'; import maxmind from 'maxmind'; +import { safeDecodeURIComponent } from 'next-basics'; import { DESKTOP_OS, @@ -65,20 +66,18 @@ export async function getLocation(ip, req) { // Cloudflare headers if (req.headers['cf-ipcountry']) { return { - country: req.headers['cf-ipcountry'], + country: safeDecodeURIComponent(req.headers['cf-ipcountry']), + subdivision1: safeDecodeURIComponent(req.headers['cf-region-code']), + city: safeDecodeURIComponent(req.headers['cf-ipcity']), }; } // Vercel headers if (req.headers['x-vercel-ip-country']) { - const country = req.headers['x-vercel-ip-country']; - const region = req.headers['x-vercel-ip-country-region']; - const city = req.headers['x-vercel-ip-city']; - return { - country, - subdivision1: region, - city: city ? decodeURIComponent(city) : undefined, + country: safeDecodeURIComponent(req.headers['x-vercel-ip-country']), + subdivision1: safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']), + city: safeDecodeURIComponent(req.headers['x-vercel-ip-city']), }; } diff --git a/lib/prisma.ts b/lib/prisma.ts index 50f26f75..753f1ae4 100644 --- a/lib/prisma.ts +++ b/lib/prisma.ts @@ -1,7 +1,7 @@ import prisma from '@umami/prisma-client'; import moment from 'moment-timezone'; import { MYSQL, POSTGRESQL, getDatabaseType } from 'lib/db'; -import { FILTER_COLUMNS, IGNORED_FILTERS, SESSION_COLUMNS } from './constants'; +import { FILTER_COLUMNS, SESSION_COLUMNS } from './constants'; import { loadWebsite } from './load'; import { maxDate } from './date'; import { QueryFilters, QueryOptions } from './types'; @@ -67,12 +67,12 @@ function getTimestampIntervalQuery(field: string): string { } } -function getFilterQuery(filters = {}): string { +function getFilterQuery(filters: QueryFilters = {}, options: QueryOptions = {}): string { const query = Object.keys(filters).reduce((arr, key) => { const filter = filters[key]; + const column = FILTER_COLUMNS[key] ?? options?.columns?.[key]; - if (filter !== undefined && !IGNORED_FILTERS.includes(key)) { - const column = FILTER_COLUMNS[key] || key; + if (filter !== undefined && column) { arr.push(`and ${column}={{${key}}}`); if (key === 'referrer') { @@ -100,7 +100,7 @@ async function parseFilters( options?.joinSession || Object.keys(filters).find(key => SESSION_COLUMNS.includes(key)) ? `inner join session on website_event.session_id = session.session_id` : '', - filterQuery: getFilterQuery(filters), + filterQuery: getFilterQuery(filters, options), params: { ...filters, websiteId, diff --git a/lib/types.ts b/lib/types.ts index 7cc9a619..dc54fd47 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -80,15 +80,15 @@ export interface WebsiteEventMetric { } export interface WebsiteEventDataStats { - field: string; - type: number; + fieldName: string; + dataType: number; total: number; } export interface WebsiteEventDataFields { - field: string; - type: number; - value?: string; + fieldName: string; + dataType: number; + fieldValue?: string; total: number; } @@ -135,9 +135,7 @@ export interface QueryFilters { endDate?: Date; timezone?: string; unit?: string; - domain?: string; eventType?: number; - eventName?: string; url?: string; referrer?: string; title?: string; @@ -149,9 +147,10 @@ export interface QueryFilters { region?: string; city?: string; language?: string; + event?: string; } export interface QueryOptions { joinSession?: boolean; - ignoreFilters?: string[]; + columns?: { [key: string]: string }; } diff --git a/package.json b/package.json index 868b3cdf..647cdf41 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "maxmind": "^4.3.6", "moment-timezone": "^0.5.35", "next": "13.3.1", - "next-basics": "^0.35.0", + "next-basics": "^0.36.0", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "react": "^18.2.0", diff --git a/pages/api/event-data/events.ts b/pages/api/event-data/events.ts index e8693108..e83e541b 100644 --- a/pages/api/event-data/events.ts +++ b/pages/api/event-data/events.ts @@ -21,7 +21,7 @@ export default async ( await useAuth(req, res); if (req.method === 'GET') { - const { websiteId, startAt, endAt, eventName } = req.query; + const { websiteId, startAt, endAt, event } = req.query; if (!(await canViewWebsite(req.auth, websiteId))) { return unauthorized(res); @@ -33,7 +33,7 @@ export default async ( const data = await getEventDataEvents(websiteId, { startDate, endDate, - eventName, + event, }); return ok(res, data); diff --git a/pages/api/event-data/fields.ts b/pages/api/event-data/fields.ts index 18b74bc3..f21bd570 100644 --- a/pages/api/event-data/fields.ts +++ b/pages/api/event-data/fields.ts @@ -11,6 +11,7 @@ export interface EventDataFieldsRequestBody { startDate: string; endDate: string; }; + field?: string; } export default async ( @@ -27,7 +28,10 @@ export default async ( return unauthorized(res); } - const data = await getEventDataFields(websiteId, new Date(+startAt), new Date(+endAt), field); + const startDate = new Date(+startAt); + const endDate = new Date(+endAt); + + const data = await getEventDataFields(websiteId, { startDate, endDate, field }); return ok(res, data); } diff --git a/pages/api/event-data/stats.ts b/pages/api/event-data/stats.ts index 969568e2..74f420c4 100644 --- a/pages/api/event-data/stats.ts +++ b/pages/api/event-data/stats.ts @@ -32,16 +32,18 @@ export default async ( const endDate = new Date(+endAt); const results = await getEventDataFields(websiteId, { startDate, endDate }); + const events = new Set(); const data = results.reduce( (obj, row) => { + events.add(row.fieldName); obj.records += Number(row.total); return obj; }, { fields: results.length, records: 0 }, ); - return ok(res, data); + return ok(res, { ...data, events: events.size }); } return methodNotAllowed(res); diff --git a/pages/api/reports/insights.ts b/pages/api/reports/insights.ts index a40c2124..44f72063 100644 --- a/pages/api/reports/insights.ts +++ b/pages/api/reports/insights.ts @@ -13,7 +13,7 @@ export interface InsightsRequestBody { }; fields: { name: string; type: string; value: string }[]; filters: string[]; - groups: string[]; + groups: { name: string; type: string }[]; } export default async ( @@ -27,21 +27,18 @@ export default async ( const { websiteId, dateRange: { startDate, endDate }, - fields, - filters, groups, + filters, } = req.body; if (!(await canViewWebsite(req.auth, websiteId))) { return unauthorized(res); } - const data = await getInsights(websiteId, { + const data = await getInsights(websiteId, groups, { + ...filters, startDate: new Date(startDate), endDate: new Date(endDate), - fields, - filters, - groups, }); return ok(res, data); diff --git a/queries/analytics/eventData/getEventDataEvents.ts b/queries/analytics/eventData/getEventDataEvents.ts index dcb21283..ec0939b6 100644 --- a/queries/analytics/eventData/getEventDataEvents.ts +++ b/queries/analytics/eventData/getEventDataEvents.ts @@ -14,10 +14,10 @@ export async function getEventDataEvents( async function relationalQuery(websiteId: string, filters: QueryFilters) { const { rawQuery, parseFilters } = prisma; - const { eventName } = filters; + const { event } = filters; const { params } = await parseFilters(websiteId, filters); - if (eventName) { + if (event) { return rawQuery( ` select @@ -31,7 +31,7 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { on website_event.event_id = event_data.website_event_id where event_data.website_id = {{websiteId::uuid}} and event_data.created_at between {{startDate}} and {{endDate}} - and websit_event.event_name = {{eventName}} + and website_event.event_name = {{event}} group by website_event.event_name, event_data.event_key, event_data.data_type, event_data.string_value order by 1 asc, 2 asc, 3 asc, 4 desc `, @@ -61,10 +61,10 @@ async function relationalQuery(websiteId: string, filters: QueryFilters) { async function clickhouseQuery(websiteId: string, filters: QueryFilters) { const { rawQuery, parseFilters } = clickhouse; - const { eventName } = filters; + const { event } = filters; const { params } = await parseFilters(websiteId, filters); - if (eventName) { + if (event) { return rawQuery( ` select @@ -76,7 +76,7 @@ async function clickhouseQuery(websiteId: string, filters: QueryFilters) { from event_data where website_id = {websiteId:UUID} and created_at between {startDate:DateTime} and {endDate:DateTime} - and event_name = {eventName:String} + and event_name = {event:String} group by event_key, data_type, string_value, event_name order by 1 asc, 2 asc, 3 asc, 4 desc limit 100 diff --git a/queries/analytics/eventData/getEventDataFields.ts b/queries/analytics/eventData/getEventDataFields.ts index a27f2281..c61de517 100644 --- a/queries/analytics/eventData/getEventDataFields.ts +++ b/queries/analytics/eventData/getEventDataFields.ts @@ -14,39 +14,23 @@ export async function getEventDataFields( async function relationalQuery(websiteId: string, filters: QueryFilters & { field?: string }) { const { rawQuery, parseFilters } = prisma; - const { field } = filters; - const { params } = await parseFilters(websiteId, filters); - - if (field) { - return rawQuery( - ` - select - event_key as field, - string_value as value, - count(*) as total - from event_data - where website_id = {{websiteId::uuid}} - and event_key = {{field}} - and created_at between {{startDate}} and {{endDate}} - group by event_key, string_value - order by 3 desc, 2 desc, 1 asc - limit 100 - `, - params, - ); - } + const { filterQuery, params } = await parseFilters(websiteId, filters, { + columns: { field: 'event_key' }, + }); return rawQuery( ` select - event_key as field, - data_type as type, + event_key as fieldName, + data_type as dataType, + string_value as fieldValue, count(*) as total from event_data where website_id = {{websiteId::uuid}} and created_at between {{startDate}} and {{endDate}} - group by event_key, data_type - order by 3 desc, 2 asc, 1 asc + ${filterQuery} + group by event_key, data_type, string_value + order by 3 desc, 2 desc, 1 asc limit 100 `, params, @@ -55,39 +39,23 @@ async function relationalQuery(websiteId: string, filters: QueryFilters & { fiel async function clickhouseQuery(websiteId: string, filters: QueryFilters & { field?: string }) { const { rawQuery, parseFilters } = clickhouse; - const { field } = filters; - const { params } = await parseFilters(websiteId, filters); - - if (field) { - return rawQuery( - ` - select - event_key as field, - string_value as value, - count(*) as total - from event_data - where website_id = {websiteId:UUID} - and event_key = {field:String} - and created_at between {startDate:DateTime} and {endDate:DateTime} - group by event_key, string_value - order by 3 desc, 2 desc, 1 asc - limit 100 - `, - params, - ); - } + const { filterQuery, params } = await parseFilters(websiteId, filters, { + columns: { field: 'event_key' }, + }); return rawQuery( ` select - event_key as field, - data_type as type, + event_key as fieldName, + data_type as dataType, + string_value as fieldValue, count(*) as total from event_data where website_id = {websiteId:UUID} and created_at between {startDate:DateTime} and {endDate:DateTime} - group by event_key, data_type - order by 3 desc, 2 asc, 1 asc + ${filterQuery} + group by event_key, data_type, string_value + order by 3 desc, 2 desc, 1 asc limit 100 `, params, diff --git a/queries/analytics/reports/getInsights.ts b/queries/analytics/reports/getInsights.ts index dfe7c397..0f778555 100644 --- a/queries/analytics/reports/getInsights.ts +++ b/queries/analytics/reports/getInsights.ts @@ -4,7 +4,9 @@ import clickhouse from 'lib/clickhouse'; import { EVENT_TYPE } from 'lib/constants'; import { QueryFilters } from 'lib/types'; -export async function getInsights(...args: [websiteId: string, filters: QueryFilters]) { +export async function getInsights( + ...args: [websiteId: string, groups: { name: string; type: string }[], filters: QueryFilters] +) { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), @@ -13,6 +15,7 @@ export async function getInsights(...args: [websiteId: string, filters: QueryFil async function relationalQuery( websiteId: string, + groups: { name: string; type: string }[], filters: QueryFilters, ): Promise< { @@ -45,6 +48,7 @@ async function relationalQuery( async function clickhouseQuery( websiteId: string, + groups: { name: string; type: string }[], filters: QueryFilters, ): Promise< { @@ -53,7 +57,6 @@ async function clickhouseQuery( }[] > { const { parseFilters, rawQuery } = clickhouse; - const { fields } = filters; const { filterQuery, params } = await parseFilters(websiteId, { ...filters, eventType: EVENT_TYPE.pageView, @@ -62,14 +65,14 @@ async function clickhouseQuery( return rawQuery( ` select - ${parseFields(fields)} + ${parseFields(groups)} from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime} and {endDate:DateTime} and event_type = {eventType:UInt32} ${filterQuery} - group by ${fields.map(({ name }) => name).join(',')} - order by total desc + group by ${groups.map(({ name }) => name).join(',')} + order by 1 desc limit 500 `, params, @@ -77,22 +80,14 @@ async function clickhouseQuery( } function parseFields(fields) { - let count = false; - let distinct = false; + const query = fields.reduce( + (arr, field) => { + const { name } = field; - const query = fields.reduce((arr, field) => { - const { name, value } = field; - - if (!count && value === 'total') { - count = true; - arr = arr.concat(`count(*) as views`); - } else if (!distinct && value === 'unique') { - distinct = true; - //arr = arr.concat(`count(distinct ${name})`); - } - - return arr.concat(name); - }, []); + return arr.concat(name); + }, + ['count(*) as views', 'count(distinct session_id) as visitors'], + ); return query.join(',\n'); } diff --git a/yarn.lock b/yarn.lock index 275bcd63..d9224c2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6371,10 +6371,10 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -next-basics@^0.35.0: - version "0.35.0" - resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.35.0.tgz#aa68fd35a0e3fbabfdaf570cd092b6a7cf8df6f5" - integrity sha512-yqXZMLe109hSJ8sebI/f2m1XNnVuQowpELOhZSGOFOmLfvUyFBAEi0ULdqX1eb8xbttLgjcrumrZfMgmEwuCPw== +next-basics@^0.36.0: + version "0.36.0" + resolved "https://registry.yarnpkg.com/next-basics/-/next-basics-0.36.0.tgz#b1675c3f2b98df2fec8df605095dab7d17f9dc7b" + integrity sha512-Nwou8pCjFuoD/ZxUw9iKC7hhZeWbo/ng0ze74yck3W89MNc/CepwCDziflAHY5XcmIVNmpXOCu9OfmzTdVRPWQ== dependencies: bcryptjs "^2.4.3" jsonwebtoken "^9.0.0"